Per discussion with @totten, re the logic that interprets "tag" and links/creates contacts accordingly: in earlier versions of this code, I had put that logic in OAuthTokenFacade. Moving it to the api4 "create" action means it will happen even when a ContactToken is created outside the OAuth flow. I decided not to put it in BAO writeRecords() because then it would run too late for the permissions checking we do in the api4 Create action.
Note: the permissions change here means that some current users of the OAuth Extension will need to enable an additional permission to allow for the acquisition of new tokens through the auth-code flow.
--- /dev/null
+<?php
+
+class CRM_OAuth_BAO_OAuthContactToken extends CRM_OAuth_DAO_OAuthContactToken {
+
+}
--- /dev/null
+<?php
+
+class CRM_OAuth_ContactFromToken {
+
+ /**
+ * Given a token, we add a new record to civicrm_contact based on the
+ * provider's template
+ */
+ public static function createContact(array $token): array {
+ $client = \Civi\Api4\OAuthClient::get(FALSE)
+ ->addWhere('id', '=', $token['client_id'])
+ ->execute()
+ ->single();
+ $provider = \Civi\Api4\OAuthProvider::get(FALSE)
+ ->addWhere('name', '=', $client['provider'])
+ ->execute()
+ ->single();
+
+ $vars = ['token' => $token, 'client' => $client, 'provider' => $provider];
+ $template = ['checkPermissions' => FALSE] + $provider['contactTemplate'];
+ $contact = civicrm_api4(
+ 'Contact',
+ 'create',
+ self::evalArrayTemplate($template, $vars)
+ )->single();
+ return $contact;
+ }
+
+ /**
+ * @param array $template
+ * Array of key-value expressions. Arrays can be nested.
+ * Ex: ['name' => '{{person.first}} {{person.last}}']
+ *
+ * Expressions begin with a variable name; a string followed by a dot
+ * denotes an array key. Ex: {{person.first}} means
+ * $vars['person']['first'].
+ *
+ * Optionally, the value may be piped through other 'filter' functions.
+ * Ex: {{person.first|lowercase}}
+ *
+ * @param array $vars
+ * Array tree of data to interpolate.
+ *
+ * @return array
+ * The template array, with '{{...}}' expressions evaluated.
+ */
+ public static function evalArrayTemplate(array $template, array $vars): array {
+ $filterFunctions = [
+ 'lowercase' => function ($s) {
+ return strtolower($s);
+ },
+ ];
+
+ $evaluateLeafNode = function (&$node) use ($filterFunctions, $vars) {
+ if (!(preg_match(';{{([a-zA-Z0-9_\.\|]+)}};', $node, $matches))) {
+ return $node;
+ }
+
+ $parts = explode('|', $matches[1]);
+ $value = (string) CRM_Utils_Array::pathGet($vars, explode('.', $parts[0]));
+ $filterSteps = array_slice($parts, 1);
+
+ foreach ($filterSteps as $f) {
+ if (isset($filterFunctions[$f])) {
+ $value = $filterFunctions[$f]($value);
+ }
+ else {
+ $value = NULL;
+ }
+ }
+
+ $node = $value;
+ };
+
+ $result = $template;
+ array_walk_recursive($result, $evaluateLeafNode);
+ return $result;
+ }
+
+}
*
* Generated from oauth-client/xml/schema/CRM/OAuth/OAuthClient.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:7487cf595064832b3d55188b3e48bffc)
+ * (GenCodeChecksum:16542326ad093d2d6826a11f1576522e)
*/
use CRM_OAuth_ExtensionUtil as E;
'entity' => 'OAuthClient',
'bao' => 'CRM_OAuth_DAO_OAuthClient',
'localizable' => 0,
+ 'readonly' => TRUE,
'add' => '5.32',
],
'provider' => [
'title' => E::ts('Client Secret'),
'description' => E::ts('Client Secret'),
'where' => 'civicrm_oauth_client.secret',
+ 'permission' => [
+ 'manage OAuth client',
+ ],
'table_name' => 'civicrm_oauth_client',
'entity' => 'OAuthClient',
'bao' => 'CRM_OAuth_DAO_OAuthClient',
'title' => E::ts('Options'),
'description' => E::ts('Extra override options for the service (JSON)'),
'where' => 'civicrm_oauth_client.options',
+ 'permission' => [
+ 'manage OAuth client',
+ ],
'table_name' => 'civicrm_oauth_client',
'entity' => 'OAuthClient',
'bao' => 'CRM_OAuth_DAO_OAuthClient',
--- /dev/null
+<?php
+
+/**
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ *
+ * Generated from oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.xml
+ * DO NOT EDIT. Generated by CRM_Core_CodeGen
+ * (GenCodeChecksum:48364c8d563f29a459f0c3ccbe4f1c8e)
+ */
+use CRM_OAuth_ExtensionUtil as E;
+
+/**
+ * Database access object for the OAuthContactToken entity.
+ */
+class CRM_OAuth_DAO_OAuthContactToken extends CRM_Core_DAO {
+ const EXT = E::LONG_NAME;
+ const TABLE_ADDED = '5.35';
+
+ /**
+ * Static instance to hold the table name.
+ *
+ * @var string
+ */
+ public static $_tableName = 'civicrm_oauth_contact_token';
+
+ /**
+ * Should CiviCRM log any modifications to this table in the civicrm_log table.
+ *
+ * @var bool
+ */
+ public static $_log = FALSE;
+
+ /**
+ * Token ID
+ *
+ * @var int
+ */
+ public $id;
+
+ /**
+ * The tag specifies how this token will be used.
+ *
+ * @var string
+ */
+ public $tag;
+
+ /**
+ * Client ID
+ *
+ * @var int
+ */
+ public $client_id;
+
+ /**
+ * Contact ID
+ *
+ * @var int
+ */
+ public $contact_id;
+
+ /**
+ * Ex: authorization_code
+ *
+ * @var string
+ */
+ public $grant_type;
+
+ /**
+ * List of scopes addressed by this token
+ *
+ * @var text
+ */
+ public $scopes;
+
+ /**
+ * Ex: Bearer or MAC
+ *
+ * @var string
+ */
+ public $token_type;
+
+ /**
+ * Token to present when accessing resources
+ *
+ * @var text
+ */
+ public $access_token;
+
+ /**
+ * Expiration time for the access_token (seconds since epoch)
+ *
+ * @var int
+ */
+ public $expires;
+
+ /**
+ * Token to present when refreshing the access_token
+ *
+ * @var text
+ */
+ public $refresh_token;
+
+ /**
+ * Identifier for the resource owner. Structure varies by service.
+ *
+ * @var string
+ */
+ public $resource_owner_name;
+
+ /**
+ * Cached details describing the resource owner
+ *
+ * @var text
+ */
+ public $resource_owner;
+
+ /**
+ * ?? copied from OAuthSysToken
+ *
+ * @var text
+ */
+ public $error;
+
+ /**
+ * The token response data, per AccessToken::jsonSerialize
+ *
+ * @var text
+ */
+ public $raw;
+
+ /**
+ * When the token was created.
+ *
+ * @var timestamp
+ */
+ public $created_date;
+
+ /**
+ * When the token was created or modified.
+ *
+ * @var timestamp
+ */
+ public $modified_date;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->__table = 'civicrm_oauth_contact_token';
+ parent::__construct();
+ }
+
+ /**
+ * Returns localized title of this entity.
+ *
+ * @param bool $plural
+ * Whether to return the plural version of the title.
+ */
+ public static function getEntityTitle($plural = FALSE) {
+ return $plural ? E::ts('OAuth Contact Tokens') : E::ts('OAuth Contact Token');
+ }
+
+ /**
+ * Returns foreign keys and entity references.
+ *
+ * @return array
+ * [CRM_Core_Reference_Interface]
+ */
+ public static function getReferenceColumns() {
+ if (!isset(Civi::$statics[__CLASS__]['links'])) {
+ Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
+ Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'client_id', 'civicrm_oauth_client', 'id');
+ Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id');
+ CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
+ }
+ return Civi::$statics[__CLASS__]['links'];
+ }
+
+ /**
+ * Returns all the column names of this table
+ *
+ * @return array
+ */
+ public static function &fields() {
+ if (!isset(Civi::$statics[__CLASS__]['fields'])) {
+ Civi::$statics[__CLASS__]['fields'] = [
+ 'id' => [
+ 'name' => 'id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('Token ID'),
+ 'description' => E::ts('Token ID'),
+ 'required' => TRUE,
+ 'where' => 'civicrm_oauth_contact_token.id',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'readonly' => TRUE,
+ 'add' => '5.35',
+ ],
+ 'tag' => [
+ 'name' => 'tag',
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('Tag'),
+ 'description' => E::ts('The tag specifies how this token will be used.'),
+ 'maxlength' => 128,
+ 'size' => CRM_Utils_Type::HUGE,
+ 'where' => 'civicrm_oauth_contact_token.tag',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'client_id' => [
+ 'name' => 'client_id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('Client ID'),
+ 'description' => E::ts('Client ID'),
+ 'where' => 'civicrm_oauth_contact_token.client_id',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'FKClassName' => 'CRM_OAuth_DAO_OAuthClient',
+ 'add' => '5.35',
+ ],
+ 'contact_id' => [
+ 'name' => 'contact_id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('Contact ID'),
+ 'description' => E::ts('Contact ID'),
+ 'where' => 'civicrm_oauth_contact_token.contact_id',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'FKClassName' => 'CRM_Contact_DAO_Contact',
+ 'add' => '5.35',
+ ],
+ 'grant_type' => [
+ 'name' => 'grant_type',
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('Grant type'),
+ 'description' => E::ts('Ex: authorization_code'),
+ 'maxlength' => 31,
+ 'size' => CRM_Utils_Type::MEDIUM,
+ 'where' => 'civicrm_oauth_contact_token.grant_type',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'scopes' => [
+ 'name' => 'scopes',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Scopes'),
+ 'description' => E::ts('List of scopes addressed by this token'),
+ 'where' => 'civicrm_oauth_contact_token.scopes',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'serialize' => self::SERIALIZE_SEPARATOR_BOOKEND,
+ 'add' => '5.35',
+ ],
+ 'token_type' => [
+ 'name' => 'token_type',
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('Token Type'),
+ 'description' => E::ts('Ex: Bearer or MAC'),
+ 'maxlength' => 128,
+ 'size' => CRM_Utils_Type::HUGE,
+ 'where' => 'civicrm_oauth_contact_token.token_type',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'access_token' => [
+ 'name' => 'access_token',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Access Token'),
+ 'description' => E::ts('Token to present when accessing resources'),
+ 'where' => 'civicrm_oauth_contact_token.access_token',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'expires' => [
+ 'name' => 'expires',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('Expiration time'),
+ 'description' => E::ts('Expiration time for the access_token (seconds since epoch)'),
+ 'where' => 'civicrm_oauth_contact_token.expires',
+ 'default' => '0',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'refresh_token' => [
+ 'name' => 'refresh_token',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Refresh Token'),
+ 'description' => E::ts('Token to present when refreshing the access_token'),
+ 'where' => 'civicrm_oauth_contact_token.refresh_token',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'resource_owner_name' => [
+ 'name' => 'resource_owner_name',
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('Resource Owner Name'),
+ 'description' => E::ts('Identifier for the resource owner. Structure varies by service.'),
+ 'maxlength' => 128,
+ 'size' => CRM_Utils_Type::HUGE,
+ 'where' => 'civicrm_oauth_contact_token.resource_owner_name',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'resource_owner' => [
+ 'name' => 'resource_owner',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Resource Owner'),
+ 'description' => E::ts('Cached details describing the resource owner'),
+ 'where' => 'civicrm_oauth_contact_token.resource_owner',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'serialize' => self::SERIALIZE_JSON,
+ 'add' => '5.35',
+ ],
+ 'error' => [
+ 'name' => 'error',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Error'),
+ 'description' => E::ts('?? copied from OAuthSysToken'),
+ 'where' => 'civicrm_oauth_contact_token.error',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'serialize' => self::SERIALIZE_JSON,
+ 'add' => '5.35',
+ ],
+ 'raw' => [
+ 'name' => 'raw',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Raw token'),
+ 'description' => E::ts('The token response data, per AccessToken::jsonSerialize'),
+ 'where' => 'civicrm_oauth_contact_token.raw',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'serialize' => self::SERIALIZE_JSON,
+ 'add' => '5.35',
+ ],
+ 'created_date' => [
+ 'name' => 'created_date',
+ 'type' => CRM_Utils_Type::T_TIMESTAMP,
+ 'title' => E::ts('Created Date'),
+ 'description' => E::ts('When the token was created.'),
+ 'required' => FALSE,
+ 'where' => 'civicrm_oauth_contact_token.created_date',
+ 'default' => 'CURRENT_TIMESTAMP',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ 'modified_date' => [
+ 'name' => 'modified_date',
+ 'type' => CRM_Utils_Type::T_TIMESTAMP,
+ 'title' => E::ts('Modified Date'),
+ 'description' => E::ts('When the token was created or modified.'),
+ 'required' => FALSE,
+ 'where' => 'civicrm_oauth_contact_token.modified_date',
+ 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
+ 'table_name' => 'civicrm_oauth_contact_token',
+ 'entity' => 'OAuthContactToken',
+ 'bao' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'localizable' => 0,
+ 'add' => '5.35',
+ ],
+ ];
+ CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
+ }
+ return Civi::$statics[__CLASS__]['fields'];
+ }
+
+ /**
+ * Return a mapping from field-name to the corresponding key (as used in fields()).
+ *
+ * @return array
+ * Array(string $name => string $uniqueName).
+ */
+ public static function &fieldKeys() {
+ if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
+ Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
+ }
+ return Civi::$statics[__CLASS__]['fieldKeys'];
+ }
+
+ /**
+ * Returns the names of this table
+ *
+ * @return string
+ */
+ public static function getTableName() {
+ return self::$_tableName;
+ }
+
+ /**
+ * Returns if this table needs to be logged
+ *
+ * @return bool
+ */
+ public function getLog() {
+ return self::$_log;
+ }
+
+ /**
+ * Returns the list of fields that can be imported
+ *
+ * @param bool $prefix
+ *
+ * @return array
+ */
+ public static function &import($prefix = FALSE) {
+ $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'oauth_contact_token', $prefix, []);
+ return $r;
+ }
+
+ /**
+ * Returns the list of fields that can be exported
+ *
+ * @param bool $prefix
+ *
+ * @return array
+ */
+ public static function &export($prefix = FALSE) {
+ $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'oauth_contact_token', $prefix, []);
+ return $r;
+ }
+
+ /**
+ * Returns the list of indices
+ *
+ * @param bool $localize
+ *
+ * @return array
+ */
+ public static function indices($localize = TRUE) {
+ $indices = [
+ 'UI_tag' => [
+ 'name' => 'UI_tag',
+ 'field' => [
+ 0 => 'tag',
+ ],
+ 'localizable' => FALSE,
+ 'sig' => 'civicrm_oauth_contact_token::0::tag',
+ ],
+ ];
+ return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
+ }
+
+}
*
* Generated from oauth-client/xml/schema/CRM/OAuth/OAuthSysToken.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:071b8b361ebc9d1b4867e4cef2172389)
+ * (GenCodeChecksum:75d2f40faea06bdd175a66124d1ac77d)
*/
use CRM_OAuth_ExtensionUtil as E;
'entity' => 'OAuthSysToken',
'bao' => 'CRM_OAuth_DAO_OAuthSysToken',
'localizable' => 0,
+ 'readonly' => TRUE,
'add' => '5.32',
],
'tag' => [
]);
}
+ /**
+ * Add support for OAuthContactToken
+ *
+ * @return bool TRUE on success
+ * @throws Exception
+ */
+ public function upgrade_0001(): bool {
+ $this->ctx->log->info('Applying update 0001');
+ $this->executeSqlFile('sql/upgrade_0001.sql');
+ return TRUE;
+ }
+
/**
* Example: Run an external SQL script when the module is installed.
*
--- /dev/null
+<?php
+
+
+namespace Civi\Api4\Action\OAuthContactToken;
+
+use Civi\Api4\Generic\Result;
+
+class Create extends \Civi\Api4\Generic\DAOCreateAction {
+
+ public function _run(Result $result) {
+ $this->fillContactIdFromTag();
+ $this->assertPermissionForTokenContact();
+ parent::_run($result);
+ }
+
+ private function fillContactIdFromTag(): void {
+ if (isset($this->values['contact_id'])) {
+ return;
+ }
+
+ $tag = $this->values['tag'] ?? NULL;
+
+ if ('linkContact:' === substr($tag, 0, 12)) {
+ $this->values['contact_id'] = substr($tag, 12);
+ }
+ elseif ('nullContactId' === $tag) {
+ $this->values['contact_id'] = NULL;
+ }
+ elseif ('createContact' === $tag) {
+ $contact = \CRM_OAuth_ContactFromToken::createContact($this->values);
+ $this->values['contact_id'] = $contact['id'];
+ }
+ else {
+ $this->values['contact_id'] = \CRM_Core_Session::singleton()
+ ->getLoggedInContactID();
+ }
+ }
+
+ /**
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ private function assertPermissionForTokenContact(): void {
+ if (!$this->getCheckPermissions()) {
+ return;
+ }
+ if (\CRM_Core_Permission::check('manage all OAuth contact tokens')) {
+ return;
+ }
+ if (\CRM_Core_Permission::check('manage my OAuth contact tokens')) {
+ $loggedInContactID = \CRM_Core_Session::singleton()
+ ->getLoggedInContactID();
+ $tokenContactID = $this->values['contact_id'] ?? NULL;
+ if ($loggedInContactID == $tokenContactID) {
+ return;
+ }
+ }
+ throw new \Civi\API\Exception\UnauthorizedException(ts(
+ "You do not have permission to create OAuth tokens for contact id %1",
+ [1 => $tokenContactID]));
+ }
+
+}
--- /dev/null
+<?php
+
+
+namespace Civi\Api4\Action\OAuthContactToken;
+
+class Delete extends \Civi\Api4\Generic\DAODeleteAction {
+
+ use OnlyModifyOwnTokensTrait;
+
+}
--- /dev/null
+<?php
+
+
+namespace Civi\Api4\Action\OAuthContactToken;
+
+class Get extends \Civi\Api4\Generic\DAOGetAction {
+
+ protected function setDefaultWhereClause() {
+ $this->applyContactTokenPermissions();
+ parent::setDefaultWhereClause();
+ }
+
+ private function applyContactTokenPermissions() {
+ if (!$this->getCheckPermissions()) {
+ return;
+ }
+ if (\CRM_Core_Permission::check(['manage all OAuth contact tokens'])) {
+ return;
+ }
+ if (\CRM_Core_Permission::check(['manage my OAuth contact tokens'])) {
+ $loggedInContactID = \CRM_Core_Session::singleton()
+ ->getLoggedInContactID();
+ $this->addWhere('contact_id', '=', $loggedInContactID);
+ return;
+ }
+ throw new \Civi\API\Exception\UnauthorizedException(ts('Insufficient permissions to get contact tokens'));
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Civi\Api4\Action\OAuthContactToken;
+
+trait OnlyModifyOwnTokensTrait {
+
+ public function isAuthorized(): bool {
+ if (\CRM_Core_Permission::check(['manage all OAuth contact tokens'])) {
+ return TRUE;
+ }
+ if (!\CRM_Core_Permission::check(['manage my OAuth contact tokens'])) {
+ return FALSE;
+ }
+ $loggedInContactID = \CRM_Core_Session::singleton()->getLoggedInContactID();
+ foreach ($this->where as $clause) {
+ [$field, $op, $val] = $clause;
+ if ($field !== 'contact_id') {
+ continue;
+ }
+ if (($op === '=' || $op === 'LIKE') && $val != $loggedInContactID) {
+ return FALSE;
+ }
+ if ($op === 'IN' && $val != [$loggedInContactID]) {
+ return FALSE;
+ }
+ }
+ $this->addWhere('contact_id', '=', $loggedInContactID);
+ return TRUE;
+ }
+
+}
--- /dev/null
+<?php
+
+
+namespace Civi\Api4\Action\OAuthContactToken;
+
+class Update extends \Civi\Api4\Generic\DAOUpdateAction {
+
+ use OnlyModifyOwnTokensTrait;
+
+}
<?php
+
namespace Civi\Api4;
use Civi\Api4\Action\OAuthClient\Create;
* Initiate the "Authorization Code" workflow.
*
* @param bool $checkPermissions
+ *
* @return \Civi\Api4\Action\OAuthClient\AuthorizationCode
*/
- public static function authorizationCode($checkPermissions = TRUE) {
+ public static function authorizationCode($checkPermissions = TRUE): Action\OAuthClient\AuthorizationCode {
$action = new \Civi\Api4\Action\OAuthClient\AuthorizationCode(static::class, __FUNCTION__);
return $action->setCheckPermissions($checkPermissions);
}
* Request access with client credentials
*
* @param bool $checkPermissions
+ *
* @return \Civi\Api4\Action\OAuthClient\ClientCredential
*/
- public static function clientCredential($checkPermissions = TRUE) {
+ public static function clientCredential($checkPermissions = TRUE): Action\OAuthClient\ClientCredential {
$action = new \Civi\Api4\Action\OAuthClient\ClientCredential(static::class, __FUNCTION__);
return $action->setCheckPermissions($checkPermissions);
}
* Request access with a username and password.
*
* @param bool $checkPermissions
+ *
* @return \Civi\Api4\Action\OAuthClient\UserPassword
*/
- public static function userPassword($checkPermissions = TRUE) {
+ public static function userPassword($checkPermissions = TRUE): Action\OAuthClient\UserPassword {
$action = new \Civi\Api4\Action\OAuthClient\UserPassword(static::class, __FUNCTION__);
return $action->setCheckPermissions($checkPermissions);
}
- public static function permissions() {
+ public static function permissions(): array {
return [
'meta' => ['access CiviCRM'],
'default' => ['manage OAuth client'],
+ 'get' => [
+ [
+ 'manage OAuth client',
+ 'manage my OAuth contact tokens',
+ 'manage all OAuth contact tokens',
+ ],
+ ],
];
}
--- /dev/null
+<?php
+
+
+namespace Civi\Api4;
+
+/**
+ * OAuthContactToken entity.
+ *
+ * Provided by the OAuth Client extension.
+ *
+ * @package Civi\Api4
+ */
+class OAuthContactToken extends Generic\DAOEntity {
+
+ public static function create($checkPermissions = TRUE) {
+ $action = new Action\OAuthContactToken\Create(static::class, __FUNCTION__);
+ return $action->setCheckPermissions($checkPermissions);
+ }
+
+ public static function get($checkPermissions = TRUE) {
+ $action = new Action\OAuthContactToken\Get(static::class, __FUNCTION__);
+ return $action->setCheckPermissions($checkPermissions);
+ }
+
+ public static function update($checkPermissions = TRUE) {
+ $action = new Action\OAuthContactToken\Update(static::class, __FUNCTION__);
+ return $action->setCheckPermissions($checkPermissions);
+ }
+
+ public static function delete($checkPermissions = TRUE) {
+ $action = new Action\OAuthContactToken\Delete(static::class, __FUNCTION__);
+ return $action->setCheckPermissions($checkPermissions);
+ }
+
+ public static function permissions(): array {
+ return [
+ 'meta' => ['access CiviCRM'],
+ 'default' => [
+ [
+ 'manage my OAuth contact tokens',
+ 'manage all OAuth contact tokens',
+ ],
+ ],
+ ];
+ }
+
+}
class OAuthTokenFacade {
- const STORAGE_TYPES = ';^OAuthSysToken$;';
+ const STORAGE_TYPES = ';^OAuth(Sys|Contact)Token$;';
/**
* Request and store a token.
* @param array $options
* With some mix of the following:
* - client: array, the OAuthClient record
- * - scope: array|string|null, list of scopes to request. if omitted, inherit default from client/provider
+ * - scope: array|string|null, list of scopes to request. if omitted,
+ * inherit default from client/provider
* - storage: string, default: "OAuthSysToken"
* - tag: string|null, a symbolic/freeform identifier for looking-up tokens
- * - grant_type: string, ex "authorization_code", "client_credentials", "password"
- * - cred: array, extra credentialing options to pass to the "token" URL (via getAccessToken($tokenOptions)),
- * eg "username", "password", "code"
+ * - grant_type: string, ex "authorization_code", "client_credentials",
+ * "password"
+ * - cred: array, extra credentialing options to pass to the "token" URL
+ * (via getAccessToken($tokenOptions)), eg "username", "password", "code"
+ *
* @return array
* @throws \API_Exception
* @see \League\OAuth2\Client\Provider\AbstractProvider::getAccessToken()
*/
- public function init($options) {
+ public function init($options): array {
$options['storage'] = $options['storage'] ?? 'OAuthSysToken';
if (!preg_match(self::STORAGE_TYPES, $options['storage'])) {
throw new \API_Exception("Invalid token storage ({$options['storage']})");
}
/** @var \League\OAuth2\Client\Provider\GenericProvider $provider */
- $provider = \Civi::service('oauth2.league')->createProvider($options['client']);
+ $provider = \Civi::service('oauth2.league')
+ ->createProvider($options['client']);
$scopeSeparator = $this->callProtected($provider, 'getScopeSeparator');
$sendOptions = $options['cred'] ?? [];
'refresh_token' => $accessToken->getRefreshToken(),
'expires' => $accessToken->getExpires(),
'raw' => $accessToken->jsonSerialize(),
+ 'storage' => $options['storage'],
];
+
try {
$owner = $provider->getResourceOwner($accessToken);
$tokenRecord['resource_owner_name'] = $this->findName($owner);
* @param mixed $obj
* @param string $method
* @param array $args
+ *
* @return mixed
*/
- protected function callProtected($obj, $method, $args = []) {
+ protected function callProtected($obj, string $method, $args = []) {
$r = new \ReflectionMethod(get_class($obj), $method);
$r->setAccessible(TRUE);
return $r->invokeArgs($obj, $args);
/**
* @param string $delim
* @param string|array|null $scopes
+ *
* @return array|null
*/
- protected function splitScopes($delim, $scopes) {
+ protected function splitScopes(string $delim, $scopes) {
if ($scopes === NULL || is_array($scopes)) {
return $scopes;
}
return NULL;
}
- protected function implodeScopes($delim, $scopes) {
+ protected function implodeScopes($delim, $scopes): ?string {
if ($scopes === NULL || is_string($scopes)) {
return $scopes;
}
}
protected function findName(ResourceOwnerInterface $owner) {
+ if (method_exists($owner, 'getName')) {
+ return $owner->getName();
+ }
$values = $owner->toArray();
$fields = ['upn', 'userPrincipalName', 'mail', 'email', 'id'];
foreach ($fields as $field) {
* Search directory tree for files which match a glob pattern.
*
* Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
- * Note: In Civi 4.3+, delegate to CRM_Utils_File::findFiles()
+ * Note: Delegate to CRM_Utils_File::findFiles(), this function kept only
+ * for backward compatibility of extension code that uses it.
*
* @param string $dir base dir
* @param string $pattern , glob pattern, eg "*.txt"
* @return array
*/
function _oauth_client_civix_find_files($dir, $pattern) {
- if (is_callable(['CRM_Utils_File', 'findFiles'])) {
- return CRM_Utils_File::findFiles($dir, $pattern);
- }
-
- $todos = [$dir];
- $result = [];
- while (!empty($todos)) {
- $subdir = array_shift($todos);
- foreach (_oauth_client_civix_glob("$subdir/$pattern") as $match) {
- if (!is_dir($match)) {
- $result[] = $match;
- }
- }
- if ($dh = opendir($subdir)) {
- while (FALSE !== ($entry = readdir($dh))) {
- $path = $subdir . DIRECTORY_SEPARATOR . $entry;
- if ($entry[0] == '.') {
- }
- elseif (is_dir($path)) {
- $todos[] = $path;
- }
- }
- closedir($dh);
- }
- }
- return $result;
+ return CRM_Utils_File::findFiles($dir, $pattern);
}
/**
'class' => 'CRM_OAuth_DAO_OAuthClient',
'table' => 'civicrm_oauth_client',
],
+ 'CRM_OAuth_DAO_OAuthContactToken' => [
+ 'name' => 'OAuthContactToken',
+ 'class' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'table' => 'civicrm_oauth_contact_token',
+ ],
'CRM_OAuth_DAO_OAuthSysToken' => [
'name' => 'OAuthSysToken',
'class' => 'CRM_OAuth_DAO_OAuthSysToken',
$prefix . ts('manage OAuth client secrets'),
ts('Access OAuth secrets'),
];
+ $permissions['create OAuth tokens via auth code flow'] = [
+ $prefix . ts('create OAuth tokens via auth code flow'),
+ ts('Create OAuth tokens via the authorization code flow'),
+ ];
+ $permissions['manage my OAuth contact tokens'] = [
+ $prefix . ts('manage my OAuth contact tokens'),
+ ts("Manage user's own OAuth tokens"),
+ ];
+ $permissions['manage all OAuth contact tokens'] = [
+ $prefix . ts('manage all OAuth contact tokens'),
+ ts("Manage OAuth tokens for all contacts"),
+ ];
}
/**
-- DO NOT EDIT. Generated by CRM_Core_CodeGen
--
-
-- +--------------------------------------------------------------------+
-- | Copyright CiviCRM LLC. All rights reserved. |
-- | |
--
-- /*******************************************************
-- *
--- * Clean up the exisiting tables
+-- * Clean up the existing tables
-- *
-- *******************************************************/
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `civicrm_oauth_systoken`;
+DROP TABLE IF EXISTS `civicrm_oauth_contact_token`;
DROP TABLE IF EXISTS `civicrm_oauth_client`;
SET FOREIGN_KEY_CHECKS=1;
`options` text COMMENT 'Extra override options for the service (JSON)',
`is_active` tinyint NOT NULL DEFAULT 1 COMMENT 'Is the client currently enabled?',
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'When the client was created.',
- `modified_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When the client was created or modified.'
+ `modified_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When the client was created or modified.'
,
PRIMARY KEY (`id`)
-
+
, INDEX `UI_provider`(
provider
)
, INDEX `UI_guid`(
guid
)
-
-
-) ;
+
+
+) ENGINE=InnoDB ;
+
+-- /*******************************************************
+-- *
+-- * civicrm_oauth_contact_token
+-- *
+-- *******************************************************/
+CREATE TABLE `civicrm_oauth_contact_token` (
+
+
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Token ID',
+ `tag` varchar(128) COMMENT 'The tag specifies how this token will be used.',
+ `client_id` int unsigned COMMENT 'Client ID',
+ `contact_id` int unsigned COMMENT 'Contact ID',
+ `grant_type` varchar(31) COMMENT 'Ex: authorization_code',
+ `scopes` text COMMENT 'List of scopes addressed by this token',
+ `token_type` varchar(128) COMMENT 'Ex: Bearer or MAC',
+ `access_token` text COMMENT 'Token to present when accessing resources',
+ `expires` int unsigned DEFAULT 0 COMMENT 'Expiration time for the access_token (seconds since epoch)',
+ `refresh_token` text COMMENT 'Token to present when refreshing the access_token',
+ `resource_owner_name` varchar(128) COMMENT 'Identifier for the resource owner. Structure varies by service.',
+ `resource_owner` text COMMENT 'Cached details describing the resource owner',
+ `error` text COMMENT '?? copied from OAuthSysToken',
+ `raw` text COMMENT 'The token response data, per AccessToken::jsonSerialize',
+ `created_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'When the token was created.',
+ `modified_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When the token was created or modified.'
+,
+ PRIMARY KEY (`id`)
+
+ , INDEX `UI_tag`(
+ tag
+ )
+
+, CONSTRAINT FK_civicrm_oauth_contact_token_client_id FOREIGN KEY (`client_id`) REFERENCES `civicrm_oauth_client`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_oauth_contact_token_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB ;
-- /*******************************************************
-- *
`error` text COMMENT 'List of scopes addressed by this token',
`raw` text COMMENT 'The token response data, per AccessToken::jsonSerialize',
`created_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'When the token was created.',
- `modified_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When the token was created or modified.'
+ `modified_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When the token was created or modified.'
,
PRIMARY KEY (`id`)
-
+
, INDEX `UI_tag`(
tag
)
-
-, CONSTRAINT FK_civicrm_oauth_systoken_client_id FOREIGN KEY (`client_id`) REFERENCES `civicrm_oauth_client`(`id`) ON DELETE CASCADE
-) ;
-
\ No newline at end of file
+, CONSTRAINT FK_civicrm_oauth_systoken_client_id FOREIGN KEY (`client_id`) REFERENCES `civicrm_oauth_client`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB ;
+
--
-- /*******************************************************
-- *
--- * Clean up the exisiting tables
+-- * Clean up the existing tables
-- *
-- *******************************************************/
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `civicrm_oauth_systoken`;
+DROP TABLE IF EXISTS `civicrm_oauth_contact_token`;
DROP TABLE IF EXISTS `civicrm_oauth_client`;
SET FOREIGN_KEY_CHECKS=1;
\ No newline at end of file
--- /dev/null
+-- +--------------------------------------------------------------------+
+-- | Copyright CiviCRM LLC. All rights reserved. |
+-- | |
+-- | This work is published under the GNU AGPLv3 license with some |
+-- | permitted exceptions and without any warranty. For full license |
+-- | and copyright information, see https://civicrm.org/licensing |
+-- +--------------------------------------------------------------------+
+--
+-- Generated from schema.tpl
+-- DO NOT EDIT. Generated by CRM_Core_CodeGen
+--
+
+-- +--------------------------------------------------------------------+
+-- | Copyright CiviCRM LLC. All rights reserved. |
+-- | |
+-- | This work is published under the GNU AGPLv3 license with some |
+-- | permitted exceptions and without any warranty. For full license |
+-- | and copyright information, see https://civicrm.org/licensing |
+-- +--------------------------------------------------------------------+
+--
+-- Generated from drop.tpl
+-- DO NOT EDIT. Generated by CRM_Core_CodeGen
+--
+-- /*******************************************************
+-- *
+-- * Clean up the existing tables
+-- *
+-- *******************************************************/
+
+SET FOREIGN_KEY_CHECKS=0;
+
+DROP TABLE IF EXISTS `civicrm_oauth_contact_token`;
+
+SET FOREIGN_KEY_CHECKS=1;
+-- /*******************************************************
+-- *
+-- * Create new tables
+-- *
+-- *******************************************************/
+
+-- /*******************************************************
+-- *
+-- * civicrm_oauth_contact_token
+-- *
+-- *******************************************************/
+CREATE TABLE `civicrm_oauth_contact_token` (
+
+
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Token ID',
+ `tag` varchar(128) COMMENT 'The tag specifies how this token will be used.',
+ `client_id` int unsigned COMMENT 'Client ID',
+ `contact_id` int unsigned COMMENT 'Contact ID',
+ `grant_type` varchar(31) COMMENT 'Ex: authorization_code',
+ `scopes` text COMMENT 'List of scopes addressed by this token',
+ `token_type` varchar(128) COMMENT 'Ex: Bearer or MAC',
+ `access_token` text COMMENT 'Token to present when accessing resources',
+ `expires` int unsigned DEFAULT 0 COMMENT 'Expiration time for the access_token (seconds since epoch)',
+ `refresh_token` text COMMENT 'Token to present when refreshing the access_token',
+ `resource_owner_name` varchar(128) COMMENT 'Identifier for the resource owner. Structure varies by service.',
+ `resource_owner` text COMMENT 'Cached details describing the resource owner',
+ `error` text COMMENT '?? copied from OAuthSysToken',
+ `raw` text COMMENT 'The token response data, per AccessToken::jsonSerialize',
+ `created_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'When the token was created.',
+ `modified_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When the token was created or modified.'
+,
+ PRIMARY KEY (`id`)
+
+ , INDEX `UI_tag`(
+ tag
+ )
+
+, CONSTRAINT FK_civicrm_oauth_contact_token_client_id FOREIGN KEY (`client_id`) REFERENCES `civicrm_oauth_client`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_oauth_contact_token_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB ;
$providers = array_merge($providers, $this->providers);
}
- public function makeDummyProviderThatGetsAToken(): void {
+ public function makeDummyProviderThatGetsAToken(): array {
$idTokenHeader = ['alg' => 'RS256', 'kid' => '123456789', 'typ' => 'JWT'];
$idTokenPayload = [
'iss' => 'https://dummy',
];
require_once 'tests/fixtures/DummyProvider.php';
+
+ return $this->providers;
}
public function makeDummyProviderClient(): array {
)->execute()->single();
}
- public function testFetchAndStoreSysToken() {
+ public function testSysToken_FetchAndStore() {
$this->makeDummyProviderThatGetsAToken();
$client = $this->makeDummyProviderClient();
$tokenRecord['resource_owner']);
}
+ public function testContactToken_AnonymousUser_LinkExistingContactRecord() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+
+ $this->assertNull(\CRM_Core_Session::singleton()->getLoggedInContactID());
+ $notLoggedInContactID = \Civi\Api4\Contact::get(FALSE)
+ ->setSelect(['id'])
+ ->setLimit(1)
+ ->execute()
+ ->single()['id'];
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // Assuming we set the tag to $notLoggedInContactID in the call to
+ // Civi\Api4\OAuthClient::authorizationCode(), this is the call that
+ // \CRM_OAuth_Page_Return::run would make upon receiving an auth code.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => "linkContact:$notLoggedInContactID",
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertEquals($notLoggedInContactID, $tokenRecord['contact_id']);
+ }
+
+ public function testContactToken_AnonymousUser_SetNullContactId() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+
+ $this->assertNull(\CRM_Core_Session::singleton()->getLoggedInContactID());
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // Assuming we set tag='nullContactId' in the call to Civi\Api4\OAuthClient::authorizationCode(),
+ // this is the call that \CRM_OAuth_Page_Return::run would make upon receiving an auth code.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => 'nullContactId',
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertNull($tokenRecord['contact_id']);
+ }
+
+ public function testContactToken_AnonymousUser_CreateContact() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+
+ $this->assertNull(\CRM_Core_Session::singleton()->getLoggedInContactID());
+ $notLoggedInContactID = \Civi\Api4\Contact::get(FALSE)
+ ->addSelect('id')
+ ->addOrderBy('id', 'DESC')
+ ->setLimit(1)
+ ->execute()
+ ->single()['id'];
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // Assuming we set tag='createContact' when calling Civi\Api4\OAuthClient::authorizationCode(),
+ // this is the call that \CRM_OAuth_Page_Return::run would make upon receiving an auth code.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => "createContact",
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertGreaterThan($notLoggedInContactID, $tokenRecord['contact_id']);
+ $contact = \Civi\Api4\Contact::get(0)
+ ->addWhere('id', '=', $tokenRecord['contact_id'])
+ ->addJoin('Email AS email')
+ ->addSelect('email.email')
+ ->execute()->single();
+ $this->assertEquals('test@baz.biff', $contact['email.email']);
+ }
+
+ public function testContactToken_LoggedInUser_Default() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+ $loggedInContactID = $this->createLoggedInUser();
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // This is what \CRM_OAuth_Page_Return::run would call upon receiving an auth code,
+ // assuming we hadn't set any tag earlier in the process.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => NULL,
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertEquals($loggedInContactID, $tokenRecord['contact_id']);
+ }
+
+ public function testContactToken_LoggedInUser_LinkOtherContact() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+ $loggedInContactID = $this->createLoggedInUser();
+ $notLoggedInContactID = \Civi\Api4\Contact::get(FALSE)
+ ->setSelect(['id'])
+ ->setLimit(1)
+ ->execute()
+ ->single()['id'];
+ $this->assertNotEquals($loggedInContactID, $notLoggedInContactID);
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // Assuming we set tag="linkContact:$notLoggedInContactID" when invoking
+ // Civi\Api4\OAuthClient::authorizationCode(), this is the call that
+ // CRM_OAuth_Page_Return::run would make upon receiving an auth code.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => "linkContact:$notLoggedInContactID",
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertEquals($notLoggedInContactID, $tokenRecord['contact_id']);
+ }
+
+ public function testContactToken_LoggedInUser_CreateContact() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+ $loggedInContactID = $this->createLoggedInUser();
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // Assuming we set tag='createContact' when calling Civi\Api4\OAuthClient::authorizationCode(),
+ // this is the call that \CRM_OAuth_Page_Return::run would make upon receiving an auth code.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => "createContact",
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertGreaterThan($loggedInContactID, $tokenRecord['contact_id']);
+ $contact = \Civi\Api4\Contact::get(0)
+ ->addWhere('id', '=', $tokenRecord['contact_id'])
+ ->addJoin('Email AS email')
+ ->addSelect('email.email')
+ ->execute()->single();
+ $this->assertEquals('test@baz.biff', $contact['email.email']);
+ }
+
+ public function testContactToken_LoggedInUser_SetNullContactId() {
+ $this->makeDummyProviderThatGetsAToken();
+ $client = $this->makeDummyProviderClient();
+ $loggedInContactID = $this->createLoggedInUser();
+
+ /** @var OAuthTokenFacade $tokenService */
+ $tokenService = \Civi::service('oauth2.token');
+
+ // Assuming we set tag="nullContactId" when invoking
+ // Civi\Api4\OAuthClient::authorizationCode(), this is the call that
+ // CRM_OAuth_Page_Return::run would make upon receiving an auth code.
+ $tokenRecord = $tokenService->init(
+ [
+ 'client' => $client,
+ 'scope' => 'foo',
+ 'tag' => "nullContactId",
+ 'storage' => 'OAuthContactToken',
+ 'grant_type' => 'authorization_code',
+ 'cred' => ['code' => 'example-auth-code'],
+ ]
+ );
+ $this->assertTrue(is_numeric($tokenRecord['id']));
+ $this->assertEquals($client['id'], $tokenRecord['client_id']);
+ $this->assertEquals(['foo'], $tokenRecord['scopes']);
+ $this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
+ $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
+ $this->assertNull($tokenRecord['contact_id']);
+ }
+
}
--- /dev/null
+<?php
+
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * Create, read, and destroy contact-specific OAuth tokens
+ *
+ * A logged in user who has permission to "manage my OAuth contact tokens"
+ * can create tokens associated with their own contact id, and can
+ * read/update/delete those tokens if they have at least view access
+ * to their own contact record.
+ *
+ * A user who has permission to "manage all OAuth contact tokens" can create
+ * tokens associated with any contact, and can read/update/delete tokens
+ * associated with any contact for whom they have at least view access.
+ *
+ * Users who have either of the "manage OAuth contact tokens" permissions can
+ * also get basic OAuthClient information, NOT including the client's secret.
+ *
+ * @group headless
+ */
+class api_v4_OAuthContactTokenTest extends \PHPUnit\Framework\TestCase implements
+ HeadlessInterface,
+ HookInterface,
+ TransactionalInterface {
+
+ use Civi\Test\ContactTestTrait;
+ use \Civi\Test\Api3TestTrait;
+
+ private $hookEvents;
+
+ public function setUpHeadless(): \Civi\Test\CiviEnvBuilder {
+ // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
+ // See: https://docs.civicrm.org/dev/en/latest/testing/phpunit/#civitest
+ return \Civi\Test::headless()->install('oauth-client')->apply();
+ }
+
+ public function setUp(): void {
+ parent::setUp();
+ $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_oauth_client'));
+ $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_oauth_contact_token'));
+ }
+
+ public function tearDown(): void {
+ parent::tearDown();
+ }
+
+ private function createClient(): ?array {
+ $createClient = Civi\Api4\OAuthClient::create(FALSE)->setValues(
+ [
+ 'provider' => 'test_example_1',
+ 'guid' => "example-client-guid",
+ 'secret' => "example-secret",
+ ]
+ )->execute();
+ $client = $createClient->first();
+ $this->assertTrue(is_numeric($client['id']));
+ return $client;
+ }
+
+ private function createTestContactIDs(): array {
+ $notLoggedInContactID = Civi\Api4\Contact::get(FALSE)
+ ->setSelect(['id'])
+ ->setLimit(1)
+ ->execute()
+ ->single()['id'];
+ $loggedInContactID = $this->createLoggedInUser();
+ return [$loggedInContactID, $notLoggedInContactID];
+ }
+
+ private function usePerms(array $permissions) {
+ $base = ['access CiviCRM'];
+ CRM_Core_Config::singleton()->userPermissionClass->permissions = array_merge($base, $permissions);
+ if ($cid = CRM_Core_Session::singleton()->getLoggedInContactID()) {
+ CRM_ACL_BAO_Cache::deleteContactCacheEntry($cid);
+ CRM_Contact_BAO_Contact_Permission::cache($cid, CRM_Core_Permission::VIEW, TRUE);
+ }
+ }
+
+ private function getTestTokenCreateValues($client, $contactId, $prefix) {
+ return [
+ 'client_id' => $client['id'],
+ 'contact_id' => $contactId,
+ 'access_token' => "$prefix-user-access-token",
+ 'refresh_token' => "$prefix-user-refresh-token",
+ ];
+ }
+
+ private function makeToken(array $values): ?array {
+ return Civi\Api4\OAuthContactToken::create(FALSE)
+ ->setValues($values)
+ ->execute()
+ ->first();
+ }
+
+ private function createOwnAndStrangerTokens(
+ $client,
+ $loggedInContactID,
+ $notLoggedInContactID
+ ): array {
+ $ownTokenCreationVals = $this->getTestTokenCreateValues(
+ $client, $loggedInContactID, 'own');
+ $strangerTokenCreationVals = $this->getTestTokenCreateValues(
+ $client, $notLoggedInContactID, 'other');
+ return [
+ $this->makeToken($ownTokenCreationVals),
+ $this->makeToken($strangerTokenCreationVals),
+ ];
+ }
+
+ public function hook_civicrm_post($op, $objectName, $objectId, &$objectRef) {
+ if ($objectName === 'OAuthContactToken') {
+ $this->hookEvents['post'][] = func_get_args();
+ }
+ }
+
+ public function testGetClientDetails() {
+ $createClient = $this->createClient();
+
+ $this->usePerms(['manage my OAuth contact tokens']);
+ $getClient = Civi\Api4\OAuthClient::get()
+ ->addWhere('id', '=', $createClient['id'])
+ ->execute()
+ ->single();
+ $this->assertEquals($createClient['guid'], $getClient['guid']);
+ $this->assertEquals($createClient['provider'], $getClient['provider']);
+ $this->assertArrayNotHasKey('secret', $getClient);
+ }
+
+ public function testCreate() {
+ $client = $this->createClient();
+ [$loggedInContactID, $notLoggedInContactID] = $this->createTestContactIDs();
+ $ownTokenCreateVals = $this->getTestTokenCreateValues(
+ $client, $loggedInContactID, 'own');
+ $strangerTokenCreateVals = $this->getTestTokenCreateValues(
+ $client, $notLoggedInContactID, 'other');
+
+ $this->usePerms(['manage all OAuth contact tokens']);
+ $createOtherContactToken = Civi\Api4\OAuthContactToken::create()
+ ->setValues($strangerTokenCreateVals)
+ ->execute();
+ $token = $createOtherContactToken->first();
+ $tokenIDOfDifferentContact = $token['id'];
+ $this->assertTrue(is_numeric($tokenIDOfDifferentContact));
+ $this->assertEquals($client['id'], $token['client_id']);
+ $this->assertEquals($notLoggedInContactID, $token['contact_id']);
+ $this->assertEquals($strangerTokenCreateVals['access_token'], $token['access_token']);
+ $this->assertEquals($strangerTokenCreateVals['refresh_token'], $token['refresh_token']);
+
+ $this->usePerms(['manage my OAuth contact tokens']);
+ $createOwnToken = Civi\Api4\OAuthContactToken::create()
+ ->setValues($ownTokenCreateVals)
+ ->execute();
+ $token = $createOwnToken->first();
+ $tokenIDOfLoggedInContact = $token['id'];
+ $this->assertTrue(is_numeric($tokenIDOfLoggedInContact));
+ $this->assertEquals($client['id'], $token['client_id']);
+ $this->assertEquals($loggedInContactID, $token['contact_id']);
+ $this->assertEquals($ownTokenCreateVals['access_token'], $token['access_token']);
+ $this->assertEquals($ownTokenCreateVals['refresh_token'], $token['refresh_token']);
+
+ $this->usePerms(['manage my OAuth contact tokens']);
+ try {
+ Civi\Api4\OAuthContactToken::create()
+ ->setValues($strangerTokenCreateVals)
+ ->execute();
+ $this->fail('Expected \Civi\API\Exception\UnauthorizedException but none was thrown');
+ }
+ catch (\Civi\API\Exception\UnauthorizedException $e) {
+ // exception successfully thrown
+ }
+ }
+
+ public function testRead() {
+ $client = $this->createClient();
+ [$loggedInContactID, $notLoggedInContactID] = $this->createTestContactIDs();
+ $ownTokenCreationVals = $this->getTestTokenCreateValues(
+ $client, $loggedInContactID, 'own');
+ $this->createOwnAndStrangerTokens($client, $loggedInContactID, $notLoggedInContactID);
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $getTokensWithFullAccess = Civi\Api4\OAuthContactToken::get()->execute();
+ $this->assertCount(2, $getTokensWithFullAccess);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
+ $getTokensWithOwnAccess = Civi\Api4\OAuthContactToken::get()->execute();
+ $this->assertCount(1, $getTokensWithOwnAccess);
+ $token = $getTokensWithOwnAccess->first();
+ $this->assertEquals($client['id'], $token['client_id']);
+ $this->assertEquals($loggedInContactID, $token['contact_id']);
+ $this->assertEquals($ownTokenCreationVals['access_token'], $token['access_token']);
+ $this->assertEquals($ownTokenCreationVals['refresh_token'], $token['refresh_token']);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
+ $getTokensForWrongContact = Civi\Api4\OAuthContactToken::get()
+ ->addWhere('contact_id', '=', $notLoggedInContactID)
+ ->execute();
+ $this->assertCount(0, $getTokensForWrongContact);
+
+ $this->usePerms(['manage all OAuth contact tokens']);
+ $getTokensWithNoContactAccess = Civi\Api4\OAuthContactToken::get()
+ ->execute();
+ $this->assertCount(0, $getTokensWithNoContactAccess);
+ }
+
+ public function testUpdate() {
+ $client = $this->createClient();
+ [$loggedInContactID, $notLoggedInContactID] = $this->createTestContactIDs();
+ [
+ $ownContactToken,
+ $strangerContactToken,
+ ] = $this->createOwnAndStrangerTokens(
+ $client,
+ $loggedInContactID,
+ $notLoggedInContactID
+ );
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $updateTokensWithFullAccess = Civi\Api4\OAuthContactToken::update()
+ ->addWhere('contact_id', '=', $notLoggedInContactID)
+ ->setValues(['access_token' => 'stranger-token-revised'])
+ ->execute();
+ $this->assertCount(1, $updateTokensWithFullAccess);
+ $token = $updateTokensWithFullAccess->first();
+ $this->assertEquals($strangerContactToken['id'], $token['id']);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
+ $updateTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::update()
+ ->addWhere('client.guid', '=', $client['guid'])
+ ->setValues(['access_token' => 'own-token-revised'])
+ ->execute();
+ $this->assertCount(1, $updateTokensWithLimitedAccess);
+ $token = $updateTokensWithLimitedAccess->first();
+ $this->assertEquals($ownContactToken['id'], $token['id']);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
+ $getUpdatedTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::get()
+ ->execute();
+ $this->assertCount(1, $getUpdatedTokensWithLimitedAccess);
+ $token = $getUpdatedTokensWithLimitedAccess->first();
+ $this->assertEquals($loggedInContactID, $token['contact_id']);
+ $this->assertEquals("own-token-revised", $token['access_token']);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
+ try {
+ Civi\Api4\OAuthContactToken::update()
+ ->addWhere('contact_id', '=', $notLoggedInContactID)
+ ->setValues(['access_token' => "stranger-token-revised"])
+ ->execute();
+ $this->fail('Expected \Civi\API\Exception\UnauthorizedException but none was thrown');
+ }
+ catch (\Civi\API\Exception\UnauthorizedException $e) {
+ // exception successfully thrown
+ }
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
+ $updateTokensForWrongContact = Civi\Api4\OAuthContactToken::update()
+ ->addWhere('contact.id', '=', $notLoggedInContactID)
+ // ^ sneaky way to update a different contact?
+ ->setValues(['access_token' => "stranger-token-revised"])
+ ->execute();
+ $this->assertCount(0, $updateTokensForWrongContact);
+ }
+
+ public function testDelete() {
+ $client = $this->createClient();
+ [$loggedInContactID, $notLoggedInContactID] = $this->createTestContactIDs();
+ $this->createOwnAndStrangerTokens($client, $loggedInContactID, $notLoggedInContactID);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
+ $deleteTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::delete()
+ ->setWhere([['client.guid', '=', $client['guid']]])
+ ->execute();
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
+ $getTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::get()->execute();
+ $this->assertCount(0, $getTokensWithLimitedAccess);
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $getTokensWithFullAccess = Civi\Api4\OAuthContactToken::get()->execute();
+ $this->assertCount(1, $getTokensWithFullAccess);
+
+ $this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
+ $this->expectException(\Civi\API\Exception\UnauthorizedException::class);
+ Civi\Api4\OAuthContactToken::delete()
+ ->addWhere('contact_id', '=', $notLoggedInContactID)
+ ->execute();
+ }
+
+ public function testGetByScope() {
+ $client = $this->createClient();
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $tokenCreationVals = [
+ 'client_id' => $client['id'],
+ 'contact_id' => 1,
+ 'access_token' => "loggedin-user-access-token",
+ 'refresh_token' => "loggedin-user-refresh-token",
+ 'scopes' => ['foo', 'bar'],
+ ];
+ $createToken = Civi\Api4\OAuthContactToken::create()
+ ->setValues($tokenCreationVals)
+ ->execute();
+ $token = $createToken->first();
+ $this->assertTrue(is_numeric($token['id']));
+ $this->assertEquals(['foo', 'bar'], $token['scopes']);
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $getTokens = Civi\Api4\OAuthContactToken::get()
+ ->addWhere('client.provider', '=', $client['provider'])
+ ->addWhere('scopes', 'CONTAINS', 'foo')
+ ->execute();
+ $this->assertCount(1, $getTokens);
+ $this->assertEquals($createToken->first()['id'], $getTokens->first()['id']);
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $getTokens = Civi\Api4\OAuthContactToken::get()
+ ->addWhere('client.provider', '=', $client['provider'])
+ ->addWhere('scopes', 'CONTAINS', 'nada')
+ ->execute();
+ $this->assertCount(0, $getTokens);
+
+ $this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
+ $getTokens = Civi\Api4\OAuthContactToken::get()
+ ->addWhere('client.provider', '=', 'some-other-provider')
+ ->addWhere('scopes', 'CONTAINS', 'foo')
+ ->execute();
+ $this->assertCount(0, $getTokens);
+ }
+
+ public function testPostHook() {
+ $client = $this->createClient();
+ [$loggedInContactID, $notLoggedInContactID] = $this->createTestContactIDs();
+ $strangerTokenCreationVals = $this->getTestTokenCreateValues(
+ $client, $loggedInContactID, 'other');
+
+ $this->usePerms(['manage all OAuth contact tokens']);
+ $this->makeToken($strangerTokenCreationVals);
+
+ self::assertCount(1, $this->hookEvents['post']);
+ }
+
+}
<path>civicrm/oauth-client/return</path>
<page_callback>CRM_OAuth_Page_Return</page_callback>
<title>Return</title>
- <access_arguments>access CiviCRM</access_arguments>
+ <access_arguments>create OAuth tokens via auth code flow;manage OAuth client</access_arguments>
</item>
</menu>
<comment>Client Secret</comment>
<add>5.32</add>
<!-- Would prefer this be write-only for std admin, and read-write with special/elevated perm -->
- <!--<permission>-->
- <!--<or>manage OAuth client secrets</or>-->
- <!--</permission>-->
+ <permission>
+ manage OAuth client
+ </permission>
</field>
<field>
<!-- Ex: urlAuthorize, urlAccessToken, urlResourceOwnerDetails, scopes -->
<serialize>JSON</serialize>
<add>5.32</add>
+ <permission>
+ manage OAuth client
+ </permission>
</field>
<!-- Lifecycle -->
--- /dev/null
+<?php
+// This file declares a new entity type. For more details, see "hook_civicrm_entityTypes" at:
+// https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes
+return [
+ [
+ 'name' => 'OAuthContactToken',
+ 'class' => 'CRM_OAuth_DAO_OAuthContactToken',
+ 'table' => 'civicrm_oauth_contact_token',
+ ],
+];
--- /dev/null
+<table>
+ <base>CRM/OAuth</base>
+ <class>OAuthContactToken</class>
+ <name>civicrm_oauth_contact_token</name>
+ <add>5.35</add>
+ <field>
+ <name>id</name>
+ <title>Token ID</title>
+ <type>int unsigned</type>
+ <required>true</required>
+ <comment>Token ID</comment>
+ <add>5.35</add>
+ </field>
+ <primaryKey>
+ <name>id</name>
+ <autoincrement>true</autoincrement>
+ </primaryKey>
+
+ <!-- Details based on how the token was requested -->
+
+ <field>
+ <name>tag</name>
+ <title>Tag</title>
+ <type>varchar</type>
+ <length>128</length>
+ <comment>The tag specifies how this token will be used.</comment>
+ <add>5.35</add>
+ </field>
+ <index>
+ <name>UI_tag</name>
+ <fieldName>tag</fieldName>
+ <add>5.35</add>
+ </index>
+
+ <field>
+ <name>client_id</name>
+ <title>Client ID</title>
+ <type>int unsigned</type>
+ <comment>Client ID</comment>
+ <add>5.35</add>
+ </field>
+ <foreignKey>
+ <name>client_id</name>
+ <table>civicrm_oauth_client</table>
+ <key>id</key>
+ <add>5.35</add>
+ <onDelete>CASCADE</onDelete>
+ </foreignKey>
+
+ <field>
+ <name>contact_id</name>
+ <title>Contact ID</title>
+ <type>int unsigned</type>
+ <comment>Contact ID</comment>
+ <add>5.35</add>
+ </field>
+ <foreignKey>
+ <name>contact_id</name>
+ <table>civicrm_contact</table>
+ <key>id</key>
+ <add>5.35</add>
+ <onDelete>CASCADE</onDelete>
+ </foreignKey>
+
+ <field>
+ <name>grant_type</name>
+ <title>Grant type</title>
+ <type>varchar</type>
+ <length>31</length>
+ <!-- FIXME: Pseudoconstant -->
+ <comment>Ex: authorization_code</comment>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>scopes</name>
+ <type>text</type>
+ <comment>List of scopes addressed by this token</comment>
+ <serialize>SEPARATOR_BOOKEND</serialize>
+ <add>5.35</add>
+ </field>
+
+ <!-- Data provided by the authentication server -->
+
+ <field>
+ <name>token_type</name>
+ <title>Token Type</title>
+ <type>varchar</type>
+ <length>128</length>
+ <comment>Ex: Bearer or MAC</comment>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>access_token</name>
+ <title>Access Token</title>
+ <type>text</type>
+ <!-- text or varchar? In theory, if the auth svc uses JWT, tokens can get long -->
+ <comment>Token to present when accessing resources</comment>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>expires</name>
+ <type>int unsigned</type>
+ <title>Expiration time</title>
+ <default>0</default>
+ <comment>Expiration time for the access_token (seconds since epoch)</comment>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>refresh_token</name>
+ <title>Refresh Token</title>
+ <type>text</type>
+ <!-- text or varchar? In theory, if the auth svc uses JWT, tokens can get long -->
+ <comment>Token to present when refreshing the access_token</comment>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>resource_owner_name</name>
+ <title>Resource Owner Name</title>
+ <type>varchar</type>
+ <length>128</length>
+ <comment>Identifier for the resource owner. Structure varies by service.</comment>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>resource_owner</name>
+ <title>Resource Owner</title>
+ <type>text</type>
+ <comment>Cached details describing the resource owner</comment>
+ <serialize>JSON</serialize>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>error</name>
+ <type>text</type>
+ <comment>?? copied from OAuthSysToken</comment>
+ <serialize>JSON</serialize>
+ <add>5.35</add>
+ </field>
+
+ <field>
+ <name>raw</name>
+ <title>Raw token</title>
+ <type>text</type>
+ <serialize>JSON</serialize>
+ <comment>The token response data, per AccessToken::jsonSerialize</comment>
+ <add>5.35</add>
+ </field>
+
+ <!-- Lifecycle -->
+
+ <field>
+ <name>created_date</name>
+ <type>timestamp</type>
+ <comment>When the token was created.</comment>
+ <required>false</required>
+ <default>CURRENT_TIMESTAMP</default>
+ <add>5.35</add>
+ </field>
+ <field>
+ <name>modified_date</name>
+ <type>timestamp</type>
+ <comment>When the token was created or modified.</comment>
+ <required>false</required>
+ <default>CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP</default>
+ <add>5.35</add>
+ </field>
+
+</table>
{
"name": "civicrm",
"version": "4.6.0",
- "lockfileVersion": 1,
+ "lockfileVersion": 2,
"requires": true,
+ "packages": {
+ "": {
+ "version": "4.6.0",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "devDependencies": {
+ "bower": "^1.8.8",
+ "civicrm-cv": "^0.1.2",
+ "jasmine-core": "~3.3.0",
+ "karma": "^5.0.9",
+ "karma-jasmine": "~2.0.1",
+ "karma-ng-html2js-preprocessor": "^1.0.0",
+ "karma-phantomjs-launcher": "^1.0.4"
+ }
+ },
+ "node_modules/@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
+ },
+ "node_modules/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dev": true,
+ "dependencies": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.44.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
+ "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
+ "dev": true
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "2.1.27",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
+ "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.44.0"
+ }
+ },
+ "node_modules/after": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
+ "dev": true
+ },
+ "node_modules/ajv": {
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz",
+ "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "dependencies": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "node_modules/arraybuffer.slice": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
+ "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
+ "dev": true
+ },
+ "node_modules/asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+ "dev": true
+ },
+ "node_modules/aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+ "dev": true
+ },
+ "node_modules/aws4": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
+ "dev": true
+ },
+ "node_modules/backo2": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+ "dev": true
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "node_modules/base64-arraybuffer": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+ "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
+ "dev": true
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "dev": true
+ },
+ "node_modules/bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "dev": true,
+ "dependencies": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+ "dev": true
+ },
+ "node_modules/blob": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
+ "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
+ "dev": true
+ },
+ "node_modules/body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "dev": true,
+ "dependencies": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ }
+ },
+ "node_modules/body-parser/node_modules/qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "dev": true
+ },
+ "node_modules/bower": {
+ "version": "1.8.8",
+ "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz",
+ "integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "dev": true
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "node_modules/caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+ "dev": true
+ },
+ "node_modules/child-process-promise": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz",
+ "integrity": "sha1-RzChHvYQ+tRQuPIjx50x172tgHQ=",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^4.0.2",
+ "node-version": "^1.0.0",
+ "promise-polyfill": "^6.0.1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
+ "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.1.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.4.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.1.2"
+ }
+ },
+ "node_modules/civicrm-cv": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/civicrm-cv/-/civicrm-cv-0.1.2.tgz",
+ "integrity": "sha1-prn+pVahci1Km3ChHGSHVXGmNKg=",
+ "dev": true,
+ "dependencies": {
+ "child-process-promise": "^2.1.3"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
+ "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
+ "dev": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "node_modules/component-bind": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+ "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
+ "dev": true
+ },
+ "node_modules/component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "node_modules/component-inherit": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/concat-stream": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "node_modules/connect": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+ "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.2",
+ "parseurl": "~1.3.3",
+ "utils-merge": "1.0.1"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "dev": true
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
+ "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^4.0.1",
+ "which": "^1.2.9"
+ }
+ },
+ "node_modules/custom-event": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
+ "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=",
+ "dev": true
+ },
+ "node_modules/dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dev": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "node_modules/date-format": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz",
+ "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+ "dev": true
+ },
+ "node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
+ },
+ "node_modules/di": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
+ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
+ "dev": true
+ },
+ "node_modules/dom-serialize": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
+ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
+ "dev": true,
+ "dependencies": {
+ "custom-event": "~1.0.0",
+ "ent": "~2.2.0",
+ "extend": "^3.0.0",
+ "void-elements": "^2.0.0"
+ }
+ },
+ "node_modules/ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "dev": true,
+ "dependencies": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+ "dev": true
+ },
+ "node_modules/engine.io-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
+ "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
+ "dev": true,
+ "dependencies": {
+ "after": "0.8.2",
+ "arraybuffer.slice": "~0.0.7",
+ "base64-arraybuffer": "0.1.5",
+ "blob": "0.0.5",
+ "has-binary2": "~1.0.2"
+ }
+ },
+ "node_modules/ent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
+ "dev": true
+ },
+ "node_modules/es6-promise": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
+ "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
+ "dev": true
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "dev": true
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
+ "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
+ "dev": true
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "node_modules/extract-zip": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz",
+ "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=",
+ "dev": true,
+ "dependencies": {
+ "concat-stream": "1.6.0",
+ "debug": "2.6.9",
+ "mkdirp": "0.5.0",
+ "yauzl": "2.4.1"
+ }
+ },
+ "node_modules/extract-zip/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/extract-zip/node_modules/minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "node_modules/extract-zip/node_modules/mkdirp": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz",
+ "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=",
+ "dev": true,
+ "dependencies": {
+ "minimist": "0.0.8"
+ }
+ },
+ "node_modules/extract-zip/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+ "dev": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
+ "dev": true,
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
+ "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.0.0"
+ }
+ },
+ "node_modules/follow-redirects/node_modules/debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/follow-redirects/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "dev": true
+ },
+ "node_modules/form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "dev": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
+ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^2.1.0",
+ "klaw": "^1.0.0"
+ }
+ },
+ "node_modules/fs-extra/node_modules/graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "node_modules/getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dev": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+ "dev": true
+ },
+ "node_modules/har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+ "dev": true
+ },
+ "node_modules/har-validator": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.5.5",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "node_modules/has-binary2": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
+ "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
+ "dev": true,
+ "dependencies": {
+ "isarray": "2.0.1"
+ }
+ },
+ "node_modules/has-binary2/node_modules/isarray": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+ "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+ "dev": true
+ },
+ "node_modules/has-cors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+ "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
+ "dev": true
+ },
+ "node_modules/hasha": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
+ "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
+ "dev": true,
+ "dependencies": {
+ "is-stream": "^1.0.1",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "dev": true,
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ }
+ },
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "dev": true,
+ "dependencies": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "node_modules/http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "dev": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "node_modules/indexof": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+ "dev": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "node_modules/is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "dev": true
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "node_modules/isbinaryfile": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz",
+ "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "node_modules/isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+ "dev": true
+ },
+ "node_modules/jasmine-core": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.3.0.tgz",
+ "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==",
+ "dev": true
+ },
+ "node_modules/jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "dev": true
+ },
+ "node_modules/json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+ "dev": true
+ },
+ "node_modules/jsonfile": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.6"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonfile/node_modules/graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "dev": true,
+ "dependencies": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "node_modules/karma": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/karma/-/karma-5.0.9.tgz",
+ "integrity": "sha512-dUA5z7Lo7G4FRSe1ZAXqOINEEWxmCjDBbfRBmU/wYlSMwxUQJP/tEEP90yJt3Uqo03s9rCgVnxtlfq+uDhxSPg==",
+ "dev": true,
+ "dependencies": {
+ "body-parser": "^1.19.0",
+ "braces": "^3.0.2",
+ "chokidar": "^3.0.0",
+ "colors": "^1.4.0",
+ "connect": "^3.7.0",
+ "di": "^0.0.1",
+ "dom-serialize": "^2.2.1",
+ "flatted": "^2.0.2",
+ "glob": "^7.1.6",
+ "graceful-fs": "^4.2.4",
+ "http-proxy": "^1.18.1",
+ "isbinaryfile": "^4.0.6",
+ "lodash": "^4.17.15",
+ "log4js": "^6.2.1",
+ "mime": "^2.4.5",
+ "minimatch": "^3.0.4",
+ "qjobs": "^1.2.0",
+ "range-parser": "^1.2.1",
+ "rimraf": "^3.0.2",
+ "socket.io": "^2.3.0",
+ "source-map": "^0.6.1",
+ "tmp": "0.2.1",
+ "ua-parser-js": "0.7.21",
+ "yargs": "^15.3.1"
+ }
+ },
+ "node_modules/karma-jasmine": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz",
+ "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==",
+ "dev": true,
+ "dependencies": {
+ "jasmine-core": "^3.3"
+ }
+ },
+ "node_modules/karma-ng-html2js-preprocessor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/karma-ng-html2js-preprocessor/-/karma-ng-html2js-preprocessor-1.0.0.tgz",
+ "integrity": "sha1-ENjIz6pBNvHIp22RpMvO7evsSjE=",
+ "dev": true
+ },
+ "node_modules/karma-phantomjs-launcher": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz",
+ "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.0.1",
+ "phantomjs-prebuilt": "^2.1.7"
+ }
+ },
+ "node_modules/kew": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
+ "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=",
+ "dev": true
+ },
+ "node_modules/klaw": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
+ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.9"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.9"
+ }
+ },
+ "node_modules/klaw/node_modules/graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.19",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+ "dev": true
+ },
+ "node_modules/log4js": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz",
+ "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==",
+ "dev": true,
+ "dependencies": {
+ "date-format": "^3.0.0",
+ "debug": "^4.1.1",
+ "flatted": "^2.0.1",
+ "rfdc": "^1.1.4",
+ "streamroller": "^2.2.4"
+ }
+ },
+ "node_modules/log4js/node_modules/debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/log4js/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz",
+ "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==",
+ "dev": true,
+ "dependencies": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "dev": true
+ },
+ "node_modules/mime": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
+ "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==",
+ "dev": true
+ },
+ "node_modules/mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "dev": true
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "~1.33.0"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "dev": true
+ },
+ "node_modules/node-version": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.1.3.tgz",
+ "integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "node_modules/oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+ "dev": true
+ },
+ "node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dev": true,
+ "dependencies": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
+ "dev": true
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+ "dev": true
+ },
+ "node_modules/phantomjs-prebuilt": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
+ "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
+ "dev": true,
+ "dependencies": {
+ "es6-promise": "^4.0.3",
+ "extract-zip": "^1.6.5",
+ "fs-extra": "^1.0.0",
+ "hasha": "^2.2.0",
+ "kew": "^0.7.0",
+ "progress": "^1.1.8",
+ "request": "^2.81.0",
+ "request-progress": "^2.0.1",
+ "which": "^1.2.10"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "dev": true
+ },
+ "node_modules/pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "node_modules/pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "dependencies": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "dev": true
+ },
+ "node_modules/progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+ "dev": true
+ },
+ "node_modules/promise-polyfill": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz",
+ "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=",
+ "dev": true
+ },
+ "node_modules/pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+ "dev": true
+ },
+ "node_modules/psl": {
+ "version": "1.1.29",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
+ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
+ "dev": true
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "node_modules/qjobs": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
+ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
+ "dev": true
+ },
+ "node_modules/qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+ "dev": true
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true
+ },
+ "node_modules/raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "dev": true,
+ "dependencies": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+ "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "node_modules/request": {
+ "version": "2.88.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+ "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+ "dev": true,
+ "dependencies": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.0",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.4.3",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "node_modules/request-progress": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
+ "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
+ "dev": true,
+ "dependencies": {
+ "throttleit": "^1.0.0"
+ }
+ },
+ "node_modules/request/node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "node_modules/request/node_modules/mime-db": {
+ "version": "1.37.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
+ "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==",
+ "dev": true
+ },
+ "node_modules/request/node_modules/mime-types": {
+ "version": "2.1.21",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
+ "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "~1.37.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+ "dev": true
+ },
+ "node_modules/rfdc": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+ "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
+ "dev": true
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+ "dev": true
+ },
+ "node_modules/socket.io": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
+ "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
+ "dev": true,
+ "dependencies": {
+ "debug": "~4.1.0",
+ "engine.io": "~3.5.0",
+ "has-binary2": "~1.0.2",
+ "socket.io-adapter": "~1.1.0",
+ "socket.io-client": "2.4.0",
+ "socket.io-parser": "~3.4.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
+ "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==",
+ "dev": true
+ },
+ "node_modules/socket.io-parser": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
+ "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==",
+ "dev": true,
+ "dependencies": {
+ "component-emitter": "1.2.1",
+ "debug": "~4.1.0",
+ "isarray": "2.0.1"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/isarray": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+ "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+ "dev": true
+ },
+ "node_modules/socket.io-parser/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/component-emitter": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/socket.io/node_modules/engine.io": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
+ "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
+ "dev": true,
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.4.1",
+ "debug": "~4.1.0",
+ "engine.io-parser": "~2.2.0",
+ "ws": "~7.4.2"
+ }
+ },
+ "node_modules/socket.io/node_modules/engine.io-client": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
+ "integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
+ "dev": true,
+ "dependencies": {
+ "component-emitter": "~1.3.0",
+ "component-inherit": "0.0.3",
+ "debug": "~3.1.0",
+ "engine.io-parser": "~2.2.0",
+ "has-cors": "1.1.0",
+ "indexof": "0.0.1",
+ "parseqs": "0.0.6",
+ "parseuri": "0.0.6",
+ "ws": "~7.4.2",
+ "xmlhttprequest-ssl": "~1.5.4",
+ "yeast": "0.1.2"
+ }
+ },
+ "node_modules/socket.io/node_modules/engine.io-client/node_modules/debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/socket.io/node_modules/engine.io-client/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/isarray": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+ "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/parseqs": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
+ "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/parseuri": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
+ "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/socket.io-client": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
+ "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
+ "dev": true,
+ "dependencies": {
+ "backo2": "1.0.2",
+ "component-bind": "1.0.0",
+ "component-emitter": "~1.3.0",
+ "debug": "~3.1.0",
+ "engine.io-client": "~3.5.0",
+ "has-binary2": "~1.0.2",
+ "indexof": "0.0.1",
+ "parseqs": "0.0.6",
+ "parseuri": "0.0.6",
+ "socket.io-parser": "~3.3.0",
+ "to-array": "0.1.4"
+ }
+ },
+ "node_modules/socket.io/node_modules/socket.io-client/node_modules/debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/socket.io/node_modules/socket.io-client/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/socket.io/node_modules/socket.io-client/node_modules/socket.io-parser": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
+ "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
+ "dev": true,
+ "dependencies": {
+ "component-emitter": "~1.3.0",
+ "debug": "~3.1.0",
+ "isarray": "2.0.1"
+ }
+ },
+ "node_modules/socket.io/node_modules/ws": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
+ "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
+ "dev": true
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "node_modules/sshpk": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
+ "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
+ "dev": true,
+ "dependencies": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "dev": true
+ },
+ "node_modules/streamroller": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz",
+ "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==",
+ "dev": true,
+ "dependencies": {
+ "date-format": "^2.1.0",
+ "debug": "^4.1.1",
+ "fs-extra": "^8.1.0"
+ }
+ },
+ "node_modules/streamroller/node_modules/date-format": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
+ "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
+ "dev": true
+ },
+ "node_modules/streamroller/node_modules/debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/streamroller/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "node_modules/streamroller/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/streamroller/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "node_modules/throttleit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
+ "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=",
+ "dev": true
+ },
+ "node_modules/tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "dependencies": {
+ "rimraf": "^3.0.0"
+ }
+ },
+ "node_modules/to-array": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "dev": true
+ },
+ "node_modules/tough-cookie": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+ "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+ "dev": true,
+ "dependencies": {
+ "psl": "^1.1.24",
+ "punycode": "^1.4.1"
+ }
+ },
+ "node_modules/tough-cookie/node_modules/punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "dev": true
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dev": true,
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.44.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
+ "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
+ "dev": true
+ },
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "2.1.27",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
+ "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.44.0"
+ }
+ },
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "node_modules/ua-parser-js": {
+ "version": "0.7.21",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
+ "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==",
+ "dev": true
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
+ },
+ "node_modules/uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+ "dev": true
+ },
+ "node_modules/uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
+ "dev": true
+ },
+ "node_modules/verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "dev": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "node_modules/void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
+ "dev": true
+ },
+ "node_modules/which": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
+ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
+ "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
+ "dev": true
+ },
+ "node_modules/y18n": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
+ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
+ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.1"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
+ "dev": true,
+ "dependencies": {
+ "fd-slicer": "~1.0.1"
+ }
+ },
+ "node_modules/yeast": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
+ "dev": true
+ }
+ },
"dependencies": {
"@types/color-name": {
"version": "1.1.1",
}
}
},
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"strip-ansi": "^6.0.0"
}
},
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",