Adding rate limiter settings to the admin console

Этот коммит содержится в:
=Corey Hulen
2015-09-21 19:01:52 -07:00
родитель cae5dbd245
Коммит e78c79b832
7 изменённых файлов: 317 добавлений и 42 удалений

Просмотреть файл

@@ -42,7 +42,7 @@ func StartServer() {
var handler http.Handler = Srv.Router var handler http.Handler = Srv.Router
if utils.Cfg.RateLimitSettings.UseRateLimiter { if utils.Cfg.RateLimitSettings.EnableRateLimiter {
l4g.Info("RateLimiter is enabled") l4g.Info("RateLimiter is enabled")
vary := throttled.VaryBy{} vary := throttled.VaryBy{}

Просмотреть файл

@@ -1,12 +1,4 @@
{ {
"LogSettings": {
"ConsoleEnable": true,
"ConsoleLevel": "DEBUG",
"FileEnable": true,
"FileLevel": "INFO",
"FileFormat": "",
"FileLocation": ""
},
"ServiceSettings": { "ServiceSettings": {
"SiteName": "Mattermost", "SiteName": "Mattermost",
"Mode": "dev", "Mode": "dev",
@@ -19,7 +11,18 @@
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t", "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "", "AnalyticsUrl": "",
"AllowedLoginAttempts": 10, "AllowedLoginAttempts": 10,
"EnableOAuthServiceProvider": false "EnableOAuthServiceProvider": false,
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": ""
},
"TeamSettings": {
"MaxUsersPerTeam": 150,
"AllowPublicLink": true,
"AllowValetDefault": false,
"TourLink": "",
"DefaultThemeColor": "#2389D7",
"DisableTeamCreation": false,
"RestrictCreationToDomains": ""
}, },
"SqlSettings": { "SqlSettings": {
"DriverName": "mysql", "DriverName": "mysql",
@@ -32,6 +35,14 @@
"Trace": false, "Trace": false,
"AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS" "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS"
}, },
"LogSettings": {
"ConsoleEnable": true,
"ConsoleLevel": "DEBUG",
"FileEnable": true,
"FileLevel": "INFO",
"FileFormat": "",
"FileLocation": ""
},
"ImageSettings": { "ImageSettings": {
"DriverName": "local", "DriverName": "local",
"Directory": "./data/", "Directory": "./data/",
@@ -63,7 +74,7 @@
"ApplePushCertPrivate": "" "ApplePushCertPrivate": ""
}, },
"RateLimitSettings": { "RateLimitSettings": {
"UseRateLimiter": true, "EnableRateLimiter": true,
"PerSec": 10, "PerSec": 10,
"MemoryStoreSize": 10000, "MemoryStoreSize": 10000,
"VaryByRemoteAddr": true, "VaryByRemoteAddr": true,
@@ -71,23 +82,8 @@
}, },
"PrivacySettings": { "PrivacySettings": {
"ShowEmailAddress": true, "ShowEmailAddress": true,
"ShowPhoneNumber": true,
"ShowSkypeId": true,
"ShowFullName": true "ShowFullName": true
}, },
"ClientSettings": {
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": ""
},
"TeamSettings": {
"MaxUsersPerTeam": 150,
"AllowPublicLink": true,
"AllowValetDefault": false,
"TourLink": "",
"DefaultThemeColor": "#2389D7",
"DisableTeamCreation": false,
"RestrictCreationToDomains": ""
},
"SSOSettings": { "SSOSettings": {
"gitlab": { "gitlab": {
"Allow": false, "Allow": false,

Просмотреть файл

@@ -30,6 +30,8 @@ type ServiceSettings struct {
AnalyticsUrl string AnalyticsUrl string
AllowedLoginAttempts int AllowedLoginAttempts int
EnableOAuthServiceProvider bool EnableOAuthServiceProvider bool
SegmentDeveloperKey string
GoogleDeveloperKey string
} }
type SSOSetting struct { type SSOSetting struct {
@@ -96,11 +98,11 @@ type EmailSettings struct {
} }
type RateLimitSettings struct { type RateLimitSettings struct {
UseRateLimiter bool EnableRateLimiter bool
PerSec int PerSec int
MemoryStoreSize int MemoryStoreSize int
VaryByRemoteAddr bool VaryByRemoteAddr bool
VaryByHeader string VaryByHeader string
} }
type PrivacySettings struct { type PrivacySettings struct {
@@ -108,11 +110,6 @@ type PrivacySettings struct {
ShowFullName bool ShowFullName bool
} }
type ClientSettings struct {
SegmentDeveloperKey string
GoogleDeveloperKey string
}
type TeamSettings struct { type TeamSettings struct {
MaxUsersPerTeam int MaxUsersPerTeam int
AllowPublicLink bool AllowPublicLink bool
@@ -124,15 +121,14 @@ type TeamSettings struct {
} }
type Config struct { type Config struct {
LogSettings LogSettings
ServiceSettings ServiceSettings ServiceSettings ServiceSettings
TeamSettings TeamSettings
SqlSettings SqlSettings SqlSettings SqlSettings
LogSettings LogSettings
ImageSettings ImageSettings ImageSettings ImageSettings
EmailSettings EmailSettings EmailSettings EmailSettings
RateLimitSettings RateLimitSettings RateLimitSettings RateLimitSettings
PrivacySettings PrivacySettings PrivacySettings PrivacySettings
ClientSettings ClientSettings
TeamSettings TeamSettings
SSOSettings map[string]SSOSetting SSOSettings map[string]SSOSetting
} }

Просмотреть файл

@@ -176,6 +176,8 @@ func getClientProperties(c *model.Config) map[string]string {
props["SiteName"] = c.ServiceSettings.SiteName props["SiteName"] = c.ServiceSettings.SiteName
props["AnalyticsUrl"] = c.ServiceSettings.AnalyticsUrl props["AnalyticsUrl"] = c.ServiceSettings.AnalyticsUrl
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey
props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications) props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
props["AllowSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.AllowSignUpWithEmail) props["AllowSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.AllowSignUpWithEmail)
@@ -186,9 +188,6 @@ func getClientProperties(c *model.Config) map[string]string {
props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress) props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress)
props["AllowPublicLink"] = strconv.FormatBool(c.TeamSettings.AllowPublicLink) props["AllowPublicLink"] = strconv.FormatBool(c.TeamSettings.AllowPublicLink)
props["SegmentDeveloperKey"] = c.ClientSettings.SegmentDeveloperKey
props["GoogleDeveloperKey"] = c.ClientSettings.GoogleDeveloperKey
props["ProfileHeight"] = fmt.Sprintf("%v", c.ImageSettings.ProfileHeight) props["ProfileHeight"] = fmt.Sprintf("%v", c.ImageSettings.ProfileHeight)
props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth) props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth)
props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth) props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth)

Просмотреть файл

@@ -11,6 +11,7 @@ var LogSettingsTab = require('./log_settings.jsx');
var LogsTab = require('./logs.jsx'); var LogsTab = require('./logs.jsx');
var ImageSettingsTab = require('./image_settings.jsx'); var ImageSettingsTab = require('./image_settings.jsx');
var PrivacySettingsTab = require('./privacy_settings.jsx'); var PrivacySettingsTab = require('./privacy_settings.jsx');
var RateSettingsTab = require('./rate_settings.jsx');
export default class AdminController extends React.Component { export default class AdminController extends React.Component {
constructor(props) { constructor(props) {
@@ -59,6 +60,8 @@ export default class AdminController extends React.Component {
tab = <ImageSettingsTab config={this.state.config} />; tab = <ImageSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'privacy_settings') { } else if (this.state.selected === 'privacy_settings') {
tab = <PrivacySettingsTab config={this.state.config} />; tab = <PrivacySettingsTab config={this.state.config} />;
} else if (this.state.selected === 'rate_settings') {
tab = <RateSettingsTab config={this.state.config} />;
} }
} }

Просмотреть файл

@@ -83,6 +83,15 @@ export default class AdminSidebar extends React.Component {
{'Privacy Settings'} {'Privacy Settings'}
</a> </a>
</li> </li>
<li>
<a
href='#'
className={this.isSelected('rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings')}
>
{'Rate Limit Settings'}
</a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>

Просмотреть файл

@@ -0,0 +1,272 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
export default class RateSettings extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter,
VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr,
saveNeeded: false,
serverError: null
};
}
handleChange(action) {
var s = {saveNeeded: true, serverError: this.state.serverError};
if (action === 'EnableRateLimiterTrue') {
s.EnableRateLimiter = true;
}
if (action === 'EnableRateLimiterFalse') {
s.EnableRateLimiter = false;
}
if (action === 'VaryByRemoteAddrTrue') {
s.VaryByRemoteAddr = true;
}
if (action === 'VaryByRemoteAddrFalse') {
s.VaryByRemoteAddr = false;
}
this.setState(s);
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.RateLimitSettings.EnableRateLimiter = React.findDOMNode(this.refs.EnableRateLimiter).checked;
config.RateLimitSettings.VaryByRemoteAddr = React.findDOMNode(this.refs.VaryByRemoteAddr).checked;
config.RateLimitSettings.VaryByHeader = React.findDOMNode(this.refs.VaryByHeader).value.trim();
var PerSec = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.PerSec).value, 10))) {
PerSec = parseInt(React.findDOMNode(this.refs.PerSec).value, 10);
}
config.RateLimitSettings.PerSec = PerSec;
React.findDOMNode(this.refs.PerSec).value = PerSec;
var MemoryStoreSize = 10000;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10))) {
MemoryStoreSize = parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10);
}
config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize;
React.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize;
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
);
}
render() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return (
<div className='wrapper--fixed'>
<div className='banner'>
<div className='banner__content'>
<h4 className='banner__heading'>{'Note:'}</h4>
<p>{'Changing properties in this section will require a server restart before taking effect.'}</p>
</div>
</div>
<h3>{'Rate Limit Settings'}</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableRateLimiter'
>
{'Enable Rate Limiter: '}
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableRateLimiter'
value='true'
ref='EnableRateLimiter'
defaultChecked={this.props.config.RateLimitSettings.EnableRateLimiter}
onChange={this.handleChange.bind(this, 'EnableRateLimiterTrue')}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableRateLimiter'
value='false'
defaultChecked={!this.props.config.RateLimitSettings.EnableRateLimiter}
onChange={this.handleChange.bind(this, 'EnableRateLimiterFalse')}
/>
{'false'}
</label>
<p className='help-text'>{'When enabled throttles rate at which APIs respond.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PerSec'
>
{'Number Of Queries Per Second:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PerSec'
ref='PerSec'
placeholder='Ex "10"'
defaultValue={this.props.config.RateLimitSettings.PerSec}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter}
/>
<p className='help-text'>{'Height of profile picture.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='MemoryStoreSize'
>
{'Memory Store Size:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='MemoryStoreSize'
ref='MemoryStoreSize'
placeholder='Ex "10000"'
defaultValue={this.props.config.RateLimitSettings.MemoryStoreSize}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter}
/>
<p className='help-text'>{'Maximum number of users sessions connected to the system as determined by VaryByRemoteAddr and VaryByHeader variables.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='VaryByRemoteAddr'
>
{'Limit By Remote Address: '}
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='VaryByRemoteAddr'
value='true'
ref='VaryByRemoteAddr'
defaultChecked={this.props.config.RateLimitSettings.VaryByRemoteAddr}
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrTrue')}
disabled={!this.state.EnableRateLimiter}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
name='VaryByRemoteAddr'
value='false'
defaultChecked={!this.props.config.RateLimitSettings.VaryByRemoteAddr}
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrFalse')}
disabled={!this.state.EnableRateLimiter}
/>
{'false'}
</label>
<p className='help-text'>{'Rate limit API access by IP address.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='VaryByHeader'
>
{'Limit By Http Header:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='VaryByHeader'
ref='VaryByHeader'
placeholder='Ex "X-Real-IP", "X-Forwarded-For"'
defaultValue={this.props.config.RateLimitSettings.VaryByHeader}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter || this.state.VaryByRemoteAddr}
/>
<p className='help-text'>{'When filled in, vary rate limiting by http header field specified (e.g. when configuring ngnix set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'}</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
>
{'Save'}
</button>
</div>
</div>
</form>
</div>
);
}
}
RateSettings.propTypes = {
config: React.PropTypes.object
};