From 098de4004e86615a2d0ce4a6c909fe5f7bea097a Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 27 Mar 2015 04:08:04 -0700 Subject: [PATCH] CRM-16173 - crmCxn - Add UI for connecting and disconnecting * Split available and actual connections * Display alert if sysadmin disables Cxn security * Display permissions when confirming --- CRM/Utils/Check/Security.php | 32 +++++++++++++ Civi/Angular/Manager.php | 6 +++ ang/crmCxn.css | 3 ++ ang/crmCxn.js | 26 +++++++++++ ang/crmCxn/ConfirmConnectCtrl.html | 12 +++++ ang/crmCxn/ConfirmConnectCtrl.js | 5 ++ ang/crmCxn/ManageCtrl.html | 73 ++++++++++++++++++++++++++++++ ang/crmCxn/ManageCtrl.js | 66 +++++++++++++++++++++++++++ ang/crmCxn/PermTable.html | 42 +++++++++++++++++ ang/crmCxn/PermTable.js | 27 +++++++++++ api/v3/Cxn.php | 13 +++++- 11 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 ang/crmCxn.css create mode 100644 ang/crmCxn.js create mode 100644 ang/crmCxn/ConfirmConnectCtrl.html create mode 100644 ang/crmCxn/ConfirmConnectCtrl.js create mode 100644 ang/crmCxn/ManageCtrl.html create mode 100644 ang/crmCxn/ManageCtrl.js create mode 100644 ang/crmCxn/PermTable.html create mode 100644 ang/crmCxn/PermTable.js diff --git a/CRM/Utils/Check/Security.php b/CRM/Utils/Check/Security.php index 393fddbf16..f0dd733adf 100644 --- a/CRM/Utils/Check/Security.php +++ b/CRM/Utils/Check/Security.php @@ -58,6 +58,7 @@ class CRM_Utils_Check_Security { */ public function checkAll() { $messages = array_merge( + $this->checkCxnOverrides(), $this->checkLogFileIsNotAccessible(), $this->checkUploadsAreNotAccessible(), $this->checkDirectoriesAreNotBrowseable(), @@ -242,6 +243,37 @@ class CRM_Utils_Check_Security { return $messages; } + /** + * Check that the sysadmin has not modified the Cxn + * security setup. + */ + public function checkCxnOverrides() { + $list = array(); + if (defined('CIVICRM_CXN_CA') && CIVICRM_CXN_CA !== 'CiviRootCA') { + $list[] = 'CIVICRM_CXN_CA'; + } + if (defined('CIVICRM_CXN_APPS_VERIFY') && !CIVICRM_CXN_APPS_VERIFY) { + $list[] = 'CIVICRM_CXN_APPS_VERIFY'; + } + if (defined('CIVICRM_CXN_APPS_URL') && CIVICRM_CXN_APPS_URL !== \Civi\Cxn\Rpc\Constants::OFFICIAL_APPMETAS_URL) { + $list[] = 'CIVICRM_CXN_APPS_URL'; + } + + $messages = array(); + + if (!empty($list)) { + $messages[] = new CRM_Utils_Check_Message( + 'checkCxnOverrides', + ts('The system administrator has disabled security settings (%1). Connections to remote applications are insecure.', array( + 1 => implode(', ', $list), + )), + ts('Security Warning') + ); + } + + return $messages; + } + /** * Determine whether $url is a public, browsable listing for $dir * diff --git a/Civi/Angular/Manager.php b/Civi/Angular/Manager.php index acf473bf3e..8977e1c14f 100644 --- a/Civi/Angular/Manager.php +++ b/Civi/Angular/Manager.php @@ -81,6 +81,12 @@ class Manager { 'ext' => 'civicrm', 'js' => array('ang/crmAutosave.js'), ); + $angularModules['crmCxn'] = array( + 'ext' => 'civicrm', + 'js' => array('ang/crmCxn.js', 'ang/crmCxn/*.js'), + 'css' => array('ang/crmCxn.css'), + 'partials' => array('ang/crmCxn'), + ); //$angularModules['crmExample'] = array( // 'ext' => 'civicrm', // 'js' => array('ang/crmExample.js'), diff --git a/ang/crmCxn.css b/ang/crmCxn.css new file mode 100644 index 0000000000..11aeb14e66 --- /dev/null +++ b/ang/crmCxn.css @@ -0,0 +1,3 @@ +.crmCxn-footer { + text-align: center; +} diff --git a/ang/crmCxn.js b/ang/crmCxn.js new file mode 100644 index 0000000000..38b4baf949 --- /dev/null +++ b/ang/crmCxn.js @@ -0,0 +1,26 @@ +(function (angular, $, _) { + + angular.module('crmCxn', [ + 'crmUtil', 'ngRoute', 'ngSanitize', 'ui.utils', 'crmUi', 'dialogService' + ]); + + angular.module('crmCxn').config([ + '$routeProvider', + function ($routeProvider) { + $routeProvider.when('/cxn', { + templateUrl: '~/crmCxn/ManageCtrl.html', + controller: 'CrmCxnManageCtrl', + resolve: { + apiCalls: function(crmApi){ + var reqs = {}; + reqs.cxns = ['Cxn', 'get', {sequential: 1}]; + reqs.appMetas = ['CxnApp', 'get', {sequential: 1, return: ['id', 'title', 'desc', 'appId', 'appUrl', 'perm']}]; + reqs.sysCheck = ['System', 'check', {}]; // FIXME: filter on checkCxnOverrides + return crmApi(reqs); + } + } + }); + } + ]); + +})(angular, CRM.$, CRM._); diff --git a/ang/crmCxn/ConfirmConnectCtrl.html b/ang/crmCxn/ConfirmConnectCtrl.html new file mode 100644 index 0000000000..599be449ed --- /dev/null +++ b/ang/crmCxn/ConfirmConnectCtrl.html @@ -0,0 +1,12 @@ +
+

{{ts('The application, \"%1\", requests permission to access your system.', {1: appMeta.title})}}

+
+
+
+
+
+
+
+
+
+
diff --git a/ang/crmCxn/ConfirmConnectCtrl.js b/ang/crmCxn/ConfirmConnectCtrl.js new file mode 100644 index 0000000000..c303d5e9bf --- /dev/null +++ b/ang/crmCxn/ConfirmConnectCtrl.js @@ -0,0 +1,5 @@ +(function(angular, $, _) { + angular.module('crmCxn').controller('CrmCxnConfirmConnectCtrl', function($scope) { + $scope.ts = CRM.ts(null); + }); +})(angular, CRM.$, CRM._); diff --git a/ang/crmCxn/ManageCtrl.html b/ang/crmCxn/ManageCtrl.html new file mode 100644 index 0000000000..e6c0331da4 --- /dev/null +++ b/ang/crmCxn/ManageCtrl.html @@ -0,0 +1,73 @@ +
+
+
+ + + +
+ +

{{ts('Existing Connections')}}

+ + + + + + + + + + + + + + + + + +
{{ts('Title')}} {{ts('Description')}} {{ts('Status')}}
{{cxn.app_meta.title}}
{{cxn.is_active ? ts('Enabled') : ts('Disabled')}} + + {{ts('Disconnect')}} +
+
+ +
+ + +

{{ts('New Connections')}}

+ + + + + + + + + + + + + + + +
{{ts('Title')}}{{ts('Description')}}
{{appMeta.title}}
+ {{ts('Connect')}} +
+
+ +
+
+ {{ts('No available applications')}} +
diff --git a/ang/crmCxn/ManageCtrl.js b/ang/crmCxn/ManageCtrl.js new file mode 100644 index 0000000000..a0eb90386a --- /dev/null +++ b/ang/crmCxn/ManageCtrl.js @@ -0,0 +1,66 @@ +(function(angular, $, _) { + + angular.module('crmCxn').controller('CrmCxnManageCtrl', function CrmCxnManageCtrl($scope, apiCalls, crmApi, crmUiAlert, crmBlocker, crmStatus, $timeout) { + var ts = $scope.ts = CRM.ts(null); + $scope.appMetas = apiCalls.appMetas.values; + $scope.cxns = apiCalls.cxns.values; + $scope.alerts = _.where(apiCalls.sysCheck.values, {name: 'checkCxnOverrides'}); + + $scope.filter = {}; + var block = $scope.block = crmBlocker(); + + _.each($scope.alerts, function(alert){ + crmUiAlert({text: alert.message, title: alert.title, type: 'error'}); + }); + + $scope.findCxnByAppId = function(appId) { + var result = _.where($scope.cxns, { + app_guid: appId + }); + switch (result.length) { + case 0: + return null; + case 1: + return result[0]; + default: + throw "Error: Too many connections for appId: " + appId; + } + }; + + $scope.hasAvailApps = function() { + // This should usu return after the 1st or 2nd item, but in testing with small# apps, we may exhaust the list. + for (var i = 0; i< $scope.appMetas.length; i++) { + if (!$scope.findCxnByAppId($scope.appMetas[i].appId)) { + return true; + } + } + return false; + }; + + $scope.refreshCxns = function() { + crmApi('Cxn', 'get', {sequential: 1}).then(function(result) { + $timeout(function(){ + $scope.cxns = result.values; + }); + }); + }; + + $scope.register = function(appMeta) { + var reg = crmApi('Cxn', 'register', {app_guid: appMeta.appId}).then($scope.refreshCxns); + return block(crmStatus({start: ts('Connecting...'), success: ts('Connected')}, reg)); + }; + + $scope.unregister = function(appMeta) { + var reg = crmApi('Cxn', 'unregister', {app_guid: appMeta.appId, debug: 1}).then($scope.refreshCxns); + return block(crmStatus({start: ts('Disconnecting...'), success: ts('Disconnected')}, reg)); + }; + + $scope.toggleCxn = function toggleCxn(cxn) { + var reg = crmApi('Cxn', 'create', {id: cxn.id, is_active: !cxn.is_active, debug: 1}).then(function(){ + cxn.is_active = !cxn.is_active; + }); + return block(crmStatus({start: ts('Saving...'), success: ts('Saved')}, reg)); + }; + }); + +})(angular, CRM.$, CRM._); diff --git a/ang/crmCxn/PermTable.html b/ang/crmCxn/PermTable.html new file mode 100644 index 0000000000..08c35ff847 --- /dev/null +++ b/ang/crmCxn/PermTable.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + +
{{ts('Entity')}}{{ts('Action(s)')}}{{ts('Filter(s)')}}{{ts('Field(s)')}}
+ {{ts('Any')}} + {{api.entity}} + +
+ + {{ts('Any')}} + {{api.actions}} + + + {{action}}, + +
+
+ {{ts('Any')}} +
{{field}} = "{{value}}",
+
+ {{ts('Any')}} + {{field}}, +
+ diff --git a/ang/crmCxn/PermTable.js b/ang/crmCxn/PermTable.js new file mode 100644 index 0000000000..eb7da355c3 --- /dev/null +++ b/ang/crmCxn/PermTable.js @@ -0,0 +1,27 @@ +(function(angular, $, _) { + + // This directive formats the data in appMeta.perm as a nice table. + // example:
+ angular.module('crmCxn').directive('crmCxnPermTable', function crmCxnPermTable() { + return { + restrict: 'EA', + scope: { + crmCxnPermTable: '=' + }, + templateUrl: '~/crmCxn/PermTable.html', + link: function(scope, element, attrs) { + scope.ts = CRM.ts(null); + scope.hasRequiredFilters = function(api) { + return !_.isEmpty(api.required); + }; + scope.isString = function(v) { + return _.isString(v); + }; + scope.apiExplorerUrl = CRM.url('civicrm/api'); + scope.$watch('crmCxnPermTable', function(crmCxnPermTable){ + scope.perm = crmCxnPermTable.perm; + }); + } + }; + }); +})(angular, CRM.$, CRM._); diff --git a/api/v3/Cxn.php b/api/v3/Cxn.php index d5ccd3fcfc..4c0d29df90 100644 --- a/api/v3/Cxn.php +++ b/api/v3/Cxn.php @@ -173,5 +173,16 @@ function civicrm_api3_cxn_unregister($params) { * API result array. */ function civicrm_api3_cxn_get($params) { - return _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params); + $result = _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params); + if (is_array($result['values'])) { + foreach (array_keys($result['values']) as $i) { + if (!empty($result['values'][$i]['app_meta'])) { + $result['values'][$i]['app_meta'] = json_decode($result['values'][$i]['app_meta'], TRUE); + } + if (!empty($result['values'][$i]['perm'])) { + $result['values'][$i]['perm'] = json_decode($result['values'][$i]['perm'], TRUE); + } + } + } + return $result; } -- 2.25.1