diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/about_build_modal.jsx | 8 | ||||
-rw-r--r-- | web/react/components/admin_console/admin_controller.jsx | 3 | ||||
-rw-r--r-- | web/react/components/admin_console/admin_sidebar.jsx | 11 | ||||
-rw-r--r-- | web/react/components/admin_console/ldap_settings.jsx | 388 | ||||
-rw-r--r-- | web/react/components/login.jsx | 146 | ||||
-rw-r--r-- | web/react/components/login_email.jsx | 137 | ||||
-rw-r--r-- | web/react/components/login_ldap.jsx | 110 | ||||
-rw-r--r-- | web/react/components/signup_team.jsx | 7 | ||||
-rw-r--r-- | web/react/components/signup_user_complete.jsx | 14 | ||||
-rw-r--r-- | web/react/components/team_signup_choose_auth.jsx | 18 | ||||
-rw-r--r-- | web/react/components/team_signup_with_sso.jsx | 12 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 22 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 1 | ||||
-rw-r--r-- | web/web.go | 30 |
14 files changed, 773 insertions, 134 deletions
diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx index 6962876d4..f71e1c9ab 100644 --- a/web/react/components/about_build_modal.jsx +++ b/web/react/components/about_build_modal.jsx @@ -33,10 +33,14 @@ export default class AboutBuildModal extends React.Component { <div className='col-sm-3 info__label'>{'Build Date:'}</div> <div className='col-sm-9'>{config.BuildDate}</div> </div> - <div className='row'> + <div className='row form-group'> <div className='col-sm-3 info__label'>{'Build Hash:'}</div> <div className='col-sm-9'>{config.BuildHash}</div> </div> + <div className='row'> + <div className='col-sm-3 info__label'>{'Enterprise Ready:'}</div> + <div className='col-sm-9'>{config.BuildEnterpriseReady}</div> + </div> </Modal.Body> <Modal.Footer> <button @@ -59,4 +63,4 @@ AboutBuildModal.defaultProps = { AboutBuildModal.propTypes = { show: React.PropTypes.bool.isRequired, onModalDismissed: React.PropTypes.func.isRequired -};
\ No newline at end of file +}; diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index e587c4f84..32b2e9bb7 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -21,6 +21,7 @@ import ServiceSettingsTab from './service_settings.jsx'; import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx'; import TeamUsersTab from './team_users.jsx'; import TeamAnalyticsTab from './team_analytics.jsx'; +import LdapSettingsTab from './ldap_settings.jsx'; export default class AdminController extends React.Component { constructor(props) { @@ -151,6 +152,8 @@ export default class AdminController extends React.Component { tab = <ServiceSettingsTab config={this.state.config} />; } else if (this.state.selected === 'legal_and_support_settings') { tab = <LegalAndSupportSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'ldap_settings') { + tab = <LdapSettingsTab config={this.state.config} />; } else if (this.state.selected === 'team_users') { if (this.state.teams) { tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />; diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index da445da37..587fb35ed 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -255,6 +255,15 @@ export default class AdminSidebar extends React.Component { <li> <a href='#' + className={this.isSelected('ldap_settings')} + onClick={this.handleClick.bind(this, 'ldap_settings', null)} + > + {'LDAP Settings'} + </a> + </li> + <li> + <a + href='#' className={this.isSelected('legal_and_support_settings')} onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)} > @@ -334,4 +343,4 @@ AdminSidebar.propTypes = { selected: React.PropTypes.string, selectedTeam: React.PropTypes.string, selectTab: React.PropTypes.func -};
\ No newline at end of file +}; diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx new file mode 100644 index 000000000..f8ea62192 --- /dev/null +++ b/web/react/components/admin_console/ldap_settings.jsx @@ -0,0 +1,388 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.jsx'; + +const DEFAULT_LDAP_PORT = 389; +const DEFAULT_QUERY_TIMEOUT = 60; + +export default class LdapSettings extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + this.handleEnable = this.handleEnable.bind(this); + this.handleDisable = this.handleDisable.bind(this); + + this.state = { + saveNeeded: false, + serverError: null, + enable: this.props.config.LdapSettings.Enable + }; + } + handleChange() { + this.setState({saveNeeded: true}); + } + handleEnable() { + this.setState({saveNeeded: true, enable: true}); + } + handleDisable() { + this.setState({saveNeeded: true, enable: false}); + } + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + const config = this.props.config; + config.LdapSettings.Enable = this.refs.Enable.checked; + config.LdapSettings.LdapServer = this.refs.LdapServer.value.trim(); + + let LdapPort = DEFAULT_LDAP_PORT; + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.LdapPort).value, 10))) { + LdapPort = parseInt(ReactDOM.findDOMNode(this.refs.LdapPort).value, 10); + } + config.LdapSettings.LdapPort = LdapPort; + + config.LdapSettings.BaseDN = this.refs.BaseDN.value.trim(); + config.LdapSettings.BindUsername = this.refs.BindUsername.value.trim(); + config.LdapSettings.BindPassword = this.refs.BindPassword.value.trim(); + config.LdapSettings.FirstNameAttribute = this.refs.FirstNameAttribute.value.trim(); + config.LdapSettings.LastNameAttribute = this.refs.LastNameAttribute.value.trim(); + config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim(); + config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim(); + config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim(); + + let QueryTimeout = DEFAULT_QUERY_TIMEOUT; + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10))) { + QueryTimeout = parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10); + } + config.LdapSettings.QueryTimeout = QueryTimeout; + + 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() { + let serverError = ''; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + let saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + <h3>{'LDAP Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Enable' + > + {'Enable Login With LDAP:'} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='Enable' + value='true' + ref='Enable' + defaultChecked={this.props.config.LdapSettings.Enable} + onChange={this.handleEnable} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='Enable' + value='false' + defaultChecked={!this.props.config.LdapSettings.Enable} + onChange={this.handleDisable} + /> + {'false'} + </label> + <p className='help-text'>{'When true, Mattermost allows login using LDAP'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='LdapServer' + > + {'LDAP Server:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='LdapServer' + ref='LdapServer' + placeholder='Ex "10.0.0.23"' + defaultValue={this.props.config.LdapSettings.LdapServer} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The domain or ip address of LDAP server.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='LdapPort' + > + {'LDAP Port:'} + </label> + <div className='col-sm-8'> + <input + type='number' + className='form-control' + id='LdapPort' + ref='LdapPort' + placeholder='Ex "389"' + defaultValue={this.props.config.LdapSettings.LdapPort} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The port to connect to the LDAP server on. Default is 389.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='BaseDN' + > + {'BaseDN:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='BaseDN' + ref='BaseDN' + placeholder='Ex "dc=mydomain,dc=com"' + defaultValue={this.props.config.LdapSettings.BaseDN} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The base dn where mattermost should search for users.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='BindUsername' + > + {'Bind Username:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='BindUsername' + ref='BindUsername' + placeholder='' + defaultValue={this.props.config.LdapSettings.BindUsername} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'Username of a user with read access to the LDAP server specified.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='BindPassword' + > + {'Bind Password:'} + </label> + <div className='col-sm-8'> + <input + type='password' + className='form-control' + id='BindPassword' + ref='BindPassword' + placeholder='' + defaultValue={this.props.config.LdapSettings.BindPassword} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'Password of the user given above.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='FirstNameAttribute' + > + {'First Name Attrubute'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='FirstNameAttribute' + ref='FirstNameAttribute' + placeholder='Ex "givenName"' + defaultValue={this.props.config.LdapSettings.FirstNameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The first name attribute of entires in the LDAP server.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='LastNameAttribute' + > + {'Last Name Attribute:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='LastNameAttribute' + ref='LastNameAttribute' + placeholder='Ex "sn"' + defaultValue={this.props.config.LdapSettings.LastNameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The last name attribute of entries in the LDAP server.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EmailAttribute' + > + {'Email Attribute:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='EmailAttribute' + ref='EmailAttribute' + placeholder='Ex "mail"' + defaultValue={this.props.config.LdapSettings.EmailAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The email attribute of entries in the LDAP server.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='UsernameAttribute' + > + {'Username Attribute:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='UsernameAttribute' + ref='UsernameAttribute' + placeholder='Ex "sAMAccountName"' + defaultValue={this.props.config.LdapSettings.UsernameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The attribute of entries in the LDAP server to use for username in Mattermost. May be the same as the ID Attribute.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='IdAttribute' + > + {'Id Attribute: '} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='IdAttribute' + ref='IdAttribute' + placeholder='Ex "sAMAccountName"' + defaultValue={this.props.config.LdapSettings.IdAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The attribute of entries in the LDAP server to use as a unique identifier. Users will use this to login. Ideally this would be the username they are used to loging in with. May be the same as the username attribute above.'}</p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='QueryTimeout' + > + {'Query Timeout (seconds):'} + </label> + <div className='col-sm-8'> + <input + type='number' + className='form-control' + id='QueryTimeout' + ref='QueryTimeout' + placeholder='Ex "60"' + defaultValue={this.props.config.LdapSettings.QueryTimeout} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'>{'The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.'}</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> + ); + } +} +LdapSettings.defaultProps = { +}; + +LdapSettings.propTypes = { + config: React.PropTypes.object +}; diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index d87bd20ad..9afaa8b0d 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -2,97 +2,19 @@ // See License.txt for license information. import * as Utils from '../utils/utils.jsx'; -import * as Client from '../utils/client.jsx'; -import UserStore from '../stores/user_store.jsx'; -import BrowserStore from '../stores/browser_store.jsx'; +import LoginEmail from './login_email.jsx'; +import LoginLdap from './login_ldap.jsx'; export default class Login extends React.Component { constructor(props) { super(props); - this.handleSubmit = this.handleSubmit.bind(this); - this.state = {}; } - handleSubmit(e) { - e.preventDefault(); - var state = {}; - - const name = this.props.teamName; - if (!name) { - state.serverError = 'Bad team name'; - this.setState(state); - return; - } - - const email = ReactDOM.findDOMNode(this.refs.email).value.trim(); - if (!email) { - state.serverError = 'An email is required'; - this.setState(state); - return; - } - - const password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - if (!password) { - state.serverError = 'A password is required'; - this.setState(state); - return; - } - - if (!BrowserStore.isLocalStorageSupported()) { - state.serverError = 'This service requires local storage to be enabled. Please enable it or exit private browsing.'; - this.setState(state); - return; - } - - state.serverError = ''; - this.setState(state); - - Client.loginByEmail(name, email, password, - () => { - UserStore.setLastEmail(email); - - const redirect = Utils.getUrlParameter('redirect'); - if (redirect) { - window.location.href = decodeURIComponent(redirect); - } else { - window.location.href = '/' + name + '/channels/town-square'; - } - }, - (err) => { - if (err.message === 'Login failed because email address has not been verified') { - window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email); - return; - } - state.serverError = err.message; - this.valid = false; - this.setState(state); - } - ); - } render() { - let serverError; - if (this.state.serverError) { - serverError = <label className='control-label'>{this.state.serverError}</label>; - } - let priorEmail = UserStore.getLastEmail(); - - const emailParam = Utils.getUrlParameter('email'); - if (emailParam) { - priorEmail = decodeURIComponent(emailParam); - } - const teamDisplayName = this.props.teamDisplayName; const teamName = this.props.teamName; - let focusEmail = false; - let focusPassword = false; - if (priorEmail === '') { - focusEmail = true; - } else { - focusPassword = true; - } - let loginMessage = []; if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { loginMessage.push( @@ -106,9 +28,16 @@ export default class Login extends React.Component { ); } - let errorClass = ''; - if (serverError) { - errorClass = ' has-error'; + if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { + loginMessage.push( + <a + className='btn btn-custom-login google' + href={'/' + teamName + '/login/google'} + > + <span className='icon' /> + <span>{'with Google Apps'}</span> + </a> + ); } const verifiedParam = Utils.getUrlParameter('verified'); @@ -125,39 +54,9 @@ export default class Login extends React.Component { let emailSignup; if (global.window.mm_config.EnableSignUpWithEmail === 'true') { emailSignup = ( - <div className='signup__email-container'> - <div className={'form-group' + errorClass}> - <input - autoFocus={focusEmail} - type='email' - className='form-control' - name='email' - defaultValue={priorEmail} - ref='email' - placeholder='Email' - spellCheck='false' - /> - </div> - <div className={'form-group' + errorClass}> - <input - autoFocus={focusPassword} - type='password' - className='form-control' - name='password' - ref='password' - placeholder='Password' - spellCheck='false' - /> - </div> - <div className='form-group'> - <button - type='submit' - className='btn btn-primary' - > - {'Sign in'} - </button> - </div> - </div> + <LoginEmail + teamName={this.props.teamName} + /> ); } @@ -211,25 +110,30 @@ export default class Login extends React.Component { ); } + let ldapLogin = null; + if (global.window.mm_config.EnableLdap === 'true') { + ldapLogin = ( + <LoginLdap + teamName={this.props.teamName} + /> + ); + } + return ( <div className='signup-team__container'> <h5 className='margin--less'>{'Sign in to:'}</h5> <h2 className='signup-team__name'>{teamDisplayName}</h2> <h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2> - <form onSubmit={this.handleSubmit}> {verifiedBox} - <div className={'form-group' + errorClass}> - {serverError} - </div> {loginMessage} {emailSignup} + {ldapLogin} {userSignUp} <div className='form-group margin--extra form-group--small'> <span><a href='/find_team'>{'Find your other teams'}</a></span> </div> {forgotPassword} {teamSignUp} - </form> </div> ); } diff --git a/web/react/components/login_email.jsx b/web/react/components/login_email.jsx new file mode 100644 index 000000000..cfe34d1c7 --- /dev/null +++ b/web/react/components/login_email.jsx @@ -0,0 +1,137 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../utils/utils.jsx'; +import * as Client from '../utils/client.jsx'; +import UserStore from '../stores/user_store.jsx'; + +export default class LoginEmail extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + serverError: '' + }; + } + handleSubmit(e) { + e.preventDefault(); + var state = {}; + + const name = this.props.teamName; + if (!name) { + state.serverError = 'Bad team name'; + this.setState(state); + return; + } + + const email = this.refs.email.value.trim(); + if (!email) { + state.serverError = 'An email is required'; + this.setState(state); + return; + } + + const password = this.refs.password.value.trim(); + if (!password) { + state.serverError = 'A password is required'; + this.setState(state); + return; + } + + state.serverError = ''; + this.setState(state); + + Client.loginByEmail(name, email, password, + () => { + UserStore.setLastEmail(email); + + const redirect = Utils.getUrlParameter('redirect'); + if (redirect) { + window.location.href = decodeURIComponent(redirect); + } else { + window.location.href = '/' + name + '/channels/town-square'; + } + }, + (err) => { + if (err.message === 'Login failed because email address has not been verified') { + window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email); + return; + } + state.serverError = err.message; + this.valid = false; + this.setState(state); + } + ); + } + render() { + let serverError; + let errorClass = ''; + if (this.state.serverError) { + serverError = <label className='control-label'>{this.state.serverError}</label>; + errorClass = ' has-error'; + } + + let priorEmail = UserStore.getLastEmail(); + let focusEmail = false; + let focusPassword = false; + if (priorEmail === '') { + focusEmail = true; + } else { + focusPassword = true; + } + + const emailParam = Utils.getUrlParameter('email'); + if (emailParam) { + priorEmail = decodeURIComponent(emailParam); + } + + return ( + <form onSubmit={this.handleSubmit}> + <div className='signup__email-container'> + <div className={'form-group' + errorClass}> + {serverError} + </div> + <div className={'form-group' + errorClass}> + <input + autoFocus={focusEmail} + type='email' + className='form-control' + name='email' + defaultValue={priorEmail} + ref='email' + placeholder='Email' + spellCheck='false' + /> + </div> + <div className={'form-group' + errorClass}> + <input + autoFocus={focusPassword} + type='password' + className='form-control' + name='password' + ref='password' + placeholder='Password' + spellCheck='false' + /> + </div> + <div className='form-group'> + <button + type='submit' + className='btn btn-primary' + > + {'Sign in'} + </button> + </div> + </div> + </form> + ); + } +} +LoginEmail.defaultProps = { +}; + +LoginEmail.propTypes = { + teamName: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/login_ldap.jsx b/web/react/components/login_ldap.jsx new file mode 100644 index 000000000..1e0e32f4f --- /dev/null +++ b/web/react/components/login_ldap.jsx @@ -0,0 +1,110 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../utils/utils.jsx'; +import * as Client from '../utils/client.jsx'; + +export default class LoginLdap extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + serverError: '' + }; + } + handleSubmit(e) { + e.preventDefault(); + var state = {}; + + const teamName = this.props.teamName; + if (!teamName) { + state.serverError = 'Bad team name'; + this.setState(state); + return; + } + + const id = this.refs.id.value.trim(); + if (!id) { + state.serverError = 'An LDAP ID is required'; + this.setState(state); + return; + } + + const password = this.refs.password.value.trim(); + if (!password) { + state.serverError = 'An LDAP password is required'; + this.setState(state); + return; + } + + state.serverError = ''; + this.setState(state); + + Client.loginByLdap(teamName, id, password, + () => { + const redirect = Utils.getUrlParameter('redirect'); + if (redirect) { + window.location.href = decodeURIComponent(redirect); + } else { + window.location.href = '/' + teamName + '/channels/town-square'; + } + }, + (err) => { + state.serverError = err.message; + this.setState(state); + } + ); + } + render() { + let serverError; + let errorClass = ''; + if (this.state.serverError) { + serverError = <label className='control-label'>{this.state.serverError}</label>; + errorClass = ' has-error'; + } + + return ( + <form onSubmit={this.handleSubmit}> + <div className='signup__email-container'> + <div className={'form-group' + errorClass}> + {serverError} + </div> + <div className={'form-group' + errorClass}> + <input + autoFocus={true} + className='form-control' + ref='id' + placeholder='LDAP Username' + spellCheck='false' + /> + </div> + <div className={'form-group' + errorClass}> + <input + type='password' + className='form-control' + ref='password' + placeholder='LDAP Password' + spellCheck='false' + /> + </div> + <div className='form-group'> + <button + type='submit' + className='btn btn-primary' + > + {'Sign in'} + </button> + </div> + </div> + </form> + ); + } +} +LoginLdap.defaultProps = { +}; + +LoginLdap.propTypes = { + teamName: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index 0ac837326..0e05bc533 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -112,6 +112,13 @@ export default class TeamSignUp extends React.Component { <SSOSignupPage service={Constants.GITLAB_SERVICE} /> </div> ); + } else if (this.state.page === 'google') { + return ( + <div> + {teamListing} + <SSOSignupPage service={Constants.GOOGLE_SERVICE} /> + </div> + ); } } } diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 2bde78726..df11fe045 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -183,11 +183,23 @@ export default class SignupUserComplete extends React.Component { href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search} > <span className='icon' /> - <span>with GitLab</span> + <span>{'with GitLab'}</span> </a> ); } + if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { + signupMessage.push( + <a + className='btn btn-custom-login google' + href={'/' + this.props.teamName + '/signup/google' + window.location.search} + > + <span className='icon' /> + <span>{'with Google'}</span> + </a> + ); + } + var emailSignup; if (global.window.mm_config.EnableSignUpWithEmail === 'true') { emailSignup = ( diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx index 0254c8b4e..19b9750b3 100644 --- a/web/react/components/team_signup_choose_auth.jsx +++ b/web/react/components/team_signup_choose_auth.jsx @@ -26,6 +26,24 @@ export default class ChooseAuthPage extends React.Component { ); } + if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { + buttons.push( + <a + className='btn btn-custom-login google btn-full' + href='#' + onClick={ + (e) => { + e.preventDefault(); + this.props.updatePage('google'); + } + } + > + <span className='icon' /> + <span>{'Create new team with Google Apps Account'}</span> + </a> + ); + } + if (global.window.mm_config.EnableSignUpWithEmail === 'true') { buttons.push( <a diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index e3f16efb0..f4b323956 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -88,6 +88,18 @@ export default class SSOSignUpPage extends React.Component { <span>{'Create team with GitLab Account'}</span> </a> ); + } else if (this.props.service === Constants.GOOGLE_SERVICE) { + button = ( + <a + className='btn btn-custom-login google btn-full' + href='#' + onClick={this.handleSubmit} + disabled={disabled} + > + <span className='icon'/> + <span>{'Create team with Google Apps Account'}</span> + </a> + ); } return ( diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index beabf5227..a12e85f67 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -243,7 +243,7 @@ export function loginByEmail(name, email, password, success, error) { dataType: 'json', contentType: 'application/json', type: 'POST', - data: JSON.stringify({name: name, email: email, password: password}), + data: JSON.stringify({name, email, password}), success: function onSuccess(data, textStatus, xhr) { track('api', 'api_users_login_success', data.team_id, 'email', data.email); BrowserStore.signalLogin(); @@ -258,6 +258,26 @@ export function loginByEmail(name, email, password, success, error) { }); } +export function loginByLdap(teamName, id, password, success, error) { + $.ajax({ + url: '/api/v1/users/login_ldap', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify({teamName, id, password}), + success: function onSuccess(data, textStatus, xhr) { + track('api', 'api_users_loginLdap_success', data.team_id, 'id', id); + success(data, textStatus, xhr); + }, + error: function onError(xhr, status, err) { + track('api', 'api_users_loginLdap_fail', teamName, 'id', id); + + var e = handleError('loginByLdap', xhr, status, err); + error(e); + } + }); +} + export function revokeSession(altId, success, error) { $.ajax({ url: '/api/v1/users/revoke_session', diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 5f027a409..29c5ecc5d 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -114,6 +114,7 @@ export default { DEFAULT_CHANNEL: 'town-square', OFFTOPIC_CHANNEL: 'off-topic', GITLAB_SERVICE: 'gitlab', + GOOGLE_SERVICE: 'google', EMAIL_SERVICE: 'email', POST_CHUNK_SIZE: 60, MAX_POST_CHUNKS: 3, diff --git a/web/web.go b/web/web.go index 63544229b..f1e8471b8 100644 --- a/web/web.go +++ b/web/web.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/gorilla/mux" "github.com/mattermost/platform/api" + "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" @@ -643,6 +644,12 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) { service := params["service"] teamName := params["team"] + if !utils.Cfg.TeamSettings.EnableUserCreation { + c.Err = model.NewAppError("signupTeam", "User sign-up is disabled.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + if len(teamName) == 0 { c.Err = model.NewAppError("signupWithOAuth", "Invalid team name", "team_name="+teamName) c.Err.StatusCode = http.StatusBadRequest @@ -699,9 +706,12 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) return } else { var user *model.User - if service == model.USER_AUTH_SERVICE_GITLAB { - glu := model.GitLabUserFromJson(body) - user = model.UserFromGitLabUser(glu) + provider := einterfaces.GetOauthProvider(service) + if provider == nil { + c.Err = model.NewAppError("signupCompleteOAuth", service+" oauth not avlailable on this server", "") + return + } else { + user = provider.GetUserFromJson(body) } if user == nil { @@ -744,8 +754,9 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) user.TeamId = team.Id user.EmailVerified = true - ruser := api.CreateUser(c, team, user) - if c.Err != nil { + ruser, err := api.CreateUser(team, user) + if err != nil { + c.Err = err return } @@ -799,9 +810,12 @@ func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) return } else { authData := "" - if service == model.USER_AUTH_SERVICE_GITLAB { - glu := model.GitLabUserFromJson(body) - authData = glu.GetAuthData() + provider := einterfaces.GetOauthProvider(service) + if provider == nil { + c.Err = model.NewAppError("signupCompleteOAuth", service+" oauth not avlailable on this server", "") + return + } else { + authData = provider.GetAuthDataFromJson(body) } if len(authData) == 0 { |