Skip to content

Commit

Permalink
feat(credentials): Manage target JMX credentials (#389)
Browse files Browse the repository at this point in the history
* Add static layout for credentials table

* Post/delete credentials to backend

* Swap column order

* Refactor SecurityPanel cards

* Populate table by notifications

* Remove delete button on modal

* Fix wording

* Fix api version

* Fix ids and console warnings

* Enforce required target select
  • Loading branch information
Janelle Law authored Mar 16, 2022
1 parent 12e9691 commit 4d04a38
Show file tree
Hide file tree
Showing 8 changed files with 637 additions and 104 deletions.
78 changes: 8 additions & 70 deletions src/app/AppLayout/AuthModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,54 +36,27 @@
* SOFTWARE.
*/
import * as React from 'react';
import { first } from 'rxjs/operators';
import { ActionGroup, Button, Form, FormGroup, Modal, ModalVariant, TextInput } from '@patternfly/react-core';
import { Modal, ModalVariant } from '@patternfly/react-core';
import { JmxAuthForm } from './JmxAuthForm';
import { ServiceContext } from '@app/Shared/Services/Services';
import { first } from 'rxjs';

export interface AuthModalProps {
visible: boolean;
onDismiss: () => void;
onSave: () => void;
}

const EnterKeyCode = 13;

export const AuthModal: React.FunctionComponent<AuthModalProps> = (props) => {
const context = React.useContext(ServiceContext);
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');

const clear = () => {
setUsername('');
setPassword('');
};

const handleSave = () => {
context.target.target().pipe(first()).subscribe(target => {
context.target.setCredentials(target.connectUrl, `${username}:${password}`);
context.target.setAuthRetry();
clear();
props.onSave();
});
};

const handleDeleteCredentials = () => {
context.target.target().pipe(first()).subscribe(target => {
context.target.deleteCredentials(target.connectUrl);
clear();
props.onSave();
});
};

const handleDismiss = () => {
clear();
props.onDismiss();
};

const handleKeyUp = (event: React.KeyboardEvent): void => {
if (event.keyCode === EnterKeyCode) {
handleSave();
}
const onSave = (username: string, password: string) => {
context.api.postTargetCredentials(username, password).pipe(first()).subscribe();
props.onSave();
};

return (
Expand All @@ -95,42 +68,7 @@ export const AuthModal: React.FunctionComponent<AuthModalProps> = (props) => {
title="Authentication Required"
description="This target JVM requires authentication. The credentials you provide here will be passed from Cryostat to the target when establishing JMX connections."
>
<Form>
<FormGroup
isRequired
label="Username"
fieldId="username"
>
<TextInput
value={username}
isRequired
type="text"
id="username"
onChange={setUsername}
onKeyUp={handleKeyUp}
autoFocus
/>
</FormGroup>
<FormGroup
isRequired
label="Password"
fieldId="password"
>
<TextInput
value={password}
isRequired
type="password"
id="password"
onChange={setPassword}
onKeyUp={handleKeyUp}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={handleSave}>Save</Button>
<Button variant="danger" onClick={handleDeleteCredentials}>Delete</Button>
<Button variant="secondary" onClick={handleDismiss}>Cancel</Button>
</ActionGroup>
</Form>
<JmxAuthForm onSave={onSave} onDismiss={handleDismiss} />
</Modal>
);
}
};
116 changes: 116 additions & 0 deletions src/app/AppLayout/JmxAuthForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import * as React from 'react';
import { first } from 'rxjs/operators';
import { ActionGroup, Button, Form, FormGroup, Modal, ModalVariant, TextInput } from '@patternfly/react-core';
import { ServiceContext } from '@app/Shared/Services/Services';

export interface JmxAuthFormProps {
onDismiss: () => void;
onSave: (username: string, password: string) => void;
}

const EnterKeyCode = 13;

export const JmxAuthForm: React.FunctionComponent<JmxAuthFormProps> = (props) => {
const context = React.useContext(ServiceContext);
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');

const clear = () => {
setUsername('');
setPassword('');
};

const handleSave = () => {
context.target
.target()
.pipe(first())
.subscribe((target) => {
context.target.setCredentials(target.connectUrl, `${username}:${password}`);
context.target.setAuthRetry();
clear();
props.onSave(username, password);
});
};

const handleDismiss = () => {
clear();
props.onDismiss();
};

const handleKeyUp = (event: React.KeyboardEvent): void => {
if (event.keyCode === EnterKeyCode) {
handleSave();
}
};

return (
<Form>
<FormGroup isRequired label="Username" fieldId="username">
<TextInput
value={username}
isRequired
type="text"
id="username"
onChange={setUsername}
onKeyUp={handleKeyUp}
autoFocus
/>
</FormGroup>
<FormGroup isRequired label="Password" fieldId="password">
<TextInput
value={password}
isRequired
type="password"
id="password"
onChange={setPassword}
onKeyUp={handleKeyUp}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={handleSave}>
Save
</Button>
<Button variant="secondary" onClick={handleDismiss}>
Cancel
</Button>
</ActionGroup>
</Form>
);
};
96 changes: 96 additions & 0 deletions src/app/SecurityPanel/CreateJmxCredentialModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import * as React from 'react';
import { Form, FormGroup, Modal, ModalVariant, ValidatedOptions } from '@patternfly/react-core';
import { JmxAuthForm } from '@app/AppLayout/JmxAuthForm';
import { TargetSelect } from '@app/TargetSelect/TargetSelect';
import { NO_TARGET } from '@app/Shared/Services/Target.service';
import { ServiceContext } from '@app/Shared/Services/Services';
import { first } from 'rxjs';

export interface CreateJmxCredentialModalProps {
visible: boolean;
onClose: () => void;
}

export const CreateJmxCredentialModal: React.FunctionComponent<CreateJmxCredentialModalProps> = (props) => {
const context = React.useContext(ServiceContext);
const [validTarget, setValidTarget] = React.useState(ValidatedOptions.default);

const onSave = React.useCallback((username: string, password: string) => {
let isValid;
context.target.target().subscribe((t) => {
isValid = t == NO_TARGET ? ValidatedOptions.error : ValidatedOptions.success;
setValidTarget(isValid);
});

if (isValid == ValidatedOptions.success) {
context.api.postTargetCredentials(username, password)
.pipe(first())
.subscribe();
props.onClose();
}
}, [props.onClose, context, context.target, context.api, validTarget, setValidTarget]);

return (
<Modal
isOpen={props.visible}
variant={ModalVariant.large}
showClose={true}
onClose={props.onClose}
title="Store JMX Credentials"
description="Creates stored credentials for a given target.
If a Target JVM requires JMX authentication, Cryostat will use stored credentials
when attempting to open JMX connections to the target."
>
<Form>
<FormGroup
label="Target"
isRequired
fieldId="target-select"
helperTextInvalid="Select a target"
validated={validTarget}
>
<TargetSelect />
</FormGroup>
</Form>
<br/>
<JmxAuthForm onSave={onSave} onDismiss={props.onClose} />
</Modal>
);
};
65 changes: 65 additions & 0 deletions src/app/SecurityPanel/ImportCertificate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import * as React from 'react';
import { Button } from '@patternfly/react-core';
import { CertificateUploadModal } from './CertificateUploadModal';
import { SecurityCard } from './SecurityPanel';

const Component = () => {
const [showModal, setShowModal] = React.useState(false);

const handleModalClose = () => {
setShowModal(false);
};

return (
<>
<Button variant="primary" aria-label="import" onClick={() => setShowModal(true)}>
Upload
</Button>
<CertificateUploadModal visible={showModal} onClose={handleModalClose} />
</>
);
};

export const ImportCertificate: SecurityCard = {
title: 'Import SSL Certificates',
description: 'Restart is needed to apply changes.',
content: Component,
};
Loading

0 comments on commit 4d04a38

Please sign in to comment.