Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
PLT-3484 OAuth2 Service Provider (#3632)
Browse files Browse the repository at this point in the history
* PLT-3484 OAuth2 Service Provider

* PM text review for OAuth 2.0 Service Provider

* PLT-3484 OAuth2 Service Provider UI tweaks (#3668)

* Tweaks to help text

* Pushing OAuth improvements (#3680)

* Re-arrange System Console for OAuth 2.0 Provider
  • Loading branch information
enahum authored and hmhealey committed Aug 3, 2016
1 parent fb7e027 commit ca2889d
Show file tree
Hide file tree
Showing 33 changed files with 1,173 additions and 702 deletions.
7 changes: 0 additions & 7 deletions actions/global_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,6 @@ export function showLeaveTeamModal() {
});
}

export function showRegisterAppModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_REGISTER_APP_MODAL,
value: true
});
}

export function emitSuggestionPretextChanged(suggestionId, pretext) {
AppDispatcher.handleViewAction({
type: ActionTypes.SUGGESTION_PRETEXT_CHANGED,
Expand Down
60 changes: 60 additions & 0 deletions actions/oauth_actions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import Client from 'client/web_client.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from 'utils/constants.jsx';

const ActionTypes = Constants.ActionTypes;

export function listOAuthApps(userId, onSuccess, onError) {
Client.listOAuthApps(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_OAUTHAPPS,
userId,
oauthApps: data
});

if (onSuccess) {
onSuccess(data);
}
},
onError
);
}

export function deleteOAuthApp(id, userId, onSuccess, onError) {
Client.deleteOAuthApp(
id,
() => {
AppDispatcher.handleServerAction({
type: ActionTypes.REMOVED_OAUTHAPP,
userId,
id
});

if (onSuccess) {
onSuccess();
}
},
onError
);
}

export function registerOAuthApp(app, onSuccess, onError) {
Client.registerOAuthApp(
app,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_OAUTHAPP,
oauthApp: data
});

if (onSuccess) {
onSuccess();
}
},
onError
);
}
30 changes: 30 additions & 0 deletions client/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,36 @@ export default class Client {
end(this.handleResponse.bind(this, 'allowOAuth2', success, error));
}

listOAuthApps(success, error) {
request.
get(`${this.getOAuthRoute()}/list`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send().
end(this.handleResponse.bind(this, 'getOAuthApps', success, error));
}

deleteOAuthApp(id, success, error) {
request.
post(`${this.getOAuthRoute()}/delete`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send({id}).
end(this.handleResponse.bind(this, 'deleteOAuthApp', success, error));
}

getOAuthAppInfo(id, success, error) {
request.
get(`${this.getOAuthRoute()}/app/${id}`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send().
end(this.handleResponse.bind(this, 'getOAuthAppInfo', success, error));
}

// Routes for Hooks

addIncomingHook(hook, success, error) {
Expand Down
17 changes: 1 addition & 16 deletions components/admin_console/admin_settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Client from 'client/web_client.jsx';

import FormError from 'components/form_error.jsx';
import SaveButton from 'components/admin_console/save_button.jsx';
import Constants from 'utils/constants.jsx';

export default class AdminSettings extends React.Component {
static get propTypes() {
Expand All @@ -22,7 +21,6 @@ export default class AdminSettings extends React.Component {

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);

this.state = Object.assign(this.getStateFromConfig(props.config), {
saveNeeded: false,
Expand All @@ -38,20 +36,6 @@ export default class AdminSettings extends React.Component {
});
}

componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
}

componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
}

onKeyDown(e) {
if (e.keyCode === Constants.KeyCodes.ENTER) {
this.handleSubmit(e);
}
}

handleSubmit(e) {
e.preventDefault();

Expand Down Expand Up @@ -118,6 +102,7 @@ export default class AdminSettings extends React.Component {
<form
className='form-horizontal'
role='form'
onSubmit={this.handleSubmit}
>
{this.renderSettings()}
<div className='form-group'>
Expand Down
6 changes: 3 additions & 3 deletions components/admin_console/admin_sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -521,11 +521,11 @@ export default class AdminSidebar extends React.Component {
}
>
<AdminSidebarSection
name='webhooks'
name='custom'
title={
<FormattedMessage
id='admin.sidebar.webhooks'
defaultMessage='Webhooks and Commands'
id='admin.sidebar.customIntegrations'
defaultMessage='Custom Integrations'
/>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class WebhookSettings extends AdminSettings {
config.ServiceSettings.EnableOnlyAdminIntegrations = this.state.enableOnlyAdminIntegrations;
config.ServiceSettings.EnablePostUsernameOverride = this.state.enablePostUsernameOverride;
config.ServiceSettings.EnablePostIconOverride = this.state.enablePostIconOverride;
config.ServiceSettings.EnableOAuthServiceProvider = this.state.enableOAuthServiceProvider;

return config;
}
Expand All @@ -35,16 +36,17 @@ export default class WebhookSettings extends AdminSettings {
enableCommands: config.ServiceSettings.EnableCommands,
enableOnlyAdminIntegrations: config.ServiceSettings.EnableOnlyAdminIntegrations,
enablePostUsernameOverride: config.ServiceSettings.EnablePostUsernameOverride,
enablePostIconOverride: config.ServiceSettings.EnablePostIconOverride
enablePostIconOverride: config.ServiceSettings.EnablePostIconOverride,
enableOAuthServiceProvider: config.ServiceSettings.EnableOAuthServiceProvider
};
}

renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.integrations.webhook'
defaultMessage='Webhooks and Commands'
id='admin.integrations.custom'
defaultMessage='Custom Integrations'
/>
</h3>
);
Expand Down Expand Up @@ -104,6 +106,23 @@ export default class WebhookSettings extends AdminSettings {
value={this.state.enableCommands}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOAuthServiceProvider'
label={
<FormattedMessage
id='admin.oauth.providerTitle'
defaultMessage='Enable OAuth 2.0 Service Provider: '
/>
}
helpText={
<FormattedMessage
id='admin.oauth.providerDescription'
defaultMessage='When true, Mattermost can act as an OAuth 2.0 service provider allowing external applications to authorize API requests to Mattermost.'
/>
}
value={this.state.enableOAuthServiceProvider}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOnlyAdminIntegrations'
label={
Expand Down
75 changes: 50 additions & 25 deletions components/authorize.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import React from 'react';
import icon50 from 'images/icon50x50.png';

export default class Authorize extends React.Component {
static get propTypes() {
return {
location: React.PropTypes.object.isRequired,
params: React.PropTypes.object.isRequired
};
}

constructor(props) {
super(props);

Expand All @@ -18,46 +25,74 @@ export default class Authorize extends React.Component {

this.state = {};
}

componentWillMount() {
Client.getOAuthAppInfo(
this.props.location.query.client_id,
(app) => {
this.setState({app});
}
);
}

componentDidMount() {
// if we get to this point remove the antiClickjack blocker
const blocker = document.getElementById('antiClickjack');
if (blocker) {
blocker.parentNode.removeChild(blocker);
}
}

handleAllow() {
const responseType = this.props.responseType;
const clientId = this.props.clientId;
const redirectUri = this.props.redirectUri;
const state = this.props.state;
const scope = this.props.scope;
const params = this.props.location.query;

Client.allowOAuth2(responseType, clientId, redirectUri, state, scope,
Client.allowOAuth2(params.response_type, params.client_id, params.redirect_uri, params.state, params.scope,
(data) => {
if (data.redirect) {
window.location.replace(data.redirect);
window.location.href = data.redirect;
}
},
() => {
//Do nothing on error
}
);
}

handleDeny() {
window.location.replace(this.props.redirectUri + '?error=access_denied');
window.location.replace(this.props.location.query.redirect_uri + '?error=access_denied');
}

render() {
const app = this.state.app;
if (!app) {
return null;
}

let icon;
if (app.icon_url) {
icon = app.icon_url;
} else {
icon = icon50;
}

return (
<div className='container-fluid'>
<div className='prompt'>
<div className='prompt__heading'>
<div className='prompt__app-icon'>
<img
src={icon50}
src={icon}
width='50'
height='50'
alt=''
/>
</div>
<div className='text'>
<FormattedMessage
<FormattedHTMLMessage
id='authorize.title'
defaultMessage='An application would like to connect to your {teamName} account'
defaultMessage='<strong>{appName}</strong> would like to connect to your <strong>Mattermost</strong> user account'
values={{
teamName: this.props.teamName
appName: app.name
}}
/>
</div>
Expand All @@ -67,7 +102,7 @@ export default class Authorize extends React.Component {
id='authorize.app'
defaultMessage='The app <strong>{appName}</strong> would like the ability to access and modify your basic information.'
values={{
appName: this.props.appName
appName: app.name
}}
/>
</p>
Expand All @@ -76,14 +111,14 @@ export default class Authorize extends React.Component {
id='authorize.access'
defaultMessage='Allow <strong>{appName}</strong> access?'
values={{
appName: this.props.appName
appName: app.name
}}
/>
</h2>
<div className='prompt__buttons'>
<button
type='submit'
className='btn authorize-btn'
className='btn btn-link authorize-btn'
onClick={this.handleDeny}
>
<FormattedMessage
Expand All @@ -107,13 +142,3 @@ export default class Authorize extends React.Component {
);
}
}

Authorize.propTypes = {
appName: React.PropTypes.string,
teamName: React.PropTypes.string,
responseType: React.PropTypes.string,
clientId: React.PropTypes.string,
redirectUri: React.PropTypes.string,
state: React.PropTypes.string,
scope: React.PropTypes.string
};
Loading

0 comments on commit ca2889d

Please sign in to comment.