Skip to content

Commit

Permalink
Security (#377)
Browse files Browse the repository at this point in the history
* implement cs with expiry

* update tests

* address comments

* address comments:remove new connection string key
  • Loading branch information
YingXue authored Dec 14, 2020
1 parent d5a052e commit 808c23d
Show file tree
Hide file tree
Showing 37 changed files with 581 additions and 298 deletions.
3 changes: 2 additions & 1 deletion src/app/api/services/dataplaneServiceHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CONTROLLER_API_ENDPOINT, DATAPLANE, DataPlaneStatusCode, HTTP_OPERATION
import { getConnectionInfoFromConnectionString, generateSasToken } from '../shared/utils';
import { PortIsInUseError } from '../models/portIsInUseError';
import { CONNECTION_STRING_NAME_LIST } from '../../constants/browserStorage';
import { getActiveConnectionString } from '../../shared/utils/hubConnectionStringHelper';

export const DATAPLANE_CONTROLLER_ENDPOINT = `${CONTROLLER_API_ENDPOINT}${DATAPLANE}`;

Expand Down Expand Up @@ -39,7 +40,7 @@ export const request = async (endpoint: string, parameters: any) => { // tslint:

export const dataPlaneConnectionHelper = async () => {
const connectionStrings = await localStorage.getItem(CONNECTION_STRING_NAME_LIST);
const connectionString = connectionStrings && connectionStrings.split(',')[0];
const connectionString = getActiveConnectionString(connectionStrings);
const connectionInfo = getConnectionInfoFromConnectionString(connectionString);
if (!(connectionInfo && connectionInfo.hostName)) {
return;
Expand Down
14 changes: 9 additions & 5 deletions src/app/connectionStrings/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
* Licensed under the MIT License
**********************************************************/
import {
getConnectionStringAction,
getConnectionStringsAction,
deleteConnectionStringAction,
setConnectionStringsAction,
upsertConnectionStringAction
} from './actions';

describe('getConnectionStringAction', () => {
describe('getConnectionStringsAction', () => {
it('returns CONNECTION_STRINGS/DELETE action object', () => {
expect(getConnectionStringAction.started()).toEqual({
expect(getConnectionStringsAction.started()).toEqual({
payload: undefined,
type: 'CONNECTION_STRINGS/GET_STARTED'
});
Expand All @@ -38,8 +38,12 @@ describe('setConnectionStringAction', () => {

describe('upsertConnectionStringAction', () => {
it('returns CONNECTION_STRINGS/UPSERT action object', () => {
expect(upsertConnectionStringAction.started({ newConnectionString: 'new', connectionString: 'old'})).toEqual({
payload: { newConnectionString: 'new', connectionString: 'old'},
const stringWithExpiry = {
connectionString: 'connectionString1',
expiration: (new Date(0)).toUTCString()
};
expect(upsertConnectionStringAction.started(stringWithExpiry)).toEqual({
payload: stringWithExpiry,
type: 'CONNECTION_STRINGS/UPSERT_STARTED'
});
});
Expand Down
17 changes: 6 additions & 11 deletions src/app/connectionStrings/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,11 @@
**********************************************************/
import actionCreatorFactory from 'typescript-fsa';
import { DELETE, SET, UPSERT, GET } from '../constants/actionTypes';
import { ConnectionStringWithExpiry } from './state';

export const CONNECTION_STRINGS = 'CONNECTION_STRINGS';
export interface UpsertConnectionStringActionPayload {
newConnectionString: string;
connectionString?: string;
}
const actionCreator = actionCreatorFactory('CONNECTION_STRINGS');

const actionCreator = actionCreatorFactory(CONNECTION_STRINGS);

export const getConnectionStringAction = actionCreator.async<void, string[]>(GET);
export const setConnectionStringsAction = actionCreator.async<string[], string[]>(SET);
export const upsertConnectionStringAction = actionCreator.async<UpsertConnectionStringActionPayload, string[]>(UPSERT);
export const deleteConnectionStringAction = actionCreator.async<string, string[]>(DELETE);
export const getConnectionStringsAction = actionCreator.async<void, ConnectionStringWithExpiry[]>(GET);
export const setConnectionStringsAction = actionCreator.async<ConnectionStringWithExpiry[], ConnectionStringWithExpiry[]>(SET);
export const upsertConnectionStringAction = actionCreator.async<ConnectionStringWithExpiry, ConnectionStringWithExpiry[]>(UPSERT);
export const deleteConnectionStringAction = actionCreator.async<string, ConnectionStringWithExpiry[]>(DELETE);
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,21 @@ exports[`connectionString matches snapshot 1`] = `
readOnly={true}
value="HostName=test.azure-devices-int.net;SharedAccessKeyName=iothubowner;SharedAccessKey=key"
/>
<StyledLinkBase
onClick={[Function]}
style={
<div
className="expiration-warning"
>
connectionStrings.expirationWarning
</div>
<CustomizedActionButton
iconProps={
Object {
"marginTop": 10,
"iconName": "Forward",
}
}
title="connectionStrings.visitConnectionCommand.ariaLabel"
onClick={[Function]}
>
connectionStrings.visitConnectionCommand.label
</StyledLinkBase>
</CustomizedActionButton>
</div>
<Component
connectionString="HostName=test.azure-devices-int.net;SharedAccessKeyName=iothubowner;SharedAccessKey=key"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ConnectionStringsView matches snapshot when connection string count exceeds max 1`] = `
<div
className="view"
>
<div
className="view-command"
>
<StyledCommandBarBase
items={
Array [
Object {
"ariaLabel": "connectionStrings.addConnectionCommand.ariaLabel",
"disabled": true,
"iconProps": Object {
"iconName": "Add",
},
"key": "add",
"onClick": [Function],
"text": "connectionStrings.addConnectionCommand.label",
},
]
}
/>
</div>
<div
className="view-scroll-vertical"
>
<div
className="connection-strings"
/>
</div>
</div>
`;

exports[`ConnectionStringsView matches snapshot when connection strings present 1`] = `
<div
className="view"
Expand Down Expand Up @@ -65,7 +31,12 @@ exports[`ConnectionStringsView matches snapshot when connection strings present
className="connection-strings"
>
<Component
connectionString="connectionString1"
connectionStringWithExpiry={
Object {
"connectionString": "connectionString1",
"expiration": "Thu, 01 Jan 1970 00:00:00 GMT",
}
}
key="connectionString1"
onDeleteConnectionString={[Function]}
onEditConnectionString={[Function]}
Expand Down
6 changes: 5 additions & 1 deletion src/app/connectionStrings/components/connectionString.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
padding: 10px;
}

.expiration-warning {
@include themify($themes) {
color: themed('notificationWarningColor')
}
}
}

15 changes: 8 additions & 7 deletions src/app/connectionStrings/components/connectionString.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { ConnectionStringDelete } from './connectionStringDelete';

describe('connectionString', () => {
const connectionString = 'HostName=test.azure-devices-int.net;SharedAccessKeyName=iothubowner;SharedAccessKey=key';
const connectionStringWithExpiry = {connectionString, expiration: (new Date(0)).toUTCString()};
it('matches snapshot', () => {
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString: jest.fn(),
onEditConnectionString: jest.fn(),
onSelectConnectionString: jest.fn()
Expand All @@ -26,7 +27,7 @@ describe('connectionString', () => {
it('calls onSelectConnectionString when link clicked', () => {
const onSelectConnectionString = jest.fn();
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString: jest.fn(),
onEditConnectionString: jest.fn(),
onSelectConnectionString
Expand All @@ -41,7 +42,7 @@ describe('connectionString', () => {
it('calls onSelectConnectionString when convenience link clicked', () => {
const onSelectConnectionString = jest.fn();
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString: jest.fn(),
onEditConnectionString: jest.fn(),
onSelectConnectionString
Expand All @@ -56,7 +57,7 @@ describe('connectionString', () => {
it('calls onEditConnectionString when edit button clicked', () => {
const onEditConnectionString = jest.fn();
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString: jest.fn(),
onEditConnectionString,
onSelectConnectionString: jest.fn()
Expand All @@ -71,7 +72,7 @@ describe('connectionString', () => {
describe('delete scenario', () => {
it('launches delete confirmation when delete clicked', () => {
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString: jest.fn(),
onEditConnectionString: jest.fn(),
onSelectConnectionString: jest.fn()
Expand All @@ -88,7 +89,7 @@ describe('connectionString', () => {
it('calls onDeleteConnectionString when ConnectionStringDelete confirmed', () => {
const onDeleteConnectionString = jest.fn();
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString,
onEditConnectionString: jest.fn(),
onSelectConnectionString: jest.fn()
Expand All @@ -106,7 +107,7 @@ describe('connectionString', () => {

it('hides delete confirmation when ConnnectionStringDelete canceled', () => {
const props: ConnectionStringProps = {
connectionString,
connectionStringWithExpiry,
onDeleteConnectionString: jest.fn(),
onEditConnectionString: jest.fn(),
onSelectConnectionString: jest.fn()
Expand Down
22 changes: 15 additions & 7 deletions src/app/connectionStrings/components/connectionString.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
**********************************************************/
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { IconButton } from 'office-ui-fabric-react/lib/components/Button';
import { IconButton, ActionButton } from 'office-ui-fabric-react/lib/components/Button';
import { Link } from 'office-ui-fabric-react/lib/components/Link';
import { getConnectionInfoFromConnectionString } from '../../api/shared/utils';
import { getResourceNameFromHostName } from '../../api/shared/hostNameUtils';
Expand All @@ -13,17 +13,22 @@ import { ResourceKeys } from '../../../localization/resourceKeys';
import { ConnectionStringDelete } from './connectionStringDelete';
import { MaskedCopyableTextField } from '../../shared/components/maskedCopyableTextField';
import { EDIT, REMOVE } from '../../constants/iconNames';
import { ConnectionStringWithExpiry } from '../state';
import { CONNECTION_STRING_EXPIRATION_WARNING_IN_DAYS } from '../../constants/browserStorage';
import { getDaysBeforeHubConnectionStringExpires } from '../../shared/utils/hubConnectionStringHelper';
import './connectionString.scss';

export interface ConnectionStringProps {
connectionString: string;
connectionStringWithExpiry: ConnectionStringWithExpiry;
onEditConnectionString(connectionString: string): void;
onDeleteConnectionString(connectionString: string): void;
onSelectConnectionString(connectionString: string): void;
}

export const ConnectionString: React.FC<ConnectionStringProps> = props => {
const { connectionString, onEditConnectionString, onDeleteConnectionString, onSelectConnectionString } = props;
const { connectionStringWithExpiry, onEditConnectionString, onDeleteConnectionString, onSelectConnectionString } = props;
const connectionString = connectionStringWithExpiry.connectionString;
const daysTillExpire = getDaysBeforeHubConnectionStringExpires(connectionStringWithExpiry);
const connectionSettings = getConnectionInfoFromConnectionString(connectionString);
const { hostName, sharedAccessKey, sharedAccessKeyName } = connectionSettings;
const resourceName = getResourceNameFromHostName(hostName);
Expand Down Expand Up @@ -97,13 +102,16 @@ export const ConnectionString: React.FC<ConnectionStringProps> = props => {
value={connectionString}
readOnly={true}
/>
<Link
style={{marginTop: 10}}
{daysTillExpire <= CONNECTION_STRING_EXPIRATION_WARNING_IN_DAYS &&
<div className="expiration-warning">
{t(ResourceKeys.connectionStrings.expirationWarning, { numberOfDays: daysTillExpire})}
</div>}
<ActionButton
onClick={onSelectConnectionStringClick}
title={t(ResourceKeys.connectionStrings.visitConnectionCommand.ariaLabel, {connectionString})}
iconProps={{iconName: 'Forward'}}
>
{t(ResourceKeys.connectionStrings.visitConnectionCommand.label)}
</Link>
</ActionButton>
</div>
<ConnectionStringDelete
connectionString={connectionString}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('ConnectionStringEdit', () => {
it('disables commit when duplicate validation', () => {
const props: ConnectionStringEditViewProps = {
connectionStringUnderEdit: '',
connectionStrings: [connectionString],
connectionStrings: [{connectionString, expiration: (new Date(0)).toUTCString()}],
onCommit: jest.fn(),
onDismiss: jest.fn()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import { getConnectionInfoFromConnectionString } from '../../api/shared/utils';
import { generateConnectionStringValidationError } from '../../shared/utils/hubConnectionStringHelper';
import { IoTHubConnectionSettings } from '../../api/services/devicesService';
import { ResourceKeys } from '../../../localization/resourceKeys';
import { ConnectionStringWithExpiry } from '../state';
import './connectionStringEditView.scss';

const LINES_FOR_CONNECTION = 8;

export interface ConnectionStringEditViewProps {
connectionStringUnderEdit?: string;
connectionStrings: string[];
connectionStringUnderEdit: string;
connectionStrings: ConnectionStringWithExpiry[];
onDismiss(): void;
onCommit(newConnectionString: string): void;
}
Expand Down Expand Up @@ -59,9 +60,10 @@ export const ConnectionStringEditView: React.FC<ConnectionStringEditViewProps> =
}

// check for duplicates and validate || after setting values (so properties display)
validationKey = (connectionStrings.indexOf(updatedConnectionString) >= 0 && updatedConnectionString !== connectionStringUnderEdit) ?
ResourceKeys.connectionStrings.editConnection.validations.duplicate :
validationKey;
validationKey = (connectionStrings.filter(s => s.connectionString === updatedConnectionString).length > 0 &&
updatedConnectionString !== connectionStringUnderEdit) ?
ResourceKeys.connectionStrings.editConnection.validations.duplicate :
validationKey;

setConnectionStringValidationKey(validationKey);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export const ConnectionStringProperties: React.FC<ConnectionStringPropertiesProp
label={t(ResourceKeys.connectionStrings.properties.sharedAccessPolicyKey.label)}
value={sharedAccessKey}
readOnly={true}

/>
</>
);
Expand Down
Loading

0 comments on commit 808c23d

Please sign in to comment.