[FLASK] `snaps-skunkworks@0.20.0` (#15706)

* snaps-skunkworks@0.20.0

* Generate LavaMoat policy

* Fix some breaking changes

* Update iframe execution env

* Fix unit tests

* Implement snap_getBip44Entropy

* Regenerate LavaMoat policy

* Prefer ControllerMessenger over direct calls

* Fix not showing warning for BIP44 legacy permission and E2E test

Co-authored-by: Maarten Zuidhoorn <maarten@zuidhoorn.com>
feature/default_network_editable
Frederik Bolding 2 years ago committed by GitHub
parent fe78890dd2
commit 7fc418a96d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      app/scripts/controllers/permissions/specifications.test.js
  2. 34
      app/scripts/metamask-controller.js
  3. 116
      lavamoat/browserify/flask/policy.json
  4. 5
      package.json
  5. 2
      shared/constants/permissions.ts
  6. 2
      test/e2e/snaps/test-snap-bip-44.spec.js
  7. 14
      ui/helpers/utils/permission.js
  8. 29
      ui/pages/permissions-connect/flask/snap-install/snap-install.js
  9. 63
      yarn.lock

@ -1,3 +1,4 @@
import { SnapCaveatType } from '@metamask/rpc-methods';
import {
CaveatTypes,
RestrictedMethods,
@ -15,14 +16,16 @@ describe('PermissionController specifications', () => {
describe('caveat specifications', () => {
it('getCaveatSpecifications returns the expected specifications object', () => {
const caveatSpecifications = getCaveatSpecifications({});
expect(Object.keys(caveatSpecifications)).toHaveLength(2);
expect(Object.keys(caveatSpecifications)).toHaveLength(3);
expect(
caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type,
).toStrictEqual(CaveatTypes.restrictReturnedAccounts);
// TODO: Use `SnapCaveatType` from `rpc-methods` when it's exported.
expect(caveatSpecifications.permittedDerivationPaths.type).toStrictEqual(
'permittedDerivationPaths',
SnapCaveatType.PermittedDerivationPaths,
);
expect(caveatSpecifications.permittedCoinTypes.type).toStrictEqual(
SnapCaveatType.PermittedCoinTypes,
);
});

@ -649,7 +649,7 @@ export default class MetamaskController extends EventEmitter {
///: BEGIN:ONLY_INCLUDE_IN(flask)
this.snapExecutionService = new IframeExecutionService({
iframeUrl: new URL(
'https://metamask.github.io/iframe-execution-environment/0.6.0',
'https://metamask.github.io/iframe-execution-environment/0.7.0',
),
messenger: this.controllerMessenger.getRestricted({
name: 'ExecutionService',
@ -1174,7 +1174,7 @@ export default class MetamaskController extends EventEmitter {
),
handleSnapRpcRequest: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:handleRpcRequest',
'SnapController:handleRequest',
),
getSnapState: this.controllerMessenger.call.bind(
this.controllerMessenger,
@ -1849,12 +1849,22 @@ export default class MetamaskController extends EventEmitter {
///: BEGIN:ONLY_INCLUDE_IN(flask)
// snaps
removeSnapError: this.snapController.removeSnapError.bind(
this.snapController,
removeSnapError: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:removeSnapError',
),
disableSnap: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:disable',
),
enableSnap: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:enable',
),
removeSnap: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:remove',
),
disableSnap: this.snapController.disableSnap.bind(this.snapController),
enableSnap: this.snapController.enableSnap.bind(this.snapController),
removeSnap: this.snapController.removeSnap.bind(this.snapController),
dismissNotifications: this.dismissNotifications.bind(this),
markNotificationsAsRead: this.markNotificationsAsRead.bind(this),
///: END:ONLY_INCLUDE_IN
@ -3760,8 +3770,9 @@ export default class MetamaskController extends EventEmitter {
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
this.appStateController,
),
getSnaps: this.snapController.getPermittedSnaps.bind(
this.snapController,
getSnaps: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:getSnaps',
origin,
),
requestPermissions: async (requestedPermissions) => {
@ -3778,8 +3789,9 @@ export default class MetamaskController extends EventEmitter {
origin,
),
getAccounts: this.getPermittedAccounts.bind(this, origin),
installSnaps: this.snapController.installSnaps.bind(
this.snapController,
installSnaps: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:install',
origin,
),
}),

@ -3350,12 +3350,14 @@
}
},
"@metamask/rpc-methods": {
"globals": {
"console.warn": true
},
"packages": {
"@metamask/controllers": true,
"@metamask/rpc-methods>@metamask/key-tree": true,
"@metamask/rpc-methods>@metamask/snap-utils": true,
"@metamask/rpc-methods>@metamask/utils": true,
"@metamask/snap-controllers": true,
"@metamask/snap-utils": true,
"@metamask/snap-utils>@metamask/utils": true,
"eth-rpc-errors": true
}
},
@ -3404,34 +3406,6 @@
"@metamask/rpc-methods>@metamask/key-tree>@scure/base": true
}
},
"@metamask/rpc-methods>@metamask/snap-utils": {
"globals": {
"URL": true
},
"packages": {
"@babel/core": true,
"@babel/core>@babel/types": true,
"@metamask/rpc-methods>@metamask/snap-utils>ajv": true,
"@metamask/rpc-methods>@metamask/snap-utils>rfdc": true,
"browserify": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
"browserify>events": true,
"browserify>path-browserify": true,
"eslint>fast-deep-equal": true,
"semver": true
}
},
"@metamask/rpc-methods>@metamask/snap-utils>rfdc": {
"packages": {
"browserify>buffer": true
}
},
"@metamask/rpc-methods>@metamask/utils": {
"packages": {
"eslint>fast-deep-equal": true
}
},
"@metamask/smart-transactions-controller": {
"globals": {
"URLSearchParams": true,
@ -3486,11 +3460,8 @@
"packages": {
"@metamask/controllers": true,
"@metamask/providers>@metamask/object-multiplex": true,
"@metamask/rpc-methods>@metamask/snap-utils": true,
"@metamask/rpc-methods>@metamask/utils": true,
"@metamask/rpc-methods": true,
"@metamask/snap-controllers>@metamask/browser-passworder": true,
"@metamask/snap-controllers>@metamask/execution-environments": true,
"@metamask/snap-controllers>@metamask/obs-store": true,
"@metamask/snap-controllers>@metamask/post-message-stream": true,
"@metamask/snap-controllers>@xstate/fsm": true,
"@metamask/snap-controllers>concat-stream": true,
@ -3499,9 +3470,10 @@
"@metamask/snap-controllers>nanoid": true,
"@metamask/snap-controllers>readable-web-to-node-stream": true,
"@metamask/snap-controllers>tar-stream": true,
"@metamask/snap-utils": true,
"@metamask/snap-utils>@metamask/utils": true,
"eth-rpc-errors": true,
"json-rpc-engine": true,
"json-rpc-engine>@metamask/safe-event-emitter": true,
"pump": true
}
},
@ -3518,46 +3490,6 @@
"browserify>buffer": true
}
},
"@metamask/snap-controllers>@metamask/obs-store": {
"packages": {
"@metamask/snap-controllers>@metamask/obs-store>through2": true,
"browserify>stream-browserify": true,
"json-rpc-engine>@metamask/safe-event-emitter": true
}
},
"@metamask/snap-controllers>@metamask/obs-store>through2": {
"packages": {
"@metamask/snap-controllers>@metamask/obs-store>through2>readable-stream": true,
"browserify>process": true,
"browserify>util": true,
"watchify>xtend": true
}
},
"@metamask/snap-controllers>@metamask/obs-store>through2>readable-stream": {
"packages": {
"@metamask/snap-controllers>@metamask/obs-store>through2>readable-stream>process-nextick-args": true,
"@metamask/snap-controllers>@metamask/obs-store>through2>readable-stream>string_decoder": true,
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": true,
"@storybook/api>util-deprecate": true,
"browserify>browser-resolve": true,
"browserify>events": true,
"browserify>process": true,
"browserify>timers-browserify": true,
"pumpify>inherits": true,
"readable-stream>core-util-is": true,
"readable-stream>isarray": true
}
},
"@metamask/snap-controllers>@metamask/obs-store>through2>readable-stream>process-nextick-args": {
"packages": {
"browserify>process": true
}
},
"@metamask/snap-controllers>@metamask/obs-store>through2>readable-stream>string_decoder": {
"packages": {
"@metamask/snap-controllers>json-rpc-middleware-stream>readable-stream>safe-buffer": true
}
},
"@metamask/snap-controllers>@metamask/post-message-stream": {
"globals": {
"WorkerGlobalScope": true,
@ -3568,8 +3500,8 @@
"removeEventListener": true
},
"packages": {
"@metamask/rpc-methods>@metamask/utils": true,
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream": true
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream": true,
"@metamask/snap-utils>@metamask/utils": true
}
},
"@metamask/snap-controllers>@metamask/post-message-stream>readable-stream": {
@ -3784,6 +3716,34 @@
"pumpify>inherits": true
}
},
"@metamask/snap-utils": {
"globals": {
"URL": true
},
"packages": {
"@babel/core": true,
"@babel/core>@babel/types": true,
"@metamask/snap-utils>ajv": true,
"@metamask/snap-utils>rfdc": true,
"browserify": true,
"browserify>buffer": true,
"browserify>crypto-browserify": true,
"browserify>events": true,
"browserify>path-browserify": true,
"eslint>fast-deep-equal": true,
"semver": true
}
},
"@metamask/snap-utils>@metamask/utils": {
"packages": {
"eslint>fast-deep-equal": true
}
},
"@metamask/snap-utils>rfdc": {
"packages": {
"browserify>buffer": true
}
},
"@ngraveio/bc-ur": {
"packages": {
"@ngraveio/bc-ur>@apocentre/alias-sampling": true,

@ -133,10 +133,11 @@
"@metamask/obs-store": "^5.0.0",
"@metamask/post-message-stream": "^4.0.0",
"@metamask/providers": "^9.0.0",
"@metamask/rpc-methods": "^0.19.0",
"@metamask/rpc-methods": "^0.20.0",
"@metamask/slip44": "^2.1.0",
"@metamask/smart-transactions-controller": "^2.3.1",
"@metamask/snap-controllers": "^0.19.0",
"@metamask/snap-controllers": "^0.20.0",
"@metamask/snap-utils": "^0.20.0",
"@ngraveio/bc-ur": "^1.1.6",
"@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.6.2",

@ -9,6 +9,7 @@ export const RestrictedMethods = Object.freeze({
snap_notify: 'snap_notify',
snap_manageState: 'snap_manageState',
snap_getBip32Entropy: 'snap_getBip32Entropy',
snap_getBip44Entropy: 'snap_getBip44Entropy',
'snap_getBip44Entropy_*': 'snap_getBip44Entropy_*',
'wallet_snap_*': 'wallet_snap_*',
///: END:ONLY_INCLUDE_IN
@ -23,6 +24,7 @@ export const PermissionNamespaces = Object.freeze({
export const EndowmentPermissions = Object.freeze({
'endowment:network-access': 'endowment:network-access',
'endowment:long-running': 'endowment:long-running',
'endowment:transaction-insight': 'endowment:transaction-insight',
} as const);
// Methods / permissions in external packages that we are temporarily excluding.

@ -69,7 +69,7 @@ describe('Test Snap bip-44', function () {
});
// deal with permissions popover
await driver.delay(1000);
await driver.press('#key-access-bip44-0', driver.Key.SPACE);
await driver.press('#key-access-bip44-legacy-0', driver.Key.SPACE);
await driver.clickElement({
text: 'Confirm',
tag: 'button',

@ -46,6 +46,20 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
leftIcon: 'fas fa-door-open',
rightIcon: null,
},
[RestrictedMethods.snap_getBip44Entropy]: {
label: (t, _, permissionValue) => {
return permissionValue.caveats[0].value.map(({ coinType }) =>
t('permission_manageBip44Keys', [
<span className="permission-label-item" key={`coin-type-${coinType}`}>
{coinTypeToProtocolName(coinType) ||
`${coinType} (Unrecognized protocol)`}
</span>,
]),
);
},
leftIcon: 'fas fa-door-open',
rightIcon: null,
},
[RestrictedMethods['snap_getBip44Entropy_*']]: {
label: (t, permissionName) => {
const coinType = permissionName.split('_').slice(-1);

@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import { flatMap } from '@metamask/snap-utils';
import { PageContainerFooter } from '../../../../components/ui/page-container';
import PermissionsConnectPermissionList from '../../../../components/app/permissions-connect-permission-list';
import PermissionsConnectFooter from '../../../../components/app/permissions-connect-footer';
@ -38,7 +39,7 @@ export default function SnapInstall({
[request, approveSnapInstall],
);
const bip44EntropyPermissions =
const bip44LegacyEntropyPermissions =
request.permissions &&
Object.keys(request.permissions).filter((v) =>
v.startsWith('snap_getBip44Entropy_'),
@ -50,8 +51,16 @@ export default function SnapInstall({
.filter(([key]) => key === 'snap_getBip32Entropy')
.map(([, value]) => value);
const bip44EntropyPermissions =
request.permissions &&
Object.entries(request.permissions)
.filter(([key]) => key === 'snap_getBip44Entropy')
.map(([, value]) => value);
const shouldShowWarning =
bip32EntropyPermissions?.length > 0 || bip44EntropyPermissions?.length > 0;
bip32EntropyPermissions?.length > 0 ||
bip44EntropyPermissions?.length > 0 ||
bip44LegacyEntropyPermissions?.length > 0;
const getCoinType = (bip44EntropyPermission) =>
bip44EntropyPermission?.split('_').slice(-1);
@ -115,7 +124,7 @@ export default function SnapInstall({
onCancel={() => setIsShowingWarning(false)}
onSubmit={onSubmit}
warnings={[
...bip32EntropyPermissions.flatMap((permission, i) =>
...flatMap(bip32EntropyPermissions, (permission, i) =>
permission.caveats[0].value.map(({ path, curve }) => ({
id: `key-access-bip32-${path.join('/')}-${curve}-${i}`,
message: t('snapInstallWarningKeyAccess', [
@ -124,10 +133,20 @@ export default function SnapInstall({
]),
})),
),
...bip44EntropyPermissions.map((permission, i) => {
...flatMap(bip44EntropyPermissions, (permission, i) =>
permission.caveats[0].value.map(({ coinType }) => ({
id: `key-access-bip44-${coinType}-${i}`,
message: t('snapInstallWarningKeyAccess', [
targetSubjectMetadata.name,
coinTypeToProtocolName(coinType) ||
t('unrecognizedProtocol', [coinType]),
]),
})),
),
...bip44LegacyEntropyPermissions.map((permission, i) => {
const coinType = getCoinType(permission);
return {
id: `key-access-bip44-${i}`,
id: `key-access-bip44-legacy-${i}`,
message: t('snapInstallWarningKeyAccess', [
targetSubjectMetadata.name,
coinTypeToProtocolName(coinType) ||

@ -3048,15 +3048,16 @@
resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.1.0.tgz#c0be8e68445b7b83cf85bcc03a56cdf8e256c973"
integrity sha512-ADuWlTUkFfN2vXlz81Bg/0BA+XRor+CdK1055p6k7H6BLIPoDKn9SBOFld9haQFuR9cKh/JYHcnlSIv5R4fUEw==
"@metamask/execution-environments@^0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@metamask/execution-environments/-/execution-environments-0.19.0.tgz#e2e269bdf66bcac96e6fb6a8b105b04c3ad906ca"
integrity sha512-nOUATeSwWY08CDTBBtYtt6EYtVCbiFiJuz64g1bkS+TS5cQte+w8fYgzRsOPXCMPkbqrO1+8RV+ZU6gujoAJHA==
"@metamask/execution-environments@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@metamask/execution-environments/-/execution-environments-0.20.0.tgz#f92689b8f79f72ff5ed61764e44d499074bbb32f"
integrity sha512-zlV1qT7I+bUZYVjo162INFu6dhwBnnO/4EXMzSPIeM/nedMp3gmCDE4L59ccpMxqGwdfuOTA2KHh9+gFKEnXdw==
dependencies:
"@metamask/object-multiplex" "^1.2.0"
"@metamask/post-message-stream" "^6.0.0"
"@metamask/providers" "^9.0.0"
"@metamask/snap-types" "^0.19.0"
"@metamask/snap-types" "^0.20.0"
"@metamask/snap-utils" "^0.20.0"
"@metamask/utils" "^2.0.0"
eth-rpc-errors "^4.0.3"
pump "^3.0.0"
@ -3179,15 +3180,14 @@
pump "^3.0.0"
webextension-polyfill-ts "^0.25.0"
"@metamask/rpc-methods@^0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.19.0.tgz#20a2dd159330fa1bac1999d59f4a0fec282bdeaa"
integrity sha512-YAr6T4Gy46VoJBD17l+Tc2WbHcMce+TQWDf+29ifroi0pN9BLKvAakdHKeQ12EoJl8NHSrJNoHCqweyLkGiNXQ==
"@metamask/rpc-methods@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.20.0.tgz#c0d3e6ea71bf9dbf69627307419393668ef736a6"
integrity sha512-YBTqrOWE6OOh9Jk870w/7FE56Sf+Xa73L/6GatPJJViL4vbpsVBc745qXRvmdbFlZb3kficNeILsadYg9Cp+Og==
dependencies:
"@metamask/controllers" "^30.0.0"
"@metamask/key-tree" "^4.0.0"
"@metamask/snap-controllers" "^0.19.0"
"@metamask/snap-utils" "^0.19.0"
"@metamask/snap-utils" "^0.20.0"
"@metamask/types" "^1.1.0"
"@metamask/utils" "^2.1.0"
eth-rpc-errors "^4.0.2"
@ -3215,24 +3215,21 @@
isomorphic-fetch "^3.0.0"
lodash "^4.17.21"
"@metamask/snap-controllers@^0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.19.0.tgz#6a17b38ec589b96f68a9c7d2747af4de34a05a3f"
integrity sha512-0062+XF+aGX3CEkHKtwj9BOmC8xBKvam+InkpuW7JxIbO5uiL2T+ZtJVkqUrs6q2ASRFV5aC4Jc4C5fJvUiIcg==
"@metamask/snap-controllers@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-controllers/-/snap-controllers-0.20.0.tgz#d4cc2717a4d6119f5af9a04569a3c6dd7f399153"
integrity sha512-zStPQSf7YUu84CbXg/UH3cYXRDkpVrl503bRBN/RpPyKntku8fxP9SSWq9BqNGLwYRKiXIjH+ogtYXyEh5LiBQ==
dependencies:
"@metamask/browser-passworder" "^3.0.0"
"@metamask/controllers" "^30.0.0"
"@metamask/execution-environments" "^0.19.0"
"@metamask/execution-environments" "^0.20.0"
"@metamask/object-multiplex" "^1.1.0"
"@metamask/obs-store" "^7.0.0"
"@metamask/post-message-stream" "^6.0.0"
"@metamask/safe-event-emitter" "^2.0.0"
"@metamask/snap-utils" "^0.19.0"
"@metamask/rpc-methods" "^0.20.0"
"@metamask/snap-utils" "^0.20.0"
"@metamask/utils" "^2.0.0"
"@types/deep-freeze-strict" "^1.1.0"
"@xstate/fsm" "^2.0.0"
concat-stream "^2.0.0"
deep-freeze-strict "^1.1.1"
eth-rpc-errors "^4.0.2"
gunzip-maybe "^1.4.2"
immer "^9.0.6"
@ -3243,21 +3240,24 @@
readable-web-to-node-stream "^3.0.2"
tar-stream "^2.2.0"
"@metamask/snap-types@^0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.19.0.tgz#ead590ecbb840cf2cd1c4feffbf8228ca87bc912"
integrity sha512-8huwKsiM05mG8/V8/zvJ3xy555P1RPk1fs1znbJaGTYk3HAJVW2i3d95iOcM6Cs+zbTwsrFfGnO2yqH31anPgA==
"@metamask/snap-types@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-types/-/snap-types-0.20.0.tgz#5ca3a79a3fbde0a7bb1214a80d8c4f43b7a7e22c"
integrity sha512-hYk1ruDolc46I+ebqpBRiL+hIfVMbrRZtHDlKiBwr3iFw73PPKacvrSUaI6rSCLeHKEwCF6IEkTfLxnGWJNYhw==
dependencies:
"@metamask/controllers" "^30.0.0"
"@metamask/snap-utils" "^0.20.0"
"@metamask/snap-utils@^0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-utils/-/snap-utils-0.19.0.tgz#226f4df5287996d3c09b1b139c30a7c9c78772d1"
integrity sha512-oPDrWbj3ggv/Tt09X1nDC0x42TLapHEYB1mR8/V2TULlcfzlL7oUDy/AxOA3sDDiX8hH2q3OA7Htcadj8PKlNw==
"@metamask/snap-utils@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@metamask/snap-utils/-/snap-utils-0.20.0.tgz#670a2f5b67381c3b944a44ee179ce0190fcdfe63"
integrity sha512-g5GnsQS/IhJM9uRNTzhwK5vBSm+eThFjX0AyOp72xXB1n0uJVL/XbE8wgJSdiniqOI+jaxMh5vHxhfd2FdqcmQ==
dependencies:
"@babel/core" "^7.18.6"
"@metamask/snap-types" "^0.20.0"
"@metamask/utils" "^2.0.0"
ajv "^8.11.0"
eth-rpc-errors "^4.0.3"
fast-deep-equal "^3.1.3"
rfdc "^1.3.0"
semver "^7.3.7"
@ -4782,11 +4782,6 @@
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
"@types/deep-freeze-strict@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/deep-freeze-strict/-/deep-freeze-strict-1.1.0.tgz#447a6a2576191344aa42310131dd3df5c41492c4"
integrity sha1-RHpqJXYZE0SqQjEBMd099cQUksQ=
"@types/end-of-stream@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@types/end-of-stream/-/end-of-stream-1.4.1.tgz#9a401b642bcb0e4a8f0b70326725fbbb0216eb10"

Loading…
Cancel
Save