SAML support (#3494)
* PLT-3073: Implement SAML/Okta Server side (EE) (#3422) * PLT-3137 Support for SAML configuration * PLT-3410 SAML Database Store * PLT-3411 CLI to add Identity Provider Certificate and Service Provider Private Key * PLT-3409 SAML Interface for EE * PLT-3139 Handle SAML authentication server side * Add localization messages * PLT-3443 SAML Obtain SP metadata * PLT-3142 Login & Switch to/from SAML * Remove Certs for Database & Clean SAML Request * Make required Username, FirstName and LastName * PLT-3140 Add SAML to System Console (#3476) * PLT-3140 Add SAML to System Console * Move web_client functions to client.jsx * Fix issues found by PM * update package.json mattermost driver * Fix text messages for SAML
Этот коммит содержится в:
@@ -176,6 +176,7 @@ export default class AdminSidebar extends React.Component {
|
||||
|
||||
render() {
|
||||
let ldapSettings = null;
|
||||
let samlSettings = null;
|
||||
let complianceSettings = null;
|
||||
|
||||
let license = null;
|
||||
@@ -198,6 +199,20 @@ export default class AdminSidebar extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (global.window.mm_license.SAML === 'true') {
|
||||
samlSettings = (
|
||||
<AdminSidebarSection
|
||||
name='saml'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='admin.sidebar.saml'
|
||||
defaultMessage='SAML'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (global.window.mm_license.Compliance === 'true') {
|
||||
complianceSettings = (
|
||||
<AdminSidebarSection
|
||||
@@ -391,6 +406,7 @@ export default class AdminSidebar extends React.Component {
|
||||
}
|
||||
/>
|
||||
{ldapSettings}
|
||||
{samlSettings}
|
||||
</AdminSidebarSection>
|
||||
<AdminSidebarSection
|
||||
name='security'
|
||||
|
||||
124
webapp/components/admin_console/file_upload_setting.jsx
Обычный файл
124
webapp/components/admin_console/file_upload_setting.jsx
Обычный файл
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import Setting from './setting.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
export default class FileUploadSetting extends Setting {
|
||||
static get propTypes() {
|
||||
return {
|
||||
id: React.PropTypes.string.isRequired,
|
||||
label: React.PropTypes.node.isRequired,
|
||||
helpText: React.PropTypes.node,
|
||||
uploadingText: React.PropTypes.node,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
disabled: React.PropTypes.bool,
|
||||
fileType: React.PropTypes.string.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
|
||||
this.state = {
|
||||
fileName: null
|
||||
};
|
||||
}
|
||||
|
||||
handleChange() {
|
||||
const files = this.refs.fileInput.files;
|
||||
if (files && files.length > 0) {
|
||||
this.setState({fileSelected: true, fileName: files[0].name});
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$(this.refs.upload_button).button('loading');
|
||||
this.props.onSubmit(this.props.id, this.refs.fileInput.files[0], (error) => {
|
||||
$(this.refs.upload_button).button('reset');
|
||||
if (error) {
|
||||
Utils.clearFileInput(this.refs.fileInput);
|
||||
}
|
||||
this.setState({fileSelected: false, fileName: null, serverError: error});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let serverError;
|
||||
if (this.state.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||
}
|
||||
|
||||
var btnClass = 'btn';
|
||||
if (this.state.fileSelected) {
|
||||
btnClass = 'btn btn-primary';
|
||||
}
|
||||
|
||||
let fileName;
|
||||
if (this.state.fileName) {
|
||||
fileName = this.state.fileName;
|
||||
} else {
|
||||
fileName = (
|
||||
<FormattedMessage
|
||||
id='admin.file_upload.noFile'
|
||||
defaultMessage='No file uploaded'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Setting
|
||||
label={this.props.label}
|
||||
helpText={this.props.helpText}
|
||||
inputId={this.props.id}
|
||||
>
|
||||
<div>
|
||||
<div className='file__upload'>
|
||||
<button
|
||||
className='btn btn-default'
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.file_upload.chooseFile'
|
||||
defaultMessage='Choose File'
|
||||
/>
|
||||
</button>
|
||||
<input
|
||||
ref='fileInput'
|
||||
type='file'
|
||||
disabled={this.props.disabled}
|
||||
accept={this.props.fileType}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={btnClass}
|
||||
disabled={!this.state.fileSelected}
|
||||
onClick={this.handleSubmit}
|
||||
ref='upload_button'
|
||||
data-loading-text={`<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ${this.props.uploadingText}`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.file_upload.uploadFile'
|
||||
defaultMessage='Upload'
|
||||
/>
|
||||
</button>
|
||||
<div className='help-text no-margin'>
|
||||
{fileName}
|
||||
</div>
|
||||
{serverError}
|
||||
</div>
|
||||
</Setting>
|
||||
);
|
||||
}
|
||||
}
|
||||
72
webapp/components/admin_console/remove_file_setting.jsx
Обычный файл
72
webapp/components/admin_console/remove_file_setting.jsx
Обычный файл
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
|
||||
import Setting from './setting.jsx';
|
||||
|
||||
export default class RemoveFileSetting extends Setting {
|
||||
static get propTypes() {
|
||||
return {
|
||||
id: React.PropTypes.string.isRequired,
|
||||
label: React.PropTypes.node.isRequired,
|
||||
helpText: React.PropTypes.node,
|
||||
removeButtonText: React.PropTypes.node.isRequired,
|
||||
removingText: React.PropTypes.node,
|
||||
fileName: React.PropTypes.string.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
disabled: React.PropTypes.bool
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleRemove = this.handleRemove.bind(this);
|
||||
|
||||
this.state = {
|
||||
serverError: null
|
||||
};
|
||||
}
|
||||
|
||||
handleRemove(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$(this.refs.remove_button).button('loading');
|
||||
this.props.onSubmit(this.props.id, (error) => {
|
||||
$(this.refs.remove_button).button('reset');
|
||||
this.setState({serverError: error});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let serverError;
|
||||
if (this.state.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Setting
|
||||
label={this.props.label}
|
||||
helpText={this.props.helpText}
|
||||
inputId={this.props.id}
|
||||
>
|
||||
<div>
|
||||
<div className='help-text remove-filename'>
|
||||
{this.props.fileName}
|
||||
</div>
|
||||
<button
|
||||
className='btn btn-danger'
|
||||
onClick={this.handleRemove}
|
||||
ref='remove_button'
|
||||
disabled={this.props.disabled}
|
||||
data-loading-text={`<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> ${this.props.removingText}`}
|
||||
>
|
||||
{this.props.removeButtonText}
|
||||
</button>
|
||||
{serverError}
|
||||
</div>
|
||||
</Setting>
|
||||
);
|
||||
}
|
||||
}
|
||||
518
webapp/components/admin_console/saml_settings.jsx
Обычный файл
518
webapp/components/admin_console/saml_settings.jsx
Обычный файл
@@ -0,0 +1,518 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import AdminSettings from './admin_settings.jsx';
|
||||
import BooleanSetting from './boolean_setting.jsx';
|
||||
import TextSetting from './text_setting.jsx';
|
||||
import FileUploadSetting from './file_upload_setting.jsx';
|
||||
import RemoveFileSetting from './remove_file_setting.jsx';
|
||||
|
||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||
import SettingsGroup from './settings_group.jsx';
|
||||
|
||||
import Client from 'utils/web_client.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
export default class SamlSettings extends AdminSettings {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||
|
||||
this.renderSettings = this.renderSettings.bind(this);
|
||||
this.uploadCertificate = this.uploadCertificate.bind(this);
|
||||
this.removeCertificate = this.removeCertificate.bind(this);
|
||||
|
||||
const settings = props.config.SamlSettings;
|
||||
|
||||
this.state = Object.assign(this.state, {
|
||||
enable: settings.Enable,
|
||||
verify: settings.Verify,
|
||||
encrypt: settings.Encrypt,
|
||||
idpUrl: settings.IdpUrl,
|
||||
idpDescriptorUrl: settings.IdpDescriptorUrl,
|
||||
assertionConsumerServiceURL: settings.AssertionConsumerServiceURL,
|
||||
idpCertificateFile: settings.IdpCertificateFile,
|
||||
publicCertificateFile: settings.PublicCertificateFile,
|
||||
privateKeyFile: settings.PrivateKeyFile,
|
||||
firstNameAttribute: settings.FirstNameAttribute,
|
||||
lastNameAttribute: settings.LastNameAttribute,
|
||||
emailAttribute: settings.EmailAttribute,
|
||||
usernameAttribute: settings.UsernameAttribute,
|
||||
nicknameAttribute: settings.NicknameAttribute,
|
||||
localeAttribute: settings.LocaleAttribute,
|
||||
loginButtonText: settings.LoginButtonText
|
||||
});
|
||||
}
|
||||
|
||||
getConfigFromState(config) {
|
||||
config.SamlSettings.Enable = this.state.enable;
|
||||
config.SamlSettings.Verify = this.state.verify;
|
||||
config.SamlSettings.Encrypt = this.state.encrypt;
|
||||
config.SamlSettings.IdpUrl = this.state.idpUrl;
|
||||
config.SamlSettings.IdpDescriptorUrl = this.state.idpDescriptorUrl;
|
||||
config.SamlSettings.AssertionConsumerServiceURL = this.state.assertionConsumerServiceURL;
|
||||
config.SamlSettings.IdpCertificateFile = this.state.idpCertificateFile;
|
||||
config.SamlSettings.PublicCertificateFile = this.state.publicCertificateFile;
|
||||
config.SamlSettings.PrivateKeyFile = this.state.privateKeyFile;
|
||||
config.SamlSettings.FirstNameAttribute = this.state.firstNameAttribute;
|
||||
config.SamlSettings.LastNameAttribute = this.state.lastNameAttribute;
|
||||
config.SamlSettings.EmailAttribute = this.state.emailAttribute;
|
||||
config.SamlSettings.UsernameAttribute = this.state.usernameAttribute;
|
||||
config.SamlSettings.NicknameAttribute = this.state.nicknameAttribute;
|
||||
config.SamlSettings.LocaleAttribute = this.state.localeAttribute;
|
||||
config.SamlSettings.LoginButtonText = this.state.loginButtonText;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
uploadCertificate(id, file, callback) {
|
||||
Client.uploadCertificateFile(
|
||||
file,
|
||||
() => {
|
||||
const fileName = file.name;
|
||||
this.handleChange(id, fileName);
|
||||
this.setState({[id]: fileName});
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
removeCertificate(id, callback) {
|
||||
Client.removeCertificateFile(
|
||||
this.state[id],
|
||||
() => {
|
||||
this.handleChange(id, '');
|
||||
this.setState({[id]: null});
|
||||
},
|
||||
(error) => {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
return (
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='admin.authentication.saml'
|
||||
defaultMessage='SAML'
|
||||
/>
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
renderSettings() {
|
||||
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.SAML === 'true';
|
||||
if (!licenseEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let idpCert;
|
||||
let privKey;
|
||||
let pubCert;
|
||||
|
||||
if (this.state.idpCertificateFile) {
|
||||
idpCert = (
|
||||
<RemoveFileSetting
|
||||
id='idpCertificateFile'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpCertificateFileTitle'
|
||||
defaultMessage='Identity Provider Public Certificate:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpCertificateFileRemoveDesc'
|
||||
defaultMessage='Remove the public authentication certificate issued by your Identity Provider.'
|
||||
/>
|
||||
}
|
||||
removeButtonText={Utils.localizeMessage('admin.saml.remove.idp_certificate', 'Remove Identity Provider Certificate')}
|
||||
removingText={Utils.localizeMessage('admin.saml.removing.certificate', 'Removing Certificate...')}
|
||||
fileName={this.state.idpCertificateFile}
|
||||
onSubmit={this.removeCertificate}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
idpCert = (
|
||||
<FileUploadSetting
|
||||
id='idpCertificateFile'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpCertificateFileTitle'
|
||||
defaultMessage='Identity Provider Public Certificate:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpCertificateFileDesc'
|
||||
defaultMessage='The public authentication certificate issued by your Identity Provider.'
|
||||
/>
|
||||
}
|
||||
uploadingText={Utils.localizeMessage('admin.saml.uploading.certificate', 'Uploading Certificate...')}
|
||||
disabled={!this.state.enable}
|
||||
fileType='.crt'
|
||||
onSubmit={this.uploadCertificate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.privateKeyFile) {
|
||||
privKey = (
|
||||
<RemoveFileSetting
|
||||
id='privateKeyFile'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.privateKeyFileTitle'
|
||||
defaultMessage='Service Provider Private Key:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.privateKeyFileFileRemoveDesc'
|
||||
defaultMessage='Remove the private key used to decrypt SAML Assertions from the Identity Provider.'
|
||||
/>
|
||||
}
|
||||
removeButtonText={Utils.localizeMessage('admin.saml.remove.privKey', 'Remove Service Provider Private Key')}
|
||||
removingText={Utils.localizeMessage('admin.saml.removing.privKey', 'Removing Private Key...')}
|
||||
fileName={this.state.privateKeyFile}
|
||||
onSubmit={this.removeCertificate}
|
||||
disabled={!this.state.enable || !this.state.encrypt}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
privKey = (
|
||||
<FileUploadSetting
|
||||
id='privateKeyFile'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.privateKeyFileTitle'
|
||||
defaultMessage='Service Provider Private Key:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.privateKeyFileFileDesc'
|
||||
defaultMessage='The private key used to decrypt SAML Assertions from the Identity Provider.'
|
||||
/>
|
||||
}
|
||||
uploadingText={Utils.localizeMessage('admin.saml.uploading.privateKey', 'Uploading Private Key...')}
|
||||
disabled={!this.state.enable || !this.state.encrypt}
|
||||
fileType='.key'
|
||||
onSubmit={this.uploadCertificate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.publicCertificateFile) {
|
||||
pubCert = (
|
||||
<RemoveFileSetting
|
||||
id='publicCertificateFile'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.publicCertificateFileTitle'
|
||||
defaultMessage='Service Provider Public Certificate:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.publicCertificateFileRemoveDesc'
|
||||
defaultMessage='Remove the certificate used to generate the signature on a SAML request to the Identity Provider for a service provider initiated SAML login, when Mattermost is the Service Provider.'
|
||||
/>
|
||||
}
|
||||
removeButtonText={Utils.localizeMessage('admin.saml.remove.sp_certificate', 'Remove Service Provider Certificate')}
|
||||
removingText={Utils.localizeMessage('admin.saml.removing.certificate', 'Removing Certificate...')}
|
||||
fileName={this.state.publicCertificateFile}
|
||||
onSubmit={this.removeCertificate}
|
||||
disabled={!this.state.enable || !this.state.encrypt}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
pubCert = (
|
||||
<FileUploadSetting
|
||||
id='publicCertificateFile'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.publicCertificateFileTitle'
|
||||
defaultMessage='Service Provider Public Certificate:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.publicCertificateFileDesc'
|
||||
defaultMessage='The certificate used to generate the signature on a SAML request to the Identity Provider for a service provider initiated SAML login, when Mattermost is the Service Provider.'
|
||||
/>
|
||||
}
|
||||
uploadingText={Utils.localizeMessage('admin.saml.uploading.certificate', 'Uploading Certificate...')}
|
||||
disabled={!this.state.enable || !this.state.encrypt}
|
||||
fileType='.crt'
|
||||
onSubmit={this.uploadCertificate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsGroup>
|
||||
<BooleanSetting
|
||||
id='enable'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.enableTitle'
|
||||
defaultMessage='Enable Login With SAML:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedHTMLMessage
|
||||
id='admin.saml.enableDescription'
|
||||
defaultMessage='When true, Mattermost allows login using SAML. Please see <a href="http://docs.mattermost.com/deployment/sso-saml.html" target="_blank">documentation</a> to learn more about configuring SAML for Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.enable}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<TextSetting
|
||||
id='idpUrl'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpUrlTitle'
|
||||
defaultMessage='SAML SSO URL:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.idpUrlEx', 'Ex "https://idp.example.org/SAML2/SSO/Login"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpUrlDesc'
|
||||
defaultMessage='The URL where Mattermost sends a SAML request to start login sequence.'
|
||||
/>
|
||||
}
|
||||
value={this.state.idpUrl}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='idpDescriptorUrl'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpDescriptorUrlTitle'
|
||||
defaultMessage='Identity Provider Issuer URL:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.idpDescriptorUrlEx', 'Ex "https://idp.example.org/SAML2/issuer"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.idpDescriptorUrlDesc'
|
||||
defaultMessage='The issuer URL for the Identity Provider you use for SAML requests.'
|
||||
/>
|
||||
}
|
||||
value={this.state.idpDescriptorUrl}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
{idpCert}
|
||||
<BooleanSetting
|
||||
id='verify'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.verifyTitle'
|
||||
defaultMessage='Verify Signature:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.verifyDescription'
|
||||
defaultMessage='When true, Mattermost verifies that the signature sent from the SAML Response matches the Service Provider Login URL'
|
||||
/>
|
||||
}
|
||||
value={this.state.verify}
|
||||
disabled={!this.state.enable}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<TextSetting
|
||||
id='assertionConsumerServiceURL'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.assertionConsumerServiceURLTitle'
|
||||
defaultMessage='Service Provider Login URL:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.assertionConsumerServiceURLEx', 'Ex "https://<your-mattermost-url>/login/sso/saml"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.assertionConsumerServiceURLDesc'
|
||||
defaultMessage='Enter https://<your-mattermost-url>/login/sso/saml. Make sure you use HTTP or HTTPS in your URL depending on your server configuration. This field is also known as the Assertion Consumer Service URL.'
|
||||
/>
|
||||
}
|
||||
value={this.state.assertionConsumerServiceURL}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable || !this.state.verify}
|
||||
/>
|
||||
<BooleanSetting
|
||||
id='encrypt'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.encryptTitle'
|
||||
defaultMessage='Enable Encryption:'
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.encryptDescription'
|
||||
defaultMessage='When true, Mattermost will decrypt SAML Assertions encrypted with your Service Provider Public Certificate.'
|
||||
/>
|
||||
}
|
||||
value={this.state.encrypt}
|
||||
disabled={!this.state.enable}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{privKey}
|
||||
{pubCert}
|
||||
<TextSetting
|
||||
id='emailAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.emailAttrTitle'
|
||||
defaultMessage='Email Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.emailAttrEx', 'Ex "Email" or "PrimaryEmail"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.emailAttrDesc'
|
||||
defaultMessage='The attribute in the SAML Assertion that will be used to populate the email addresses of users in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.emailAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='usernameAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.usernameAttrTitle'
|
||||
defaultMessage='Username Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.usernameAttrEx', 'Ex "Username"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.usernameAttrDesc'
|
||||
defaultMessage='The attribute in the SAML Assertion that will be used to populate the username field in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.usernameAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='firstNameAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.firstnameAttrTitle'
|
||||
defaultMessage='First Name Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.firstnameAttrEx', 'Ex "FirstName"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.firstnameAttrDesc'
|
||||
defaultMessage='The attribute in the SAML Assertion that will be used to populate the first name of users in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.firstNameAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='lastNameAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.lastnameAttrTitle'
|
||||
defaultMessage='Last Name Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.lastnameAttrEx', 'Ex "LastName"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.lastnameAttrDesc'
|
||||
defaultMessage='The attribute in the SAML Assertion that will be used to populate the last name of users in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.lastNameAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='nicknameAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.nicknameAttrTitle'
|
||||
defaultMessage='Nickname Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.nicknameAttrEx', 'Ex "Nickname"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.nicknameAttrDesc'
|
||||
defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the nickname of users in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.nicknameAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='localeAttribute'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.localeAttrTitle'
|
||||
defaultMessage='Preferred Language Attribute:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.localeAttrEx', 'Ex "Locale" or "PrimaryLanguage"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.localeAttrDesc'
|
||||
defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the language of users in Mattermost.'
|
||||
/>
|
||||
}
|
||||
value={this.state.localeAttribute}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
<TextSetting
|
||||
id='loginButtonText'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='admin.saml.loginButtonTextTitle'
|
||||
defaultMessage='Login Button Text:'
|
||||
/>
|
||||
}
|
||||
placeholder={Utils.localizeMessage('admin.saml.loginButtonTextEx', 'Ex "With OKTA"')}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.saml.loginButtonTextDesc'
|
||||
defaultMessage='(Optional) The text that appears in the login button on the login page. Defaults to "With SAML".'
|
||||
/>
|
||||
}
|
||||
value={this.state.loginButtonText}
|
||||
onChange={this.handleChange}
|
||||
disabled={!this.state.enable}
|
||||
/>
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import Client from 'utils/web_client.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import ConfirmModal from '../confirm_modal.jsx';
|
||||
@@ -374,12 +375,13 @@ export default class UserItem extends React.Component {
|
||||
let authServiceText;
|
||||
let passwordReset;
|
||||
if (user.auth_service) {
|
||||
const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
|
||||
authServiceText = (
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.authServiceNotEmail'
|
||||
defaultMessage=', <strong>Sign-in Method:</strong> {service}'
|
||||
values={{
|
||||
service: Utils.toTitleCase(user.auth_service)
|
||||
service
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
Ссылка в новой задаче
Block a user