Commit 64efe431 authored by Jordi Mallach's avatar Jordi Mallach 🔥 Committed by Jordi Mallach

Add Phabricator OAuth2 support.

parent acf0c6a1
......@@ -23,6 +23,11 @@ import GitLabSettings from 'components/admin_console/gitlab_settings.jsx';
import MessageExportSettings from 'components/admin_console/message_export_settings';
import OAuthSettings from 'components/admin_console/oauth_settings.jsx';
import PasswordSettings from 'components/admin_console/password_settings.jsx';
<<<<<<< HEAD
=======
import PhabricatorSettings from 'components/admin_console/phabricator_settings.jsx';
import PluginSettings from 'components/admin_console/plugin_settings.jsx';
>>>>>>> 95ba0b2c... Add Phabricator OAuth2 support.
import PluginManagement from 'components/admin_console/plugin_management';
import CustomPluginSettings from 'components/admin_console/custom_plugin_settings';
import CustomIntegrationSettings from 'components/admin_console/custom_integrations_settings';
......
......@@ -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;
......@@ -219,7 +221,7 @@ export default class AdminSidebar extends React.Component {
/>
);
} else {
oauthSettings = (
gitlabSettings = (
<AdminSidebarSection
name='gitlab'
title={
......@@ -230,6 +232,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') {
......@@ -530,6 +544,8 @@ export default class AdminSidebar extends React.Component {
}
/>
{oauthSettings}
{gitlabSettings}
{phabricatorSettings}
{ldapSettings}
{samlSettings}
{mfaSettings}
......
......@@ -22,11 +22,13 @@ export default class OAuthSettings extends AdminSettings {
this.renderOffice365 = this.renderOffice365.bind(this);
this.renderGoogle = this.renderGoogle.bind(this);
this.renderGitLab = this.renderGitLab.bind(this);
this.renderPhabricator = this.renderPhabricator.bind(this);
this.changeType = this.changeType.bind(this);
}
getConfigFromState(config) {
config.GitLabSettings.Enable = false;
config.PhabricatorSettings.Enable = false;
config.GoogleSettings.Enable = false;
config.Office365Settings.Enable = false;
......@@ -39,6 +41,15 @@ export default class OAuthSettings extends AdminSettings {
config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
}
if (this.state.oauthType === Constants.PHABRICATOR_SERVICE) {
config.PhabricatorSettings.Enable = true;
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;
}
if (this.state.oauthType === Constants.GOOGLE_SERVICE) {
config.GoogleSettings.Enable = true;
config.GoogleSettings.Id = this.state.id;
......@@ -70,6 +81,9 @@ export default class OAuthSettings extends AdminSettings {
if (config.GitLabSettings.Enable) {
oauthType = Constants.GITLAB_SERVICE;
settings = config.GitLabSettings;
} else if (config.PhabricatorSettings.Enable) {
oauthType = Constants.PHABRICATOR_SERVICE;
settings = config.PhabricatorSettings;
} else if (config.GoogleSettings.Enable) {
oauthType = Constants.GOOGLE_SERVICE;
settings = config.GoogleSettings;
......@@ -95,6 +109,8 @@ export default class OAuthSettings extends AdminSettings {
if (value === Constants.GITLAB_SERVICE) {
settings = this.config.GitLabSettings;
gitLabUrl = settings.UserApiEndpoint.replace('/api/v4/user', '');
} else if (value === Constants.PHABRICATOR_SERVICE) {
settings = this.config.PhabricatorSettings;
} else if (value === Constants.GOOGLE_SERVICE) {
settings = this.config.GoogleSettings;
} else if (value === Constants.OFFICE365_SERVICE) {
......@@ -410,6 +426,103 @@ export default class OAuthSettings extends AdminSettings {
);
}
renderPhabricator() {
return (
<div>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.phabricator.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.phabriator.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into Phabricator'
/>
}
value={this.state.id}
onChange={this.handleChange}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.phabricator.clientSecretTitle'
defaultMessage='Application Secret Key:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientSecretExample', 'Ex "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}
/>
<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://phabricator.example.com. If your Phabricator instance is not set up with SSL, start the URL with http:// instead of https://.'
/>
}
value={this.state.gitLabUrl}
onChange={this.updateGitLabUrl}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.phabricator.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={''}
value={this.state.userApiEndpoint}
disabled={true}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.phabricator.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={''}
value={this.state.authEndpoint}
disabled={true}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.phabricator.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={''}
value={this.state.tokenEndpoint}
disabled={true}
/>
</div>
);
}
renderSettings() {
let contents;
let helpText;
......@@ -421,6 +534,14 @@ export default class OAuthSettings extends AdminSettings {
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/>
);
} else if (this.state.oauthType === Constants.PHABRICATOR_SERVICE) {
contents = this.renderPhabricator();
helpText = (
<FormattedHTMLMessage
id='admin.phabricator.EnableHtmlDesc'
defaultMessage='<ol>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>'
/>
);
} else if (this.state.oauthType === Constants.GOOGLE_SERVICE) {
contents = this.renderGoogle();
helpText = (
......@@ -442,6 +563,7 @@ export default class OAuthSettings extends AdminSettings {
const oauthTypes = [];
oauthTypes.push({value: 'off', text: Utils.localizeMessage('admin.oauth.off', 'Do not allow sign-in via an OAuth 2.0 provider.')});
oauthTypes.push({value: Constants.GITLAB_SERVICE, text: Utils.localizeMessage('admin.oauth.gitlab', 'GitLab')});
oauthTypes.push({value: Constants.PHABRICATOR_SERVICE, text: Utils.localizeMessage('admin.oauth.phabricator', 'Phabricator')});
if (this.props.license.IsLicensed === 'true') {
if (this.props.license.GoogleOAuth === 'true') {
oauthTypes.push({value: Constants.GOOGLE_SERVICE, text: Utils.localizeMessage('admin.oauth.google', 'Google Apps')});
......
// 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/v4/user', ''),
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/'
});
}
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}
/>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.phabricator.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientIdExample', 'Ex "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}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.phabricator.clientSecretTitle'
defaultMessage='Application Secret Key:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientSecretExample', 'Ex "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}
/>
<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://phabricator.example.com/. 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}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.phabricator.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={''}
value={this.state.userApiEndpoint}
disabled={true}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.phabricator.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={''}
value={this.state.authEndpoint}
disabled={true}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.phabricator.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={''}
value={this.state.tokenEndpoint}
disabled={true}
/>
</SettingsGroup>
);
}
}
......@@ -22,6 +22,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 experimentalPrimaryTeam = config.ExperimentalPrimaryTeam;
......@@ -41,6 +42,7 @@ function mapStateToProps(state) {
enableSignInWithUsername,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
experimentalPrimaryTeam,
......
......@@ -44,6 +44,7 @@ export default 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,
......@@ -319,6 +320,7 @@ export default 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 ||
......@@ -388,6 +390,7 @@ export default 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;
......@@ -514,7 +517,7 @@ export default 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'
......@@ -557,6 +560,26 @@ export default 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
......
......@@ -15,6 +15,7 @@ function mapStateToProps(state) {
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';
......@@ -28,6 +29,7 @@ function mapStateToProps(state) {
noAccounts,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
enableLDAP,
......
......@@ -181,6 +181,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
......@@ -384,6 +404,7 @@ SignupController.propTypes = {
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,
......
......@@ -487,6 +487,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
......@@ -612,6 +630,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
......
......@@ -26,6 +26,7 @@ function mapStateToProps(state, ownProps) {
const enforceMultifactorAuthentication = config.EnforceMultifactorAuthentication === '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';
......@@ -41,6 +42,7 @@ function mapStateToProps(state, ownProps) {
enforceMultifactorAuthentication,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableLdap,
enableSaml,
......
......@@ -61,6 +61,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,
......@@ -465,6 +468,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
......@@ -578,6 +595,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
......@@ -629,6 +653,7 @@ export default class SecurityTab extends React.Component {
if (this.props.activeSection === SECTION_SIGNIN) {
let emailOption;
let gitlabOption;
let phabricatorOption;