diff --git a/client/src/app/modals/deploy-diagram/AuthTypes.js b/client/src/app/modals/deploy-diagram/AuthTypes.js new file mode 100644 index 0000000000..56d2be4d34 --- /dev/null +++ b/client/src/app/modals/deploy-diagram/AuthTypes.js @@ -0,0 +1,7 @@ +const AuthTypes = { + none: 'none', + basic: 'basic', + bearer: 'bearer' +}; + +export default AuthTypes; diff --git a/client/src/app/modals/deploy-diagram/DeployDiagramModal.js b/client/src/app/modals/deploy-diagram/DeployDiagramModal.js index db9c281ae0..d3f8750995 100644 --- a/client/src/app/modals/deploy-diagram/DeployDiagramModal.js +++ b/client/src/app/modals/deploy-diagram/DeployDiagramModal.js @@ -1,6 +1,7 @@ import React from 'react'; import View from './View'; +import AuthTypes from './AuthTypes'; import errorMessageFunctions from './error-messages'; @@ -15,6 +16,17 @@ const defaultState = { error: '' }; +const initialFormValues = { + endpointUrl: '', + tenantId: '', + deploymentName: '', + authType: 'none', + username: '', + password: '', + bearer: '' +}; + + class DeployDiagramModal extends React.Component { constructor(props) { super(props); @@ -69,8 +81,34 @@ class DeployDiagramModal extends React.Component { } } + validateUsername = username => { + if (!username.length) { + return 'Username must not be void.'; + } + } + + validatePassword = password => { + if (!password.length) { + return 'Password must not be void.'; + } + } + + validateBearer = bearer => { + if (!bearer.length) { + return 'Token must not be void.'; + } + } + render() { const { endpoints } = this.props; + const validators = { + endpointUrl: this.validateEndpointUrl, + deploymentName: this.validateDeploymentName, + username: this.validateUsername, + password: this.validatePassword, + bearer: this.validateBearer + }; + return ; } @@ -101,6 +137,12 @@ class DeployDiagramModal extends React.Component { tenantId: values.tenantId }; + const auth = this.getAuth(values); + + if (auth) { + payload.auth = auth; + } + return payload; } @@ -120,6 +162,21 @@ class DeployDiagramModal extends React.Component { return url; } + getAuth({ authType, username, password, bearer }) { + switch (authType) { + case AuthTypes.basic: + return { + username, + password + }; + case AuthTypes.bearer: { + return { + bearer + }; + } + } + } + getErrorMessage(error) { for (const getMessage of errorMessageFunctions) { const errorMessage = getMessage(error); diff --git a/client/src/app/modals/deploy-diagram/View.js b/client/src/app/modals/deploy-diagram/View.js index 49f3d0a0e4..c650e7387f 100644 --- a/client/src/app/modals/deploy-diagram/View.js +++ b/client/src/app/modals/deploy-diagram/View.js @@ -13,6 +13,8 @@ import { ModalWrapper } from '../../primitives'; +import AuthTypes from './AuthTypes'; + import css from './View.less'; @@ -27,8 +29,7 @@ const View = (props) => { initialValues, onClose, onDeploy, - validateEndpointUrl, - validateDeploymentName + validators } = props; return ( @@ -44,7 +45,7 @@ const View = (props) => { initialValues={ initialValues } onSubmit={ onDeploy } > - {({ errors, isSubmitting, submitCount, touched }) => ( + {({ isSubmitting, values }) => ( { isSubmitting && } @@ -54,55 +55,45 @@ const View = (props) => { { error && } - - Endpoint URL - - - - - { errors.endpointUrl && touched.endpointUrl ? ( - {errors.endpointUrl} - ) : null} - - - Should point to a running Camunda Engine REST API endpoint. - - + + + + + - Deployment name + Auth type - - - { errors.deploymentName && touched.deploymentName ? ( - {errors.deploymentName} - ) : null} + + None + HTTP Basic + Bearer token + - - Tenant id (optional) - + { values.authType === AuthTypes.basic && } - - - + { values.authType === AuthTypes.bearer && } { ); }; +function FormControl({ + field, + hint, + label, + validated, + form: { touched, errors, submitCount, isSubmitting }, + ...props +}) { + const { name } = field; + + return ( + + + { label } + + + + + + { errors[name] && touched[name] ? ( + {errors[name]} + ) : null} + + { hint ? ( + { hint } + ) : null } + + + ); +} + function DeployError({ message }) { return ( @@ -153,4 +182,42 @@ function DeploySuccess({ message }) { ); } +function AuthBasic({ validators, ...props }) { + return ( + + + + + + ); +} + +function AuthBearer({ validators, ...props }) { + return ( + + ); +} + export default View; diff --git a/client/src/app/modals/deploy-diagram/View.less b/client/src/app/modals/deploy-diagram/View.less index a959ec5fd4..177ff19e2a 100644 --- a/client/src/app/modals/deploy-diagram/View.less +++ b/client/src/app/modals/deploy-diagram/View.less @@ -74,7 +74,8 @@ display: inline-block; } - input { + input, + select { width: 100%; padding: 6px; @@ -108,7 +109,8 @@ } button:disabled, - input:disabled { + input:disabled, + select:disabled { color: #808080; } diff --git a/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js b/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js index e6db0d29ab..7b4959f488 100644 --- a/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js +++ b/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js @@ -9,6 +9,7 @@ import { import { DeployDiagramModal } from '..'; import View from '../View'; +import AuthTypes from '../AuthTypes'; const MOCK_ENDPOINT_URL = 'http://example.com/deployment/create'; @@ -337,6 +338,116 @@ describe('', function() { }); + describe('authentication', function() { + + it('should not pass auth option when no auth method was chosen', async function() { + // given + const endpointUrl = 'http://example.com/', + deploymentName = 'deploymentName'; + + const onDeployStub = sinon.stub().resolves(); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + + // when + await instance.handleDeploy({ + endpointUrl, + deploymentName + }, { + setSubmitting: sinon.spy() + }); + + // expect + expect(onDeployStub).to.be.calledOnce; + + const payload = onDeployStub.getCall(0).args[0]; + + expect(payload).to.not.have.property('auth'); + }); + + + it('should pass username and password when authenticating with Basic', async function() { + // given + const endpointUrl = 'http://example.com/', + deploymentName = 'deploymentName', + username = 'username', + password = 'password', + authType = AuthTypes.basic; + + const onDeployStub = sinon.stub().resolves(); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + + // when + await instance.handleDeploy({ + endpointUrl, + deploymentName, + username, + password, + authType + }, { + setSubmitting: sinon.spy() + }); + + // expect + expect(onDeployStub).to.be.calledOnce; + + const payload = onDeployStub.getCall(0).args[0]; + + expect(payload).to.have.property('auth'); + expect(payload.auth).to.have.property('username').eql(username); + expect(payload.auth).to.have.property('password').eql(password); + }); + + + it('should pass token when authenticating with Bearer', async function() { + // given + const endpointUrl = 'http://example.com/', + deploymentName = 'deploymentName', + bearer = 'bearer', + authType = AuthTypes.bearer; + + const onDeployStub = sinon.stub().resolves(); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + + // when + await instance.handleDeploy({ + endpointUrl, + deploymentName, + bearer, + authType + }, { + setSubmitting: sinon.spy() + }); + + // expect + expect(onDeployStub).to.be.calledOnce; + + const payload = onDeployStub.getCall(0).args[0]; + + expect(payload).to.have.property('auth'); + expect(payload.auth).to.have.property('bearer').eql(bearer); + }); + + }); + + describe('', function() { it('should render', function() { @@ -346,7 +457,7 @@ describe('', function() { it('should render error message', function() { // given - const wrapper = mount(); + const wrapper = mount(); // then expect(wrapper.find('.deploy-message.error')).to.have.lengthOf(1); @@ -357,7 +468,7 @@ describe('', function() { it('should render success message', function() { // given - const wrapper = mount(); + const wrapper = mount(); // then expect(wrapper.find('.deploy-message.success')).to.have.lengthOf(1);