diff --git a/client/components/support-user/README.md b/client/components/support-user/README.md new file mode 100644 index 00000000000000..c828150d3d33ac --- /dev/null +++ b/client/components/support-user/README.md @@ -0,0 +1,3 @@ +Support User +============= +This component is used to provide user support. diff --git a/client/components/support-user/index.jsx b/client/components/support-user/index.jsx new file mode 100644 index 00000000000000..44ee44e4e55782 --- /dev/null +++ b/client/components/support-user/index.jsx @@ -0,0 +1,126 @@ +/** + * External dependencies + */ +import React from 'react'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import observe from 'lib/mixins/data-observe'; +import User from 'lib/user'; +import userSettings from 'lib/user-settings'; +import Dialog from 'components/dialog'; +import KeyboardShortcuts from 'lib/keyboard-shortcuts'; + +module.exports = React.createClass( { + displayName: 'SupportUser', + + mixins: [ observe( 'userSettings' ), React.addons.LinkedStateMixin ], + + componentDidMount: function() { + KeyboardShortcuts.on( 'open-support-user', this.toggleShowDialog ); + }, + + componentWillUnmount: function() { + KeyboardShortcuts.off( 'open-support-user', this.toggleShowDialog ); + }, + + getInitialState: function() { + return { + supportUser: null, + isSupportUser: false, + showDialog: false + }; + }, + + isEnabled: function() { + if ( ! userSettings.hasSettings() ) { + userSettings.fetchSettings(); + return false; + } + return ! userSettings.getSetting( 'user_login_can_be_changed' ); + }, + + toggleShowDialog: function() { + if ( this.isEnabled() ) { + this.setState( { showDialog: ! this.state.showDialog } ); + } + }, + + closeDialog: function() { + }, + + onChangeUser: function( e ) { + e.preventDefault(); + + if ( this.state.supportUser && this.state.supportPassword ) { + let user = new User(); + user.clear(); + user.changeUser( this.state.supportUser, this.state.supportPassword ); + this.setState( { isSupportUser: true } ); + this.setState( { supportPassword: null } ); + } + + this.setState( { showDialog: false } ); + }, + + onRestoreUser: function( e ) { + if ( this.state.isSupportUser && this.state.restoreUser ) { + let user = new User(); + user.clear().fetch(); + this.setState( { + supportUser: null, + supportPassword: null, + isSupportUser: false, + showDialog: false + } ); + window.location.reload.bind( window.location ); + } + }, + + render: function() { + if ( this.state.isSupportUser ) { + return ( + +
+
+ +
+
+
+ ); + } else { + return ( + +
+
+ + + +
+
+
+ ); } + } +} ); diff --git a/client/components/support-user/style.scss b/client/components/support-user/style.scss new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/client/layout/index.jsx b/client/layout/index.jsx index 81b696f292f257..d78516baaec6fc 100644 --- a/client/layout/index.jsx +++ b/client/layout/index.jsx @@ -25,6 +25,7 @@ var Masterbar = require( './masterbar' ), PulsingDot = require( 'components/pulsing-dot' ), SitesListNotices = require( 'lib/sites-list/notices' ), PollerPool = require( 'lib/data-poller' ), + SupportUser = require( 'components/support-user' ), KeyboardShortcutsMenu; if ( config.isEnabled( 'keyboard-shortcuts' ) ) { @@ -102,6 +103,7 @@ module.exports = React.createClass( { return (
{ config.isEnabled( 'keyboard-shortcuts' ) ? : null } +
diff --git a/client/lib/keyboard-shortcuts/key-bindings.js b/client/lib/keyboard-shortcuts/key-bindings.js index 8c7cf73d63460e..412d4ac4492a5f 100644 --- a/client/lib/keyboard-shortcuts/key-bindings.js +++ b/client/lib/keyboard-shortcuts/key-bindings.js @@ -126,6 +126,14 @@ KeyBindings.prototype.get = function() { keys: [ 'n' ], text: i18n.translate( 'Open Notifications' ) } + }, + { + eventName: 'open-support-user', + keys: [ 's', 'u' ], + description: { + keys: [], + text: '', + } } ], diff --git a/client/lib/user/user.js b/client/lib/user/user.js index adde48fa7bf672..f2d6b9a0426513 100644 --- a/client/lib/user/user.js +++ b/client/lib/user/user.js @@ -238,6 +238,14 @@ User.prototype.set = function( attributes ) { return changed; }; +User.prototype.changeUser = function( username, password ) { + wpcom.changeUser( username, password, function( error ) { + if ( ! error ) { + this.fetch(); + } + }.bind( this ) ); +}; + /** * Expose `User` */ diff --git a/shared/lib/wp/browser.js b/shared/lib/wp/browser.js index b91ac9b0bf618d..e88f9ff55dfa72 100644 --- a/shared/lib/wp/browser.js +++ b/shared/lib/wp/browser.js @@ -9,6 +9,7 @@ const debug = debugFactory( 'calypso:wp' ); */ import wpcomUndocumented from 'lib/wpcom-undocumented'; import config from 'config'; +import wpcomSupport from 'lib/wp/support'; let wpcom; @@ -36,4 +37,4 @@ if ( config.isEnabled( 'oauth' ) ) { /** * Expose `wpcom` */ -module.exports = wpcom; +module.exports = wpcomSupport( wpcom ); diff --git a/shared/lib/wp/support.js b/shared/lib/wp/support.js new file mode 100644 index 00000000000000..2f375d048229c6 --- /dev/null +++ b/shared/lib/wp/support.js @@ -0,0 +1,102 @@ +export default function wpcomSupport( wpcom ) { + let supportUser = ''; + let supportToken = ''; + + /** + * Request parameters are as follows: + * - param (required) A string or object that contains the path + * - query (optional) An object that expands the response object + * - body (required*) Required by POST and PUT, but not by GET and DEL + * - fn (required) A callback function to handle the returned result + * - ...rest (optional) Some queries have additional parameters after + * callback function + * + * Return the index of the query parameter, or if one does not exist, + * return false. + */ + const getQueryIndex = function( req, args ) { + let fnIndex, queryIndex; + + // Find the index of the callback in the arguments + fnIndex = args.findIndex( function( e ) { + return 'function' === typeof e; + } ); + + // Set queryIndex based on the request type and fnIndex + if ( req === 'post' || req === 'put' ) { + queryIndex = ( 3 === fnIndex ) ? 1 : false; + } else { + queryIndex = ( 2 === fnIndex ) ? 1 : false; + } + return queryIndex; + } + + /** + * Add the supportUser and supportToken to the query. + */ + const addSupportData = function( query ) { + return Object.assign( {}, query, { + support_user: supportUser, + _support_token: supportToken + } ); + } + + /** + * Mutate the query parameter of the request by adding values for + * support_user and _support_token to the query parameter. + */ + const extendRequest = function( req, args ) { + if ( ! supportUser || ! supportToken ) { + return args; + } + + let queryIndex = getQueryIndex( req, args ); + if ( queryIndex ) { + args[ queryIndex ] = addSupportData( args[ queryIndex ] ); + } else { + args.splice( 1, 0, addSupportData( {} ) ); + } + return args; + } + + const del = wpcom.req.del.bind( wpcom.req ); + const get = wpcom.req.get.bind( wpcom.req ); + const post = wpcom.req.post.bind( wpcom.req ); + const put = wpcom.req.put.bind( wpcom.req ); + + return Object.assign( wpcom, { + changeUser: function( username, password, fn ) { + var args = { + apiVersion: '1.1', + path: '/internal/support/' + username + '/grant' + }; + + return wpcom.req.post( args, { password: password }, function( error, response ) { + if ( ! error ) { + supportUser = response.username; + supportToken = response.token; + } + + fn( error, response ); + } ); + }, + restoreUser: function() { + supportUser = ''; + supportToken = ''; + }, + req: { + del: function( ...args ) { + return del.apply( wpcom, extendRequest( 'del', args ) ); + }, + get: function( ...args ) { + return get.apply( wpcom, extendRequest( 'get', args ) ); + }, + post: function( ...args ) { + return post.apply( wpcom, extendRequest( 'post', args ) ); + }, + put: function( ...args ) { + return put.apply( wpcom, extendRequest( 'put', args ) ); + } + } + } ); +};