Merge pull request #1745 from mattermost/plt-1118

PLT-1118 Add ability to switch between SSO and email account
Этот коммит содержится в:
Christopher Speller
2016-01-04 15:55:51 -05:00
родитель 07efe75860 a6ae90ac2a
Коммит 67db3ca8ce
20 изменённых файлов: 1213 добавлений и 266 удалений

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

@@ -0,0 +1,53 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import EmailToSSO from './email_to_sso.jsx';
import SSOToEmail from './sso_to_email.jsx';
export default class ClaimAccount extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let content;
if (this.props.email === '') {
content = <p>{'No email specified.'}</p>;
} else if (this.props.currentType === '' && this.props.newType !== '') {
content = (
<EmailToSSO
email={this.props.email}
type={this.props.newType}
teamName={this.props.teamName}
teamDisplayName={this.props.teamDisplayName}
/>
);
} else {
content = (
<SSOToEmail
email={this.props.email}
currentType={this.props.currentType}
teamName={this.props.teamName}
teamDisplayName={this.props.teamDisplayName}
/>
);
}
return (
<div>
{content}
</div>
);
}
}
ClaimAccount.defaultProps = {
};
ClaimAccount.propTypes = {
currentType: React.PropTypes.string.isRequired,
newType: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
};

97
web/react/components/claim/email_to_sso.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,97 @@
// 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 EmailToSSO extends React.Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
this.state = {};
}
submit(e) {
e.preventDefault();
var state = {};
var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password) {
state.error = 'Please enter your password.';
this.setState(state);
return;
}
state.error = null;
this.setState(state);
var postData = {};
postData.password = password;
postData.email = this.props.email;
postData.team_name = this.props.teamName;
postData.service = this.props.type;
Client.switchToSSO(postData,
(data) => {
if (data.follow_link) {
window.location.href = data.follow_link;
}
},
(error) => {
this.setState({error});
}
);
}
render() {
var error = null;
if (this.state.error) {
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
}
var formClass = 'form-group';
if (error) {
formClass += ' has-error';
}
const uiType = Utils.toTitleCase(this.props.type) + ' SSO';
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
<h3>{'Switch Email/Password Account to ' + uiType}</h3>
<form onSubmit={this.submit}>
<p>{'Upon claiming your account, you will only be able to login with ' + Utils.toTitleCase(this.props.type) + ' SSO.'}</p>
<p>{'Enter the password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
<div className={formClass}>
<input
type='password'
className='form-control'
name='password'
ref='password'
placeholder='Password'
spellCheck='false'
/>
</div>
{error}
<button
type='submit'
className='btn btn-primary'
>
{'Switch account to ' + uiType}
</button>
</form>
</div>
</div>
);
}
}
EmailToSSO.defaultProps = {
};
EmailToSSO.propTypes = {
type: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
};

113
web/react/components/claim/sso_to_email.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,113 @@
// 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 SSOToEmail extends React.Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
this.state = {};
}
submit(e) {
e.preventDefault();
const state = {};
const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password) {
state.error = 'Please enter a password.';
this.setState(state);
return;
}
const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim();
if (!confirmPassword || password !== confirmPassword) {
state.error = 'Passwords do not match.';
this.setState(state);
return;
}
state.error = null;
this.setState(state);
var postData = {};
postData.password = password;
postData.email = this.props.email;
postData.team_name = this.props.teamName;
Client.switchToEmail(postData,
(data) => {
if (data.follow_link) {
window.location.href = data.follow_link;
}
},
(error) => {
this.setState({error});
}
);
}
render() {
var error = null;
if (this.state.error) {
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
}
var formClass = 'form-group';
if (error) {
formClass += ' has-error';
}
const uiType = Utils.toTitleCase(this.props.currentType) + ' SSO';
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
<h3>{'Switch ' + uiType + ' Account to Email'}</h3>
<form onSubmit={this.submit}>
<p>{'Upon changing your account type, you will only be able to login with your email and password.'}</p>
<p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
<div className={formClass}>
<input
type='password'
className='form-control'
name='password'
ref='password'
placeholder='New Password'
spellCheck='false'
/>
</div>
<div className={formClass}>
<input
type='password'
className='form-control'
name='passwordconfirm'
ref='passwordconfirm'
placeholder='Confirm Password'
spellCheck='false'
/>
</div>
{error}
<button
type='submit'
className='btn btn-primary'
>
{'Switch ' + uiType + ' account to email and password'}
</button>
</form>
</div>
</div>
);
}
}
SSOToEmail.defaultProps = {
};
SSOToEmail.propTypes = {
currentType: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
teamDisplayName: React.PropTypes.string.isRequired
};

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

@@ -1,10 +1,12 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
import LoginEmail from './login_email.jsx';
import LoginLdap from './login_ldap.jsx';
import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
export default class Login extends React.Component {
constructor(props) {
super(props);
@@ -40,15 +42,24 @@ export default class Login extends React.Component {
);
}
const verifiedParam = Utils.getUrlParameter('verified');
let verifiedBox = '';
if (verifiedParam) {
verifiedBox = (
<div className='alert alert-success'>
<i className='fa fa-check' />
{' Email Verified'}
</div>
);
const extraParam = Utils.getUrlParameter('extra');
let extraBox = '';
if (extraParam) {
let msg;
if (extraParam === Constants.SIGNIN_CHANGE) {
msg = ' Sign-in method changed successfully';
} else if (extraParam === Constants.SIGNIN_VERIFIED) {
msg = ' Email Verified';
}
if (msg != null) {
extraBox = (
<div className='alert alert-success'>
<i className='fa fa-check' />
{msg}
</div>
);
}
}
let emailSignup;
@@ -124,7 +135,7 @@ export default class Login extends React.Component {
<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>
{verifiedBox}
{extraBox}
{loginMessage}
{emailSignup}
{ldapLogin}

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

@@ -6,6 +6,9 @@ import SettingItemMax from '../setting_item_max.jsx';
import AccessHistoryModal from '../access_history_modal.jsx';
import ActivityLogModal from '../activity_log_modal.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import TeamStore from '../../stores/team_store.jsx';
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import Constants from '../../utils/constants.jsx';
@@ -18,9 +21,19 @@ export default class SecurityTab extends React.Component {
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
this.updateNewPassword = this.updateNewPassword.bind(this);
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
this.getDefaultState = this.getDefaultState.bind(this);
this.createPasswordSection = this.createPasswordSection.bind(this);
this.createSignInSection = this.createSignInSection.bind(this);
this.state = this.setupInitialState();
this.state = this.getDefaultState();
}
getDefaultState() {
return {
currentPassword: '',
newPassword: '',
confirmPassword: '',
authService: this.props.user.auth_service
};
}
submitPassword(e) {
e.preventDefault();
@@ -51,13 +64,13 @@ export default class SecurityTab extends React.Component {
data.new_password = newPassword;
Client.updatePassword(data,
function success() {
() => {
this.props.updateSection('');
AsyncClient.getMe();
this.setState(this.setupInitialState());
}.bind(this),
function fail(err) {
var state = this.setupInitialState();
this.setState(this.getDefaultState());
},
(err) => {
var state = this.getDefaultState();
if (err.message) {
state.serverError = err.message;
} else {
@@ -65,7 +78,7 @@ export default class SecurityTab extends React.Component {
}
state.passwordError = '';
this.setState(state);
}.bind(this)
}
);
}
updateCurrentPassword(e) {
@@ -77,86 +90,60 @@ export default class SecurityTab extends React.Component {
updateConfirmPassword(e) {
this.setState({confirmPassword: e.target.value});
}
setupInitialState() {
return {currentPassword: '', newPassword: '', confirmPassword: ''};
}
render() {
var serverError;
if (this.state.serverError) {
serverError = this.state.serverError;
}
var passwordError;
if (this.state.passwordError) {
passwordError = this.state.passwordError;
}
createPasswordSection() {
let updateSectionStatus;
var updateSectionStatus;
var passwordSection;
if (this.props.activeSection === 'password') {
var inputs = [];
var submit = null;
if (this.props.activeSection === 'password' && this.props.user.auth_service === '') {
const inputs = [];
if (this.props.user.auth_service === '') {
inputs.push(
<div
key='currentPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>Current Password</label>
<div className='col-sm-7'>
<input
className='form-control'
type='password'
onChange={this.updateCurrentPassword}
value={this.state.currentPassword}
/>
</div>
inputs.push(
<div
key='currentPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>{'Current Password'}</label>
<div className='col-sm-7'>
<input
className='form-control'
type='password'
onChange={this.updateCurrentPassword}
value={this.state.currentPassword}
/>
</div>
);
inputs.push(
<div
key='newPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>New Password</label>
<div className='col-sm-7'>
<input
className='form-control'
type='password'
onChange={this.updateNewPassword}
value={this.state.newPassword}
/>
</div>
</div>
);
inputs.push(
<div
key='newPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>{'New Password'}</label>
<div className='col-sm-7'>
<input
className='form-control'
type='password'
onChange={this.updateNewPassword}
value={this.state.newPassword}
/>
</div>
);
inputs.push(
<div
key='retypeNewPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>Retype New Password</label>
<div className='col-sm-7'>
<input
className='form-control'
type='password'
onChange={this.updateConfirmPassword}
value={this.state.confirmPassword}
/>
</div>
</div>
);
inputs.push(
<div
key='retypeNewPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>{'Retype New Password'}</label>
<div className='col-sm-7'>
<input
className='form-control'
type='password'
onChange={this.updateConfirmPassword}
value={this.state.confirmPassword}
/>
</div>
);
submit = this.submitPassword;
} else {
inputs.push(
<div
key='oauthPasswordInfo'
className='form-group'
>
<label className='col-sm-12'>Log in occurs through GitLab. Please see your GitLab account settings page to update your password.</label>
</div>
);
}
</div>
);
updateSectionStatus = function resetSection(e) {
this.props.updateSection('');
@@ -164,51 +151,157 @@ export default class SecurityTab extends React.Component {
e.preventDefault();
}.bind(this);
passwordSection = (
return (
<SettingItemMax
title='Password'
inputs={inputs}
submit={submit}
server_error={serverError}
client_error={passwordError}
submit={this.submitPassword}
server_error={this.state.serverError}
client_error={this.state.passwordError}
updateSection={updateSectionStatus}
/>
);
} else {
var describe;
if (this.props.user.auth_service === '') {
var d = new Date(this.props.user.last_password_update);
var hour = '12';
if (d.getHours() % 12) {
hour = String(d.getHours() % 12);
}
var min = String(d.getMinutes());
if (d.getMinutes() < 10) {
min = '0' + d.getMinutes();
}
var timeOfDay = ' am';
if (d.getHours() >= 12) {
timeOfDay = ' pm';
}
}
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
} else {
describe = 'Log in done through GitLab';
var describe;
var d = new Date(this.props.user.last_password_update);
var hour = '12';
if (d.getHours() % 12) {
hour = String(d.getHours() % 12);
}
var min = String(d.getMinutes());
if (d.getMinutes() < 10) {
min = '0' + d.getMinutes();
}
var timeOfDay = ' am';
if (d.getHours() >= 12) {
timeOfDay = ' pm';
}
describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
updateSectionStatus = function updateSection() {
this.props.updateSection('password');
}.bind(this);
return (
<SettingItemMin
title='Password'
describe={describe}
updateSection={updateSectionStatus}
/>
);
}
createSignInSection() {
let updateSectionStatus;
const user = this.props.user;
if (this.props.activeSection === 'signin') {
const inputs = [];
const teamName = TeamStore.getCurrent().name;
let emailOption;
if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') {
emailOption = (
<div>
<a
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + user.email}
>
{'Switch to using email and password'}
</a>
<br/>
</div>
);
}
updateSectionStatus = function updateSection() {
this.props.updateSection('password');
let gitlabOption;
if (global.window.mm_config.EnableSignUpWithGitLab === 'true' && user.auth_service === '') {
gitlabOption = (
<div>
<a
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + user.email + '&new_type=' + Constants.GITLAB_SERVICE}
>
{'Switch to using GitLab SSO'}
</a>
<br/>
</div>
);
}
let googleOption;
if (global.window.mm_config.EnableSignUpWithGoogle === 'true' && user.auth_service === '') {
googleOption = (
<div>
<a
className='btn btn-primary'
href={'/' + teamName + '/claim?email=' + user.email + '&new_type=' + Constants.GOOGLE_SERVICE}
>
{'Switch to using Google SSO'}
</a>
<br/>
</div>
);
}
inputs.push(
<div key='userSignInOption'>
{emailOption}
{gitlabOption}
<br/>
{googleOption}
</div>
);
updateSectionStatus = function updateSection(e) {
this.props.updateSection('');
this.setState({serverError: null});
e.preventDefault();
}.bind(this);
passwordSection = (
<SettingItemMin
title='Password'
describe={describe}
const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>;
return (
<SettingItemMax
title='Sign-in Method'
extraInfo={extraInfo}
inputs={inputs}
server_error={this.state.serverError}
updateSection={updateSectionStatus}
/>
);
}
updateSectionStatus = function updateSection() {
this.props.updateSection('signin');
}.bind(this);
let describe = 'Email and Password';
if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
describe = 'GitLab SSO';
}
return (
<SettingItemMin
title='Sign-in Method'
describe={describe}
updateSection={updateSectionStatus}
/>
);
}
render() {
const passwordSection = this.createPasswordSection();
let signInSection;
let numMethods = 0;
numMethods = global.window.mm_config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods;
numMethods = global.window.mm_config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods;
if (global.window.mm_config.EnableSignUpWithEmail && numMethods > 0) {
signInSection = this.createSignInSection();
}
return (
<div>
<div className='modal-header'>
@@ -233,9 +326,11 @@ export default class SecurityTab extends React.Component {
</h4>
</div>
<div className='user-settings'>
<h3 className='tab-header'>Security Settings</h3>
<h3 className='tab-header'>{'Security Settings'}</h3>
<div className='divider-dark first'/>
{passwordSection}
<div className='divider-light'/>
{signInSection}
<div className='divider-dark'/>
<br></br>
<ToggleModalButton

19
web/react/pages/claim_account.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import ClaimAccount from '../components/claim/claim_account.jsx';
function setupClaimAccountPage(props) {
ReactDOM.render(
<ClaimAccount
email={props.Email}
currentType={props.CurrentType}
newType={props.NewType}
teamName={props.TeamName}
teamDisplayName={props.TeamDisplayName}
/>,
document.getElementById('claim')
);
}
global.window.setup_claim_account_page = setupClaimAccountPage;

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

@@ -228,6 +228,40 @@ export function resetPassword(data, success, error) {
track('api', 'api_users_reset_password');
}
export function switchToSSO(data, success, error) {
$.ajax({
url: '/api/v1/users/switch_to_sso',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success,
error: function onError(xhr, status, err) {
var e = handleError('switchToSSO', xhr, status, err);
error(e);
}
});
track('api', 'api_users_switch_to_sso');
}
export function switchToEmail(data, success, error) {
$.ajax({
url: '/api/v1/users/switch_to_email',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(data),
success,
error: function onError(xhr, status, err) {
var e = handleError('switchToEmail', xhr, status, err);
error(e);
}
});
track('api', 'api_users_switch_to_email');
}
export function logout() {
track('api', 'api_users_logout');
var currentTeamUrl = TeamStore.getCurrentTeamUrl();

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

@@ -117,6 +117,8 @@ export default {
GITLAB_SERVICE: 'gitlab',
GOOGLE_SERVICE: 'google',
EMAIL_SERVICE: 'email',
SIGNIN_CHANGE: 'signin_change',
SIGNIN_VERIFIED: 'verified',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
POST_FOCUS_CONTEXT_RADIUS: 10,

16
web/templates/claim_account.html Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
{{define "claim_account"}}
<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content" id="claim"></div>
</div>
</div>
<script>
window.setup_claim_account_page({{ .Props }});
</script>
</body>
</html>
{{end}}

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

@@ -8,7 +8,6 @@ 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"
@@ -71,8 +70,7 @@ func InitWeb() {
mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(loginCompleteOAuth)).Methods("GET")
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET")
mainrouter.Handle("/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(completeOAuth)).Methods("GET")
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
@@ -92,6 +90,7 @@ func InitWeb() {
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/claim", api.AppHandler(claimAccount)).Methods("GET")
mainrouter.Handle("/{team}/pl/{postid}", api.AppHandler(postPermalink)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
mainrouter.Handle("/{team}/channels/{channelname}", api.AppHandler(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
@@ -565,7 +564,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
} else {
c.LogAudit("Email Verified")
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?verified=true&email="+url.QueryEscape(email), http.StatusTemporaryRedirect)
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?extra=verified&email="+url.QueryEscape(email), http.StatusTemporaryRedirect)
return
}
}
@@ -687,89 +686,63 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
}
redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete"
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
api.GetAuthorizationCode(c, w, r, teamName, service, redirectUri, "")
if authUrl, err := api.GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil {
c.Err = err
return
} else {
http.Redirect(w, r, authUrl, http.StatusFound)
}
}
func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
func completeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
service := params["service"]
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
uri := c.GetSiteURL() + "/signup/" + service + "/complete"
uri := c.GetSiteURL() + "/" + service + "/complete"
if body, team, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
if body, team, props, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
c.Err = err
return
} else {
var user *model.User
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 {
c.Err = model.NewAppError("signupCompleteOAuth", "Could not create user out of "+service+" user object", "")
return
}
suchan := api.Srv.Store.User().GetByAuth(team.Id, user.AuthData, service)
euchan := api.Srv.Store.User().GetByEmail(team.Id, user.Email)
if team.Email == "" {
team.Email = user.Email
if result := <-api.Srv.Store.Team().Update(team); result.Err != nil {
c.Err = result.Err
return
action := props["action"]
switch action {
case model.OAUTH_ACTION_SIGNUP:
api.CreateOAuthUser(c, w, r, service, body, team)
if c.Err == nil {
root(c, w, r)
}
} else {
found := true
count := 0
for found {
if found = api.IsUsernameTaken(user.Username, team.Id); c.Err != nil {
return
} else if found {
user.Username = user.Username + strconv.Itoa(count)
count += 1
}
break
case model.OAUTH_ACTION_LOGIN:
api.LoginByOAuth(c, w, r, service, body, team)
if c.Err == nil {
root(c, w, r)
}
break
case model.OAUTH_ACTION_EMAIL_TO_SSO:
api.CompleteSwitchWithOAuth(c, w, r, service, body, team, props["email"])
if c.Err == nil {
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/login?extra=signin_change", http.StatusTemporaryRedirect)
}
break
case model.OAUTH_ACTION_SSO_TO_EMAIL:
api.LoginByOAuth(c, w, r, service, body, team)
if c.Err == nil {
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+team.Name+"/"+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
}
break
default:
api.LoginByOAuth(c, w, r, service, body, team)
if c.Err == nil {
root(c, w, r)
}
break
}
if result := <-suchan; result.Err == nil {
c.Err = model.NewAppError("signupCompleteOAuth", "This "+service+" account has already been used to sign up for team "+team.DisplayName, "email="+user.Email)
return
}
if result := <-euchan; result.Err == nil {
c.Err = model.NewAppError("signupCompleteOAuth", "Team "+team.DisplayName+" already has a user with the email address attached to your "+service+" account", "email="+user.Email)
return
}
user.TeamId = team.Id
user.EmailVerified = true
ruser, err := api.CreateUser(team, user)
if err != nil {
c.Err = err
return
}
api.Login(c, w, r, ruser, "")
if c.Err != nil {
return
}
page := NewHtmlTemplatePage("home", "Home")
page.Team = team
page.User = ruser
page.Render(c, w)
}
}
@@ -791,57 +764,14 @@ func loginWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
redirectUri := c.GetSiteURL() + "/login/" + service + "/complete"
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_LOGIN
api.GetAuthorizationCode(c, w, r, teamName, service, redirectUri, loginHint)
}
func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
service := params["service"]
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
uri := c.GetSiteURL() + "/login/" + service + "/complete"
if body, team, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil {
if authUrl, err := api.GetAuthorizationCode(c, service, teamName, stateProps, loginHint); err != nil {
c.Err = err
return
} else {
authData := ""
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 {
c.Err = model.NewAppError("loginCompleteOAuth", "Could not parse auth data out of "+service+" user object", "")
return
}
var user *model.User
if result := <-api.Srv.Store.User().GetByAuth(team.Id, authData, service); result.Err != nil {
c.Err = result.Err
return
} else {
user = result.Data.(*model.User)
api.Login(c, w, r, user, "")
if c.Err != nil {
return
}
page := NewHtmlTemplatePage("home", "Home")
page.Team = team
page.User = user
page.Render(c, w)
root(c, w, r)
}
http.Redirect(w, r, authUrl, http.StatusFound)
}
}
@@ -1172,3 +1102,58 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("ok"))
}
func claimAccount(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !CheckBrowserCompatability(c, r) {
return
}
params := mux.Vars(r)
teamName := params["team"]
email := r.URL.Query().Get("email")
newType := r.URL.Query().Get("new_type")
var team *model.Team
if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
l4g.Error("Couldn't find team name=%v, err=%v", teamName, tResult.Err.Message)
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
return
} else {
team = tResult.Data.(*model.Team)
}
authType := ""
if len(email) != 0 {
if uResult := <-api.Srv.Store.User().GetByEmail(team.Id, email); uResult.Err != nil {
l4g.Error("Couldn't find user teamid=%v, email=%v, err=%v", team.Id, email, uResult.Err.Message)
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
return
} else {
user := uResult.Data.(*model.User)
authType = user.AuthService
// if user is not logged in to their SSO account, ask them to log in
if len(authType) != 0 && user.Id != c.Session.UserId {
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_SSO_TO_EMAIL
stateProps["email"] = email
if authUrl, err := api.GetAuthorizationCode(c, authType, team.Name, stateProps, ""); err != nil {
c.Err = err
return
} else {
http.Redirect(w, r, authUrl, http.StatusFound)
}
}
}
}
page := NewHtmlTemplatePage("claim_account", "Claim Account")
page.Props["Email"] = email
page.Props["CurrentType"] = authType
page.Props["NewType"] = newType
page.Props["TeamDisplayName"] = team.DisplayName
page.Props["TeamName"] = team.Name
page.Render(c, w)
}