Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDA-4089 Browser login autoconnect #1811

Merged
merged 2 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/Symphony.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"isAutoUpdateEnabled": true,
"autoUpdateCheckInterval": "30",
"enableBrowserLogin": false,
"browserLoginAutoConnect": false,
"overrideUserAgent": false,
"minimizeOnClose" : "ENABLED",
"launchOnStartup" : "ENABLED",
Expand Down
4 changes: 4 additions & 0 deletions installer/mac/postinstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ always_on_top=$(sed -n '5p' ${settingsFilePath});
bring_to_front=$(sed -n '6p' ${settingsFilePath});
dev_tools_enabled=$(sed -n '7p' ${settingsFilePath});
enable_browser_login=$(sed -n '8p' ${settingsFilePath});
browser_login_autoconnect=$(sed -n '9p' ${settingsFilePath});

## If any of the above values turn out to be empty, set default values ##
if [ "$pod_url" = "" ]; then pod_url="https://my.symphony.com"; fi
Expand All @@ -28,6 +29,8 @@ if [ "$always_on_top" = "" ] || [ "$always_on_top" = 'false' ]; then always_on_t
if [ "$bring_to_front" = "" ] || [ "$bring_to_front" = 'false' ]; then bring_to_front='DISABLED'; else bring_to_front='ENABLED'; fi
if [ "$dev_tools_enabled" = "" ]; then dev_tools_enabled=true; fi
if [ "$enable_browser_login" = "" ]; then enable_browser_login=false; fi
if [ "$browser_login_autoconnect" = "" ]; then browser_login_autoconnect=false; fi

pod_url_escaped=$(sed 's#[&/\]#\\&#g' <<<"$pod_url")
context_origin_url_escaped=$(sed 's#[&/\]#\\&#g' <<<"$context_origin_url")

Expand All @@ -40,6 +43,7 @@ sed -i "" -E "s#\"launchOnStartup\" ?: ?\"([Ee][Nn][Aa][Bb][Ll][Ee][Dd]|[Dd][Ii]
sed -i "" -E "s#\"bringToFront\" ?: ?\"([Ee][Nn][Aa][Bb][Ll][Ee][Dd]|[Dd][Ii][Ss][Aa][Bb][Ll][Ee][Dd])\"#\"bringToFront\":\ \"$bring_to_front\"#g" "${newPath}"
sed -i "" -E "s#\"devToolsEnabled\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"devToolsEnabled\":\ $dev_tools_enabled#g" "${newPath}"
sed -i "" -E "s#\"enableBrowserLogin\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"enableBrowserLogin\":\ $enable_browser_login#g" "${newPath}"
sed -i "" -E "s#\"browserLoginAutoConnect\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"browserLoginAutoConnect\":\ $browser_login_autoconnect#g" "${newPath}"

## Get Symphony Permissions from the temp file ##
media=$(sed -n '1p' ${permissionsFilePath});
Expand Down
4 changes: 3 additions & 1 deletion installer/win/WixSharpInstaller/Symphony.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ static public void Main(string[] args)
new PublicProperty("USER_DATA_PATH", ""),
new PublicProperty("OVERRIDE_USER_AGENT", "false"),
new PublicProperty("ENABLE_BROWSER_LOGIN", "false"),
new PublicProperty("BROWSER_LOGIN_AUTOCONNECT", "false"),
new PublicProperty("CHROME_FLAGS", ""),
new Property("MSIINSTALLPERUSER", "1"),
new Property("PROGRAMSFOLDER", System.Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%"))
Expand Down Expand Up @@ -187,7 +188,7 @@ static public void Main(string[] args)
new ElevatedManagedAction(CustomActions.UpdateConfig, Return.check, When.After, Step.InstallFiles, Condition.NOT_BeingRemoved )
{
// The UpdateConfig action needs the built-in property INSTALLDIR as well as most of the custom properties
UsesProperties = "INSTALLDIR,POD_URL,CONTEXT_ORIGIN_URL,MINIMIZE_ON_CLOSE,ALWAYS_ON_TOP,AUTO_START,BRING_TO_FRONT,MEDIA,LOCATION,NOTIFICATIONS,MIDI_SYSEX,POINTER_LOCK,FULL_SCREEN,OPEN_EXTERNAL,CUSTOM_TITLE_BAR,DEV_TOOLS_ENABLED,AUTO_LAUNCH_PATH,USER_DATA_PATH,OVERRIDE_USER_AGENT,CHROME_FLAGS,ENABLE_BROWSER_LOGIN"
UsesProperties = "INSTALLDIR,POD_URL,CONTEXT_ORIGIN_URL,MINIMIZE_ON_CLOSE,ALWAYS_ON_TOP,AUTO_START,BRING_TO_FRONT,MEDIA,LOCATION,NOTIFICATIONS,MIDI_SYSEX,POINTER_LOCK,FULL_SCREEN,OPEN_EXTERNAL,CUSTOM_TITLE_BAR,DEV_TOOLS_ENABLED,AUTO_LAUNCH_PATH,USER_DATA_PATH,OVERRIDE_USER_AGENT,CHROME_FLAGS,ENABLE_BROWSER_LOGIN,BROWSER_LOGIN_AUTOCONNECT"
},

// CleanRegistry
Expand Down Expand Up @@ -362,6 +363,7 @@ public static ActionResult UpdateConfig(Session session)
data = ReplaceBooleanProperty(data, "devToolsEnabled", session.Property("DEV_TOOLS_ENABLED"));
data = ReplaceBooleanProperty(data, "overrideUserAgent", session.Property("OVERRIDE_USER_AGENT"));
data = ReplaceBooleanProperty(data, "enableBrowserLogin", session.Property("ENABLE_BROWSER_LOGIN"));
data = ReplaceBooleanProperty(data, "browserLoginAutoConnect", session.Property("BROWSER_LOGIN_AUTOCONNECT"));
// Write the contents back to the file
System.IO.File.WriteAllText(filename, data);
}
Expand Down
28 changes: 25 additions & 3 deletions installer/win/install_instructions_win.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,15 +535,15 @@ Expected values:
Expected values:

* "true"
SDA will authenticate the user by relying on third-party browser
SDA will authenticate the user by relying on default browser
* "false"
SDA will authenticate the user in SDA

#### Example, install with user-agent override
#### Example, install with browser login enabled

msiexec /i Symphony.msi ENABLE_BROWSER_LOGIN="true"

#### Example, install without user-agent override
#### Example, install without browser login enabled

msiexec /i Symphony.msi ENABLE_BROWSER_LOGIN="false"

Expand All @@ -554,3 +554,25 @@ or


-------------------------------------------------------------------
### BROWSER_LOGIN_AUTOCONNECT

Acts in combination with ENABLE_BROWSER_LOGIN, if ENABLE_BROWSER_LOGIN is set to true.

Expected values:

* "true"
SDA will automatically authenticate the user by relying on default browser
* "false"
User will need to click on Login button to start browser login flow.

#### Example, install with browser login autoconnect enabled

msiexec /i Symphony.msi BROWSER_LOGIN_AUTOCONNECT="true"

#### Example, install with browser login autoconnect disabled

msiexec /i Symphony.msi BROWSER_LOGIN_AUTOCONNECT="false"

or

msiexec /i Symphony.msi
75 changes: 49 additions & 26 deletions spec/__snapshots__/welcome.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -99,43 +99,66 @@ exports[`welcome should render correctly 1`] = `
1,000 institutions.
</span>
</div>
<div>
<div
className="Welcome-login-text"
>
<span>
Log in with your pod URL
</span>
<div
className="Welcome-login-text"
>
<span>
Log in with your pod URL
</span>
</div>
<div
className="Welcome-input-container"
>
<span>
Pod URL
</span>
<div>
<input
className="Welcome-main-container-podurl-box"
data-testid="Welcome-main-container-podurl-box"
disabled={false}
onChange={[Function]}
type="url"
value="https://[POD].symphony.com"
/>
<label
className="Welcome-input-message"
>
Find your pod URL in your invitation email.
</label>
</div>
</div>
<div
className="Welcome-auto-connect-wrapper"
>
<label
className="switch"
>
<input
checked={false}
disabled={false}
onChange={[Function]}
type="checkbox"
/>
<span
className="slider round"
/>
</label>
<div
className="Welcome-input-container"
className="auto-connect-labels"
>
<span>
Pod URL
<span
className="auto-connect-label"
>
Automatically redirect to your web browser on launch
</span>
<div>
<input
className="Welcome-main-container-podurl-box"
data-testid="Welcome-main-container-podurl-box"
onChange={[Function]}
tabIndex={0}
type="url"
value="https://[POD].symphony.com"
/>
<label
className="Welcome-input-message"
>
Find your pod URL in your invitation email.
</label>
</div>
</div>
</div>
<button
className="Welcome-continue-button"
disabled={true}
onClick={[Function]}
style={Object {}}
tabIndex={1}
>
log in
</button>
Expand Down
29 changes: 16 additions & 13 deletions spec/welcome.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ describe('welcome', () => {
message: '',
urlValid: true,
isPodConfigured: false,
isSeamlessLoginEnabled: true,
isBrowserLoginEnabled: false,
browserLoginAutoConnect: false,
};
const onLabelEvent = 'on';
const removeListenerLabelEvent = 'removeListener';
Expand All @@ -26,17 +27,6 @@ describe('welcome', () => {
expect(spy).toBeCalledWith(welcomeLabel, expect.any(Function));
});

it('should remove listener `welcome` when component is unmounted', () => {
const spyMount = jest.spyOn(ipcRenderer, onLabelEvent);
const spyUnmount = jest.spyOn(ipcRenderer, removeListenerLabelEvent);

const wrapper = shallow(React.createElement(Welcome));
expect(spyMount).toBeCalledWith(welcomeLabel, expect.any(Function));

wrapper.unmount();
expect(spyUnmount).toBeCalledWith(welcomeLabel, expect.any(Function));
});

it('should call `updateState` when component is mounted', () => {
const spy = jest.spyOn(Welcome.prototype, 'setState');
shallow(React.createElement(Welcome));
Expand Down Expand Up @@ -113,11 +103,24 @@ describe('welcome', () => {
message: '',
urlValid: true,
isPodConfigured: true,
isSeamlessLoginEnabled: true,
isBrowserLoginEnabled: false,
browserLoginAutoConnect: false,
isLoading: false,
};
const wrapper = shallow(React.createElement(Welcome));
ipcRenderer.send('welcome', welcomeMock);
const podUrlBox = `input.Welcome-main-container-podurl-box`;
expect(wrapper.find(podUrlBox).getElements()).toEqual([]);
});

it('should remove listener `welcome` when component is unmounted', () => {
const spyMount = jest.spyOn(ipcRenderer, onLabelEvent);
const spyUnmount = jest.spyOn(ipcRenderer, removeListenerLabelEvent);

const wrapper = shallow(React.createElement(Welcome));
expect(spyMount).toBeCalledWith(welcomeLabel, expect.any(Function));

wrapper.unmount();
expect(spyUnmount).toBeCalledWith(welcomeLabel, expect.any(Function));
});
});
1 change: 1 addition & 0 deletions src/app/app-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ export class AppMenu {
enabled:
!bringToFrontCC || bringToFrontCC === CloudConfigDataTypes.NOT_SET,
},
this.buildSeparator(),
{
type: 'checkbox',
label: i18n.t('Browser login')(),
Expand Down
3 changes: 3 additions & 0 deletions src/app/auto-update-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export class AutoUpdate {
data: info,
});
}
if (isMac) {
config.backupGlobalConfig();
}
});

this.autoUpdater.on('error', (error) => {
Expand Down
3 changes: 2 additions & 1 deletion src/app/config-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export interface IConfig {
installVariant?: string;
bootCount?: number;
startedAfterAutoUpdate?: boolean;
enableBrowserLogin: boolean;
enableBrowserLogin?: boolean;
browserLoginAutoConnect?: boolean;
}

export interface IGlobalConfig {
Expand Down
19 changes: 12 additions & 7 deletions src/app/main-api-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const broadcastMessage = (method, data) => {
mainEvents.publish(apiCmds.onSwiftSearchMessage, [method, data]);
};

const getSeamlessLoginUrl = (pod: string) =>
const getBrowserLoginUrl = (pod: string) =>
`${pod}/login/sso/initsso?RelayState=${pod}/client-bff/device-login/index.html?callbackScheme=symphony&action=login`;
const AUTH_STATUS_PATH = '/login/checkauth?type=user';
/**
Expand Down Expand Up @@ -384,9 +384,14 @@ ipcMain.on(
mainWebContents.focus();
}
break;
case apiCmds.seamlessLogin:
case apiCmds.browserLogin:
await config.updateUserConfig({
browserLoginAutoConnect: arg.browserLoginAutoConnect,
});
if (!arg.isPodConfigured) {
await config.updateUserConfig({ url: arg.newPodUrl });
await config.updateUserConfig({
url: arg.newPodUrl,
});
}
const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
const { url: userConfigURL } = config.getUserConfigFields(['url']);
Expand All @@ -398,7 +403,7 @@ ipcMain.on(
: globalConfigURL;
const { subdomain, domain, tld } = whitelistHandler.parseDomain(podUrl);
const formattedPodUrl = `https://${subdomain}.${domain}${tld}`;
const loginUrl = getSeamlessLoginUrl(formattedPodUrl);
const loginUrl = getBrowserLoginUrl(formattedPodUrl);
logger.info(
'main-api-handler:',
'check if sso is enabled for the pod',
Expand All @@ -408,19 +413,19 @@ ipcMain.on(
const authResponse = (await response.json()) as IAuthResponse;
logger.info('main-api-handler:', 'check auth response', authResponse);
if (
arg.isSeamlessLoginEnabled &&
arg.isBrowserLoginEnabled &&
authResponse.authenticationType === 'sso'
) {
logger.info(
'main-api-handler:',
'seamless login is enabled - logging in',
'browser login is enabled - logging in',
loginUrl,
);
await shell.openExternal(loginUrl);
} else {
logger.info(
'main-api-handler:',
'seamless login is not enabled - loading main window with',
'browser login is not enabled - loading main window with',
formattedPodUrl,
);
const mainWebContents = windowHandler.getMainWebContents();
Expand Down
24 changes: 14 additions & 10 deletions src/app/protocol-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class ProtocolHandler {
);
// Handle protocol for Seamless login
if (url?.includes('skey') && url?.includes('anticsrf')) {
await this.handleSeamlessLogin(url);
await this.handleBrowserLogin(url);
return;
}

Expand Down Expand Up @@ -112,19 +112,20 @@ class ProtocolHandler {
/**
* Sets session cookies and navigates to the pod url
*/
public async handleSeamlessLogin(protocolUri: string): Promise<void> {
public async handleBrowserLogin(protocolUri: string): Promise<void> {
const globalConfig = config.getGlobalConfigFields(['url']);
const userConfig = config.getUserConfigFields(['url']);
const url = userConfig.url ? userConfig.url : globalConfig.url;
const { subdomain, tld, domain } = whitelistHandler.parseDomain(url);
const redirectURL = userConfig.url ? userConfig.url : globalConfig.url;
const { subdomain, tld, domain } =
whitelistHandler.parseDomain(redirectURL);
const cookieDomain = `.${subdomain}.${domain}${tld}`;
if (protocolUri) {
const urlParams = new URLSearchParams(new URL(protocolUri).search);
const skeyValue = urlParams.get('skey');
const anticsrfValue = urlParams.get('anticsrf');
if (skeyValue && anticsrfValue) {
const skeyCookie: CookiesSetDetails = {
url,
url: redirectURL,
name: 'skey',
value: skeyValue,
secure: true,
Expand All @@ -133,7 +134,7 @@ class ProtocolHandler {
domain: cookieDomain,
};
const csrfCookie: CookiesSetDetails = {
url,
url: redirectURL,
name: 'anti-csrf-cookie',
value: anticsrfValue,
secure: true,
Expand All @@ -152,10 +153,13 @@ class ProtocolHandler {
}
}
const mainWebContents = windowHandler.getMainWebContents();
if (mainWebContents && !mainWebContents?.isDestroyed() && url) {
logger.info('protocol-handler: redirecting main webContents ', url);
windowHandler.setMainWindowOrigin(url);
mainWebContents?.loadURL(url);
if (mainWebContents && !mainWebContents?.isDestroyed() && redirectURL) {
logger.info(
'protocol-handler: redirecting main webContents ',
redirectURL,
);
windowHandler.setMainWindowOrigin(redirectURL);
mainWebContents?.loadURL(redirectURL);
}
}
}
Expand Down
Loading