diff options
-rw-r--r-- | api/admin.go | 5 | ||||
-rw-r--r-- | config/config.json | 1 | ||||
-rw-r--r-- | einterfaces/ldap.go | 1 | ||||
-rw-r--r-- | i18n/en.json | 8 | ||||
-rw-r--r-- | model/config.go | 8 | ||||
-rw-r--r-- | utils/config.go | 16 | ||||
-rw-r--r-- | webapp/components/admin_console/ldap_settings.jsx | 100 | ||||
-rw-r--r-- | webapp/i18n/en.json | 3 |
8 files changed, 85 insertions, 57 deletions
diff --git a/api/admin.go b/api/admin.go index 2990691a6..7b041619e 100644 --- a/api/admin.go +++ b/api/admin.go @@ -148,6 +148,11 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) { return } + if err := utils.ValidateLdapFilter(cfg); err != nil { + c.Err = err + return + } + c.LogAudit("") utils.SaveConfig(utils.CfgFileName, cfg) diff --git a/config/config.json b/config/config.json index 62dcfcffc..27c697be0 100644 --- a/config/config.json +++ b/config/config.json @@ -135,6 +135,7 @@ "BaseDN": "", "BindUsername": "", "BindPassword": "", + "UserFilter": "", "FirstNameAttribute": "", "LastNameAttribute": "", "EmailAttribute": "", diff --git a/einterfaces/ldap.go b/einterfaces/ldap.go index 2977ab812..3917b42f8 100644 --- a/einterfaces/ldap.go +++ b/einterfaces/ldap.go @@ -12,6 +12,7 @@ type LdapInterface interface { GetUser(id string) (*model.User, *model.AppError) CheckPassword(id string, password string) *model.AppError SwitchToEmail(userId, ldapId, ldapPassword string) *model.AppError + ValidateFilter(filter string) *model.AppError } var theLdapInterface LdapInterface diff --git a/i18n/en.json b/i18n/en.json index a4c84a462..6cdeeaad2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1824,6 +1824,14 @@ "translation": "User not registered on LDAP server" }, { + "id": "ent.ldap.do_login.user_filtered.app_error", + "translation": "User is not permitted to use Mattermost. (LDAP user filter)" + }, + { + "id": "ent.ldap.validate_filter.app_error", + "translation": "Invalid LDAP Filter" + }, + { "id": "ent.mfa.activate.authenticate.app_error", "translation": "Error attempting to authenticate MFA token" }, diff --git a/model/config.go b/model/config.go index e7ab07f8c..666b2770b 100644 --- a/model/config.go +++ b/model/config.go @@ -169,6 +169,9 @@ type LdapSettings struct { BindUsername *string BindPassword *string + // Filtering + UserFilter *string + // User Mapping FirstNameAttribute *string LastNameAttribute *string @@ -366,6 +369,11 @@ func (o *Config) SetDefaults() { *o.LdapSettings.Enable = false } + if o.LdapSettings.UserFilter == nil { + o.LdapSettings.UserFilter = new(string) + *o.LdapSettings.UserFilter = "" + } + if o.ServiceSettings.SessionLengthWebInDays == nil { o.ServiceSettings.SessionLengthWebInDays = new(int) *o.ServiceSettings.SessionLengthWebInDays = 30 diff --git a/utils/config.go b/utils/config.go index 93c8ffc7c..d8f52ce49 100644 --- a/utils/config.go +++ b/utils/config.go @@ -13,6 +13,7 @@ import ( l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" ) @@ -167,6 +168,11 @@ func LoadConfig(fileName string) { map[string]interface{}{"Filename": fileName, "Error": err.Message})) } + if err := ValidateLdapFilter(&config); err != nil { + panic(T("utils.config.load_config.validating.panic", + map[string]interface{}{"Filename": fileName, "Error": err.Message})) + } + configureLog(&config.LogSettings) TestConnection(&config) @@ -243,3 +249,13 @@ func getClientConfig(c *model.Config) map[string]string { return props } + +func ValidateLdapFilter(cfg *model.Config) *model.AppError { + ldapInterface := einterfaces.GetLdapInterface() + if *cfg.LdapSettings.Enable && ldapInterface != nil && *cfg.LdapSettings.UserFilter != "" { + if err := ldapInterface.ValidateFilter(*cfg.LdapSettings.UserFilter); err != nil { + return err + } + } + return nil +} diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index 7996a3aab..190f707b9 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -4,56 +4,14 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Client from 'utils/client.jsx'; +import * as Utils from 'utils/utils.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; const DEFAULT_LDAP_PORT = 389; const DEFAULT_QUERY_TIMEOUT = 60; -var holders = defineMessages({ - serverEx: { - id: 'admin.ldap.serverEx', - defaultMessage: 'Ex "10.0.0.23"' - }, - portEx: { - id: 'admin.ldap.portEx', - defaultMessage: 'Ex "389"' - }, - baseEx: { - id: 'admin.ldap.baseEx', - defaultMessage: 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"' - }, - firstnameAttrEx: { - id: 'admin.ldap.firstnameAttrEx', - defaultMessage: 'Ex "givenName"' - }, - lastnameAttrEx: { - id: 'admin.ldap.lastnameAttrEx', - defaultMessage: 'Ex "sn"' - }, - emailAttrEx: { - id: 'admin.ldap.emailAttrEx', - defaultMessage: 'Ex "mail" or "userPrincipalName"' - }, - usernameAttrEx: { - id: 'admin.ldap.usernameAttrEx', - defaultMessage: 'Ex "sAMAccountName"' - }, - idAttrEx: { - id: 'admin.ldap.idAttrEx', - defaultMessage: 'Ex "sAMAccountName"' - }, - queryEx: { - id: 'admin.ldap.queryEx', - defaultMessage: 'Ex "60"' - }, - saving: { - id: 'admin.ldap.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; class LdapSettings extends React.Component { @@ -102,6 +60,7 @@ class LdapSettings extends React.Component { config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim(); config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim(); config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim(); + config.LdapSettings.UserFilter = this.refs.UserFilter.value.trim(); let QueryTimeout = DEFAULT_QUERY_TIMEOUT; if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10))) { @@ -129,7 +88,6 @@ class LdapSettings extends React.Component { ); } render() { - const {formatMessage} = this.props.intl; let serverError = ''; if (this.state.serverError) { serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; @@ -251,7 +209,7 @@ class LdapSettings extends React.Component { className='form-control' id='LdapServer' ref='LdapServer' - placeholder={formatMessage(holders.serverEx)} + placeholder={Utils.localizeMessage('admin.ldap.serverEx', 'Ex "10.0.0.23"')} defaultValue={this.props.config.LdapSettings.LdapServer} onChange={this.handleChange} disabled={!this.state.enable} @@ -280,7 +238,7 @@ class LdapSettings extends React.Component { className='form-control' id='LdapPort' ref='LdapPort' - placeholder={formatMessage(holders.portEx)} + placeholder={Utils.localizeMessage('admin.ldap.portEx', 'Ex "389"')} defaultValue={this.props.config.LdapSettings.LdapPort} onChange={this.handleChange} disabled={!this.state.enable} @@ -309,7 +267,7 @@ class LdapSettings extends React.Component { className='form-control' id='BaseDN' ref='BaseDN' - placeholder={formatMessage(holders.baseEx)} + placeholder={Utils.localizeMessage('admin.ldap.baseEx', 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"')} defaultValue={this.props.config.LdapSettings.BaseDN} onChange={this.handleChange} disabled={!this.state.enable} @@ -383,6 +341,35 @@ class LdapSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='UserFilter' + > + <FormattedMessage + id='admin.ldap.userFilterTitle' + defaultMessage='User Filter:' + /> + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='UserFilter' + ref='UserFilter' + placeholder={Utils.localizeMessage('admin.ldap.userFilterEx', 'Ex. "(objectClass=user)"')} + defaultValue={this.props.config.LdapSettings.UserFilter} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'> + <FormattedMessage + id='admin.ldap.userFilterDisc' + defaultMessage='LDAP Filter to use when searching for user objects.' + /> + </p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='FirstNameAttribute' > <FormattedMessage @@ -396,7 +383,7 @@ class LdapSettings extends React.Component { className='form-control' id='FirstNameAttribute' ref='FirstNameAttribute' - placeholder={formatMessage(holders.firstnameAttrEx)} + placeholder={Utils.localizeMessage('admin.ldap.firstnameAttrEx', 'Ex "givenName"')} defaultValue={this.props.config.LdapSettings.FirstNameAttribute} onChange={this.handleChange} disabled={!this.state.enable} @@ -425,7 +412,7 @@ class LdapSettings extends React.Component { className='form-control' id='LastNameAttribute' ref='LastNameAttribute' - placeholder={formatMessage(holders.lastnameAttrEx)} + placeholder={Utils.localizeMessage('admin.ldap.lastnameAttrEx', 'Ex "sn"')} defaultValue={this.props.config.LdapSettings.LastNameAttribute} onChange={this.handleChange} disabled={!this.state.enable} @@ -454,7 +441,7 @@ class LdapSettings extends React.Component { className='form-control' id='EmailAttribute' ref='EmailAttribute' - placeholder={formatMessage(holders.emailAttrEx)} + placeholder={Utils.localizeMessage('admin.ldap.emailAttrEx', 'Ex "mail" or "userPrincipalName"')} defaultValue={this.props.config.LdapSettings.EmailAttribute} onChange={this.handleChange} disabled={!this.state.enable} @@ -483,7 +470,7 @@ class LdapSettings extends React.Component { className='form-control' id='UsernameAttribute' ref='UsernameAttribute' - placeholder={formatMessage(holders.usernameAttrEx)} + placeholder={Utils.localizeMessage('admin.ldap.usernameAttrEx', 'Ex "sAMAccountName"')} defaultValue={this.props.config.LdapSettings.UsernameAttribute} onChange={this.handleChange} disabled={!this.state.enable} @@ -512,7 +499,7 @@ class LdapSettings extends React.Component { className='form-control' id='IdAttribute' ref='IdAttribute' - placeholder={formatMessage(holders.idAttrEx)} + placeholder={Utils.localizeMessage('admin.ldap.idAttrEx', 'Ex "sAMAccountName"')} defaultValue={this.props.config.LdapSettings.IdAttribute} onChange={this.handleChange} disabled={!this.state.enable} @@ -541,7 +528,7 @@ class LdapSettings extends React.Component { className='form-control' id='QueryTimeout' ref='QueryTimeout' - placeholder={formatMessage(holders.queryEx)} + placeholder={Utils.localizeMessage('admin.ldap.queryEx', 'Ex "60"')} defaultValue={this.props.config.LdapSettings.QueryTimeout} onChange={this.handleChange} disabled={!this.state.enable} @@ -563,7 +550,7 @@ class LdapSettings extends React.Component { className={saveClass} onClick={this.handleSubmit} id='save-button' - data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)} + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + Utils.localizeMessage('admin.ldap.saving', 'Saving Config...')} > <FormattedMessage id='admin.ldap.save' @@ -581,8 +568,7 @@ LdapSettings.defaultProps = { }; LdapSettings.propTypes = { - intl: intlShape.isRequired, config: React.PropTypes.object }; -export default injectIntl(LdapSettings); +export default LdapSettings; diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 8eca7451d..7914fd1c7 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -289,6 +289,9 @@ "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.", "admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"", "admin.ldap.usernameAttrTitle": "Username Attribute:", + "admin.ldap.userFilterTitle": "User Filter:", + "admin.ldap.userFilterEx": "Ex. \"(objectClass=user)\"", + "admin.ldap.userFilterDisc": "LDAP Filter to use when searching for user objects.", "admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.", "admin.license.choose": "Choose File", "admin.license.chooseFile": "Choose File", |