Adding LDAP test connection button. Reordering LDAP settings. (#3912)

This commit is contained in:
Christopher Speller
2016-09-01 17:05:20 -04:00
committed by GitHub
parent 949e57076a
commit 5d7f239962
11 changed files with 272 additions and 53 deletions

View File

@@ -42,6 +42,7 @@ func InitAdmin() {
BaseRoutes.Admin.Handle("/reset_mfa", ApiAdminSystemRequired(adminResetMfa)).Methods("POST")
BaseRoutes.Admin.Handle("/reset_password", ApiAdminSystemRequired(adminResetPassword)).Methods("POST")
BaseRoutes.Admin.Handle("/ldap_sync_now", ApiAdminSystemRequired(ldapSyncNow)).Methods("POST")
BaseRoutes.Admin.Handle("/ldap_test", ApiAdminSystemRequired(ldapTest)).Methods("POST")
BaseRoutes.Admin.Handle("/saml_metadata", ApiAppHandler(samlMetadata)).Methods("GET")
BaseRoutes.Admin.Handle("/add_certificate", ApiAdminSystemRequired(addCertificate)).Methods("POST")
BaseRoutes.Admin.Handle("/remove_certificate", ApiAdminSystemRequired(removeCertificate)).Methods("POST")
@@ -643,7 +644,7 @@ func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
ldapI.SyncNow()
} else {
l4g.Error("%v", model.NewLocAppError("saveComplianceReport", "ent.compliance.licence_disable.app_error", nil, "").Error())
l4g.Error("%v", model.NewLocAppError("ldapSyncNow", "ent.ldap.disabled.app_error", nil, "").Error())
}
}
}()
@@ -653,6 +654,24 @@ func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(rdata)))
}
func ldapTest(c *Context, w http.ResponseWriter, r *http.Request) {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil && utils.IsLicensed && *utils.License.Features.LDAP && *utils.Cfg.LdapSettings.Enable {
if err := ldapI.RunTest(); err != nil {
c.Err = err
c.Err.StatusCode = 500
}
} else {
c.Err = model.NewLocAppError("ldapTest", "ent.ldap.disabled.app_error", nil, "")
c.Err.StatusCode = http.StatusNotImplemented
}
if c.Err == nil {
rdata := map[string]string{}
rdata["status"] = "ok"
w.Write([]byte(model.MapToJson(rdata)))
}
}
func samlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
samlInterface := einterfaces.GetSamlInterface()

View File

@@ -161,6 +161,18 @@ func TestEmailTest(t *testing.T) {
}
}
func TestLdapTest(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
if _, err := th.BasicClient.TestLdap(utils.Cfg); err == nil {
t.Fatal("Shouldn't have permissions")
}
if _, err := th.SystemAdminClient.TestLdap(utils.Cfg); err == nil {
t.Fatal("should have errored")
}
}
func TestGetTeamAnalyticsStandard(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
th.CreatePrivateChannel(th.BasicClient, th.BasicTeam)

View File

@@ -16,6 +16,7 @@ type LdapInterface interface {
Syncronize() *model.AppError
StartLdapSyncJob()
SyncNow()
RunTest() *model.AppError
GetAllLdapUsers() ([]*model.User, *model.AppError)
}

View File

@@ -2503,6 +2503,10 @@
"id": "ent.ldap.validate_filter.app_error",
"translation": "Invalid LDAP Filter"
},
{
"id": "ent.ldap.disabled.app_error",
"translation": "LDAP disabled or licence does not support LDAP."
},
{
"id": "ent.mfa.activate.authenticate.app_error",
"translation": "Error attempting to authenticate MFA token"

View File

@@ -887,6 +887,19 @@ func (c *Client) TestEmail(config *Config) (*Result, *AppError) {
}
}
// TestLdap will run a connection test on the current LDAP settings.
// It will return the standard OK response if settings work. Otherwise
// it will return an appropriate error.
func (c *Client) TestLdap(config *Config) (*Result, *AppError) {
if r, err := c.DoApiPost("/admin/ldap_test", config.ToJson()); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetComplianceReports() (*Result, *AppError) {
if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil {
return nil, err

View File

@@ -471,6 +471,15 @@ export default class Client {
end(this.handleResponse.bind(this, 'ldapSyncNow', success, error));
}
ldapTest(success, error) {
request.
post(`${this.getAdminRoute()}/ldap_test`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
end(this.handleResponse.bind(this, 'ldap_test', success, error));
}
// Team Routes Section
createTeamFromSignup(teamSignup, success, error) {

View File

@@ -21,6 +21,7 @@ export default class AdminSettings extends React.Component {
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.doSubmit = this.doSubmit.bind(this);
this.state = Object.assign(this.getStateFromConfig(props.config), {
saveNeeded: false,
@@ -39,6 +40,10 @@ export default class AdminSettings extends React.Component {
handleSubmit(e) {
e.preventDefault();
this.doSubmit();
}
doSubmit(callback) {
this.setState({
saving: true,
serverError: null
@@ -59,12 +64,20 @@ export default class AdminSettings extends React.Component {
saveNeeded: false,
saving: false
});
if (callback) {
callback();
}
},
(err) => {
this.setState({
saving: false,
serverError: err.message
});
if (callback) {
callback();
}
}
);
}

View File

@@ -207,7 +207,7 @@ export default class AdminSidebar extends React.Component {
title={
<FormattedMessage
id='admin.sidebar.ldap'
defaultMessage='LDAP'
defaultMessage='AD/LDAP'
/>
}
/>

View File

@@ -8,6 +8,7 @@ import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
import SyncNowButton from './sync_now_button.jsx';
import LdapTestButton from './ldap_test_button.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -76,7 +77,7 @@ export default class LdapSettings extends AdminSettings {
<h3>
<FormattedMessage
id='admin.authentication.ldap'
defaultMessage='LDAP'
defaultMessage='AD/LDAP'
/>
</h3>
);
@@ -150,6 +151,23 @@ export default class LdapSettings extends AdminSettings {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<BooleanSetting
id='skipCertificateVerification'
label={
<FormattedMessage
id='admin.ldap.skipCertificateVerification'
defaultMessage='Skip Certificate Verification:'
/>
}
helpText={
<FormattedMessage
id='admin.ldap.skipCertificateVerificationDesc'
defaultMessage='Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.'
/>
}
value={this.state.skipCertificateVerification}
onChange={this.handleChange}
/>
<TextSetting
id='baseDN'
label={
@@ -338,12 +356,31 @@ export default class LdapSettings extends AdminSettings {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='loginFieldName'
label={
<FormattedMessage
id='admin.ldap.loginNameTitle'
defaultMessage='Sign-in Field Default Text:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.loginNameEx', 'Ex "LDAP Username"')}
helpText={
<FormattedMessage
id='admin.ldap.loginNameDesc'
defaultMessage='The placeholder text that appears in the login field on the login page. Defaults to "LDAP Username".'
/>
}
value={this.state.loginFieldName}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='syncIntervalMinutes'
label={
<FormattedMessage
id='admin.ldap.syncIntervalTitle'
defaultMessage='Synchronization Interval (minutes)'
defaultMessage='Synchronization Interval (minutes):'
/>
}
helpText={
@@ -356,22 +393,24 @@ export default class LdapSettings extends AdminSettings {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<BooleanSetting
id='skipCertificateVerification'
<TextSetting
id='maxPageSize'
label={
<FormattedMessage
id='admin.ldap.skipCertificateVerification'
defaultMessage='Skip Certificate Verification'
id='admin.ldap.maxPageSizeTitle'
defaultMessage='Maximum Page Size:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.maxPageSizeEx', 'Ex "2000"')}
helpText={
<FormattedMessage
id='admin.ldap.skipCertificateVerificationDesc'
defaultMessage='Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.'
id='admin.ldap.maxPageSizeHelpText'
defaultMessage='The maximum number of users the Mattermost server will request from the LDAP server at one time. 0 is unlimited.'
/>
}
value={this.state.skipCertificateVerification}
value={this.state.maxPageSize}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='queryTimeout'
@@ -392,47 +431,14 @@ export default class LdapSettings extends AdminSettings {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='maxPageSize'
label={
<FormattedMessage
id='admin.ldap.maxPageSizeTitle'
defaultMessage='Maximum Page Size'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.maxPageSizeEx', 'Ex "2000"')}
helpText={
<FormattedMessage
id='admin.ldap.maxPageSizeHelpText'
defaultMessage='The maximum number of users the Mattermost server will request from the LDAP server at one time. 0 is unlimited.'
/>
}
value={this.state.maxPageSize}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='loginFieldName'
label={
<FormattedMessage
id='admin.ldap.loginNameTitle'
defaultMessage='Sign-in Field Default Text:'
/>
}
placeholder={Utils.localizeMessage('admin.ldap.loginNameEx', 'Ex "LDAP Username"')}
helpText={
<FormattedMessage
id='admin.ldap.loginNameDesc'
defaultMessage='The placeholder text that appears in the login field on the login page. Defaults to "LDAP Username".'
/>
}
value={this.state.loginFieldName}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<SyncNowButton
disabled={!this.state.enable}
/>
<LdapTestButton
disabled={!this.state.enable}
submitFunction={this.doSubmit}
saveNeeded={this.state.saveNeeded}
/>
</SettingsGroup>
);
}

View File

@@ -0,0 +1,139 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
export default class LdapTestButton extends React.Component {
static get propTypes() {
return {
disabled: React.PropTypes.bool,
submitFunction: React.PropTypes.func,
saveNeeded: React.PropTypes.bool
};
}
constructor(props) {
super(props);
this.handleLdapTest = this.handleLdapTest.bind(this);
this.state = {
buisy: false,
fail: null,
success: false
};
}
handleLdapTest(e) {
e.preventDefault();
this.setState({
buisy: true,
fail: null,
success: false
});
const doRequest = () => { //eslint-disable-line func-style
Client.ldapTest(
() => {
this.setState({
buisy: false,
success: true
});
},
(err) => {
this.setState({
buisy: false,
fail: err.message
});
}
);
};
// If we need to run the save function then run it with our request function as callback
if (this.props.saveNeeded) {
this.props.submitFunction(doRequest);
} else {
doRequest();
}
}
render() {
let message = null;
if (this.state.fail) {
message = (
<div className='alert alert-warning'>
<i className='fa fa-warning'/>
<FormattedMessage
id='admin.ldap.testFailure'
defaultMessage='LDAP Test Failure: {error}'
values={{
error: this.state.fail
}}
/>
</div>
);
} else if (this.state.success) {
message = (
<div className='alert alert-success'>
<i className='fa fa-success'/>
<FormattedMessage
id='admin.ldap.testSuccess'
defaultMessage='LDAP Test Successful'
values={{
error: this.state.fail
}}
/>
</div>
);
}
let helpText = (
<FormattedHTMLMessage
id='admin.ldap.testHelpText'
defaultMessage='Tests if the Mattermost server can connect to the LDAP server specified. See log file for more detailed error messages.'
/>
);
let contents = null;
if (this.state.loading) {
contents = (
<span>
<span className='fa fa-refresh icon--rotate'/>
{Utils.localizeMessage('admin.reload.loading', ' Loading...')}
</span>
);
} else {
contents = (
<FormattedMessage
id='admin.ldap.ldap_test_button'
defaultMessage='LDAP Test'
/>
);
}
return (
<div className='form-group reload-config'>
<div className='col-sm-offset-4 col-sm-8'>
<div>
<button
className='btn btn-default'
onClick={this.handleLdapTest}
disabled={this.props.disabled}
>
{contents}
</button>
{message}
</div>
<div className='help-text'>
{helpText}
</div>
</div>
</div>
);
}
}

View File

@@ -143,6 +143,7 @@
"admin.authentication.gitlab": "GitLab",
"admin.authentication.oauth": "OAuth 2.0",
"admin.authentication.saml": "SAML",
"admin.authentication.ldap": "AD/LDAP",
"admin.banner.heading": "Note:",
"admin.cluster.enableDescription": "When true, Mattermost will run in High Availability mode. Please see <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a> to learn more about configuring High Availability for Mattermost.",
"admin.cluster.enableTitle": "Enable High Availability Mode:",
@@ -415,7 +416,7 @@
"admin.ldap.loginNameTitle": "Login Field Name:",
"admin.ldap.maxPageSizeEx": "Ex \"2000\"",
"admin.ldap.maxPageSizeHelpText": "The maximum number of users the Mattermost server will request from the LDAP server at one time. 0 is unlimited.",
"admin.ldap.maxPageSizeTitle": "Maximum Page Size",
"admin.ldap.maxPageSizeTitle": "Maximum Page Size:",
"admin.ldap.nicknameAttrDesc": "(Optional) The attribute in the LDAP server that will be used to populate the nickname of users in Mattermost.",
"admin.ldap.nicknameAttrEx": "Ex \"nickname\"",
"admin.ldap.nicknameAttrTitle": "Nickname Attribute:",
@@ -429,11 +430,13 @@
"admin.ldap.serverDesc": "The domain or IP address of LDAP server.",
"admin.ldap.serverEx": "Ex \"10.0.0.23\"",
"admin.ldap.serverTitle": "LDAP Server:",
"admin.ldap.skipCertificateVerification": "Skip Certificate Verification",
"admin.ldap.skipCertificateVerification": "Skip Certificate Verification:",
"admin.ldap.skipCertificateVerificationDesc": "Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.",
"admin.ldap.syncFailure": "Sync Failure: {error}",
"admin.ldap.testFailure": "LDAP Test Failure: {error}",
"admin.ldap.testHelpText": "Tests if the Mattermost server can connect to the LDAP server specified. See log file for more detailed error messages.",
"admin.ldap.syncIntervalHelpText": "LDAP Synchronization updates Mattermost user information to reflect updates on the LDAP server. For example, when a users name changes on the LDAP server, the change updates in Mattermost when synchronization is performed. Accounts removed from or disabled in the LDAP server have their Mattermost accounts set to “Inactive” and have their account sessions revoked. Mattermost performs synchronization on the interval entered. For example, if 60 is entered, Mattermost synchronizes every 60 minutes.",
"admin.ldap.syncIntervalTitle": "Synchronization Interval (minutes)",
"admin.ldap.syncIntervalTitle": "Synchronization Interval (minutes):",
"admin.ldap.syncNowHelpText": "Initiates an LDAP synchronization immediately.",
"admin.ldap.sync_button": "LDAP Synchronize Now",
"admin.ldap.uernameAttrDesc": "The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.",
@@ -684,7 +687,7 @@
"admin.sidebar.gitlab": "GitLab",
"admin.sidebar.images": "Images",
"admin.sidebar.integrations": "Integrations",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.ldap": "AD/LDAP",
"admin.sidebar.legalAndSupport": "Legal and Support",
"admin.sidebar.license": "Edition and License",
"admin.sidebar.localization": "Localization",