diff options
-rw-r--r-- | api/user.go | 9 | ||||
-rw-r--r-- | api/user_test.go | 17 | ||||
-rw-r--r-- | model/client.go | 8 | ||||
-rw-r--r-- | webapp/client/client.jsx | 6 | ||||
-rw-r--r-- | webapp/components/signup_user_complete.jsx | 195 | ||||
-rw-r--r-- | webapp/utils/web_client.jsx | 22 |
6 files changed, 234 insertions, 23 deletions
diff --git a/api/user.go b/api/user.go index 60162d8f1..c53a643c7 100644 --- a/api/user.go +++ b/api/user.go @@ -436,6 +436,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { password := props["password"] mfaToken := props["token"] deviceId := props["device_id"] + ldapOnly := props["ldap_only"] == "true" if len(password) == 0 { c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "") @@ -460,7 +461,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { } else { c.LogAudit("attempt") - if user, err = getUserForLogin(loginId); err != nil { + if user, err = getUserForLogin(loginId, ldapOnly); err != nil { c.LogAudit("failure") c.Err = err return @@ -485,13 +486,13 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(user.ToJson())) } -func getUserForLogin(loginId string) (*model.User, *model.AppError) { +func getUserForLogin(loginId string, onlyLdap bool) (*model.User, *model.AppError) { ldapAvailable := *utils.Cfg.LdapSettings.Enable && einterfaces.GetLdapInterface() != nil if result := <-Srv.Store.User().GetForLogin( loginId, - *utils.Cfg.EmailSettings.EnableSignInWithUsername, - *utils.Cfg.EmailSettings.EnableSignInWithEmail, + *utils.Cfg.EmailSettings.EnableSignInWithUsername && !onlyLdap, + *utils.Cfg.EmailSettings.EnableSignInWithEmail && !onlyLdap, ldapAvailable, ); result.Err != nil { diff --git a/api/user_test.go b/api/user_test.go index 1a3b36d4b..9dd57dc20 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -204,6 +204,23 @@ func TestLogin(t *testing.T) { } } +func TestLoginByLdap(t *testing.T) { + th := Setup() + Client := th.CreateClient() + + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + rteam, _ := Client.CreateTeam(&team) + + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "corey" + model.NewId(), Password: "pwd"} + ruser, _ := Client.CreateUser(&user, "") + LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) + store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) + + if _, err := Client.LoginByLdap(ruser.Data.(*model.User).Id, user.Password); err == nil { + t.Fatal("should've failed to log in with non-ldap user") + } +} + func TestLoginWithDeviceId(t *testing.T) { th := Setup().InitBasic() Client := th.BasicClient diff --git a/model/client.go b/model/client.go index 1575df9e0..152aaa706 100644 --- a/model/client.go +++ b/model/client.go @@ -362,6 +362,14 @@ func (c *Client) Login(loginId string, password string) (*Result, *AppError) { return c.login(m) } +func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { + m := make(map[string]string) + m["login_id"] = loginId + m["password"] = password + m["ldap_only"] = "true" + return c.login(m) +} + func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { m := make(map[string]string) m["login_id"] = loginId diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx index 5d0dd07c9..c81c5a1d7 100644 --- a/webapp/client/client.jsx +++ b/webapp/client/client.jsx @@ -759,6 +759,12 @@ export default class Client { this.track('api', 'api_users_login', '', 'id', id); } + loginByLdap = (loginId, password, mfaToken, success, error) => { + this.doLogin({login_id: loginId, password, token: mfaToken, ldap_only: 'true'}, success, error); + + this.track('api', 'api_users_login', '', 'login_id', loginId); + } + doLogin = (outgoingData, success, error) => { var outer = this; // eslint-disable-line consistent-this diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx index 6dd26f391..5c06cefed 100644 --- a/webapp/components/signup_user_complete.jsx +++ b/webapp/components/signup_user_complete.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import FormError from 'components/form_error.jsx'; import LoadingScreen from 'components/loading_screen.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; @@ -19,11 +20,15 @@ import ReactDOM from 'react-dom'; import logoImage from 'images/logo.png'; -class SignupUserComplete extends React.Component { +export default class SignupUserComplete extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); + this.handleLdapSignup = this.handleLdapSignup.bind(this); + + this.handleLdapIdChange = this.handleLdapIdChange.bind(this); + this.handleLdapPasswordChange = this.handleLdapPasswordChange.bind(this); this.state = { data: '', @@ -35,9 +40,12 @@ class SignupUserComplete extends React.Component { teamId: '', openServer: false, loading: true, - inviteId: '' + inviteId: '', + ldapId: '', + ldapPassword: '' }; } + componentWillMount() { let data = this.props.location.query.d; let hash = this.props.location.query.h; @@ -148,6 +156,48 @@ class SignupUserComplete extends React.Component { }); } + handleLdapSignup(e) { + e.preventDefault(); + + this.setState({ldapError: ''}); + + Client.webLoginByLdap( + this.state.ldapId, + this.state.ldapPassword, + null, + () => { + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); + }, + (err) => { + if (err.id === 'ent.ldap.do_login.user_not_registered.app_error' || err.id === 'ent.ldap.do_login.user_filtered.app_error') { + this.setState({ + ldapError: ( + <FormattedMessage + id='login.userNotFound' + defaultMessage="We couldn't find an account matching your login credentials." + /> + ) + }); + } else if (err.id === 'ent.ldap.do_login.invalid_password.app_error') { + this.setState({ + ldapError: ( + <FormattedMessage + id='login.invalidPassword' + defaultMessage='Your password is incorrect.' + /> + ) + }); + } else { + this.setState({ldapError: err.message}); + } + } + ); + } + handleSubmit(e) { e.preventDefault(); @@ -277,6 +327,80 @@ class SignupUserComplete extends React.Component { } ); } + + handleLdapIdChange(e) { + e.preventDefault(); + + this.setState({ + ldapId: e.target.value + }); + } + + handleLdapPasswordChange(e) { + e.preventDefault(); + + this.setState({ + ldapPassword: e.target.value + }); + } + + renderLdapLogin() { + let ldapIdPlaceholder; + if (global.window.mm_config.LdapLoginFieldName) { + ldapIdPlaceholder = global.window.mm_config.LdapLoginFieldName; + } else { + ldapIdPlaceholder = Utils.localizeMessage('login.ldap_username', 'LDAP Username'); + } + + let errorClass = ''; + if (this.state.ldapError) { + errorClass += ' has-error'; + } + + return ( + <form + onSubmit={this.handleLdapSignup} + > + <div className='signup__email-container'> + <FormError error={this.state.ldapError}/> + <div className={'form-group' + errorClass}> + <input + className='form-control' + name='ldapId' + value={this.state.ldapId} + onChange={this.handleLdapIdChange} + placeholder={ldapIdPlaceholder} + spellCheck='false' + /> + </div> + <div className={'form-group' + errorClass}> + <input + type='password' + className='form-control' + name='password' + value={this.state.ldapPassword} + onChange={this.handleLdapPasswordChange} + placeholder={Utils.localizeMessage('login.password', 'Password')} + spellCheck='false' + /> + </div> + <div className='form-group'> + <button + type='submit' + className='btn btn-primary' + disabled={!this.state.ldapId || !this.state.ldapPassword} + > + <FormattedMessage + id='login.signIn' + defaultMessage='Sign in' + /> + </button> + </div> + </div> + </form> + ); + } + render() { Client.track('signup', 'signup_user_01_welcome'); @@ -431,6 +555,23 @@ class SignupUserComplete extends React.Component { ); } + let ldapSignup; + if (global.window.mm_config.EnableLdap === 'true' && global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP) { + ldapSignup = ( + <div className='inner__content'> + <h5> + <strong> + <FormattedMessage + id='signup_user_completed.withLdap' + defaultMessage='With your LDAP credentials' + /> + </strong> + </h5> + {this.renderLdapLogin()} + </div> + ); + } + let emailSignup; if (global.window.mm_config.EnableSignUpWithEmail === 'true') { emailSignup = ( @@ -494,7 +635,7 @@ class SignupUserComplete extends React.Component { ); } - if (signupMessage.length > 0 && emailSignup) { + if (signupMessage.length > 0 && (emailSignup || ldapSignup)) { signupMessage = ( <div> {signupMessage} @@ -508,7 +649,36 @@ class SignupUserComplete extends React.Component { ); } - if (signupMessage.length === 0 && !emailSignup) { + if (ldapSignup && emailSignup) { + ldapSignup = ( + <div> + {ldapSignup} + <div className='or__container'> + <FormattedMessage + id='signup_user_completed.or' + defaultMessage='or' + /> + </div> + </div> + ); + } + + let terms = null; + if (!this.state.noOpenServerError && (emailSignup || ldapSignup)) { + terms = ( + <p> + <FormattedHTMLMessage + id='create_team.agreement' + defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." + values={{ + siteName: global.window.mm_config.SiteName + }} + /> + </p> + ); + } + + if (signupMessage.length === 0 && !emailSignup && !ldapSignup) { emailSignup = ( <div> <FormattedMessage @@ -519,22 +689,10 @@ class SignupUserComplete extends React.Component { ); } - let terms = ( - <p> - <FormattedHTMLMessage - id='create_team.agreement' - defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." - values={{ - siteName: global.window.mm_config.SiteName - }} - /> - </p> - ); - if (this.state.noOpenServerError) { signupMessage = null; emailSignup = null; - terms = null; + ldapSignup = null; } return ( @@ -566,6 +724,7 @@ class SignupUserComplete extends React.Component { /> </h4> {signupMessage} + {ldapSignup} {emailSignup} {serverError} {terms} @@ -581,5 +740,3 @@ SignupUserComplete.defaultProps = { SignupUserComplete.propTypes = { location: React.PropTypes.object }; - -export default SignupUserComplete; diff --git a/webapp/utils/web_client.jsx b/webapp/utils/web_client.jsx index 3efb32806..642e523b7 100644 --- a/webapp/utils/web_client.jsx +++ b/webapp/utils/web_client.jsx @@ -60,6 +60,28 @@ class WebClientClass extends Client { } ); } + + webLoginByLdap(loginId, password, token, success, error) { + this.loginByLdap( + loginId, + password, + token, + (data) => { + this.track('api', 'api_users_login_success', '', 'login_id', loginId); + BrowserStore.signalLogin(); + + if (success) { + success(data); + } + }, + (err) => { + this.track('api', 'api_users_login_fail', name, 'login_id', loginId); + if (error) { + error(err); + } + } + ); + } } var WebClient = new WebClientClass(); |