Commit 3832092c authored by Stelios Milidonis's avatar Stelios Milidonis

release 5.22

parent 5ca5fdbe
......@@ -575,6 +575,19 @@ exports[`components/AdminSidebar Plugins should match snapshot 1`] = `
/>
}
/>
<AdminSidebarSection
key="16"
name="phabricator"
parentLink=""
subsection={false}
title={
<FormattedMessage
defaultMessage="Phabricator"
id="admin.sidebar.phabricator"
values={Object {}}
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
icon="fa-plug"
......@@ -1167,6 +1180,19 @@ exports[`components/AdminSidebar should match snapshot 1`] = `
/>
}
/>
<AdminSidebarSection
key="16"
name="authentication/phabricator"
parentLink=""
subsection={false}
title={
<FormattedMessage
defaultMessage="Phabricator"
id="admin.sidebar.phabricator"
values={Object {}}
/>
}
/>
<AdminSidebarSection
key="9"
name="authentication/gitlab"
......@@ -1588,6 +1614,19 @@ exports[`components/AdminSidebar should match snapshot, not prevent the console
/>
}
/>
<AdminSidebarSection
key="16"
name="authentication/phabricator"
parentLink=""
subsection={false}
title={
<FormattedMessage
defaultMessage="Phabricator"
id="admin.sidebar.phabricator"
values={Object {}}
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
icon="fa-cogs"
......
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class PhabricatorSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.updatePhabricatorUrl = this.updatePhabricatorUrl.bind(this);
}
getConfigFromState(config) {
config.PhabricatorSettings.Enable = this.state.enable;
config.PhabricatorSettings.Id = this.state.id;
config.PhabricatorSettings.Secret = this.state.secret;
config.PhabricatorSettings.UserApiEndpoint = this.state.userApiEndpoint;
config.PhabricatorSettings.AuthEndpoint = this.state.authEndpoint;
config.PhabricatorSettings.TokenEndpoint = this.state.tokenEndpoint;
return config;
}
getStateFromConfig(config) {
return {
enable: config.PhabricatorSettings.Enable,
id: config.PhabricatorSettings.Id,
secret: config.PhabricatorSettings.Secret,
phabricatorUrl: config.PhabricatorSettings.UserApiEndpoint.replace('/api/user.whoami', ''),
userApiEndpoint: config.PhabricatorSettings.UserApiEndpoint,
authEndpoint: config.PhabricatorSettings.AuthEndpoint,
tokenEndpoint: config.PhabricatorSettings.TokenEndpoint,
};
}
updatePhabricatorUrl(id, value) {
let trimmedValue = value;
if (value.endsWith('/')) {
trimmedValue = value.slice(0, -1);
}
this.setState({
saveNeeded: true,
phabricatorUrl: value,
userApiEndpoint: trimmedValue + '/api/user.whoami',
authEndpoint: trimmedValue + '/oauthserver/auth/',
tokenEndpoint: trimmedValue + '/oauthserver/token/',
});
}
isPhabricatorURLSetByEnv = () => {
// Assume that if one of these has been set using an environment variable,
// all of them have been set that way
return this.isSetByEnv('PhabricatorSettings.AuthEndpoint') ||
this.isSetByEnv('PhabricatorSettings.TokenEndpoint') ||
this.isSetByEnv('PhabricatorSettings.UserApiEndpoint');
};
renderTitle() {
return (
<FormattedMessage
id='admin.authentication.phabricator'
defaultMessage='Phabricator'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.phabricator.enableTitle'
defaultMessage='Enable authentication with Phabricator: '
/>
}
helpText={
<div>
<FormattedMessage
id='admin.phabricator.enableDescription'
defaultMessage='When true, Mattermost allows team creation and account signup using Phabricator OAuth.'
/>
<br/>
<FormattedHTMLMessage
id='admin.phabricator.EnableHtmlDesc'
defaultMessage='<ol><li>Log in to your Phabricator account and go to Applications -> OAuth Server -> Create OAuth Server.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/phabricator/complete" (example: http://localhost:8065/login/phabricator/complete) and "<your-mattermost-url>/signup/phabricator/complete". </li><li>Then use "Show Application Secret" and "Client PHID" fields from Phabricator to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/>
</div>
}
value={this.state.enable}
onChange={this.handleChange}
setByEnv={this.isSetByEnv('PhabricatorSettings.Enable')}
/>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.phabricator.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientIdExample', 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.phabricator.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into Phabricator'
/>
}
value={this.state.id}
onChange={this.handleChange}
disabled={!this.state.enable}
setByEnv={this.isSetByEnv('PhabricatorSettings.Id')}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.phabricator.clientSecretTitle'
defaultMessage='Application Secret Key:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientSecretExample', 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.phabricator.clientSecretDescription'
defaultMessage='Obtain this value via the instructions above for logging into Phabricator.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
disabled={!this.state.enable}
setByEnv={this.isSetByEnv('PhabricatorSettings.Secret')}
/>
<TextSetting
id='phabricatorUrl'
label={
<FormattedMessage
id='admin.phabricator.siteUrl'
defaultMessage='Phabricator Site URL:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.siteUrlExample', 'E.g.: https://')}
helpText={
<FormattedMessage
id='admin.phabricator.siteUrlDescription'
defaultMessage='Enter the URL of your Phabricator instance, e.g. https://example.com:3000. If your Phabricator instance is not set up with SSL, start the URL with http:// instead of https://.'
/>
}
value={this.state.phabricatorUrl}
onChange={this.updatePhabricatorUrl}
disabled={!this.state.enable}
setByEnv={this.isPhabricatorURLSetByEnv()}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.phabricator.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={''}
value={this.state.userApiEndpoint}
disabled={true}
setByEnv={false}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.phabricator.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={''}
value={this.state.authEndpoint}
disabled={true}
setByEnv={false}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.phabricator.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={''}
value={this.state.tokenEndpoint}
disabled={true}
setByEnv={false}
/>
</SettingsGroup>
);
}
}
......@@ -145,6 +145,21 @@ const ErrorMessage: React.FC<Props> = ({type, message, service, isGuest}: Props)
}}
/>
</p>
<p>
<FormattedMessage
id='error.oauth_missing_code.phabricator'
defaultMessage='For {link} please make sure you followed the setup instructions.'
values={{
link: (
<ErrorLink
url={'https://docs.mattermost.com/deployment/sso-phabricator.html'}
messageId={t('error.oauth_missing_code.phabricator.link')}
defaultMessage={'Phabricator'}
/>
),
}}
/>
</p>
<p>
<FormattedMessage
id='error.oauth_missing_code.forum'
......@@ -207,4 +222,4 @@ const ErrorMessage: React.FC<Props> = ({type, message, service, isGuest}: Props)
return errorMessage;
};
export default ErrorMessage;
\ No newline at end of file
export default ErrorMessage;
......@@ -31,6 +31,7 @@ function mapStateToProps(state) {
const enableSignInWithUsername = config.EnableSignInWithUsername === 'true';
const enableSignUpWithEmail = config.EnableSignUpWithEmail === 'true';
const enableSignUpWithGitLab = config.EnableSignUpWithGitLab === 'true';
const enableSignUpWithPhabricator = config.EnableSignUpWithPhabricator === 'true';
const enableSignUpWithGoogle = config.EnableSignUpWithGoogle === 'true';
const enableSignUpWithOffice365 = config.EnableSignUpWithOffice365 === 'true';
const ldapLoginFieldName = config.LdapLoginFieldName;
......@@ -65,6 +66,7 @@ function mapStateToProps(state) {
enableSignInWithUsername,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
experimentalPrimaryTeam,
......
......@@ -51,6 +51,7 @@ class LoginController extends React.Component {
enableSignInWithUsername: PropTypes.bool.isRequired,
enableSignUpWithEmail: PropTypes.bool.isRequired,
enableSignUpWithGitLab: PropTypes.bool.isRequired,
enableSignUpWithPhabricator: PropTypes.bool.isRequired,
enableSignUpWithGoogle: PropTypes.bool.isRequired,
enableSignUpWithOffice365: PropTypes.bool.isRequired,
experimentalPrimaryTeam: PropTypes.string,
......@@ -422,6 +423,7 @@ class LoginController extends React.Component {
checkSignUpEnabled = () => {
return this.props.enableSignUpWithEmail ||
this.props.enableSignUpWithGitLab ||
this.props.enableSignUpWithPhabricator ||
this.props.enableSignUpWithOffice365 ||
this.props.enableSignUpWithGoogle ||
this.props.enableLdap ||
......@@ -535,6 +537,7 @@ class LoginController extends React.Component {
const ldapEnabled = this.state.ldapEnabled;
const gitlabSigninEnabled = this.props.enableSignUpWithGitLab;
const phabricatorSigninEnabled = this.props.enableSignUpWithPhabricator;
const googleSigninEnabled = this.props.enableSignUpWithGoogle;
const office365SigninEnabled = this.props.enableSignUpWithOffice365;
const samlSigninEnabled = this.state.samlEnabled;
......@@ -650,7 +653,7 @@ class LoginController extends React.Component {
);
}
if ((emailSigninEnabled || usernameSigninEnabled || ldapEnabled) && (gitlabSigninEnabled || googleSigninEnabled || samlSigninEnabled || office365SigninEnabled)) {
if ((emailSigninEnabled || usernameSigninEnabled || ldapEnabled) && (gitlabSigninEnabled || phabricatorSigninEnabled || googleSigninEnabled || samlSigninEnabled || office365SigninEnabled)) {
loginControls.push(
<div
key='divider'
......@@ -693,6 +696,26 @@ class LoginController extends React.Component {
);
}
if (phabricatorSigninEnabled) {
loginControls.push(
<a
className='btn btn-custom-login phabricator'
key='phabricator'
href={Client4.getOAuthRoute() + '/phabricator/login' + this.props.location.search}
>
<span>
<span className='icon'/>
<span>
<FormattedMessage
id='login.phabricator'
defaultMessage='Phabricator'
/>
</span>
</span>
</a>
);
}
if (googleSigninEnabled) {
loginControls.push(
<a
......
......@@ -25,6 +25,7 @@ describe('components/login/LoginController', () => {
enableSignInWithUsername: false,
enableSignUpWithEmail: false,
enableSignUpWithGitLab: false,
enableSignUpWithPhabricator: false,
enableSignUpWithGoogle: false,
enableSignUpWithOffice365: false,
experimentalPrimaryTeam: '',
......
......@@ -9,9 +9,9 @@ import FastClick from 'fastclick';
import {Route, Switch, Redirect} from 'react-router-dom';
import {setUrl} from 'mattermost-redux/actions/general';
import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
//import {getConfig} from 'mattermost-redux/selectors/entities/general';
import * as UserAgent from 'utils/user_agent';
//import * as UserAgent from 'utils/user_agent';
import {EmojiIndicesByAlias} from 'utils/emoji.jsx';
import {trackLoadTime} from 'actions/diagnostics_actions.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
......@@ -145,9 +145,9 @@ export default class Root extends React.Component {
};
// Keyboard navigation for accessibility
if (!UserAgent.isInternetExplorer()) {
this.a11yController = new A11yController();
}
// if (!UserAgent.isInternetExplorer()) {
// this.a11yController = new A11yController();
// }
}
onConfigLoaded = () => {
......@@ -204,23 +204,23 @@ export default class Root extends React.Component {
loadRecentlyUsedCustomEmojis()(store.dispatch, store.getState);
const iosDownloadLink = getConfig(store.getState()).IosAppDownloadLink;
const androidDownloadLink = getConfig(store.getState()).AndroidAppDownloadLink;
const toResetPasswordScreen = this.props.location.pathname === '/reset_password_complete';
// redirect to the mobile landing page if the user hasn't seen it before
let mobileLanding;
if (UserAgent.isAndroidWeb()) {
mobileLanding = androidDownloadLink;
} else if (UserAgent.isIosWeb()) {
mobileLanding = iosDownloadLink;
}
if (mobileLanding && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen && !this.props.location.pathname.includes('/landing')) {
this.props.history.push('/landing#' + this.props.location.pathname + this.props.location.search);
BrowserStore.setLandingPageSeen(true);
}
// const iosDownloadLink = getConfig(store.getState()).IosAppDownloadLink;
// const androidDownloadLink = getConfig(store.getState()).AndroidAppDownloadLink;
//
// const toResetPasswordScreen = this.props.location.pathname === '/reset_password_complete';
//
// // redirect to the mobile landing page if the user hasn't seen it before
// let mobileLanding;
// if (UserAgent.isAndroidWeb()) {
// mobileLanding = androidDownloadLink;
// } else if (UserAgent.isIosWeb()) {
// mobileLanding = iosDownloadLink;
// }
//
// if (mobileLanding && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen && !this.props.location.pathname.includes('/landing')) {
// this.props.history.push('/landing#' + this.props.location.pathname + this.props.location.search);
// BrowserStore.setLandingPageSeen(true);
// }
}
componentDidUpdate(prevProps) {
......
......@@ -71,7 +71,7 @@ exports[`components/SignupController should match snapshot for addUserToTeamFrom
id="signup_user_completed.haveAccount"
values={Object {}}
/>
<Link
to="/login?id=ppni7a9t87fn3j4d56rwocdctc"
>
......@@ -179,7 +179,44 @@ exports[`components/SignupController should match snapshot for all signup option
id="signup_user_completed.haveAccount"
values={Object {}}
/>
<Link
to="/login"
>
<FormattedMessage
defaultMessage="Click here to sign in."
id="signup_user_completed.signIn"
values={Object {}}
/>
</Link>
<a
className="btn btn-custom-login btn--full phabricator"
href="/oauth/phabricator/signup"
key="phabricator"
>
<span>
<span
className="icon"
/>
<span>
<FormattedMessage
defaultMessage="Phabricator Single Sign-On"
id="signup.phabricator"
values={Object {}}
/>
</span>
</span>
</a>
</div>
<span
className="color--light"
>
<FormattedMessage
defaultMessage="Already have an account?"
id="signup_user_completed.haveAccount"
values={Object {}}
/>
<Link
to="/login"
>
......@@ -278,6 +315,24 @@ exports[`components/SignupController should match snapshot for all signup option
</span>
</span>
</a>
<a
className="btn btn-custom-login btn--full phabricator"
href="/oauth/phabricator/signup"
key="phabricator"
>
<span>
<span
className="icon"
/>
<span>
<FormattedMessage
defaultMessage="Phabricator Single Sign-On"
id="signup.phabricator"
values={Object {}}
/>
</span>
</span>
</a>
<a
className="btn btn-custom-login btn--full google"
href="/oauth/google/signup"
......@@ -369,7 +424,7 @@ exports[`components/SignupController should match snapshot for all signup option
id="signup_user_completed.haveAccount"
values={Object {}}
/>
<Link
to="/login"
>
......
......@@ -23,6 +23,7 @@ function mapStateToProps(state, ownProps) {
const noAccounts = config.NoAccounts === 'true';
const enableSignUpWithEmail = config.EnableSignUpWithEmail === 'true';
const enableSignUpWithGitLab = config.EnableSignUpWithGitLab === 'true';
const enableSignUpWithPhabricator = config.EnableSignUpWithPhabricator === 'true';
const enableSignUpWithGoogle = config.EnableSignUpWithGoogle === 'true';
const enableSignUpWithOffice365 = config.EnableSignUpWithOffice365 === 'true';
const enableLDAP = config.EnableLdap === 'true';
......@@ -48,6 +49,7 @@ function mapStateToProps(state, ownProps) {
noAccounts,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
enableLDAP,
......
......@@ -28,6 +28,7 @@ export default class SignupController extends React.Component {
noAccounts: PropTypes.bool.isRequired,
enableSignUpWithEmail: PropTypes.bool.isRequired,
enableSignUpWithGitLab: PropTypes.bool.isRequired,
enableSignUpWithPhabricator: PropTypes.bool.isRequired,
enableSignUpWithGoogle: PropTypes.bool.isRequired,
enableSignUpWithOffice365: PropTypes.bool.isRequired,
enableLDAP: PropTypes.bool.isRequired,
......@@ -191,7 +192,25 @@ export default class SignupController extends React.Component {
</a>
);
}
if (this.props.enableSignUpWithPhabricator) {
signupControls.push(
<a
className='btn btn-custom-login btn--full phabricator'
key='phabricator'
href={Client4.getOAuthRoute() + '/phabricator/signup' + window.location.search}
>
<span>
<span className='icon'/>
<span>
<FormattedMessage
id='signup.phabricator'
defaultMessage='Phabricator Single Sign-On'
/>
</span>
</span>
</a>
);
}
if (this.props.isLicensed && this.props.enableSignUpWithGoogle) {
signupControls.push(
<a
......
......@@ -35,6 +35,7 @@ describe('components/SignupController', () => {
enableLDAP: true,
enableSignUpWithEmail: true,
enableSignUpWithGitLab: true,
enableSignUpWithPhabricator: true,
enableSignUpWithGoogle: true,
enableSignUpWithOffice365: true,
samlLoginButtonText: 'SAML',
......
......@@ -210,6 +210,7 @@ export default class UserSettingsDisplay extends React.Component {
firstOption,
secondOption,
thirdOption,
fourthOption,
description,
disabled,
} = props;
......@@ -265,7 +266,15 @@ export default class UserSettingsDisplay extends React.Component {
/>
);
}
let fourthMessage;
if (fourthOption) {
fourthMessage = (
<FormattedMessage
id={fourthOption.radionButtonText.id}
defaultMessage={fourthOption.radionButtonText.message}
/>
);
}
const messageTitle = (
<FormattedMessage
id={title.id}
......@@ -281,14 +290,18 @@ export default class UserSettingsDisplay extends React.Component {
);
if (this.props.activeSection === section) {
const format = [false, false, false];
const format = [false, false, false, false];
if (value === firstOption.value) {
format[0] = true;
} else if (value === secondOption.value) {
format[1] = true;
} else {
format[2] = true;
}
} else if (thirdOption && value === thirdOption.value) {
format[2] = true;
} else {
format[3] = true;
}
const name = section + 'Format';
const key = section + 'UserDisplay';
......@@ -323,6 +336,25 @@ export default class UserSettingsDisplay extends React.Component {
);
}
let fourthSection;
if (fourthMessage) {
fourthSection = (
<div className='radio'>
<label>
<input
id={name + 'D'}
type='radio'
name={name}
checked={format[3]}
onChange={() => this.handleOnChange(fourthDisplay)}
/>
{fourthMessage}