--- /dev/null
+<?php
+
+class CRM_OAuth_Angular {
+
+ public static function getSettings() {
+ $s = [];
+
+ $s['redirectUrl'] = \CRM_OAuth_BAO_OAuthClient::getRedirectUri();
+ $s['providers'] = civicrm_api4('OAuthProvider', 'get', [])->indexBy('name');
+
+ return $s;
+ }
+
+}
--- /dev/null
+<div oauth-util-import="CRM.oauthUtil.providers" to="theProviders"></div>
+
+<div id="bootstrap-theme">
+ <div ng-if="!routeParams.provider">
+ <div oauth-provider-list></div>
+ </div>
+
+ <div ng-if="routeParams.provider">
+ <div oauth-provider-detail="{provider: theProviders[routeParams.provider]}"></div>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+{
+ "title": "OAuth2 Client Administration",
+ "server_route": "civicrm/admin/oauth",
+ "permission": "manage OAuth client"
+}
--- /dev/null
+<div oauth-util-import="CRM.oauthUtil.redirectUrl" to="redirectUrl"></div>
+<div class="help">
+ <p>{{ts('Please register with your web-service provider first. Be sure to:')}}</p>
+ <ul>
+ <li>
+ <p>
+ {{ts('Configure the "Redirect URL":')}}
+ </p>
+ <pre>{{redirectUrl}}</pre>
+ <div ng-if="redirectUrl.startsWith('http:/') && !redirectUrl.match('//(localhost|127\.0\.0\.1)')">
+ <p>
+ {{ts('WARNING: Most web-service providers require "https://" URLs. Alternatively, "http://" may be accepted for strictly local URLs ("localhost" or "127.0.0.1").')}}
+ </p>
+ <p>
+ {{ts('If you are doing development or testing on a local HTTP virtual-host, then consider a work-around like "bin/local-redir.sh".')}}
+ </p>
+ </div>
+ </li>
+
+ <li ng-if="options.provider.options.scopes.length > 0">
+ <p>
+ {{ts('Configure the scopes:')}}
+ </p>
+ <pre>{{options.provider.options.scopes.join("\n")}}</pre>
+ </li>
+
+ <li>
+ {{ts('Determine the client credentials ("Client ID" and "Client Secret").')}}
+ </li>
+ </ul>
+ <p>{{ts('Finally, copy the client credentials below:')}}</p>
+</div>
\ No newline at end of file
--- /dev/null
+<div oauth-util-import="CRM.oauthUtil.providers" to="theProviders"></div>
+
+<div ng-form="create" class="form-horizontal">
+ <div class="form-group">
+ <div>
+ <label for="guid">{{ts('Client ID')}}:</label>
+ <input class="form-control" ng-model="options.client.guid" type="text" id="guid" />
+ </div>
+ <div>
+ <label for="secret">{{ts('Client Secret')}}:</label>
+ <input class="form-control" ng-model="options.client.secret" type="text" id="secret" />
+ </div>
+ </div>
+</div>
--- /dev/null
+<div oauth-util-import="CRM.oauthUtil.providers" to="theProviders"></div>
+
+<div ng-form="update" class="form-horizontal">
+ <div class="form-group">
+ <div>
+ <label for="provider{{options.client.id}}">{{ts('Provider')}}:</label>
+ <input class="form-control" ng-model="options.client.provider" disabled id="provider{{options.client.id}}">
+ </div>
+ <div>
+ <label for="id{{options.client.id}}">{{ts('Client ID (Private)')}}:</label>
+ <input class="form-control" ng-model="options.client.id" type="text" id="id{{options.client.id}}" disabled/>
+ </div>
+ <div>
+ <label for="guid{{options.client.id}}">{{ts('Client ID (Public)')}}:</label>
+ <input class="form-control" ng-model="options.client.guid" type="text" id="guid{{options.client.id}}"/>
+ </div>
+ <div>
+ <label for="secret{{options.client.id}}">{{ts('Client Secret')}}:</label>
+ <input class="form-control" ng-model="options.client.secret" type="text" id="secret{{options.client.id}}"/>
+ </div>
+ <div ng-if="options.client.created_date">
+ <label for="created_date{{options.client.id}}">{{ts('Created Date')}}:</label>
+ <input class="form-control" ng-model="options.client.created_date" disabled
+ id="created_date{{options.client.id}}">
+ </div>
+ <div ng-if="options.client.modified_date">
+ <label for="modified_date{{options.client.id}}">{{ts('Modified Date')}}:</label>
+ <input class="form-control" ng-model="options.client.modified_date" disabled
+ id="modified_date{{options.client.id}}">
+ </div>
+ </div>
+</div>
--- /dev/null
+<div
+ af-api4="['OAuthClient', 'get', {select: ['id','provider','guid'], orderBy: {provider:'ASC'}}]"
+ af-api4-ctrl="listCtrl">
+
+ <div ng-if="apiData.result.length == 0">
+ {{ts('There are no clients!')}}
+ </div>
+
+ <table>
+ <thead>
+ <tr>
+ <th>{{ts('ID')}}</th>
+ <th>{{ts('Provider')}}</th>
+ <th>{{ts('GUID')}}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="availClient in listCtrl.result">
+ <td>
+ <a ng-href="#!/?id={{availClient.id}}">{{availClient.id}}</a>
+ </td>
+ <td>{{availClient.provider}}</td>
+ <td>{{availClient.guid}}</td>
+ <td>
+ <!--
+ <a af-api4-action="['Afform', 'revert', {where: [['name','=', availClient.name]]}]"
+ af-api4-start-msg="ts('Reverting...')"
+ af-api4-success-msg="ts('Reverted')"
+ af-api4-success="listCtrl.refresh()"
+ class="btn btn-xs btn-default"
+ ng-if="availClient.has_local && availClient.has_base"
+ >{{ts('Revert')}}</a>
+ -->
+ <a af-api4-action="['OAuthClient', 'delete', {where: [['id','=', availClient.id]]}]"
+ af-api4-start-msg="ts('Deleting...')"
+ af-api4-success-msg="ts('Deleted')"
+ af-api4-success="listCtrl.refresh()"
+ class="btn btn-xs btn-default"
+ >{{ts('Delete')}}</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+</div>
--- /dev/null
+<div af-api4-ctrl="tokens" af-api4="['OAuthSysToken', 'get', {'where': [['client_id', '=', options.clientId]]}]">
+</div>
+<div ng-if="tokens.result.length == 0">
+ {{ts('No tokens found')}}
+</div>
+
+<table class="table" ng-if="tokens.result.length > 0">
+ <tr>
+ <th>{{ts('ID')}}</th>
+ <th>{{ts('Tag')}}</th>
+ <th>{{ts('On Behalf Of')}}</th>
+ <th>{{ts('Scopes')}}</th>
+ <th>{{ts('Created Date')}}</th>
+ <th>{{ts('Actions')}}</th>
+ </tr>
+ <tr ng-repeat="token in tokens.result">
+ <td>{{token.id}}</td>
+ <td>{{token.tag}}</td>
+ <td>{{token.resource_owner_name}}</td>
+ <td>{{token.scopes.join(" ")}}</td>
+ <td>{{token.created_date}}</td>
+ <td>
+ <div class="btn-group">
+ <a class="btn btn-danger"
+ af-api4-action="['OAuthSysToken', 'delete', {where: [['id', '=', token.id]]}]"
+ af-api4-start-msg="ts('Deleting...')"
+ af-api4-success-msg="ts('Deleted')"
+ af-api4-success="tokens.refresh()"
+ >{{ts('Delete')}}</a>
+ </div>
+ </td>
+ </tr>
+</table>
--- /dev/null
+<h1>{{options.provider.title}}</h1>
+
+<div af-api4-ctrl="theClients" af-api4="['OAuthClient', 'get', {where: [['provider','=',options.provider.name]]}]"></div>
+
+<div ng-if="!theClients.loading">
+ <div class="panel panel-info" ng-init="selected = {tab: theClients.result.length > 0 ? 'client_' + theClients.result[0].id : 'new'}">
+ <ul class="panel-heading nav nav-tabs">
+ <li role="presentation" ng-repeat="theClient in theClients.result" ng-class="{active: selected.tab === 'client_' + theClient.id}"><a ng-click="selected.tab = 'client_' + theClient.id">{{ts('Client #%1', {1: theClient.id})}}</a></li>
+ <li role="presentation" ng-class="{active: selected.tab === 'new'}"><a ng-click="selected.tab = 'new'">{{ts('Register Client')}}</a></li>
+ <li role="presentation" ng-class="{active: selected.tab === 'details'}"><a ng-click="selected.tab = 'details'">{{ts('Details')}}</a></li>
+ </ul>
+
+ <div class="panel-body" ng-if="selected.tab === 'details'">
+ <pre>{{options.provider|json}}</pre>
+ </div>
+
+ <div class="panel-body" ng-repeat="resultClient in theClients.result" ng-if="selected.tab === 'client_'+resultClient.id">
+ <div ng-form="editClientForm">
+ <h4>{{ts('Tokens')}}</h4>
+
+ <div oauth-client-tokens="{clientId: resultClient.id}"></div>
+
+ <div class="btn-group" oauth-util-grant-ctrl="granter">
+ <a class="btn btn-primary" ng-click="granter.authCode(resultClient.id)">{{ts('Add (Auth Code)')}}</a>
+ </div>
+
+ <h4>{{ts('Properties')}}</h4>
+
+ <div oauth-client-editor="{client: resultClient}"></div>
+ <div class="btn-group">
+ <a class="btn btn-primary"
+ af-api4-action="['OAuthClient', 'update', {where: [['id', '=', resultClient.id]], values:resultClient}]">{{ts('Save')}}</a>
+ <a class="btn btn-danger"
+ af-api4-action="['OAuthClient', 'delete', {where: [['id', '=', resultClient.id]]}]"
+ af-api4-success="selected.tab = 'details'; theClients.refresh()"
+ >{{ts('Delete')}}</a>
+ </div>
+
+ </div>
+ </div>
+
+ <div class="panel-body" ng-if="selected.tab === 'new'" ng-form="newClientForm" ng-init="theNew = {provider: options.provider.name}">
+ <div oauth-client-create-help="{provider: options.provider}"></div>
+ <div crm-ui-debug="theNew"></div>
+ <div oauth-client-creator="{client: theNew}"></div>
+ <div class="btn-group">
+ <a class="btn btn-primary"
+ af-api4-action="['OAuthClient', 'create', {values:theNew}]"
+ af-api4-success="theNew = {provider: options.provider.name}; theClients.refresh(); selected.tab = 'client_' + response[0].id"
+ >{{ts('Add')}}</a>
+ </div>
+ </div>
+ </div>
+
+</div>
--- /dev/null
+<div oauth-util-import="CRM.oauthUtil.providers" to="theProviders"></div>
+
+<div class="help">
+ <p>
+ {{ts('CiviCRM may be configured as a client that interacts with remote web-services, such as Google Mail or Microsoft Exchange. Please choose the type of web-service you wish to connect to:')}}
+ </p>
+
+ <!--
+ To do so, you must first register with the service to obtain credentials (Client ID and Client Secret). Copy the assigned credentials below.
+ -->
+</div>
+
+<table>
+ <thead>
+ <tr>
+ <th>{{ts('Name')}}</th>
+ <th>{{ts('Title')}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="provider in theProviders">
+ <td>
+ <a ng-href="#!/?provider={{provider.name}}">{{provider.name}}</a>
+ </td>
+ <td>
+ <a ng-href="#!/?provider={{provider.name}}">{{provider.title}}</a>
+ </td>
+ </tr>
+ </tbody>
+</table>
--- /dev/null
+<?php
+// This file declares an Angular module which can be autoloaded
+// in CiviCRM. See also:
+// \https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_angularModules/n
+return [
+ 'js' => [
+ 'ang/oauthUtil.js',
+ // 'ang/oauthUtil/*.js',
+ // 'ang/oauthUtil/*/*.js',
+ ],
+ // 'css' => ['ang/oauthUtil.css'],
+ // 'partials' => ['ang/oauthUtil'],
+ // 'requires' => ['crmUi', 'crmUtil'],
+ 'settings' => [],
+ 'settingsFactory' => ['CRM_OAuth_Angular', 'getSettings'],
+ 'exports' => [
+ 'oauth-util-import' => 'A',
+ 'oauth-util-grant-ctrl' => 'A',
+ ],
+];
--- /dev/null
+(function(angular, $, _) {
+ angular.module('oauthUtil', CRM.angRequires('oauthUtil'));
+ // Import data from the 'CRM.foo' settings.
+ // Ex: <div oauth-util-import="CRM.oauthUtil.providers" to="theProviders" />
+ angular.module('oauthUtil').directive('oauthUtilImport', function() {
+ return {
+ restrict: 'EA',
+ scope: {
+ to: '=',
+ oauthUtilImport: '@'
+ },
+ controller: function($scope, $parse) {
+ $scope.to = $parse($scope.oauthUtilImport)({CRM: CRM});
+ }
+ };
+ });
+ angular.module('oauthUtil').directive('oauthUtilGrantCtrl', function() {
+ return {
+ restrict: 'EA',
+ scope: {
+ oauthUtilGrantCtrl: '='
+ },
+ controllerAs: 'oauthUtilGrantCtrl',
+ controller: function($scope, $parse, crmBlocker, crmApi4, crmStatus) {
+ var block = crmBlocker();
+ var ctrl = this;
+ ctrl.authCode = function(clientId) {
+ var confirmOpt = {
+ message: ts('You are about to be redirected to an external site.'),
+ options: {no: ts('Cancel'), yes: ts('Continue')}
+ };
+ CRM.confirm(confirmOpt)
+ .on('crmConfirm:yes', function(){
+ var going = crmApi4('OAuthClient', 'authorizationCode', {
+ 'landingUrl': window.location.href,
+ 'where': [['id', '=', clientId]]
+ }).then(function(r){
+ window.location = r[0].url;
+ });
+ return block(crmStatus({start: ts('Redirecting...'), success: ts('Redirecting...')}, going));
+ });
+ };
+
+ $scope.oauthUtilGrantCtrl = this;
+ }
+ };
+ });
+})(angular, CRM.$, CRM._);
<compatibility>
<ver>5.0</ver>
</compatibility>
+ <requires>
+ <ext version="~4.5">org.civicrm.afform</ext>
+ </requires>
<comments>This is a new, undeveloped module</comments>
<classloader>
<psr4 prefix="Civi\" path="Civi"/>