From 82b3b02aa9cc8464d87ac3bccec64c1801024b23 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Wed, 11 Sep 2019 12:40:23 -0700 Subject: [PATCH] rebase; update perm controller; add perm log add perm activity log ui add perm history and ui add seen accounts to perm history --- app/_locales/cs/messages.json | 8 +- app/_locales/de/messages.json | 8 +- app/_locales/en/messages.json | 50 ++- app/_locales/es/messages.json | 8 +- app/_locales/fr/messages.json | 8 +- app/_locales/hn/messages.json | 8 +- app/_locales/ht/messages.json | 8 +- app/_locales/it/messages.json | 8 +- app/_locales/ja/messages.json | 8 +- app/_locales/ko/messages.json | 8 +- app/_locales/nl/messages.json | 8 +- app/_locales/ph/messages.json | 8 +- app/_locales/pt/messages.json | 8 +- app/_locales/ru/messages.json | 8 +- app/_locales/sk/messages.json | 8 +- app/_locales/sl/messages.json | 8 +- app/_locales/ta/messages.json | 8 +- app/_locales/th/messages.json | 8 +- app/_locales/tr/messages.json | 8 +- app/_locales/vi/messages.json | 8 +- app/_locales/zh_CN/messages.json | 8 +- app/_locales/zh_TW/messages.json | 8 +- app/scripts/controllers/permissions/index.js | 1 + .../permissions/loggerMiddleware.js | 151 ++++++++ .../permissions-safe-methods.json | 0 .../{ => permissions}/permissions.js | 144 +++----- .../permissions/requestMiddleware.js | 41 +++ .../permissions/restrictedMethods.js | 37 ++ app/scripts/metamask-controller.js | 18 +- package.json | 1 + .../clear-permissions-activity.component.js | 39 +++ .../clear-permissions-activity.container.js | 16 + .../clear-permissions-activity/index.js | 1 + .../clear-permissions-history.component.js | 39 +++ .../clear-permissions-history.container.js | 16 + .../modals/clear-permissions-history/index.js | 1 + .../clear-permissions.component.js | 2 +- ui/app/components/app/modals/modal.js | 28 ++ ui/app/helpers/utils/util.js | 8 + ui/app/pages/settings/index.scss | 10 +- .../permissions-activity/index.js | 1 + .../permissions-activity.component.js | 156 +++++++++ .../permissions-history/index.js | 1 + .../permissions-history.component.js | 120 +++++++ .../permissions-tab/permissions-list/index.js | 1 + .../permissions-list.component.js | 294 ++++++++++++++++ .../permissions-tab.component.js | 328 +++--------------- .../permissions-tab.container.js | 14 +- ui/app/selectors/selectors.js | 10 + ui/app/store/actions.js | 22 +- yarn.lock | 33 +- 51 files changed, 1259 insertions(+), 492 deletions(-) create mode 100644 app/scripts/controllers/permissions/index.js create mode 100644 app/scripts/controllers/permissions/loggerMiddleware.js rename app/scripts/{lib => controllers/permissions}/permissions-safe-methods.json (100%) rename app/scripts/controllers/{ => permissions}/permissions.js (60%) create mode 100644 app/scripts/controllers/permissions/requestMiddleware.js create mode 100644 app/scripts/controllers/permissions/restrictedMethods.js create mode 100644 ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.component.js create mode 100644 ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.container.js create mode 100644 ui/app/components/app/modals/clear-permissions-activity/index.js create mode 100644 ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.component.js create mode 100644 ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.container.js create mode 100644 ui/app/components/app/modals/clear-permissions-history/index.js create mode 100644 ui/app/pages/settings/permissions-tab/permissions-activity/index.js create mode 100644 ui/app/pages/settings/permissions-tab/permissions-activity/permissions-activity.component.js create mode 100644 ui/app/pages/settings/permissions-tab/permissions-history/index.js create mode 100644 ui/app/pages/settings/permissions-tab/permissions-history/permissions-history.component.js create mode 100644 ui/app/pages/settings/permissions-tab/permissions-list/index.js create mode 100644 ui/app/pages/settings/permissions-tab/permissions-list/permissions-list.component.js diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 9e43ccb65188..9f645afa4e72 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Naozaj chcete vymazať schválené webové stránky?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Schválené údaje webových stránek byly úspěšně zrušeny." }, - "permissionsData": { + "permissionsSettings": { "message": "Údaje o schválení" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Jasné údaje o schválení" }, "reject": { diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index b270e61dc0af..2a1540e05d93 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "Vertragsinteraktion" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Genehmigte Website-Daten wurden erfolgreich gelöscht." }, - "permissionsData": { + "permissionsSettings": { "message": "Genehmigungsdaten" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Löschen Sie die genehmigten Website-Daten, damit alle Websites erneut eine Genehmigung anfordern müssen." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Genehmigungsdaten löschen" }, "reject": { diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 6322e97255b4..7db92150e717 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -5,7 +5,7 @@ "showIncomingTransactionsDescription": { "message": "Select this to use Etherscan to show incoming transactions in the transactions list" }, - "caveat:filterParams": { + "caveat_filterParams": { "message": "Only with: " }, "caveat_filterResponse": { @@ -41,27 +41,57 @@ "contractInteraction": { "message": "Contract Interaction" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Permissions cleared successfully." }, - "permissionsData": { + "permissionsSettings": { "message": "Manage Permissions" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "All currently granted permissions, by dapp/website. Deselect permissions and click \"Update Permissions\" to remove them." }, - "permissionsDataEmpty": { + "permissionsEmpty": { "message": "No permissions found." }, - "clearPermissionsDataDescription": { - "message": "Clear permissions so that all dapps/websites must request access again." - }, - "clearPermissionsData": { + "clearPermissions": { "message": "Clear Permissions" }, - "updatePermissionsData": { + "clearPermissionsDescription": { + "message": "Clear permissions so that all dapps/websites must request access again." + }, + "updatePermissions": { "message": "Update Permissions" }, + "permissionsActivitySettings": { + "message": "Permissions Activity" + }, + "permissionsActivityDescription": { + "message": "A log of the most recent permissions activity, including restricted method calls." + }, + "permissionsActivityEmpty": { + "message": "No permissions activity found." + }, + "clearPermissionsActivity": { + "message": "Clear Permissions Activity" + }, + "clearPermissionsActivityDescription": { + "message": "Clear the permissions activity log." + }, + "permissionsHistorySettings": { + "message": "Permissions History" + }, + "permissionsHistoryDescription": { + "message": "A list of dapps/domains and all permissions they've ever had." + }, + "permissionsHistoryEmpty": { + "message": "No permissions history found." + }, + "clearPermissionsHistory": { + "message": "Clear Permissions History" + }, + "clearPermissionsHistoryDescription": { + "message": "Clear the permissions history." + }, "reject": { "message": "Reject" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 5c36d21e28a1..edf6d5688a82 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "Interacción con contrato" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Los datos aprobados del sitio web se borraron con éxito." }, - "permissionsData": { + "permissionsSettings": { "message": "Datos de aprobación" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Borrar la información privada de modo que todos los sitios deban volver a requerir acceso para acceder a los datos de la cuenta." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Borrar datos de aprobación" }, "reject": { diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 79e595d34f6e..c44a057eb4bd 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "Interaction avec un contrat" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Les données de site Web approuvées ont été supprimées." }, - "permissionsData": { + "permissionsSettings": { "message": "Données d'approbation" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Effacer les données de site Web approuvées afin que tous les sites doivent à nouveau demander l'approbation." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Effacer les données d'approbation" }, "reject": { diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 893927879245..660aed6a661b 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "क्या आप वाकई अनुमोदित वेबसाइटों को साफ़ करना चाहते हैं?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "स्वीकृत वेबसाइट डेटा सफलतापूर्वक मंजूरी दे दी।" }, - "permissionsData": { + "permissionsSettings": { "message": "स्वीकृति डेटा" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "अनुमोदित वेबसाइट डेटा साफ़ करें ताकि सभी साइटों को फिर से अनुमोदन का अनुरोध करना होगा।" }, - "clearPermissionsData": { + "clearPermissions": { "message": "अनुमोदन डेटा साफ़ करें" }, "approve": { diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index ac54658107d2..8358ce5a133b 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Èske ou sèten ou vle klè sitwèb apwouve?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Done sou sit wèb apwouve yo te klarifye avèk siksè." }, - "permissionsData": { + "permissionsSettings": { "message": "Done sou vi prive" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Done sou vi prive klè pou tout sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ankò." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Klè Done sou vi prive" }, "providerRequestInfo": { diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index f3c061ce77e2..639b67685841 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "Interazione Contratto" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Dati del sito Web approvati cancellati correttamente." }, - "permissionsData": { + "permissionsSettings": { "message": "Dati di approvazione" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Cancella i dati del sito web approvati, quindi tutti i siti devono richiedere nuovamente l'approvazione." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Cancella i dati di approvazione" }, "reject": { diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 367b016830c6..643da3a84ded 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "コントラクトへのアクセス" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "承認されたウェブサイトデータが正常に消去されました。" }, - "permissionsData": { + "permissionsSettings": { "message": "承認データ" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "承認されたウェブサイトのデータをクリアすると、すべてのサイトで承認を再度要求する必要があります" }, - "clearPermissionsData": { + "clearPermissions": { "message": "承認データのクリア" }, "reject": { diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index ad92c487fad8..d55fb027b059 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "계약 상호 작용" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다." }, - "permissionsData": { + "permissionsSettings": { "message": "승인 데이터" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "승인 된 웹 사이트 데이터를 삭제하여 모든 사이트에서 승인을 다시 요청해야합니다." }, - "clearPermissionsData": { + "clearPermissions": { "message": "승인 데이터 삭제" }, "reject": { diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index 83eade80b51f..c1e2290fa6af 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Weet je zeker dat je goedgekeurde websites wilt wissen?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Goedgekeurde websitegegevens zijn met succes gewist." }, - "permissionsData": { + "permissionsSettings": { "message": "Goedkeuringsgegevens" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Goedgekeurde websitegegevens wissen zodat alle sites opnieuw goedkeuring moeten aanvragen." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Gegevens over goedkeuring wissen" }, "reject": { diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 7d506dea8d0c..2bc94990d823 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Matagumpay na na-clear ang data ng aprubadong website." }, - "permissionsData": { + "permissionsSettings": { "message": "Data ng Pag-apruba" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "I-clear ang naaprubahang data ng website upang ang lahat ng site ay dapat humiling muli ng pag-apruba" }, - "clearPermissionsData": { + "clearPermissions": { "message": "Tanggalin ang data ng pag-apruba" }, "appName": { diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 440d8acbca27..78f31df8343f 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Tem certeza de que deseja limpar sites aprovados?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Dados aprovados do website foram limpos com sucesso." }, - "permissionsData": { + "permissionsSettings": { "message": "Dados de aprovação" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Limpe os dados aprovados do website para que todos os sites solicitem aprovação novamente." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Limpar dados de aprovação" }, "reject": { diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 6c8a6d57d807..04107f6b7490 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Утвержденные данные веб-сайта успешно удалены." }, - "permissionsData": { + "permissionsSettings": { "message": "Данные об утверждении" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Очистите утвержденные данные веб-сайта, чтобы все сайты снова запросили подтверждение." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Четкие данные об утверждении" }, "reject": { diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 035b5a751d57..c2be7a75fbab 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "Zmluvná interakcia" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Schválené údaje webových stránek byly úspěšně zrušeny." }, - "permissionsData": { + "permissionsSettings": { "message": "Údaje o schválení" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Jasné údaje o schválení" }, "reject": { diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index b71fd6fcf2f8..6b34e70ba040 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "Interakcija s pogodbo" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Odobrene spletne strani uspešno počiščene." }, - "permissionsData": { + "permissionsSettings": { "message": "Podatki o odobritvi" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Počistite seznam odobrenih spletnih strani, tako da bodo morale ponovno zahtevati odobritev." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Počisti podatke o odobritvi" }, "reject": { diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index 53911403e808..9dc4e8d930d1 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "அங்கீகரிக்கப்பட்ட வலைத்தளங்களை நிச்சயமாக நீக்க விரும்புகிறீர்களா?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவு வெற்றிகரமாக அழிக்கப்பட்டது." }, - "permissionsData": { + "permissionsSettings": { "message": "ஒப்புதல் தரவு" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவை அழிக்கவும், அனைத்து தளங்களும் ஒப்புதல் மீண்டும் கோர வேண்டும்." }, - "clearPermissionsData": { + "clearPermissions": { "message": "ஒப்புதல் தரவை அழி" }, "approve": { diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index b2d300187ecc..16b608225536 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "คุณแน่ใจหรือไม่ว่าต้องการล้างเว็บไซต์ที่ผ่านการอนุมัติ" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "อนุมัติข้อมูลเว็บไซต์ที่ได้รับอนุมัติแล้ว" }, - "permissionsData": { + "permissionsSettings": { "message": "ข้อมูลการอนุมัติ" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "ล้างข้อมูลเว็บไซต์ที่ได้รับการอนุมัติเพื่อให้ทุกไซต์ต้องขออนุมัติอีกครั้ง" }, - "clearPermissionsData": { + "clearPermissions": { "message": "ล้างข้อมูลการอนุมัติ" }, "reject": { diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 8e14fca59dd7..4ffcc06636a4 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Onaylanan web sitesi verileri başarıyla temizlendi." }, - "permissionsData": { + "permissionsSettings": { "message": "Onay Verileri" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Onaylanan web sitesi verilerini temizle, tüm sitelerin tekrar onay isteğinde bulunması gerekir." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Onay verilerini temizle" }, "reject": { diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 29f85705c010..ab7eb1c113e9 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -2,16 +2,16 @@ "confirmClear": { "message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "Đã xóa thành công dữ liệu trang web được phê duyệt." }, - "permissionsData": { + "permissionsSettings": { "message": "Dữ liệu phê duyệt" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "Xóa dữ liệu trang web được phê duyệt để tất cả các trang web phải yêu cầu phê duyệt lại." }, - "clearPermissionsData": { + "clearPermissions": { "message": "Xóa dữ liệu phê duyệt" }, "reject": { diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index ce39353e6d5e..0639d6bdbc02 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "合约交互" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "已批准的网站数据已成功清除。" }, - "permissionsData": { + "permissionsSettings": { "message": "审批数据" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "清除已批准的网站数据,以便所有网站都必须再次申请" }, - "clearPermissionsData": { + "clearPermissions": { "message": "清除批准数据" }, "reject": { diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index a3616e408122..6e4c40034f8f 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -11,16 +11,16 @@ "contractInteraction": { "message": "合約互動" }, - "clearPermissionsDataSuccess": { + "clearPermissionsSuccess": { "message": "已批准的網站紀錄已成功清除。" }, - "permissionsData": { + "permissionsSettings": { "message": "審核紀錄" }, - "permissionsDataDescription": { + "permissionsDescription": { "message": "清除之前已批准過的網站審核紀錄,所有網站都必須再次申請" }, - "clearPermissionsData": { + "clearPermissions": { "message": "清除批准數據" }, "reject": { diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js new file mode 100644 index 000000000000..8f677ac7eb76 --- /dev/null +++ b/app/scripts/controllers/permissions/index.js @@ -0,0 +1 @@ +module.exports = require('./permissions') diff --git a/app/scripts/controllers/permissions/loggerMiddleware.js b/app/scripts/controllers/permissions/loggerMiddleware.js new file mode 100644 index 000000000000..73bd367a0904 --- /dev/null +++ b/app/scripts/controllers/permissions/loggerMiddleware.js @@ -0,0 +1,151 @@ + +const clone = require('clone') +const { isValidAddress } = require('ethereumjs-util') + +const LOG_LIMIT = 100 + +/** + * Create middleware for logging requests and responses to restricted and + * permissions-related methods. + */ +module.exports = function createLoggerMiddleware ({ + walletPrefix, restrictedMethods, store, logStoreKey, historyStoreKey, +}) { + return (req, res, next, _end) => { + + let activityEntry, requestedMethods + const { origin, method } = req + const isInternal = method.startsWith(walletPrefix) + + if (isInternal || restrictedMethods.includes(method)) { + activityEntry = logActivity(req, isInternal) + if (method === `${walletPrefix}requestPermissions`) { + requestedMethods = getRequestedMethods(req) + } + } else { + return next() + } + + next(cb => { + const time = Date.now() + addResponse(activityEntry, res, time) + if (!res.error && requestedMethods) { + logHistory(requestedMethods, origin, res.result, time) + } + cb() + }) + } + + function logActivity (request, isInternal) { + const activityEntry = { + id: request.id, + method: request.method, + methodType: isInternal ? 'internal' : 'restricted', + origin: request.origin, + request: cloneObj(request), + requestTime: Date.now(), + response: null, + responseTime: null, + success: null, + } + commitActivity(activityEntry) + return activityEntry + } + + function addResponse (activityEntry, response, time) { + if (!response) return + activityEntry.response = cloneObj(response) + activityEntry.responseTime = time + activityEntry.success = !response.error + } + + function commitActivity (entry) { + const logs = store.getState()[logStoreKey] + if (logs.length > LOG_LIMIT - 2) { + logs.pop() + } + logs.push(entry) + store.updateState({ [logStoreKey]: logs }) + } + + function getRequestedMethods (request) { + if ( + !request.params || + typeof request.params[0] !== 'object' || + Array.isArray(request.params[0]) + ) return null + return Object.keys(request.params[0]) + } + + function logHistory (requestedMethods, origin, result, time) { + + let accounts + const entries = result + .map(perm => { + if (perm.parentCapability === 'eth_accounts') { + accounts = getAccountsFromPermission(perm) + } + return perm.parentCapability + }) + .reduce((acc, m) => { + if (requestedMethods.includes(m)) { + acc[m] = { + lastApproved: time, + } + if (m === 'eth_accounts') { + acc[m].accounts = accounts + } + } + return acc + }, {}) + + if (Object.keys(entries).length > 0) { + commitHistory(origin, entries, accounts) + } + } + + function commitHistory (origin, entries, accounts) { + + const history = store.getState()[historyStoreKey] + + if (Array.isArray(accounts)) { + if (history[origin] && history[origin]['eth_accounts']) { + history[origin]['eth_accounts']['accounts'] + .filter(acc => !accounts.includes(acc)) + .forEach(acc => accounts.unshift(acc)) + } + accounts.sort() + } + + history[origin] = { + ...history[origin], + ...entries, + } + store.updateState({ [historyStoreKey]: history }) + } +} + +// the call to clone is set to disallow circular references +// we attempt cloning at a depth of 3 and 2, then return a +// shallow copy of the object +function cloneObj (obj) { + for (let i = 3; i > 1; i--) { + try { + return clone(obj, false, i) + } catch (_) {} + } + return { ...obj } +} + +function getAccountsFromPermission (perm) { + if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) return [] + const accounts = {} + for (const c of perm.caveats) { + if (c.type === 'filterResponse' && Array.isArray(c.value)) { + for (const v of c.value) { + if (isValidAddress(v)) accounts[v] = true + } + } + } + return Object.keys(accounts) +} diff --git a/app/scripts/lib/permissions-safe-methods.json b/app/scripts/controllers/permissions/permissions-safe-methods.json similarity index 100% rename from app/scripts/lib/permissions-safe-methods.json rename to app/scripts/controllers/permissions/permissions-safe-methods.json diff --git a/app/scripts/controllers/permissions.js b/app/scripts/controllers/permissions/permissions.js similarity index 60% rename from app/scripts/controllers/permissions.js rename to app/scripts/controllers/permissions/permissions.js index 2d6b073f687d..807341f3fce4 100644 --- a/app/scripts/controllers/permissions.js +++ b/app/scripts/controllers/permissions/permissions.js @@ -1,83 +1,65 @@ const JsonRpcEngine = require('json-rpc-engine') const asMiddleware = require('json-rpc-engine/src/asMiddleware') -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const ObservableStore = require('obs-store') const RpcCap = require('json-rpc-capabilities-middleware').CapabilitiesController -const { errors: rpcErrors } = require('eth-json-rpc-errors') + +const getRestrictedMethods = require('./restrictedMethods') +const createRequestMiddleware = require('./requestMiddleware') +const createLoggerMiddleware = require('./loggerMiddleware') // Methods that do not require any permissions to use: -const SAFE_METHODS = require('../lib/permissions-safe-methods.json') +const SAFE_METHODS = require('./permissions-safe-methods.json') -const METHOD_PREFIX = 'wallet_' +const METADATA_STORE_KEY = 'siteMetadata' +const LOG_STORE_KEY = 'permissionsLog' +const HISTORY_STORE_KEY = 'permissionsHistory' +const WALLET_METHOD_PREFIX = 'wallet_' const INTERNAL_METHOD_PREFIX = 'metamask_' function prefix (method) { - return METHOD_PREFIX + method + return WALLET_METHOD_PREFIX + method } // class PermissionsController extends SafeEventEmitter { class PermissionsController { - constructor ({ - openPopup, closePopup, keyringController, - } = {}, restoredPermissions = {}, restoredState = { siteMetadata: {} }) { + constructor ( + { openPopup, closePopup, keyringController} = {}, + restoredPermissions = {}, + restoredState = {}) { this.store = new ObservableStore({ - siteMetadata: { ...restoredState.siteMetadata }, + [METADATA_STORE_KEY]: restoredState[METADATA_STORE_KEY] || {}, + [LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [], + [HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {}, }) this._openPopup = openPopup this._closePopup = closePopup this.keyringController = keyringController + this._restrictedMethods = getRestrictedMethods(this) this._initializePermissions(restoredPermissions) } createMiddleware (options) { const { origin } = options const engine = new JsonRpcEngine() - engine.push(this.createRequestMiddleware(options)) + engine.push(createRequestMiddleware({ + internalPrefix: INTERNAL_METHOD_PREFIX, + store: this.store, + storeKey: METADATA_STORE_KEY, + })) + engine.push(createLoggerMiddleware({ + walletPrefix: WALLET_METHOD_PREFIX, + restrictedMethods: Object.keys(this._restrictedMethods), + store: this.store, + logStoreKey: LOG_STORE_KEY, + historyStoreKey: HISTORY_STORE_KEY, + })) engine.push(this.permissions.providerMiddlewareFunction.bind( this.permissions, { origin } )) return asMiddleware(engine) } - /** - * Create middleware for preprocessing permissions requests. - */ - createRequestMiddleware () { - return createAsyncMiddleware(async (req, res, next) => { - - if (typeof req.method !== 'string') { - res.error = rpcErrors.invalidRequest(null, req) - return // TODO:json-rpc-engine - } - - if (req.method.startsWith(INTERNAL_METHOD_PREFIX)) { - switch (req.method.split(INTERNAL_METHOD_PREFIX)[1]) { - case 'sendSiteMetadata': - if ( - req.siteMetadata && - typeof req.siteMetadata.name === 'string' - ) { - this.store.putState({ - siteMetadata: { - ...this.store.getState().siteMetadata, - [req.origin]: req.siteMetadata, - }, - }) - } - break - default: - res.error = rpcErrors.methodNotFound(null, req.method) - break - } - if (!res.error) res.result = true - return - } - - return next() - }) - } - /** * Returns the accounts that should be exposed for the given origin domain, * if any. This method exists for when a trusted context needs to know @@ -124,6 +106,24 @@ class PermissionsController { this.permissions.clearDomains() } + /** + * Clears the permissions log. + */ + clearLog () { + this.store.updateState({ + [LOG_STORE_KEY]: [], + }) + } + + /** + * Clears the permissions history. + */ + clearHistory () { + this.store.updateState({ + [HISTORY_STORE_KEY]: {}, + }) + } + /** * User approval callback. * @param {object} approved the approved request object @@ -172,44 +172,9 @@ class PermissionsController { safeMethods: SAFE_METHODS, // optional prefix for internal methods - methodPrefix: METHOD_PREFIX, - - restrictedMethods: { - - 'eth_accounts': { - description: 'View Ethereum accounts', - method: (_, res, __, end) => { - this.keyringController.getAccounts() - .then((accounts) => { - res.result = accounts - end() - }) - .catch((reason) => { - res.error = reason - end(reason) - }) - }, - }, - - // Restricted methods themselves are defined as - // json-rpc-engine middleware functions. - 'readYourProfile': { - description: 'Read from your profile', - method: (_req, res, _next, end) => { - res.result = this.testProfile - end() - }, - }, - 'writeToYourProfile': { - description: 'Write to your profile.', - method: (req, res, _next, end) => { - const [ key, value ] = req.params - this.testProfile[key] = value - res.result = this.testProfile - return end() - }, - }, - }, + methodPrefix: WALLET_METHOD_PREFIX, + + restrictedMethods: this._restrictedMethods, /** * A promise-returning callback used to determine whether to approve @@ -229,12 +194,7 @@ class PermissionsController { return new Promise((resolve, reject) => { this.pendingApprovals[id] = { resolve, reject } - }, - // TODO: This should be persisted/restored state. - {}) - - // TODO: Attenuate requested permissions in approval screen. - // Like selecting the account to display. + }) }, }, initState) } diff --git a/app/scripts/controllers/permissions/requestMiddleware.js b/app/scripts/controllers/permissions/requestMiddleware.js new file mode 100644 index 000000000000..446c66a92756 --- /dev/null +++ b/app/scripts/controllers/permissions/requestMiddleware.js @@ -0,0 +1,41 @@ + +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const { errors: rpcErrors } = require('eth-json-rpc-errors') + +/** + * Create middleware for preprocessing permissions requests. + */ +module.exports = function createRequestMiddleware ({ + internalPrefix, store, storeKey, +}) { + return createAsyncMiddleware(async (req, res, next) => { + + if (typeof req.method !== 'string') { + res.error = rpcErrors.invalidRequest(null, req) + return + } + + if (req.method.startsWith(internalPrefix)) { + switch (req.method.split(internalPrefix)[1]) { + case 'sendSiteMetadata': + if ( + req.siteMetadata && + typeof req.siteMetadata.name === 'string' + ) { + store.updateState({ + [storeKey]: { + ...store.getState()[storeKey], + [req.origin]: req.siteMetadata, + }, + }) + } + res.result = true + return + default: + break + } + } + + return next() + }) +} diff --git a/app/scripts/controllers/permissions/restrictedMethods.js b/app/scripts/controllers/permissions/restrictedMethods.js new file mode 100644 index 000000000000..de917c2addd6 --- /dev/null +++ b/app/scripts/controllers/permissions/restrictedMethods.js @@ -0,0 +1,37 @@ + +module.exports = function getRestrictedMethods (permissionsController) { + return { + 'eth_accounts': { + description: 'View Ethereum accounts', + method: (_, res, __, end) => { + permissionsController.keyringController.getAccounts() + .then((accounts) => { + res.result = accounts + end() + }) + .catch((reason) => { + res.error = reason + end(reason) + }) + }, + }, + + 'readYourProfile': { + description: 'Read from your profile', + method: (_req, res, _next, end) => { + res.result = permissionsController.testProfile + end() + }, + }, + + 'writeToYourProfile': { + description: 'Write to your profile.', + method: (req, res, _next, end) => { + const [ key, value ] = req.params + permissionsController.testProfile[key] = value + res.result = permissionsController.testProfile + return end() + }, + }, + } +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 99797fd647ea..d245f2bb1cf0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -36,7 +36,7 @@ const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const TokenRatesController = require('./controllers/token-rates') const DetectTokensController = require('./controllers/detect-tokens') -const { PermissionsController } = require('./controllers/permissions') +const { PermissionsController } = require('./controllers/permissions/permissions') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') @@ -265,7 +265,7 @@ module.exports = class MetamaskController extends EventEmitter { openPopup: opts.openPopup, closePopup: opts.closePopup, }, - initState.PermissionsController, initState.SiteMetadata) + initState.PermissionsController, initState.PermissionsMetadata) this.store.updateStructure({ AppStateController: this.appStateController.store, @@ -281,7 +281,7 @@ module.exports = class MetamaskController extends EventEmitter { OnboardingController: this.onboardingController.store, IncomingTransactionsController: this.incomingTransactionsController.store, PermissionsController: this.permissionsController.permissions, - SiteMetadata: this.permissionsController.store, + PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, }) @@ -305,7 +305,7 @@ module.exports = class MetamaskController extends EventEmitter { OnboardingController: this.onboardingController.store, IncomingTransactionsController: this.incomingTransactionsController.store, PermissionsController: this.permissionsController.permissions, - SiteMetadata: this.permissionsController.store, + PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, }) this.memStore.subscribe(this.sendUpdate.bind(this)) @@ -368,9 +368,7 @@ module.exports = class MetamaskController extends EventEmitter { } function updatePublicConfigStore (memState) { - selectPublicState(memState).then(publicState => { - publicConfigStore.putState(publicState) - }) + publicConfigStore.putState(selectPublicState(memState)) } function selectPublicState ({ isUnlocked, network, completedOnboarding, provider }) { @@ -528,10 +526,12 @@ module.exports = class MetamaskController extends EventEmitter { // permissions approvePermissionsRequest: nodeify(this.permissionsController.approvePermissionsRequest, this.permissionsController), - rejectPermissionsRequest: nodeify(this.permissionsController.rejectPermissionsRequest, this.permissionsController), - removePermissionsFor: this.permissionsController.removePermissionsFor.bind(this.permissionsController), clearPermissions: this.permissionsController.clearPermissions.bind(this.permissionsController), + clearPermissionsHistory: this.permissionsController.clearHistory.bind(this.permissionsController), + clearPermissionsLog: this.permissionsController.clearLog.bind(this.permissionsController), getApprovedAccounts: nodeify(this.permissionsController.getAccounts.bind(this.permissionsController)), + rejectPermissionsRequest: nodeify(this.permissionsController.rejectPermissionsRequest, this.permissionsController), + removePermissionsFor: this.permissionsController.removePermissionsFor.bind(this.permissionsController), } } diff --git a/package.json b/package.json index 612a04a1560e..f1ebec08c2c2 100644 --- a/package.json +++ b/package.json @@ -167,6 +167,7 @@ "request-promise": "^4.2.1", "reselect": "^3.0.1", "safe-event-emitter": "^1.0.1", + "safe-json-stringify": "^1.2.0", "single-call-balance-checker-abi": "^1.0.0", "string.prototype.matchall": "^3.0.1", "swappable-obj-proxy": "^1.1.0", diff --git a/ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.component.js b/ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.component.js new file mode 100644 index 000000000000..47ea494accba --- /dev/null +++ b/ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.component.js @@ -0,0 +1,39 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal, { ModalContent } from '../../modal' + +export default class ClearPermissionsActivity extends PureComponent { + static propTypes = { + hideModal: PropTypes.func.isRequired, + clearPermissionsLog: PropTypes.func.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + handleClear = () => { + const { clearPermissionsLog, hideModal } = this.props + clearPermissionsLog() + hideModal() + } + + render () { + const { t } = this.context + + return ( + this.props.hideModal()} + submitText={t('ok')} + cancelText={t('nevermind')} + submitType="secondary" + > + + + ) + } +} diff --git a/ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.container.js b/ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.container.js new file mode 100644 index 000000000000..6014259dde56 --- /dev/null +++ b/ui/app/components/app/modals/clear-permissions-activity/clear-permissions-activity.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' +import ClearPermissionsActivityComponent from './clear-permissions-activity.component' +import { clearPermissionsLog } from '../../../../store/actions' + +const mapDispatchToProps = dispatch => { + return { + clearPermissionsLog: () => dispatch(clearPermissionsLog()), + } +} + +export default compose( + withModalProps, + connect(null, mapDispatchToProps) +)(ClearPermissionsActivityComponent) diff --git a/ui/app/components/app/modals/clear-permissions-activity/index.js b/ui/app/components/app/modals/clear-permissions-activity/index.js new file mode 100644 index 000000000000..9fe31660477a --- /dev/null +++ b/ui/app/components/app/modals/clear-permissions-activity/index.js @@ -0,0 +1 @@ +export { default } from './clear-permissions-activity.container' diff --git a/ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.component.js b/ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.component.js new file mode 100644 index 000000000000..c4e88d18ea60 --- /dev/null +++ b/ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.component.js @@ -0,0 +1,39 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal, { ModalContent } from '../../modal' + +export default class ClearPermissionsHistory extends PureComponent { + static propTypes = { + hideModal: PropTypes.func.isRequired, + clearPermissionsHistory: PropTypes.func.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + handleClear = () => { + const { clearPermissionsHistory, hideModal } = this.props + clearPermissionsHistory() + hideModal() + } + + render () { + const { t } = this.context + + return ( + this.props.hideModal()} + submitText={t('ok')} + cancelText={t('nevermind')} + submitType="secondary" + > + + + ) + } +} diff --git a/ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.container.js b/ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.container.js new file mode 100644 index 000000000000..badeefadf20c --- /dev/null +++ b/ui/app/components/app/modals/clear-permissions-history/clear-permissions-history.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' +import ClearPermissionsHistoryComponent from './clear-permissions-history.component' +import { clearPermissionsHistory } from '../../../../store/actions' + +const mapDispatchToProps = dispatch => { + return { + clearPermissionsHistory: () => dispatch(clearPermissionsHistory()), + } +} + +export default compose( + withModalProps, + connect(null, mapDispatchToProps) +)(ClearPermissionsHistoryComponent) diff --git a/ui/app/components/app/modals/clear-permissions-history/index.js b/ui/app/components/app/modals/clear-permissions-history/index.js new file mode 100644 index 000000000000..ef3419811830 --- /dev/null +++ b/ui/app/components/app/modals/clear-permissions-history/index.js @@ -0,0 +1 @@ +export { default } from './clear-permissions-history.container' diff --git a/ui/app/components/app/modals/clear-permissions/clear-permissions.component.js b/ui/app/components/app/modals/clear-permissions/clear-permissions.component.js index 0d2774fbf45f..4224fbf4c349 100644 --- a/ui/app/components/app/modals/clear-permissions/clear-permissions.component.js +++ b/ui/app/components/app/modals/clear-permissions/clear-permissions.component.js @@ -30,7 +30,7 @@ export default class ClearPermissions extends PureComponent { submitType="secondary" > diff --git a/ui/app/components/app/modals/modal.js b/ui/app/components/app/modals/modal.js index f1f0b9fa0d47..ffd63947f9c5 100644 --- a/ui/app/components/app/modals/modal.js +++ b/ui/app/components/app/modals/modal.js @@ -25,6 +25,8 @@ import CancelTransaction from './cancel-transaction' import MetaMetricsOptInModal from './metametrics-opt-in-modal' import RejectTransactions from './reject-transactions' import ClearPermissions from './clear-permissions' +import ClearPermissionsActivity from './clear-permissions-activity' +import ClearPermissionsHistory from './clear-permissions-history' import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' import ConfirmDeleteNetwork from './confirm-delete-network' import AddToAddressBookModal from './add-to-addressbook-modal' @@ -183,6 +185,32 @@ const MODALS = { }, }, + CLEAR_PERMISSIONS_ACTIVITY: { + contents: h(ClearPermissionsActivity), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + + CLEAR_PERMISSIONS_HISTORY: { + contents: h(ClearPermissionsHistory), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + METAMETRICS_OPT_IN_MODAL: { contents: h(MetaMetricsOptInModal), mobileModalStyle: { diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js index b9e8e83c53b5..2dc8a7ad44ab 100644 --- a/ui/app/helpers/utils/util.js +++ b/ui/app/helpers/utils/util.js @@ -2,6 +2,7 @@ const abi = require('human-standard-token-abi') const ethUtil = require('ethereumjs-util') const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') import { DateTime } from 'luxon' +import safeStringify from 'safe-json-stringify' const MIN_GAS_PRICE_GWEI_BN = new ethUtil.BN(1) const GWEI_FACTOR = new ethUtil.BN(1e9) @@ -62,6 +63,7 @@ module.exports = { addressSlicer, isEthNetwork, isValidAddressHead, + stringify, } function isEthNetwork (netId) { @@ -331,3 +333,9 @@ function isValidAddressHead (address) { return addressLengthIsLessThanFull && addressIsHex } + +function stringify (val) { + if (typeof val === 'string') return val + if (typeof val === 'object') return safeStringify(val, null, 2) + return val.toString() +} diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss index fab57eebee07..16825c97bf8a 100644 --- a/ui/app/pages/settings/index.scss +++ b/ui/app/pages/settings/index.scss @@ -269,14 +269,14 @@ &__content-list-item { margin: 5px 0 0 10px; - &__caveat { + &__nested { list-style-type: disc; margin: 5px 0 0 50px; - } - &__caveat-value { - list-style-type: disc; - margin: 5px 0 0 30px; + &__nested { + list-style-type: disc; + margin: 5px 0 0 30px; + } } } diff --git a/ui/app/pages/settings/permissions-tab/permissions-activity/index.js b/ui/app/pages/settings/permissions-tab/permissions-activity/index.js new file mode 100644 index 000000000000..85e4c9198bb9 --- /dev/null +++ b/ui/app/pages/settings/permissions-tab/permissions-activity/index.js @@ -0,0 +1 @@ +export { default } from './permissions-activity.component' diff --git a/ui/app/pages/settings/permissions-tab/permissions-activity/permissions-activity.component.js b/ui/app/pages/settings/permissions-tab/permissions-activity/permissions-activity.component.js new file mode 100644 index 000000000000..5101ee9b3f2b --- /dev/null +++ b/ui/app/pages/settings/permissions-tab/permissions-activity/permissions-activity.component.js @@ -0,0 +1,156 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { stringify } from '../../../../helpers/utils/util' + +export default class PermissionsActivity extends Component { + + static propTypes = { + permissionsLog: PropTypes.array.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + shouldComponentUpdate (nextProps) { + if ( + (nextProps.permissionsLog.length === 0 && this.props.permissionsLog.length === 0) || + nextProps.permissionsLog.length !== this.props.permissionsLog.length || + nextProps.permissionsLog[0].id !== this.props.permissionsLog[0].id + ) { + return true + } + return false + } + + renderPermissionsActivityList () { + const { permissionsLog } = this.props + + /** + * Log Entry Shape + * { + * method: request.method, + * methodType: 'internal' | 'restricted', + * request: rpcRequest, + * requestTime: Date.now(), + * response: rpcResponse, + * responseTime: Date.now() | null, + * success: true | false, + * } + */ + + return ( + + ) + } + + renderRpcResponse (e) { + if (!e.response) return 'This request did not produce a response.' + if (!e.success) return this.renderRpcError(e.response.error) + if (e.methodType === 'internal') { + if ( + e.method.endsWith('getPermissions') || + e.method.endsWith('requestPermissions') + ) { + if (e.response.result.length === 0) { + return ( +
  • + {'None.'} +
  • + ) + } + return e.response.result.map(perm => ( +
  • + {perm.parentCapability} +
  • + )) + } else { + return ( +
  • + {stringify(e.response.result)} +
  • + ) + } + } else { + if (Array.isArray(e.response.result)) { + if (e.response.result.length === 0) { + return ( +
  • + {'None.'} +
  • + ) + } + return e.response.result.map((r, i) => ( +
  • + {stringify(r)} +
  • + )) + } else { + return ( +
  • + {stringify(e.response.result)} +
  • + ) + } + } + } + + renderRpcError (error) { + return `Error: ${error.code}: ${error.message}` + } + + render () { + const { t } = this.context + return ( +
    +
    + { t('permissionsActivitySettings') } + + { t('permissionsActivityDescription') } + +
    +
    + { + this.props.permissionsLog && this.props.permissionsLog.length > 0 + ? this.renderPermissionsActivityList() + : t('permissionsActivityEmpty') + } +
    +
    + ) + } +} diff --git a/ui/app/pages/settings/permissions-tab/permissions-history/index.js b/ui/app/pages/settings/permissions-tab/permissions-history/index.js new file mode 100644 index 000000000000..a0674073e42c --- /dev/null +++ b/ui/app/pages/settings/permissions-tab/permissions-history/index.js @@ -0,0 +1 @@ +export { default } from './permissions-history.component' diff --git a/ui/app/pages/settings/permissions-tab/permissions-history/permissions-history.component.js b/ui/app/pages/settings/permissions-tab/permissions-history/permissions-history.component.js new file mode 100644 index 000000000000..d8221df78729 --- /dev/null +++ b/ui/app/pages/settings/permissions-tab/permissions-history/permissions-history.component.js @@ -0,0 +1,120 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { addressSlicer } from '../../../../helpers/utils/util' + +export default class PermissionsHistory extends Component { + + static propTypes = { + permissionsHistory: PropTypes.object.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + renderPermissionsHistoryList () { + const { permissionsHistory } = this.props + + /** + * Permissions History Shape + * { + * [origin]: { + * [methodName]: { + * lastApproved: Date.now() + * accounts?: [ ... ] // if methodName === 'eth_accounts' + * }, + * ... + * }, + * ... + * } + */ + + return ( + + ) + } + + renderMethodList (methods) { + return Object.keys(methods).sort().map(m => { + const date = new Date(methods[m].lastApproved) + const dateString = date.toLocaleDateString() + const timeString = date.toLocaleTimeString( + [], {hour: '2-digit', minute: '2-digit'} + ) + return ( +
  • + {m} +
      +
    • + {`Last Approved: ${dateString} ${timeString}`} +
    • + { + m === 'eth_accounts' + ? this.renderAccountsList(methods[m]) + : '' + } +
    +
  • + ) + }) + } + + renderAccountsList (historyEntry) { + if (!historyEntry.accounts) return '' + return ( +
  • + {'Accounts Seen'} +
      + { + historyEntry.accounts.map((address, i) => ( +
    • + {addressSlicer(address)} +
    • + )) + } +
    +
  • + ) + } + + render () { + const { t } = this.context + return ( +
    +
    + { t('permissionsHistorySettings') } + + { t('permissionsHistoryDescription') } + +
    +
    + { + this.props.permissionsHistory && + Object.keys(this.props.permissionsHistory).length > 0 + ? this.renderPermissionsHistoryList() + : t('permissionsHistoryEmpty') + } +
    +
    + ) + } +} diff --git a/ui/app/pages/settings/permissions-tab/permissions-list/index.js b/ui/app/pages/settings/permissions-tab/permissions-list/index.js new file mode 100644 index 000000000000..30533cea26ee --- /dev/null +++ b/ui/app/pages/settings/permissions-tab/permissions-list/index.js @@ -0,0 +1 @@ +export { default } from './permissions-list.component' diff --git a/ui/app/pages/settings/permissions-tab/permissions-list/permissions-list.component.js b/ui/app/pages/settings/permissions-tab/permissions-list/permissions-list.component.js new file mode 100644 index 000000000000..b069fa2fe053 --- /dev/null +++ b/ui/app/pages/settings/permissions-tab/permissions-list/permissions-list.component.js @@ -0,0 +1,294 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import deepEqual from 'fast-deep-equal' +import Button from '../../../../components/ui/button' + +import { addressSlicer, isValidAddress, stringify } from '../../../../helpers/utils/util' + +export default class PermissionsList extends Component { + + static propTypes = { + permissions: PropTypes.object.isRequired, + permissionsDescriptions: PropTypes.object.isRequired, + removePermissionsFor: PropTypes.func.isRequired, + siteMetadata: PropTypes.object, + } + + static contextTypes = { + t: PropTypes.func, + } + + constructor (props) { + super(props) + this.state = { + ...this.getPermissionsState(props), + // domains: { + // [name]: { + // selected: boolean, + // permissions: [ id1, id2, ... ] + // } + // }, + // permissions: { + // [id]: { + // domain: name, + // methodname: methodname, + // selected: boolean, + // } + // } + } + } + + componentDidUpdate () { + const newState = this.getPermissionsState(this.props) + // if permissions have been added or removed, reset state + // does not take caveat changes into account, but we don't yet allow them + // to change after creation + if (!deepEqual( + Object.keys(this.state.permissions), Object.keys(newState.permissions)) + ) { + this.setState(newState) + } + } + + getPermissionsState (props) { + const { permissions } = props + return Object.keys(permissions).reduce((acc, domain) => { + permissions[domain].permissions.forEach(perm => { + if (!acc.domains[domain]) { + acc.domains[domain] = { + permissions: [perm.id], + selected: true, + iconError: false, + } + } else { + acc.domains[domain].permissions.push(perm.id) + } + acc.permissions[perm.id] = { + domain, + methodName: perm.parentCapability, + selected: true, + } + }) + return acc + }, { domains: {}, permissions: {} }) + } + + onDomainToggle = domain => () => { + const permissions = { ...this.state.permissions } + const selected = !this.state.domains[domain].selected + this.state.domains[domain].permissions.forEach(id => { + permissions[id].selected = selected + }) + this.setState({ + domains: { + ...this.state.domains, + [domain]: { + ...this.state.domains[domain], + selected, + }, + }, + permissions, + }) + } + + onPermissionToggle = id => () => { + const perm = { ...this.state.permissions[id] } + perm.selected = !perm.selected + const newState = { + permissions: { + ...this.state.permissions, + [id]: perm, + }, + } + if (perm.selected && !this.state.domains[perm.domain].selected) { + const domains = { ...this.state.domains } + domains[perm.domain].selected = perm.selected + newState.domains = domains + } + this.setState(newState) + } + + onIconError = domain => () => { + this.setState({ + domains: { + ...this.state.domains, + [domain]: { + ...this.state.domains[domain], + iconError: true, + }, + }, + }) + } + + updatePermissions () { + this.props.removePermissionsFor( + Object.values(this.state.permissions).reduce((acc, permState) => { + if (!permState.selected) { + if (!acc[permState.domain]) acc[permState.domain] = [] + acc[permState.domain].push(permState.methodName) + } + return acc + }, {}) + ) + } + + renderPermissionsList () { + const { permissions, permissionsDescriptions, siteMetadata } = this.props + return ( + + ) + } + + renderPermissionsListItem (permission, description) { + return ( +
  • + + + { + permission.caveats && permission.caveats.length > 0 + ? this.renderCaveatList(permission) + : null + } +
  • + ) + } + + renderCaveatList (permission) { + const { t } = this.context + return ( + + ) + } + + renderCaveatValue (value) { + if (Array.isArray(value)) { + return ( + + ) + } else if (typeof value !== 'object') { + return value + } else { + return stringify(value) + } + } + + render () { + const { t } = this.context + const hasPermissions = Object.keys(this.props.permissions).length > 0 + return ( +
    +
    + { t('permissionsSettings') } + + { t('permissionsDescription') } + +
    +
    + { + hasPermissions + ? this.renderPermissionsList() + : t('permissionsEmpty') + } +
    +
    +
    + +
    +
    +
    + ) + } +} diff --git a/ui/app/pages/settings/permissions-tab/permissions-tab.component.js b/ui/app/pages/settings/permissions-tab/permissions-tab.component.js index 08dc191672f2..1e3c893d7f99 100644 --- a/ui/app/pages/settings/permissions-tab/permissions-tab.component.js +++ b/ui/app/pages/settings/permissions-tab/permissions-tab.component.js @@ -1,9 +1,10 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import deepEqual from 'fast-deep-equal' -import Button from '../../../components/ui/button' -import { addressSlicer, isValidAddress } from '../../../helpers/utils/util' +import Button from '../../../components/ui/button' +import PermissionsList from './permissions-list' +import PermissionsActivity from './permissions-activity' +import PermissionsHistory from './permissions-history' export default class PermissionsTab extends Component { @@ -11,8 +12,12 @@ export default class PermissionsTab extends Component { warning: PropTypes.string, permissions: PropTypes.object.isRequired, permissionsDescriptions: PropTypes.object.isRequired, + permissionsHistory: PropTypes.object.isRequired, + permissionsLog: PropTypes.array.isRequired, removePermissionsFor: PropTypes.func.isRequired, showClearPermissionsModal: PropTypes.func.isRequired, + showClearPermissionsActivityModal: PropTypes.func.isRequired, + showClearPermissionsHistoryModal: PropTypes.func.isRequired, siteMetadata: PropTypes.object, } @@ -20,151 +25,29 @@ export default class PermissionsTab extends Component { t: PropTypes.func, } - constructor (props) { - super(props) - this.state = { - ...this.getPermissionsState(props), - // domains: { - // [name]: { - // selected: boolean, - // permissions: [ id1, id2, ... ] - // } - // }, - // permissions: { - // [id]: { - // domain: name, - // methodname: methodname, - // selected: boolean, - // } - // } - } - } - - componentDidUpdate () { - const newState = this.getPermissionsState(this.props) - // if permissions have been added or removed, reset state - // does not take caveat changes into account, but we don't yet allow them - // to change after creation - if (!deepEqual( - Object.keys(this.state.permissions), Object.keys(newState.permissions)) - ) { - this.setState(newState) - } - } - - getPermissionsState (props) { - const { permissions } = props - return Object.keys(permissions).reduce((acc, domain) => { - permissions[domain].permissions.forEach(perm => { - if (!acc.domains[domain]) { - acc.domains[domain] = { - permissions: [perm.id], - selected: true, - iconError: false, - } - } else { - acc.domains[domain].permissions.push(perm.id) - } - acc.permissions[perm.id] = { - domain, - methodName: perm.parentCapability, - selected: true, - } - }) - return acc - }, { domains: {}, permissions: {} }) - } - - onDomainToggle = domain => () => { - const permissions = { ...this.state.permissions } - const selected = !this.state.domains[domain].selected - this.state.domains[domain].permissions.forEach(id => { - permissions[id].selected = selected - }) - this.setState({ - domains: { - ...this.state.domains, - [domain]: { - ...this.state.domains[domain], - selected, - }, - }, - permissions, - }) - } - - onPermissionToggle = id => () => { - const perm = { ...this.state.permissions[id] } - perm.selected = !perm.selected - const newState = { - permissions: { - ...this.state.permissions, - [id]: perm, - }, - } - if (perm.selected && !this.state.domains[perm.domain].selected) { - const domains = { ...this.state.domains } - domains[perm.domain].selected = perm.selected - newState.domains = domains - } - this.setState(newState) - } - - onIconError = domain => () => { - this.setState({ - domains: { - ...this.state.domains, - [domain]: { - ...this.state.domains[domain], - iconError: true, - }, - }, - }) - } - - updatePermissions () { - this.props.removePermissionsFor( - Object.values(this.state.permissions).reduce((acc, permState) => { - if (!permState.selected) { - if (!acc[permState.domain]) acc[permState.domain] = [] - acc[permState.domain].push(permState.methodName) - } - return acc - }, {}) - ) - } - - renderPermissions () { + renderClearButton (messageKey, clickHandler, isDisabled) { const { t } = this.context - const hasPermissions = Object.keys(this.props.permissions).length > 0 return (
    - { t('permissionsData') } + { t(messageKey) } - { t('permissionsDataDescription') } + { t(`${messageKey}Description`) }
    -
    - { - hasPermissions - ? this.renderPermissionsList() - : t('permissionsDataEmpty') - } -
    @@ -172,163 +55,48 @@ export default class PermissionsTab extends Component { ) } - renderPermissionsList () { - const { permissions, permissionsDescriptions, siteMetadata } = this.props + render () { + const { warning } = this.props + const hasPermissions = Object.keys(this.props.permissions).length > 0 + const hasPermissionsActivity = this.props.permissionsLog.length > 0 + const hasPermissionsHistory = Object.keys(this.props.permissionsHistory).length > 0 + return ( -
      +
      + { warning &&
      { warning }
      } + { - Object.keys(permissions).sort().map(domain => { - if ( - permissions[domain].permissions.length === 0 || - !this.state.domains[domain] // state may lag behind props slightly - ) return null - // TODO: these elements look like trash and their CSS is placeholder only - return ( -
    • -
      - - - { - !this.state.domains[domain].iconError && - siteMetadata[domain].icon ? ( - - ) : ( - - {siteMetadata[domain].name.charAt(0).toUpperCase()} - - ) - } - {domain} - - -
        - { - permissions[domain].permissions.map(perm => { - return this.renderPermissionsListItem( - perm, permissionsDescriptions[perm.parentCapability] - ) - }) - } -
      -
      -
    • - ) - }) + this.renderClearButton( + 'clearPermissions', + this.props.showClearPermissionsModal, + !hasPermissions + ) } -
    - ) - } - - renderPermissionsListItem (permission, description) { - return ( -
  • - - { - permission.caveats && permission.caveats.length > 0 - ? this.renderCaveatList(permission) - : null + this.renderClearButton( + 'clearPermissionsHistory', + this.props.showClearPermissionsHistoryModal, + !hasPermissionsHistory + ) } -
  • - ) - } - - renderCaveatList (permission) { - const { t } = this.context - return ( -
      + { - permission.caveats.map((caveat, i) => ( -
    • - {t('caveat_' + caveat.type)} - {this.renderCaveatValue(caveat.value)} -
    • - )) + this.renderClearButton( + 'clearPermissionsActivity', + this.props.showClearPermissionsActivityModal, + !hasPermissionsActivity + ) } -
    - ) - } - - renderCaveatValue (value) { - if (Array.isArray(value)) { - return ( -
      - { - value.map((v, i) => ( -
    • - { - typeof v === 'string' && isValidAddress(v) - ? addressSlicer(v) - : typeof v !== 'object' - ? v - : JSON.stringify(v) - } -
    • - )) - } -
    - ) - } else if (typeof value !== 'object') { - return value - } else { - return JSON.stringify(value) - } - } - - renderClearPermissions () { - const { t } = this.context - const { showClearPermissionsModal } = this.props - const hasPermissions = Object.keys(this.props.permissions).length > 0 - return ( -
    -
    - { t('clearPermissionsData') } - - { t('clearPermissionsDataDescription') } - -
    -
    -
    - -
    -
    -
    - ) - } - - render () { - const { warning } = this.props - - return ( -
    - { warning &&
    { warning }
    } - { this.renderPermissions() } - { this.renderClearPermissions() }
    ) } diff --git a/ui/app/pages/settings/permissions-tab/permissions-tab.container.js b/ui/app/pages/settings/permissions-tab/permissions-tab.container.js index 1f0046962478..e868f2397cf6 100644 --- a/ui/app/pages/settings/permissions-tab/permissions-tab.container.js +++ b/ui/app/pages/settings/permissions-tab/permissions-tab.container.js @@ -7,7 +7,11 @@ import { removePermissionsFor, } from '../../../store/actions' import { - getAllPermissions, getPermissionsDescriptions, getSiteMetadata, + getAllPermissions, + getPermissionsDescriptions, + getSiteMetadata, + getPermissionsHistory, + getPermissionsLog, } from '../../../selectors/selectors' const mapStateToProps = state => { @@ -18,6 +22,8 @@ const mapStateToProps = state => { permissions: getAllPermissions(state), permissionsDescriptions: getPermissionsDescriptions(state), siteMetadata: getSiteMetadata(state), + permissionsHistory: getPermissionsHistory(state), + permissionsLog: getPermissionsLog(state), } } @@ -26,6 +32,12 @@ const mapDispatchToProps = dispatch => { showClearPermissionsModal: () => dispatch( showModal({ name: 'CLEAR_PERMISSIONS' }) ), + showClearPermissionsActivityModal: () => dispatch( + showModal({ name: 'CLEAR_PERMISSIONS_ACTIVITY' }) + ), + showClearPermissionsHistoryModal: () => dispatch( + showModal({ name: 'CLEAR_PERMISSIONS_HISTORY' }) + ), removePermissionsFor: (domains) => dispatch( removePermissionsFor(domains) ), diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 82896037f361..58a21b51edbb 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -55,6 +55,8 @@ const selectors = { getAllPermissions, getPermissionsRequests, getPermissionsDescriptions, + getPermissionsHistory, + getPermissionsLog, getSiteMetadata, getActiveTab, getMetaMetricState, @@ -357,6 +359,14 @@ function getPermissionsRequests (state) { return state.metamask.permissionsRequests } +function getPermissionsHistory (state) { + return state.metamask.permissionsHistory +} + +function getPermissionsLog (state) { + return state.metamask.permissionsLog +} + function getSiteMetadata (state) { return state.metamask.siteMetadata } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index fdb7c3d27560..1e1b012bd017 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -346,9 +346,11 @@ var actions = { // Permissions approvePermissionsRequest, + clearPermissions, + clearPermissionsHistory, + clearPermissionsLog, rejectPermissionsRequest, removePermissionsFor, - clearPermissions, selectApprovedAccount, setFirstTimeFlowType, @@ -2697,6 +2699,24 @@ function clearPermissions () { } } +/** + * Clears the permission log. + */ +function clearPermissionsHistory () { + return () => { + background.clearPermissionsHistory() + } +} + +/** + * Clears the permission log. + */ +function clearPermissionsLog () { + return () => { + background.clearPermissionsLog() + } +} + // //// function setFirstTimeFlowType (type) { diff --git a/yarn.lock b/yarn.lock index 5a9520426bb4..71786f63a6e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13885,6 +13885,11 @@ interpret@^1.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== +intersect-objects@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/intersect-objects/-/intersect-objects-1.0.0.tgz#b7630d28994b89b0f04d44728106136549ce816e" + integrity sha512-MS1xypHKJhWopnJgn4IbitVvt2vFy2KjINQJAPhAtDejZ+ZbMDfyPc6JsS/mWFRt9Eoku4A4usE4f2loEOoeKQ== + into-stream@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" @@ -13893,11 +13898,6 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -intersect-objects@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/intersect-objects/-/intersect-objects-1.0.0.tgz#b7630d28994b89b0f04d44728106136549ce816e" - integrity sha512-MS1xypHKJhWopnJgn4IbitVvt2vFy2KjINQJAPhAtDejZ+ZbMDfyPc6JsS/mWFRt9Eoku4A4usE4f2loEOoeKQ== - invariant@2.2.4, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -15518,6 +15518,16 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" +json-rpc-engine@^5.0.0: + version "5.1.4" + resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.4.tgz#c18d1959eb175049fa7301d4866931ae2f879e47" + integrity sha512-nBFWYJ1mvlZL7gqq0M9230SxedL9CbSYO1WgrFi/C1Zo+ZrHUZWLRbr7fUdlLt9TC0G+sf/aEUeuJjR2lHsMvA== + dependencies: + async "^2.0.1" + eth-json-rpc-errors "^1.1.0" + promise-to-callback "^1.0.0" + safe-event-emitter "^1.0.1" + json-rpc-engine@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.3.tgz#d7410b649e107ed3437db33797f44c51d507002c" @@ -17897,14 +17907,14 @@ metamask-inpage-provider@rekmarks/metamask-inpage-provider#permissions: version "2.0.1" resolved "https://codeload.github.com/rekmarks/metamask-inpage-provider/tar.gz/27f7c52a57c823dc96282b590c078deaf80c935c" dependencies: - json-rpc-engine "^5.1.3" + json-rpc-engine "^5.0.0" json-rpc-middleware-stream "^2.1.1" loglevel "^1.6.1" obj-multiplex "^1.0.0" - obs-store "^4.0.3" + obs-store "^4.0.1" pump "^3.0.0" safe-event-emitter "^1.0.1" - uuid "^3.3.2" + through2 "^2.0.3" metamask-logo@^2.1.4: version "2.1.4" @@ -19356,7 +19366,7 @@ oboe@2.1.3: dependencies: http-https "^1.0.0" -obs-store@^4.0.3: +obs-store@^4.0.1, obs-store@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/obs-store/-/obs-store-4.0.3.tgz#b632ec7814baa604fae084a4c97e87c0b7a6d14c" integrity sha512-+mm13kCRDv6IcvUDKTw0LIy5+dQhIktYaR/RwwZUFzOTi/fjMaNBnk42Adb94qZqJ00qWkjhQSZH7MXlKnTi8A== @@ -23372,6 +23382,11 @@ safe-json-parse@~1.0.1: resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c= +safe-json-stringify@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"