diff --git a/.eslintrc.js b/.eslintrc.js
index 6203ad10c9..93c132eb86 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -8,7 +8,8 @@ module.exports = {
],
env: {
'node': true,
- 'mocha': true
+ 'mocha': true,
+ 'browser': true
},
globals: {
'expect': false,
diff --git a/frontend/components/UserBlock/helpers.js b/frontend/components/UserBlock/helpers.js
index 884eb83ee1..cf5a6ca42c 100644
--- a/frontend/components/UserBlock/helpers.js
+++ b/frontend/components/UserBlock/helpers.js
@@ -11,12 +11,15 @@ const userActionOptions = (isCurrentUser, user, invite) => {
if (invite) return inviteActions;
- return [
+ const result = [
userEnableAction,
userPromotionAction,
- { disabled: false, label: 'Require Password Reset', value: 'reset_password' },
- { disabled: false, label: 'Modify Details', value: 'modify_details' },
];
+ if (!user.sso_enabled) {
+ result.push({ disabled: false, label: 'Require Password Reset', value: 'reset_password' });
+ }
+ result.push({ disabled: false, label: 'Modify Details', value: 'modify_details' });
+ return result;
};
const userStatusLabel = (user, invite) => {
diff --git a/frontend/components/forms/ConfirmSSOInviteForm/ConfirmSSOInviteForm.jsx b/frontend/components/forms/ConfirmSSOInviteForm/ConfirmSSOInviteForm.jsx
new file mode 100644
index 0000000000..b103f7d628
--- /dev/null
+++ b/frontend/components/forms/ConfirmSSOInviteForm/ConfirmSSOInviteForm.jsx
@@ -0,0 +1,54 @@
+import React, { Component, PropTypes } from 'react';
+
+import Form from 'components/forms/Form';
+import formFieldInterface from 'interfaces/form_field';
+import Button from 'components/buttons/Button';
+import InputFieldWithIcon from 'components/forms/fields/InputFieldWithIcon';
+import helpers from './helpers';
+
+const formFields = ['name', 'username', 'password', 'password_confirmation'];
+const { validate } = helpers;
+
+class ConfirmSSOInviteForm extends Component {
+ static propTypes = {
+ baseError: PropTypes.string,
+ className: PropTypes.string,
+ fields: PropTypes.shape({
+ name: formFieldInterface.isRequired,
+ username: formFieldInterface.isRequired,
+ password: formFieldInterface.isRequired,
+ password_confirmation: formFieldInterface.isRequired,
+ }).isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+ };
+
+ render () {
+ const { baseError, className, fields, handleSubmit } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+export default Form(ConfirmSSOInviteForm, {
+ fields: formFields,
+ validate,
+});
diff --git a/frontend/components/forms/ConfirmSSOInviteForm/ConfirmSSOInviteForm.tests.jsx b/frontend/components/forms/ConfirmSSOInviteForm/ConfirmSSOInviteForm.tests.jsx
new file mode 100644
index 0000000000..2b456d109a
--- /dev/null
+++ b/frontend/components/forms/ConfirmSSOInviteForm/ConfirmSSOInviteForm.tests.jsx
@@ -0,0 +1,122 @@
+import React from 'react';
+import expect, { createSpy, restoreSpies } from 'expect';
+import { mount } from 'enzyme';
+import { noop } from 'lodash';
+
+import ConfirmInviteForm from 'components/forms/ConfirmInviteForm';
+import { fillInFormInput } from 'test/helpers';
+
+describe('ConfirmInviteForm - component', () => {
+ afterEach(restoreSpies);
+
+ const handleSubmitSpy = createSpy();
+ const inviteToken = 'abc123';
+ const formData = { invite_token: inviteToken };
+ const form = mount();
+
+ const nameInput = form.find({ name: 'name' }).find('input');
+ const passwordConfirmationInput = form.find({ name: 'password_confirmation' }).find('input');
+ const passwordInput = form.find({ name: 'password' }).find('input');
+ const submitBtn = form.find('button');
+ const usernameInput = form.find({ name: 'username' }).find('input');
+
+ it('renders', () => {
+ expect(form.length).toEqual(1);
+ });
+
+ it('renders the base error', () => {
+ const baseError = 'Unable to authenticate the current user';
+ const formWithError = mount();
+ const formWithoutError = mount();
+
+ expect(formWithError.text()).toInclude(baseError);
+ expect(formWithoutError.text()).toNotInclude(baseError);
+ });
+
+ it('calls the handleSubmit prop with the invite_token when valid', () => {
+ fillInFormInput(nameInput, 'Gnar Dog');
+ fillInFormInput(usernameInput, 'gnardog');
+ fillInFormInput(passwordInput, 'p@ssw0rd');
+ fillInFormInput(passwordConfirmationInput, 'p@ssw0rd');
+ submitBtn.simulate('click');
+
+ expect(handleSubmitSpy).toHaveBeenCalledWith({
+ ...formData,
+ name: 'Gnar Dog',
+ username: 'gnardog',
+ password: 'p@ssw0rd',
+ password_confirmation: 'p@ssw0rd',
+ });
+ });
+
+ describe('name input', () => {
+ it('changes form state on change', () => {
+ fillInFormInput(nameInput, 'Gnar Dog');
+
+ expect(form.state().formData).toInclude({ name: 'Gnar Dog' });
+ });
+
+ it('validates the field must be present', () => {
+ fillInFormInput(nameInput, '');
+ form.find('button').simulate('click');
+
+ expect(form.state().errors).toInclude({ name: 'Full name must be present' });
+ });
+ });
+
+ describe('username input', () => {
+ it('changes form state on change', () => {
+ fillInFormInput(usernameInput, 'gnardog');
+
+ expect(form.state().formData).toInclude({ username: 'gnardog' });
+ });
+
+ it('validates the field must be present', () => {
+ fillInFormInput(usernameInput, '');
+ submitBtn.simulate('click');
+
+ expect(form.state().errors).toInclude({ username: 'Username must be present' });
+ });
+ });
+
+ describe('password input', () => {
+ it('changes form state on change', () => {
+ fillInFormInput(passwordInput, 'p@ssw0rd');
+
+ expect(form.state().formData).toInclude({ password: 'p@ssw0rd' });
+ });
+
+ it('validates the field must be present', () => {
+ fillInFormInput(passwordInput, '');
+ form.find('button').simulate('click');
+
+ expect(form.state().errors).toInclude({ password: 'Password must be present' });
+ });
+ });
+
+ describe('password_confirmation input', () => {
+ it('changes form state on change', () => {
+ fillInFormInput(passwordConfirmationInput, 'p@ssw0rd');
+
+ expect(form.state().formData).toInclude({ password_confirmation: 'p@ssw0rd' });
+ });
+
+ it('validates the password_confirmation matches the password', () => {
+ fillInFormInput(passwordInput, 'p@ssw0rd');
+ fillInFormInput(passwordConfirmationInput, 'another-password');
+ form.find('button').simulate('click');
+
+ expect(form.state().errors).toInclude({
+ password_confirmation: 'Password confirmation does not match password',
+ });
+ });
+
+ it('validates the field must be present', () => {
+ fillInFormInput(passwordConfirmationInput, '');
+ form.find('button').simulate('click');
+
+ expect(form.state().errors).toInclude({ password_confirmation: 'Password confirmation must be present' });
+ });
+ });
+});
+
diff --git a/frontend/components/forms/ConfirmSSOInviteForm/helpers.js b/frontend/components/forms/ConfirmSSOInviteForm/helpers.js
new file mode 100644
index 0000000000..62d7193efc
--- /dev/null
+++ b/frontend/components/forms/ConfirmSSOInviteForm/helpers.js
@@ -0,0 +1,23 @@
+import { size } from 'lodash';
+
+const validate = (formData) => {
+ const errors = {};
+ const {
+ name,
+ username,
+ } = formData;
+
+ if (!name) {
+ errors.name = 'Full name must be present';
+ }
+
+ if (!username) {
+ errors.username = 'Username must be present';
+ }
+
+ const valid = !size(errors);
+
+ return { valid, errors };
+};
+
+export default { validate };
diff --git a/frontend/components/forms/ConfirmSSOInviteForm/index.js b/frontend/components/forms/ConfirmSSOInviteForm/index.js
new file mode 100644
index 0000000000..4a9113f3cf
--- /dev/null
+++ b/frontend/components/forms/ConfirmSSOInviteForm/index.js
@@ -0,0 +1 @@
+export default from './ConfirmSSOInviteForm';
diff --git a/frontend/components/forms/InviteUserForm/InviteUserForm.jsx b/frontend/components/forms/InviteUserForm/InviteUserForm.jsx
index ad3ded2b49..d6c7733769 100644
--- a/frontend/components/forms/InviteUserForm/InviteUserForm.jsx
+++ b/frontend/components/forms/InviteUserForm/InviteUserForm.jsx
@@ -18,6 +18,7 @@ class InviteUserForm extends Component {
invitedBy: userInterface,
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
+ canUseSSO: PropTypes.bool,
};
constructor (props) {
@@ -28,11 +29,13 @@ class InviteUserForm extends Component {
admin: null,
email: null,
name: null,
+ sso_enabled: null,
},
formData: {
admin: false,
email: '',
name: '',
+ sso_enabled: false,
},
};
}
@@ -80,14 +83,14 @@ class InviteUserForm extends Component {
const valid = this.validate();
if (valid) {
- const { formData: { admin, email, name } } = this.state;
+ const { formData: { admin, email, name, sso_enabled } } = this.state;
const { invitedBy, onSubmit } = this.props;
-
return onSubmit({
admin,
email,
invited_by: invitedBy.id,
name,
+ sso_enabled,
});
}
@@ -126,7 +129,7 @@ class InviteUserForm extends Component {
}
render () {
- const { errors, formData: { admin, email, name } } = this.state;
+ const { errors, formData: { admin, email, name, ssoEnabled } } = this.state;
const { onCancel, serverErrors } = this.props;
const { onFormSubmit, onInputChange, onCheckboxChange } = this;
const baseError = serverErrors.base;
@@ -162,6 +165,19 @@ class InviteUserForm extends Component {
Enable Admin
+
+
single sign on
+
+ Enable Single Sign On
+
+
+
-