-
Notifications
You must be signed in to change notification settings - Fork 235
feat(connect-form): add kerberos authentication #2718
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
Changes from all commits
5d9dea7
11d71c7
1e06f0a
6505f19
5839b2a
59ce2c7
34de024
9295be9
df5e119
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -156,12 +156,12 @@ describe('AuthenticationDefault Component', function () { | |
it('decodes the password as a uri component before rendering', function () { | ||
renderComponent({ | ||
connectionStringUrl: new ConnectionStringUrl( | ||
'mongodb://C%3BIb86n5b8%7BAnExew%5BTU%25XZy%2C)E6G!dk:password@outerspace:12345' | ||
'mongodb://username:C%3BIb86n5b8%7BAnExew%5BTU%25XZy%2C)E6G!dk@outerspace:12345' | ||
), | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
|
||
expect(screen.getByLabelText('Username').getAttribute('value')).to.equal( | ||
expect(screen.getByLabelText('Password').getAttribute('value')).to.equal( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah thanks for catching. |
||
'C;Ib86n5b8{AnExew[TU%XZy,)E6G!dk' | ||
); | ||
}); | ||
|
@@ -171,12 +171,26 @@ describe('AuthenticationDefault Component', function () { | |
errors: [ | ||
{ | ||
fieldName: 'username', | ||
message: 'pineapples', | ||
message: 'username error', | ||
}, | ||
], | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
|
||
expect(screen.getByText('username error')).to.be.visible; | ||
}); | ||
|
||
it('renders a password error when there is a password error', function () { | ||
renderComponent({ | ||
errors: [ | ||
{ | ||
fieldName: 'password', | ||
message: 'password error', | ||
}, | ||
], | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
|
||
expect(screen.getByText('pineapples')).to.be.visible; | ||
expect(screen.getByText('password error')).to.be.visible; | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import React from 'react'; | ||
import { render, screen, fireEvent } from '@testing-library/react'; | ||
import { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
import ConnectionStringUrl from 'mongodb-connection-string-url'; | ||
|
||
import AuthenticationGssapi from './authentication-gssapi'; | ||
import { ConnectionFormError } from '../../../utils/validation'; | ||
import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; | ||
|
||
function renderComponent({ | ||
errors = [], | ||
connectionStringUrl = new ConnectionStringUrl('mongodb://localhost:27017'), | ||
updateConnectionFormField, | ||
}: { | ||
connectionStringUrl?: ConnectionStringUrl; | ||
errors?: ConnectionFormError[]; | ||
updateConnectionFormField: UpdateConnectionFormField; | ||
}) { | ||
render( | ||
<AuthenticationGssapi | ||
errors={errors} | ||
connectionStringUrl={connectionStringUrl} | ||
updateConnectionFormField={updateConnectionFormField} | ||
/> | ||
); | ||
} | ||
|
||
describe('AuthenticationGssapi Component', function () { | ||
let updateConnectionFormFieldSpy: sinon.SinonSpy; | ||
beforeEach(function () { | ||
updateConnectionFormFieldSpy = sinon.spy(); | ||
}); | ||
|
||
describe('when the kerberosPrincipal input is changed', function () { | ||
beforeEach(function () { | ||
renderComponent({ | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(0); | ||
|
||
fireEvent.change(screen.getByLabelText('Principal'), { | ||
target: { value: 'good sandwich' }, | ||
}); | ||
}); | ||
|
||
it('calls to update the form field', function () { | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(1); | ||
expect(updateConnectionFormFieldSpy.firstCall.args[0]).to.deep.equal({ | ||
type: 'update-username', | ||
username: 'good sandwich', | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the serviceName input is changed', function () { | ||
beforeEach(function () { | ||
renderComponent({ | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(0); | ||
|
||
fireEvent.change(screen.getByLabelText('Service Name'), { | ||
target: { value: 'good sandwich' }, | ||
}); | ||
}); | ||
|
||
it('calls to update the form field', function () { | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(1); | ||
expect(updateConnectionFormFieldSpy.firstCall.args[0]).to.deep.equal({ | ||
key: 'SERVICE_NAME', | ||
type: 'update-auth-mechanism-property', | ||
value: 'good sandwich', | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the serviceRealm input is changed', function () { | ||
beforeEach(function () { | ||
renderComponent({ | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(0); | ||
|
||
fireEvent.change(screen.getByLabelText('Service Realm'), { | ||
target: { value: 'good sandwich' }, | ||
}); | ||
}); | ||
|
||
it('calls to update the form field', function () { | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(1); | ||
expect(updateConnectionFormFieldSpy.firstCall.args[0]).to.deep.equal({ | ||
key: 'SERVICE_REALM', | ||
type: 'update-auth-mechanism-property', | ||
value: 'good sandwich', | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the canoncalize hostname is changed', function () { | ||
beforeEach(function () { | ||
renderComponent({ | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
|
||
expect(updateConnectionFormFieldSpy.callCount).to.equal(0); | ||
const checkbox = screen.getByLabelText('Canonicalize Host Name'); | ||
fireEvent.click(checkbox); | ||
}); | ||
|
||
it('calls to update the form field', function () { | ||
expect(updateConnectionFormFieldSpy.callCount).to.equal(1); | ||
expect(updateConnectionFormFieldSpy.firstCall.args[0]).to.deep.equal({ | ||
key: 'CANONICALIZE_HOST_NAME', | ||
type: 'update-auth-mechanism-property', | ||
value: 'true', | ||
}); | ||
}); | ||
}); | ||
|
||
it('renders an error when there is a kerberosPrincipal error', function () { | ||
renderComponent({ | ||
errors: [ | ||
{ | ||
fieldName: 'kerberosPrincipal', | ||
message: 'kerberosPrincipal error', | ||
}, | ||
], | ||
updateConnectionFormField: updateConnectionFormFieldSpy, | ||
}); | ||
|
||
expect(screen.getByText('kerberosPrincipal error')).to.be.visible; | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,105 @@ | ||
import React from 'react'; | ||
import { Checkbox, TextInput } from '@mongodb-js/compass-components'; | ||
|
||
import ConnectionStringUrl from 'mongodb-connection-string-url'; | ||
import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; | ||
import FormFieldContainer from '../../form-field-container'; | ||
import { | ||
ConnectionFormError, | ||
errorMessageByFieldName, | ||
} from '../../../utils/validation'; | ||
import { | ||
getConnectionStringUsername, | ||
parseAuthMechanismProperties, | ||
} from '../../../utils/connection-string-helpers'; | ||
|
||
function AuthenticationGSSAPI({ | ||
errors, | ||
connectionStringUrl, | ||
updateConnectionFormField, | ||
}: { | ||
connectionStringUrl: ConnectionStringUrl; | ||
errors: ConnectionFormError[]; | ||
updateConnectionFormField: UpdateConnectionFormField; | ||
}): React.ReactElement { | ||
const kerberosPrincipalError = errorMessageByFieldName( | ||
errors, | ||
'kerberosPrincipal' | ||
); | ||
const principal = getConnectionStringUsername(connectionStringUrl); | ||
const authMechanismProperties = | ||
parseAuthMechanismProperties(connectionStringUrl); | ||
const serviceName = authMechanismProperties.get('SERVICE_NAME'); | ||
const serviceRealm = authMechanismProperties.get('SERVICE_REALM'); | ||
const canonicalizeHostname = authMechanismProperties.get( | ||
'CANONICALIZE_HOST_NAME' | ||
); | ||
|
||
function AuthenticationGSSAPI(): React.ReactElement { | ||
return ( | ||
<> | ||
<p>Kerberos</p> | ||
<FormFieldContainer> | ||
<TextInput | ||
onChange={({ | ||
target: { value }, | ||
}: React.ChangeEvent<HTMLInputElement>) => { | ||
updateConnectionFormField({ | ||
type: 'update-username', | ||
username: value, | ||
}); | ||
}} | ||
label="Principal" | ||
errorMessage={kerberosPrincipalError} | ||
state={kerberosPrincipalError ? 'error' : undefined} | ||
value={principal || ''} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously we had an info link to https://docs.mongodb.com/manual/core/kerberos/#principals There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah that's a good one, yeah, i'll merge this and add it right away |
||
/> | ||
</FormFieldContainer> | ||
|
||
<FormFieldContainer> | ||
<TextInput | ||
onChange={({ | ||
target: { value }, | ||
}: React.ChangeEvent<HTMLInputElement>) => { | ||
updateConnectionFormField({ | ||
type: 'update-auth-mechanism-property', | ||
key: 'SERVICE_NAME', | ||
value: value, | ||
}); | ||
}} | ||
label="Service Name" | ||
value={serviceName || ''} | ||
/> | ||
</FormFieldContainer> | ||
|
||
<FormFieldContainer> | ||
<Checkbox | ||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { | ||
updateConnectionFormField({ | ||
type: 'update-auth-mechanism-property', | ||
key: 'CANONICALIZE_HOST_NAME', | ||
value: event.target.checked ? 'true' : '', | ||
}); | ||
}} | ||
label="Canonicalize Host Name" | ||
checked={canonicalizeHostname === 'true'} | ||
bold={false} | ||
/> | ||
</FormFieldContainer> | ||
|
||
<FormFieldContainer> | ||
<TextInput | ||
onChange={({ | ||
target: { value }, | ||
}: React.ChangeEvent<HTMLInputElement>) => { | ||
updateConnectionFormField({ | ||
type: 'update-auth-mechanism-property', | ||
key: 'SERVICE_REALM', | ||
value: value, | ||
}); | ||
}} | ||
label="Service Realm" | ||
value={serviceRealm || ''} | ||
/> | ||
</FormFieldContainer> | ||
</> | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙌