From d55f710f84d78749fdc0f6d1a0f7eaff274dafa5 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Tue, 27 Apr 2021 00:08:04 -0700 Subject: [PATCH] add support for OAuthContactTokens, with tests, upgrade step, new permissions 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. --- .../CRM/OAuth/BAO/OAuthContactToken.php | 5 + .../CRM/OAuth/ContactFromToken.php | 80 + .../CRM/OAuth/DAO/OAuthClient.php | 9 +- .../CRM/OAuth/DAO/OAuthContactToken.php | 483 ++++ .../CRM/OAuth/DAO/OAuthSysToken.php | 3 +- ext/oauth-client/CRM/OAuth/Upgrader.php | 12 + .../Api4/Action/OAuthContactToken/Create.php | 62 + .../Api4/Action/OAuthContactToken/Delete.php | 10 + .../Api4/Action/OAuthContactToken/Get.php | 29 + .../OnlyModifyOwnTokensTrait.php | 31 + .../Api4/Action/OAuthContactToken/Update.php | 10 + ext/oauth-client/Civi/Api4/OAuthClient.php | 19 +- .../Civi/Api4/OAuthContactToken.php | 47 + .../Civi/OAuth/OAuthTokenFacade.php | 31 +- ext/oauth-client/oauth_client.civix.php | 35 +- ext/oauth-client/oauth_client.php | 12 + ext/oauth-client/sql/auto_install.sql | 59 +- ext/oauth-client/sql/auto_uninstall.sql | 3 +- ext/oauth-client/sql/upgrade_0001.sql | 73 + .../phpunit/Civi/OAuth/AuthCodeFlowTest.php | 237 +- .../phpunit/api/v4/OAuthContactTokenTest.php | 345 +++ ext/oauth-client/xml/Menu/oauth_client.xml | 2 +- .../xml/schema/CRM/OAuth/OAuthClient.xml | 9 +- .../OAuth/OAuthContactToken.entityType.php | 10 + .../schema/CRM/OAuth/OAuthContactToken.xml | 175 ++ package-lock.json | 2094 ++++++++++++++++- 26 files changed, 3812 insertions(+), 73 deletions(-) create mode 100644 ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php create mode 100644 ext/oauth-client/CRM/OAuth/ContactFromToken.php create mode 100644 ext/oauth-client/CRM/OAuth/DAO/OAuthContactToken.php create mode 100644 ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php create mode 100644 ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php create mode 100644 ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Get.php create mode 100644 ext/oauth-client/Civi/Api4/Action/OAuthContactToken/OnlyModifyOwnTokensTrait.php create mode 100644 ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php create mode 100644 ext/oauth-client/Civi/Api4/OAuthContactToken.php create mode 100644 ext/oauth-client/sql/upgrade_0001.sql create mode 100644 ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php create mode 100644 ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.entityType.php create mode 100644 ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.xml diff --git a/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php b/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php new file mode 100644 index 0000000000..920707b34f --- /dev/null +++ b/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php @@ -0,0 +1,5 @@ +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; + } + +} diff --git a/ext/oauth-client/CRM/OAuth/DAO/OAuthClient.php b/ext/oauth-client/CRM/OAuth/DAO/OAuthClient.php index 0307da99b4..92d2f4378e 100644 --- a/ext/oauth-client/CRM/OAuth/DAO/OAuthClient.php +++ b/ext/oauth-client/CRM/OAuth/DAO/OAuthClient.php @@ -6,7 +6,7 @@ * * 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; @@ -123,6 +123,7 @@ class CRM_OAuth_DAO_OAuthClient extends CRM_Core_DAO { 'entity' => 'OAuthClient', 'bao' => 'CRM_OAuth_DAO_OAuthClient', 'localizable' => 0, + 'readonly' => TRUE, 'add' => '5.32', ], 'provider' => [ @@ -164,6 +165,9 @@ class CRM_OAuth_DAO_OAuthClient extends CRM_Core_DAO { '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', @@ -176,6 +180,9 @@ class CRM_OAuth_DAO_OAuthClient extends CRM_Core_DAO { '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', diff --git a/ext/oauth-client/CRM/OAuth/DAO/OAuthContactToken.php b/ext/oauth-client/CRM/OAuth/DAO/OAuthContactToken.php new file mode 100644 index 0000000000..691a37b578 --- /dev/null +++ b/ext/oauth-client/CRM/OAuth/DAO/OAuthContactToken.php @@ -0,0 +1,483 @@ +__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; + } + +} diff --git a/ext/oauth-client/CRM/OAuth/DAO/OAuthSysToken.php b/ext/oauth-client/CRM/OAuth/DAO/OAuthSysToken.php index c39e59eec1..6e924542e9 100644 --- a/ext/oauth-client/CRM/OAuth/DAO/OAuthSysToken.php +++ b/ext/oauth-client/CRM/OAuth/DAO/OAuthSysToken.php @@ -6,7 +6,7 @@ * * 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; @@ -188,6 +188,7 @@ class CRM_OAuth_DAO_OAuthSysToken extends CRM_Core_DAO { 'entity' => 'OAuthSysToken', 'bao' => 'CRM_OAuth_DAO_OAuthSysToken', 'localizable' => 0, + 'readonly' => TRUE, 'add' => '5.32', ], 'tag' => [ diff --git a/ext/oauth-client/CRM/OAuth/Upgrader.php b/ext/oauth-client/CRM/OAuth/Upgrader.php index acc937d6d6..ce471b7a8f 100644 --- a/ext/oauth-client/CRM/OAuth/Upgrader.php +++ b/ext/oauth-client/CRM/OAuth/Upgrader.php @@ -27,6 +27,18 @@ class CRM_OAuth_Upgrader extends CRM_OAuth_Upgrader_Base { ]); } + /** + * 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. * diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php new file mode 100644 index 0000000000..78055ededf --- /dev/null +++ b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php @@ -0,0 +1,62 @@ +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])); + } + +} diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php new file mode 100644 index 0000000000..d0d02bc1ae --- /dev/null +++ b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php @@ -0,0 +1,10 @@ +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')); + } + +} diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/OnlyModifyOwnTokensTrait.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/OnlyModifyOwnTokensTrait.php new file mode 100644 index 0000000000..77817ab4aa --- /dev/null +++ b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/OnlyModifyOwnTokensTrait.php @@ -0,0 +1,31 @@ +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; + } + +} diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php new file mode 100644 index 0000000000..954566a652 --- /dev/null +++ b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php @@ -0,0 +1,10 @@ +setCheckPermissions($checkPermissions); } @@ -38,9 +40,10 @@ class OAuthClient extends Generic\DAOEntity { * 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); } @@ -49,17 +52,25 @@ class OAuthClient extends Generic\DAOEntity { * 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', + ], + ], ]; } diff --git a/ext/oauth-client/Civi/Api4/OAuthContactToken.php b/ext/oauth-client/Civi/Api4/OAuthContactToken.php new file mode 100644 index 0000000000..59cd596251 --- /dev/null +++ b/ext/oauth-client/Civi/Api4/OAuthContactToken.php @@ -0,0 +1,47 @@ +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', + ], + ], + ]; + } + +} diff --git a/ext/oauth-client/Civi/OAuth/OAuthTokenFacade.php b/ext/oauth-client/Civi/OAuth/OAuthTokenFacade.php index dcad4770bf..58a9c3edd2 100644 --- a/ext/oauth-client/Civi/OAuth/OAuthTokenFacade.php +++ b/ext/oauth-client/Civi/OAuth/OAuthTokenFacade.php @@ -6,7 +6,7 @@ use League\OAuth2\Client\Provider\ResourceOwnerInterface; class OAuthTokenFacade { - const STORAGE_TYPES = ';^OAuthSysToken$;'; + const STORAGE_TYPES = ';^OAuth(Sys|Contact)Token$;'; /** * Request and store a token. @@ -14,24 +14,28 @@ class OAuthTokenFacade { * @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'] ?? []; @@ -60,7 +64,9 @@ class OAuthTokenFacade { '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); @@ -82,9 +88,10 @@ class OAuthTokenFacade { * @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); @@ -93,9 +100,10 @@ class OAuthTokenFacade { /** * @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; } @@ -111,7 +119,7 @@ class OAuthTokenFacade { return NULL; } - protected function implodeScopes($delim, $scopes) { + protected function implodeScopes($delim, $scopes): ?string { if ($scopes === NULL || is_string($scopes)) { return $scopes; } @@ -125,6 +133,9 @@ class OAuthTokenFacade { } 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) { diff --git a/ext/oauth-client/oauth_client.civix.php b/ext/oauth-client/oauth_client.civix.php index 64892d344e..0347786a07 100644 --- a/ext/oauth-client/oauth_client.civix.php +++ b/ext/oauth-client/oauth_client.civix.php @@ -221,7 +221,8 @@ function _oauth_client_civix_upgrader() { * 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" @@ -229,32 +230,7 @@ function _oauth_client_civix_upgrader() { * @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); } /** @@ -479,6 +455,11 @@ function _oauth_client_civix_civicrm_entityTypes(&$entityTypes) { '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', diff --git a/ext/oauth-client/oauth_client.php b/ext/oauth-client/oauth_client.php index 84930ee6c4..91bd0f85e5 100644 --- a/ext/oauth-client/oauth_client.php +++ b/ext/oauth-client/oauth_client.php @@ -51,6 +51,18 @@ function oauth_client_civicrm_permission(&$permissions) { $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"), + ]; } /** diff --git a/ext/oauth-client/sql/auto_install.sql b/ext/oauth-client/sql/auto_install.sql index b5297d59d3..dbb478e5c0 100644 --- a/ext/oauth-client/sql/auto_install.sql +++ b/ext/oauth-client/sql/auto_install.sql @@ -10,7 +10,6 @@ -- DO NOT EDIT. Generated by CRM_Core_CodeGen -- - -- +--------------------------------------------------------------------+ -- | Copyright CiviCRM LLC. All rights reserved. | -- | | @@ -24,13 +23,14 @@ -- -- /******************************************************* -- * --- * 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; @@ -55,19 +55,53 @@ CREATE TABLE `civicrm_oauth_client` ( `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 ; -- /******************************************************* -- * @@ -91,15 +125,14 @@ CREATE TABLE `civicrm_oauth_systoken` ( `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 ; + diff --git a/ext/oauth-client/sql/auto_uninstall.sql b/ext/oauth-client/sql/auto_uninstall.sql index db6fecf209..a3c96fd566 100644 --- a/ext/oauth-client/sql/auto_uninstall.sql +++ b/ext/oauth-client/sql/auto_uninstall.sql @@ -11,13 +11,14 @@ -- -- /******************************************************* -- * --- * 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 diff --git a/ext/oauth-client/sql/upgrade_0001.sql b/ext/oauth-client/sql/upgrade_0001.sql new file mode 100644 index 0000000000..17fc3191a2 --- /dev/null +++ b/ext/oauth-client/sql/upgrade_0001.sql @@ -0,0 +1,73 @@ +-- +--------------------------------------------------------------------+ +-- | 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 ; diff --git a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php index 250d5c6ef8..94579c9c25 100644 --- a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php +++ b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php @@ -37,7 +37,7 @@ class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements $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', @@ -102,6 +102,8 @@ class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements ]; require_once 'tests/fixtures/DummyProvider.php'; + + return $this->providers; } public function makeDummyProviderClient(): array { @@ -114,7 +116,7 @@ class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements )->execute()->single(); } - public function testFetchAndStoreSysToken() { + public function testSysToken_FetchAndStore() { $this->makeDummyProviderThatGetsAToken(); $client = $this->makeDummyProviderClient(); @@ -155,4 +157,235 @@ class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements $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']); + } + } diff --git a/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php b/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php new file mode 100644 index 0000000000..131bf6d73f --- /dev/null +++ b/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php @@ -0,0 +1,345 @@ +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']); + } + +} diff --git a/ext/oauth-client/xml/Menu/oauth_client.xml b/ext/oauth-client/xml/Menu/oauth_client.xml index 1039478f63..f4d9687206 100644 --- a/ext/oauth-client/xml/Menu/oauth_client.xml +++ b/ext/oauth-client/xml/Menu/oauth_client.xml @@ -4,6 +4,6 @@ civicrm/oauth-client/return CRM_OAuth_Page_Return Return - access CiviCRM + create OAuth tokens via auth code flow;manage OAuth client diff --git a/ext/oauth-client/xml/schema/CRM/OAuth/OAuthClient.xml b/ext/oauth-client/xml/schema/CRM/OAuth/OAuthClient.xml index 9b3649a6f9..695c874c33 100644 --- a/ext/oauth-client/xml/schema/CRM/OAuth/OAuthClient.xml +++ b/ext/oauth-client/xml/schema/CRM/OAuth/OAuthClient.xml @@ -55,9 +55,9 @@ Client Secret 5.32 - - - + + manage OAuth client + @@ -67,6 +67,9 @@ JSON 5.32 + + manage OAuth client + diff --git a/ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.entityType.php b/ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.entityType.php new file mode 100644 index 0000000000..f0791fd315 --- /dev/null +++ b/ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.entityType.php @@ -0,0 +1,10 @@ + 'OAuthContactToken', + 'class' => 'CRM_OAuth_DAO_OAuthContactToken', + 'table' => 'civicrm_oauth_contact_token', + ], +]; diff --git a/ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.xml b/ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.xml new file mode 100644 index 0000000000..d03931bc0e --- /dev/null +++ b/ext/oauth-client/xml/schema/CRM/OAuth/OAuthContactToken.xml @@ -0,0 +1,175 @@ + + CRM/OAuth + OAuthContactToken + civicrm_oauth_contact_token + 5.35 + + id + Token ID + int unsigned + true + Token ID + 5.35 + + + id + true + + + + + + tag + Tag + varchar + 128 + The tag specifies how this token will be used. + 5.35 + + + UI_tag + tag + 5.35 + + + + client_id + Client ID + int unsigned + Client ID + 5.35 + + + client_id +
civicrm_oauth_client
+ id + 5.35 + CASCADE + + + + contact_id + Contact ID + int unsigned + Contact ID + 5.35 + + + contact_id + civicrm_contact
+ id + 5.35 + CASCADE +
+ + + grant_type + Grant type + varchar + 31 + + Ex: authorization_code + 5.35 + + + + scopes + text + List of scopes addressed by this token + SEPARATOR_BOOKEND + 5.35 + + + + + + token_type + Token Type + varchar + 128 + Ex: Bearer or MAC + 5.35 + + + + access_token + Access Token + text + + Token to present when accessing resources + 5.35 + + + + expires + int unsigned + Expiration time + 0 + Expiration time for the access_token (seconds since epoch) + 5.35 + + + + refresh_token + Refresh Token + text + + Token to present when refreshing the access_token + 5.35 + + + + resource_owner_name + Resource Owner Name + varchar + 128 + Identifier for the resource owner. Structure varies by service. + 5.35 + + + + resource_owner + Resource Owner + text + Cached details describing the resource owner + JSON + 5.35 + + + + error + text + ?? copied from OAuthSysToken + JSON + 5.35 + + + + raw + Raw token + text + JSON + The token response data, per AccessToken::jsonSerialize + 5.35 + + + + + + created_date + timestamp + When the token was created. + false + CURRENT_TIMESTAMP + 5.35 + + + modified_date + timestamp + When the token was created or modified. + false + CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + 5.35 + + + diff --git a/package-lock.json b/package-lock.json index df903d93cd..43928e72ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2082 @@ { "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", @@ -1800,6 +3874,15 @@ } } }, + "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", @@ -1811,15 +3894,6 @@ "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", -- 2.25.1