...
 
Commits (7)
......@@ -259,6 +259,14 @@ export default class AdminConsole extends React.Component {
schema: AdminDefinition.settings.authentication.gitlab.schema,
}}
/>
<SCRoute
path={`${props.match.url}/phabricator`}
component={SchemaAdminSettings}
extraProps={{
...extraProps,
schema: AdminDefinition.settings.authentication.phabricator.schema,
}}
/>
<SCRoute
path={`${props.match.url}/oauth`}
component={SchemaAdminSettings}
......
......@@ -498,6 +498,11 @@ export default {
display_name: t('admin.team.showUsername'),
display_name_default: 'Show username (default)',
},
{
value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_USERNAME,
display_name: t('admin.team.showNicknameOrUsername'),
display_name_default: 'Show nickname if one exists, otherwise show username',
},
{
value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_FULLNAME,
display_name: t('admin.team.showNickname'),
......@@ -835,6 +840,106 @@ export default {
],
},
},
phabricator: {
schema: {
id: 'PhabricatorSettings',
name: t('admin.authentication.phabricator'),
name_default: 'Phabricator',
onConfigLoad: (config) => {
const newState = {};
newState['PhabricatorSettings.Url'] = config.PhabricatorSettings.UserApiEndpoint.replace('/api/v4/user', '');
return newState;
},
onConfigSave: (config) => {
const newConfig = {...config};
newConfig.PhabricatorSettings.UserApiEndpoint = config.PhabricatorSettings.Url.replace(/\/$/, '') + '/api/user.whoami';
return newConfig;
},
settings: [
{
type: Constants.SettingsTypes.TYPE_BOOL,
key: 'PhabricatorSettings.Enable',
label: t('admin.phabricator.enableTitle'),
label_default: 'Enable authentication with Phabricator: ',
help_text: t('admin.phabricator.enableDescription'),
help_text_default: 'When true, Mattermost allows team creation and account signup using Phabricator OAuth.\n \n1. Log in to your Phabricator account and go to Applications → OAuth Server → Create OAuth Server.\n2. Enter Redirect URIs "<your-mattermost-url>/login/phabricator/complete" (example: http://localhost:8065/login/phabricator/complete) and "<your-mattermost-url>/signup/phabricator/complete".\n3. Then use "Show Application Secret" and "Client PHID" fields from Phabricator to complete the options below.\n4. Complete the Endpoint URLs below.',
help_text_markdown: true,
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.Id',
label: t('admin.phabricator.clientIdTitle'),
label_default: 'Application ID:',
help_text: t('admin.phabricator.clientIdDescription'),
help_text_default: 'Obtain this value via the instructions above for logging into Phabricator.',
placeholder: t('admin.phabricator.clientIdExample'),
placeholder_default: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"',
isDisabled: needsUtils.stateValueFalse('PhabricatorSettings.Enable'),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.Secret',
label: t('admin.phabricator.clientSecretTitle'),
label_default: 'Application Secret Key:',
help_text: t('admin.phabricator.clientSecretDescription'),
help_text_default: 'Obtain this value via the instructions above for logging into Phabricator.',
placeholder: t('admin.phabricator.clientSecretExample'),
placeholder_default: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"',
isDisabled: needsUtils.stateValueFalse('PhabricatorSettings.Enable'),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.Url',
label: t('admin.phabricator.siteUrl'),
label_default: 'Phabricator Site URL:',
help_text: t('admin.phabricator.siteUrlDescription'),
help_text_default: 'Enter the URL of your Phabricator instance, e.g. https://phabricator.example.com/. If your Phabricator instance is not set up with SSL, start the URL with http:// instead of https://.',
placeholder: t('admin.phabricator.siteUrlExample'),
placeholder_default: 'E.g.: https://',
isDisabled: needsUtils.stateValueFalse('PhabricatorSettings.Enable'),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.UserApiEndpoint',
label: t('admin.phabricator.userTitle'),
label_default: 'User API Endpoint:',
dynamic_value: (value, config, state) => {
if (state['PhabricatorSettings.Url']) {
return state['PhabricatorSettings.Url'].replace(/\/$/, '') + '/api/user.whoami';
}
return '';
},
isDisabled: () => true,
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.AuthEndpoint',
label: t('admin.phabricator.authTitle'),
label_default: 'Auth Endpoint:',
dynamic_value: (value, config, state) => {
if (state['PhabricatorSettings.Url']) {
return state['PhabricatorSettings.Url'].replace(/\/$/, '') + '/oauthserver/auth/';
}
return '';
},
isDisabled: () => true,
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.TokenEndpoint',
label: t('admin.phabricator.tokenTitle'),
label_default: 'Token Endpoint:',
dynamic_value: (value, config, state) => {
if (state['PhabricatorSettings.Url']) {
return state['PhabricatorSettings.Url'].replace(/\/$/, '') + '/oauthserver/token/';
}
return '';
},
isDisabled: () => true,
},
],
},
},
oauth: {
schema: {
id: 'OAuthSettings',
......@@ -1107,7 +1212,7 @@ export default {
label: t('admin.email.allowSignupTitle'),
label_default: 'Enable account creation with email:',
help_text: t('admin.email.allowSignupDescription'),
help_text_default: 'When true, Mattermost allows account creation using email and password. This value should be false only when you want to limit sign up to a single sign-on service like AD/LDAP, SAML or GitLab.',
help_text_default: 'When true, Mattermost allows account creation using email and password. This value should be false only when you want to limit sign up to a single sign-on service like AD/LDAP, SAML, GitLab or Phabricator.',
},
{
type: Constants.SettingsTypes.TYPE_BOOL,
......@@ -1911,7 +2016,7 @@ export default {
label: t('admin.service.ssoSessionDays'),
label_default: 'Session Length SSO (days):',
help_text: t('admin.service.ssoSessionDaysDesc'),
help_text_default: 'The number of days from the last time a user entered their credentials to the expiry of the users session. If the authentication method is SAML or GitLab, the user may automatically be logged back in to Mattermost if they are already logged in to SAML or GitLab. After changing this setting, the setting will take effect after the next time the user enters their credentials.',
help_text_default: 'The number of days from the last time a user entered their credentials to the expiry of the users session. If the authentication method is SAML, GitLab or Phabricator, the user may automatically be logged back in to Mattermost if they are already logged in to SAML, GitLab or Phabricator. After changing this setting, the setting will take effect after the next time the user enters their credentials.',
placeholder: t('admin.service.sessionDaysEx'),
placeholder_default: 'E.g.: "30"',
},
......
......@@ -72,6 +72,8 @@ export default class AdminSidebar extends React.Component {
let oauthSettings = null;
let ldapSettings = null;
let samlSettings = null;
let gitlabSettings = null;
let phabricatorSettings = null;
let clusterSettings = null;
let metricsSettings = null;
let complianceSettings = null;
......@@ -234,7 +236,7 @@ export default class AdminSidebar extends React.Component {
/>
);
} else {
oauthSettings = (
gitlabSettings = (
<AdminSidebarSection
name='gitlab'
title={
......@@ -245,6 +247,18 @@ export default class AdminSidebar extends React.Component {
}
/>
);
phabricatorSettings = (
<AdminSidebarSection
name='phabricator'
title={
<FormattedMessage
id='admin.sidebar.phabricator'
defaultMessage='Phabricator'
/>
}
/>
);
}
if (this.props.license.IsLicensed === 'true') {
......@@ -534,6 +548,8 @@ export default class AdminSidebar extends React.Component {
}
/>
{oauthSettings}
{gitlabSettings}
{phabricatorSettings}
{ldapSettings}
{samlSettings}
{mfaSettings}
......
// 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>
);
}
}
......@@ -4,8 +4,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import {Link} from 'react-router-dom';
import {FormattedHTMLMessage, FormattedMessage, intlShape} from 'react-intl';
import {isLicenseExpired, isLicenseExpiring, isLicensePastGracePeriod} from 'utils/license_utils.jsx';
import {AnnouncementBarTypes, AnnouncementBarMessages} from 'utils/constants.jsx';
......@@ -16,6 +15,7 @@ import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import AnnouncementBar from './announcement_bar.jsx';
import TextButtonWithSpinner from './text_button_with_spinner.jsx';
import TextDismissableBar from './text_dismissable_bar';
const RENEWAL_LINK = 'https://licensing.mattermost.com/renew';
......@@ -29,6 +29,10 @@ export default class ConfigurationAnnouncementBar extends React.PureComponent {
sendVerificationEmail: PropTypes.func.isRequired,
};
static contextTypes = {
intl: intlShape,
};
handleEmailResend = (email) => {
this.props.sendVerificationEmail(email);
}
......@@ -105,17 +109,21 @@ export default class ConfigurationAnnouncementBar extends React.PureComponent {
}
}
const {formatMessage} = this.context.intl;
if (this.props.config.SendEmailNotifications !== 'true' &&
this.props.config.EnablePreviewModeBanner === 'true') {
this.props.config.EnablePreviewModeBanner === 'true'
) {
const emailMessage = formatMessage({
id: AnnouncementBarMessages.PREVIEW_MODE,
defaultMessage: 'Preview Mode: Email notifications have not been configured',
});
return (
<AnnouncementBar
<TextDismissableBar
allowDismissal={true}
text={emailMessage}
type={AnnouncementBarTypes.ANNOUNCEMENT}
message={
<FormattedMessage
id={AnnouncementBarMessages.PREVIEW_MODE}
defaultMessage='Preview Mode: Email notifications have not been configured'
/>
}
/>
);
}
......@@ -124,44 +132,20 @@ export default class ConfigurationAnnouncementBar extends React.PureComponent {
let id;
let defaultMessage;
if (this.props.config.EnableSignUpWithGitLab === 'true') {
id = t('announcement_bar.error.site_url_gitlab');
defaultMessage = 'Please configure your {docsLink} in the System Console or in gitlab.rb if you\'re using GitLab Mattermost.';
id = t('announcement_bar.error.site_url_gitlab.full');
defaultMessage = 'Please configure your [Site URL](https://docs.mattermost.com/administration/config-settings.html#site-url) in the [System Console](/admin_console/general/configuration) or in gitlab.rb if you\'re using GitLab Mattermost.';
} else {
id = t('announcement_bar.error.site_url');
defaultMessage = 'Please configure your {docsLink} in the System Console.';
id = t('announcement_bar.error.site_url.full');
defaultMessage = 'Please configure your [Site URL](https://docs.mattermost.com/administration/config-settings.html#site-url) in the [System Console](/admin_console/general/configuration).';
}
const siteURLMessage = formatMessage({id, defaultMessage});
return (
<AnnouncementBar
<TextDismissableBar
allowDismissal={true}
text={siteURLMessage}
type={AnnouncementBarTypes.ANNOUNCEMENT}
message={
<FormattedMessage
id={id}
defaultMessage={defaultMessage}
values={{
docsLink: (
<a
href='https://docs.mattermost.com/administration/config-settings.html#site-url'
rel='noopener noreferrer'
target='_blank'
>
<FormattedMessage
id='announcement_bar.error.site_url.docsLink'
defaultMessage='Site URL'
/>
</a>
),
link: (
<Link to='/admin_console/general/configuration'>
<FormattedMessage
id='announcement_bar.error.site_url.link'
defaultMessage='the System Console'
/>
</Link>
),
}}
/>
}
/>
);
}
......
......@@ -131,6 +131,21 @@ export default function ErrorMessage({type, message, service}) {
}}
/>
</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'
......
......@@ -32,6 +32,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;
......@@ -66,6 +67,7 @@ function mapStateToProps(state) {
enableSignInWithUsername,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
experimentalPrimaryTeam,
......
......@@ -46,6 +46,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,
......@@ -410,6 +411,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 ||
......@@ -534,6 +536,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;
......@@ -662,7 +665,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'
......@@ -705,6 +708,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
......
......@@ -10,9 +10,7 @@ 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 * as UserAgent from 'utils/user_agent.jsx';
import {EmojiIndicesByAlias} from 'utils/emoji.jsx';
import {trackLoadTime} from 'actions/diagnostics_actions.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
......@@ -197,20 +195,6 @@ 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
if (iosDownloadLink && UserAgent.isIosWeb() && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen) {
this.props.history.push('/get_ios_app?redirect_to=' + encodeURIComponent(this.props.location.pathname) + encodeURIComponent(this.props.location.search));
BrowserStore.setLandingPageSeen(true);
} else if (androidDownloadLink && UserAgent.isAndroidWeb() && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen) {
this.props.history.push('/get_android_app?redirect_to=' + encodeURIComponent(this.props.location.pathname) + encodeURIComponent(this.props.location.search));
BrowserStore.setLandingPageSeen(true);
}
}
redirectIfNecessary = (props) => {
......
......@@ -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,
......
......@@ -189,6 +189,26 @@ export default class SignupController extends React.Component {
);
}
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
......
......@@ -204,6 +204,7 @@ export default class UserSettingsDisplay extends React.Component {
firstOption,
secondOption,
thirdOption,
fourthOption,
description,
} = props;
......@@ -257,6 +258,16 @@ 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}
......@@ -272,13 +283,15 @@ 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 {
} else if (thirdOption && value === thirdOption.value) {
format[2] = true;
} else {
format[3] = true;
}
const name = section + 'Format';
......@@ -295,6 +308,11 @@ export default class UserSettingsDisplay extends React.Component {
thirdDisplay[display] = thirdOption.value;
}
const fourthDisplay = {};
if (fourthOption) {
fourthDisplay[display] = fourthOption.value;
}
let thirdSection;
if (thirdMessage) {
thirdSection = (
......@@ -314,6 +332,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}
</label>
<br/>
</div>
);
}
const inputs = [
<div key={key}>
<div className='radio'>
......@@ -347,6 +384,7 @@ export default class UserSettingsDisplay extends React.Component {
<br/>
</div>
{thirdSection}
{fourthSection}
<div>
<br/>
{messageDesc}
......@@ -517,6 +555,13 @@ export default class UserSettingsDisplay extends React.Component {
message: 'Show first and last name',
},
},
fourthOption: {
value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_USERNAME,
radionButtonText: {
id: 'user.settings.display.teammateNameDisplayNicknameUsername',
message: 'Show nickname if one exists, otherwise show username',
},
},
description: {
id: t('user.settings.display.teammateNameDisplayDescription'),
message: 'Set how to display other user\'s names in posts and the Direct Messages list.',
......
......@@ -551,6 +551,24 @@ class UserSettingsGeneralTab extends React.Component {
{helpText}
</div>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
inputs.push(
<div
key='oauthEmailInfo'
className='form-group'
>
<div className='setting-list__hint col-sm-12'>
<FormattedMessage
id='user.settings.general.emailPhabricatorCantUpdate'
defaultMessage='Login occurs through Phabricator. Email cannot be updated. Email address used for notifications is {email}.'
values={{
email: this.state.originalEmail,
}}
/>
</div>
{helpText}
</div>
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
inputs.push(
<div
......@@ -678,6 +696,16 @@ class UserSettingsGeneralTab extends React.Component {
}}
/>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
describe = (
<FormattedMessage
id='user.settings.general.loginPhabricator'
defaultMessage='Login done through Phabricator ({email})'
values={{
email: this.state.originalEmail,
}}
/>
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
describe = (
<FormattedMessage
......
......@@ -22,6 +22,7 @@ function mapStateToProps(state, ownProps) {
const enableOAuthServiceProvider = config.EnableOAuthServiceProvider === 'true';
const enableSignUpWithEmail = config.EnableSignUpWithEmail === 'true';
const enableSignUpWithGitLab = config.EnableSignUpWithGitLab === 'true';
const enableSignUpWithPhabricator = config.EnableSignUpWithPhabricator === 'true';
const enableSignUpWithGoogle = config.EnableSignUpWithGoogle === 'true';
const enableLdap = config.EnableLdap === 'true';
const enableSaml = config.EnableSaml === 'true';
......@@ -33,6 +34,7 @@ function mapStateToProps(state, ownProps) {
enableOAuthServiceProvider,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableLdap,
enableSaml,
......
......@@ -48,6 +48,9 @@ export default class SecurityTab extends React.Component {
// Whether or not sign-up with GitLab is enabled.
enableSignUpWithGitLab: PropTypes.bool,
// Whether or not sign-up with Phabricator is enabled.
enableSignUpWithPhabricator: PropTypes.bool,
// Whether or not sign-up with Google is enabled.
enableSignUpWithGoogle: PropTypes.bool,
......@@ -301,6 +304,20 @@ export default class SecurityTab extends React.Component {
</div>
</div>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
inputs.push(
<div
key='oauthEmailInfo'
className='form-group'
>
<div className='setting-list__hint col-sm-12'>
<FormattedMessage
id='user.settings.security.passwordPhabricatorCantUpdate'
defaultMessage='Login occurs through Phabricator. Password cannot be updated.'
/>
</div>
</div>
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
inputs.push(
<div
......@@ -413,6 +430,13 @@ export default class SecurityTab extends React.Component {
defaultMessage='Login done through GitLab'
/>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
describe = (
<FormattedMessage
id='user.settings.security.loginPhabricator'
defaultMessage='Login done through Phabricator'
/>
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
describe = (
<FormattedMessage
......@@ -464,6 +488,7 @@ export default class SecurityTab extends React.Component {
if (this.props.activeSection === SECTION_SIGNIN) {
let emailOption;
let gitlabOption;
let phabricatorOption;
let googleOption;
let office365Option;
let ldapOption;
......@@ -487,6 +512,23 @@ export default class SecurityTab extends React.Component {
);
}
if (this.props.enableSignUpWithPhabricator) {
phabricatorOption = (
<div className='padding-bottom x2'>
<Link
className='btn btn-primary'
to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.PHABRICATOR_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchPhabricator'
defaultMessage='Switch to using Phabricator SSO'
/>
</Link>
<br/>
</div>
);
}
if (this.props.enableSignUpWithGoogle) {
googleOption = (
<div className='padding-bottom x2'>
......@@ -583,6 +625,7 @@ export default class SecurityTab extends React.Component {
<div key='userSignInOption'>
{emailOption}
{gitlabOption}
{phabricatorOption}
{googleOption}
{office365Option}
{ldapOption}
......@@ -623,6 +666,13 @@ export default class SecurityTab extends React.Component {
defaultMessage='GitLab'
/>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
describe = (
<FormattedMessage
id='user.settings.security.phabricator'
defaultMessage='Phabricator'
/>
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
describe = (
<FormattedMessage
......@@ -801,6 +851,7 @@ export default class SecurityTab extends React.Component {
let numMethods = 0;
numMethods = this.props.enableSignUpWithGitLab ? numMethods + 1 : numMethods;
numMethods = this.props.enableSignUpWithPhabricator ? numMethods + 1 : numMethods;
numMethods = this.props.enableSignUpWithGoogle ? numMethods + 1 : numMethods;
numMethods = this.props.enableSignUpWithOffice365 ? numMethods + 1 : numMethods;
numMethods = this.props.enableLdap ? numMethods + 1 : numMethods;
......
......@@ -1269,6 +1269,7 @@
"admin.team.restrictTitle": "Restrict account creation to specified email domains:",
"admin.team.showFullname": "Show first and last name",
"admin.team.showNickname": "Show nickname if one exists, otherwise show first and last name",
"admin.team.showNicknameOrUsername": "Show nickname if one exists, otherwise show username",
"admin.team.showUsername": "Show username (default)",
"admin.team.siteNameDescription": "Name of service shown in login screens and UI.",
"admin.team.siteNameExample": "E.g.: \"Mattermost\"",
......@@ -1366,10 +1367,8 @@
"announcement_bar.error.preview_mode": "Preview Mode: Email notifications have not been configured",
"announcement_bar.error.send_again": "Send again",
"announcement_bar.error.sending": "Sending",
"announcement_bar.error.site_url": "Please configure your {docsLink} in the {link}.",
"announcement_bar.error.site_url_gitlab": "Please configure your {docsLink} in the System Console or in gitlab.rb if you're using GitLab Mattermost.",
"announcement_bar.error.site_url.docsLink": "Site URL",
"announcement_bar.error.site_url.link": "System Console",
"announcement_bar.error.site_url.full": "Please configure your [Site URL](https://docs.mattermost.com/administration/config-settings.html#site-url) in the [System Console](/admin_console/general/configuration).",
"announcement_bar.error.site_url_gitlab.full": "Please configure your [Site URL](https://docs.mattermost.com/administration/config-settings.html#site-url) in the [System Console](/admin_console/general/configuration) or in gitlab.rb if you're using GitLab Mattermost.",
"announcement_bar.notification.email_verified": "Email verified",
"api.channel.add_member.added": "{addedUsername} added to the channel by {username}.",
"api.channel.delete_channel.archived": "{username} archived the channel.",
......@@ -2746,6 +2745,7 @@
"user.settings.display.teammateNameDisplayDescription": "Set how to display other user's names in posts and the Direct Messages list.",
"user.settings.display.teammateNameDisplayFullname": "Show first and last name",
"user.settings.display.teammateNameDisplayNicknameFullname": "Show nickname if one exists, otherwise show first and last name",
"user.settings.display.teammateNameDisplayNicknameUsername": "Show nickname if one exists, otherwise show username",
"user.settings.display.teammateNameDisplayTitle": "Teammate Name Display",
"user.settings.display.teammateNameDisplayUsername": "Show username",
"user.settings.display.theme.applyToAllTeams": "Apply new theme to all my teams",
......
......@@ -2719,6 +2719,7 @@
"user.settings.display.teammateNameDisplayDescription": "Choisissez comment afficher les noms des autres utilisateurs dans les messages et la liste de messages personnels.",
"user.settings.display.teammateNameDisplayFullname": "Afficher le prénom d'abord puis le nom",
"user.settings.display.teammateNameDisplayNicknameFullname": "Afficher le pseudo s'il existe, sinon afficher le prénom d'abord puis le nom",
"user.settings.display.teammateNameDisplayNicknameUsername": "Afficher le pseudo s'il exists, sinon afficher le nom d'utilisateur",
"user.settings.display.teammateNameDisplayTitle": "Affichage des noms des membres de l'équipe",
"user.settings.display.teammateNameDisplayUsername": "Afficher le nom d'utilisateur",
"user.settings.display.theme.applyToAllTeams": "Appliquer le nouveau thème à toutes mes équipes",
......
......@@ -2719,6 +2719,7 @@
"user.settings.display.teammateNameDisplayDescription": "投稿やダイレクトメッセージ中の他のユーザーの名前の表示方法を設定します。",
"user.settings.display.teammateNameDisplayFullname": "氏名を表示する",
"user.settings.display.teammateNameDisplayNicknameFullname": "ニックネームがあればそれを表示する。無ければ氏名を表示する。",
"user.settings.display.teammateNameDisplayNicknameFullname": "ニックネームがあればそれを表示する。無ければユーザー名を表示する",
"user.settings.display.teammateNameDisplayTitle": "チームメイトの名前の表示",
"user.settings.display.teammateNameDisplayUsername": "ユーザー名を表示する",
"user.settings.display.theme.applyToAllTeams": "全ての自分のチームに新しいテーマを適用する",
......
......@@ -11581,8 +11581,8 @@
"dev": true
},
"mattermost-redux": {
"version": "github:mattermost/mattermost-redux#02e1ee9f4504cee6f3b6a4b256d02296e8a19150",
"from": "github:mattermost/mattermost-redux#02e1ee9f4504cee6f3b6a4b256d02296e8a19150",
"version": "git+https://gitlab.collabora.com/sysadmin/mattermost/mattermost-redux#collabora-5.7",
"from": "git+https://gitlab.collabora.com/sysadmin/mattermost/mattermost-redux#collabora-5.7",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "1.2.0",
......
......@@ -327,6 +327,18 @@
}
}
&.phabricator {
background: #4a5f88;
&:hover {
background: darken(#4a5f88, 10%);
}
.icon {
background-image: url('../images/phabricatorLogo.png');
}
}
&.google {
background: #dd4b39;
......
......@@ -738,6 +738,7 @@ export const Constants = {
OFFTOPIC_CHANNEL: 'off-topic',
OFFTOPIC_CHANNEL_UI_NAME: 'Off-Topic',
GITLAB_SERVICE: 'gitlab',
PHABRICATOR_SERVICE: 'phabricator',
GOOGLE_SERVICE: 'google',
OFFICE365_SERVICE: 'office365',
EMAIL_SERVICE: 'email',
......@@ -1291,13 +1292,14 @@ export const Constants = {
AUTOCOMPLETE_TIMEOUT: 100,
ANIMATION_TIMEOUT: 1000,
SEARCH_TIMEOUT_MILLISECONDS: 100,
DIAGNOSTICS_SEGMENT_KEY: 'fwb7VPbFeQ7SKp3wHm1RzFUuXZudqVok',
DIAGNOSTICS_SEGMENT_KEY: '',
TEST_ID_COUNT: 0,
CENTER: 'center',
RHS: 'rhs',
RHS_ROOT: 'rhsroot',
TEAMMATE_NAME_DISPLAY: {
SHOW_USERNAME: 'username',
SHOW_NICKNAME_USERNAME: 'nickname_username',
SHOW_NICKNAME_FULLNAME: 'nickname_full_name',
SHOW_FULLNAME: 'full_name',
},
......