Merge pull request #2407 from dragonchaser/backport-backchannel-logout
Backport backchannel logout
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"build": "node --openssl-legacy-provider scripts/build.js && rm -f build/service-worker.js",
|
||||
"licenses": "NODE_PATH=./node_modules node ../scripts/js-license-ranger.js",
|
||||
"licenses:check": "license-checker-rseidelsohn --summary --relativeLicensePath --onlyAllow 'Python-2.0;Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;Public Domain;Unicode-TOU;Unlicense;WTFPL;ODC-By-1.0;BlueOak-1.0.0;OFL-1.1' --excludePackages 'identifier;kpop;unicoderegexp' --clarificationsFile license-checker-clarifications.json",
|
||||
"licenses:check": "license-checker-rseidelsohn --summary --relativeLicensePath --onlyAllow 'Python-2.0;Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;Public Domain;Unicode-TOU;Unlicense;WTFPL;ODC-By-1.0;BlueOak-1.0.0;OFL-1.1' --excludePackages 'identifier;unicoderegexp' --clarificationsFile license-checker-clarifications.json",
|
||||
"licenses:csv": "license-checker-rseidelsohn --relativeLicensePath --csv --out ../../third-party-licenses/node/idp/third-party-licenses.csv",
|
||||
"licenses:save": "license-checker-rseidelsohn --relativeLicensePath --out /dev/null --files ../../third-party-licenses/node/idp/third-party-licenses",
|
||||
"lint": "eslint ./**/*.{tsx,ts,jsx,js}",
|
||||
@@ -89,7 +89,6 @@
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"kpop": "https://download.kopano.io/community/kapp:/kpop-2.7.2.tgz",
|
||||
"query-string": "^9.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
@@ -154,10 +153,5 @@
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"kpop>cldr": ""
|
||||
}
|
||||
}
|
||||
"packageManager": "pnpm@9.15.4"
|
||||
}
|
||||
|
||||
Generated
-151
@@ -4,9 +4,6 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
kpop>cldr: ''
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -65,9 +62,6 @@ importers:
|
||||
i18next-resources-to-backend:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
kpop:
|
||||
specifier: https://download.kopano.io/community/kapp:/kpop-2.7.2.tgz
|
||||
version: https://download.kopano.io/community/kapp:/kpop-2.7.2.tgz(@gluejs/glue@0.3.0)(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@material-ui/icons@4.11.3(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(notistack@0.8.9(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(oidc-client@1.11.5)(react-dom@17.0.2(react@17.0.2))(react-intl@2.9.0(prop-types@15.8.1)(react@17.0.2))(react@17.0.2)
|
||||
query-string:
|
||||
specifier: ^9.2.0
|
||||
version: 9.2.0
|
||||
@@ -1516,9 +1510,6 @@ packages:
|
||||
'@fontsource/roboto@5.2.5':
|
||||
resolution: {integrity: sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==}
|
||||
|
||||
'@gluejs/glue@0.3.0':
|
||||
resolution: {integrity: sha512-byvFoZCbZW+A3Pg8JUU+8FjoPuF5l1v7mDeLJQP/YSeEcEDiD/YdUKLBUapPrcuyxclrtS8+peX4cxkh6awwTw==}
|
||||
|
||||
'@gulpjs/to-absolute-glob@4.0.0':
|
||||
resolution: {integrity: sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -2724,9 +2715,6 @@ packages:
|
||||
core-js@3.40.0:
|
||||
resolution: {integrity: sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==}
|
||||
|
||||
core-js@3.43.0:
|
||||
resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
@@ -2738,11 +2726,6 @@ packages:
|
||||
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
crc32@0.2.2:
|
||||
resolution: {integrity: sha512-PFZEGbDUeoNbL2GHIEpJRQGheXReDody/9axKTxhXtQqIL443wnNigtVZO9iuCIMPApKZRv7k2xr8euXHqNxQQ==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
@@ -2754,9 +2737,6 @@ packages:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
crypto-js@4.2.0:
|
||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||
|
||||
crypto-random-string@2.0.0:
|
||||
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3718,9 +3698,6 @@ packages:
|
||||
resolution: {integrity: sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
hsv-rgb@1.0.0:
|
||||
resolution: {integrity: sha512-Azd6IP11LZm0cEczEnJw5B6zIgWdGlE4TSoM2eh+RPRbXSQCy/0JS2POEq0wOtbAZtxTJhEMGm3GUYGbnTIJGw==}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
@@ -3850,23 +3827,6 @@ packages:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
intl-format-cache@2.2.9:
|
||||
resolution: {integrity: sha512-Zv/u8wRpekckv0cLkwpVdABYST4hZNTDaX7reFetrYTJwxExR2VyTqQm+l0WmL0Qo8Mjb9Tf33qnfj0T7pjxdQ==}
|
||||
|
||||
intl-messageformat-parser@1.4.0:
|
||||
resolution: {integrity: sha512-/XkqFHKezO6UcF4Av2/Lzfrez18R0jyw7kRFhSeB/YRakdrgSc9QfFZUwNJI9swMwMoNPygK1ArC5wdFSjPw+A==}
|
||||
deprecated: We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser
|
||||
|
||||
intl-messageformat@2.2.0:
|
||||
resolution: {integrity: sha512-I+tSvHnXqJYjDfNmY95tpFMj30yoakC6OXAo+wu/wTMy6tA/4Fd4mvV7Uzs4cqK/Ap29sHhwjcY+78a8eifcXw==}
|
||||
|
||||
intl-relativeformat@2.2.0:
|
||||
resolution: {integrity: sha512-4bV/7kSKaPEmu6ArxXf9xjv1ny74Zkwuey8Pm01NH4zggPP7JHwg2STk8Y3JdspCKRDriwIyLRfEXnj2ZLr4Bw==}
|
||||
deprecated: This package has been deprecated, please see migration guide at 'https://github.com/formatjs/formatjs/tree/master/packages/intl-relativeformat#migration-guide'
|
||||
|
||||
invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
|
||||
is-arguments@1.2.0:
|
||||
resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4045,10 +4005,6 @@ packages:
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
iso-639-1@2.1.15:
|
||||
resolution: {integrity: sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4346,20 +4302,6 @@ packages:
|
||||
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
kpop@https://download.kopano.io/community/kapp:/kpop-2.7.2.tgz:
|
||||
resolution: {tarball: https://download.kopano.io/community/kapp:/kpop-2.7.2.tgz}
|
||||
version: 2.7.1
|
||||
engines: {node: '>=6.11.0'}
|
||||
peerDependencies:
|
||||
'@gluejs/glue': ^0.3.0
|
||||
'@material-ui/core': ^4.11.0
|
||||
'@material-ui/icons': ^4.9.1
|
||||
notistack: ^0.8.8
|
||||
oidc-client: ^1.11.0
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0
|
||||
react-intl: ^2.6.0
|
||||
|
||||
language-subtag-registry@0.3.23:
|
||||
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
|
||||
|
||||
@@ -4636,13 +4578,6 @@ packages:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
notistack@0.8.9:
|
||||
resolution: {integrity: sha512-nRHQVWUfgHnvnKrjRbRX9f+YAnbyh96yRyO5bEP/FCLVLuTZcJOwUr0GZ7Xr/8wK3+hXa9JYpXUkUhSxj1K8NQ==}
|
||||
peerDependencies:
|
||||
'@material-ui/core': ^3.2.0 || ^4.0.0
|
||||
react: ^16.8.0
|
||||
react-dom: ^16.8.0
|
||||
|
||||
now-and-later@3.0.0:
|
||||
resolution: {integrity: sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
@@ -4698,9 +4633,6 @@ packages:
|
||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
oidc-client@1.11.5:
|
||||
resolution: {integrity: sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
@@ -5364,12 +5296,6 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
react-intl@2.9.0:
|
||||
resolution: {integrity: sha512-27jnDlb/d2A7mSJwrbOBnUgD+rPep+abmoJE511Tf8BnoONIAUehy/U1zZCHGO17mnOwMWxqN4qC0nW11cD6rA==}
|
||||
peerDependencies:
|
||||
prop-types: ^15.5.4
|
||||
react: ^0.14.9 || ^15.0.0 || ^16.0.0
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -5698,9 +5624,6 @@ packages:
|
||||
seq@0.3.5:
|
||||
resolution: {integrity: sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g==}
|
||||
|
||||
serialize-javascript@4.0.0:
|
||||
resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==}
|
||||
|
||||
serialize-javascript@6.0.2:
|
||||
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
||||
|
||||
@@ -7918,8 +7841,6 @@ snapshots:
|
||||
|
||||
'@fontsource/roboto@5.2.5': {}
|
||||
|
||||
'@gluejs/glue@0.3.0': {}
|
||||
|
||||
'@gulpjs/to-absolute-glob@4.0.0':
|
||||
dependencies:
|
||||
is-negated-glob: 1.0.0
|
||||
@@ -9403,8 +9324,6 @@ snapshots:
|
||||
|
||||
core-js@3.40.0: {}
|
||||
|
||||
core-js@3.43.0: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cosmiconfig@6.0.0:
|
||||
@@ -9423,8 +9342,6 @@ snapshots:
|
||||
path-type: 4.0.0
|
||||
yaml: 1.10.2
|
||||
|
||||
crc32@0.2.2: {}
|
||||
|
||||
cross-fetch@4.0.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
@@ -9443,8 +9360,6 @@ snapshots:
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
crypto-js@4.2.0: {}
|
||||
|
||||
crypto-random-string@2.0.0: {}
|
||||
|
||||
css-blank-pseudo@7.0.1(postcss@8.5.4):
|
||||
@@ -10683,8 +10598,6 @@ snapshots:
|
||||
dependencies:
|
||||
lru-cache: 7.18.3
|
||||
|
||||
hsv-rgb@1.0.0: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-minifier-terser@6.1.0:
|
||||
@@ -10833,22 +10746,6 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
side-channel: 1.1.0
|
||||
|
||||
intl-format-cache@2.2.9: {}
|
||||
|
||||
intl-messageformat-parser@1.4.0: {}
|
||||
|
||||
intl-messageformat@2.2.0:
|
||||
dependencies:
|
||||
intl-messageformat-parser: 1.4.0
|
||||
|
||||
intl-relativeformat@2.2.0:
|
||||
dependencies:
|
||||
intl-messageformat: 2.2.0
|
||||
|
||||
invariant@2.2.4:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
is-arguments@1.2.0:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -11007,8 +10904,6 @@ snapshots:
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
iso-639-1@2.1.15: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-instrument@6.0.3:
|
||||
@@ -11539,20 +11434,6 @@ snapshots:
|
||||
|
||||
klona@2.0.6: {}
|
||||
|
||||
kpop@https://download.kopano.io/community/kapp:/kpop-2.7.2.tgz(@gluejs/glue@0.3.0)(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@material-ui/icons@4.11.3(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(notistack@0.8.9(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(oidc-client@1.11.5)(react-dom@17.0.2(react@17.0.2))(react-intl@2.9.0(prop-types@15.8.1)(react@17.0.2))(react@17.0.2):
|
||||
dependencies:
|
||||
'@gluejs/glue': 0.3.0
|
||||
'@material-ui/core': 4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)
|
||||
'@material-ui/icons': 4.11.3(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)
|
||||
crc32: 0.2.2
|
||||
hsv-rgb: 1.0.0
|
||||
iso-639-1: 2.1.15
|
||||
notistack: 0.8.9(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)
|
||||
oidc-client: 1.11.5
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
react-intl: 2.9.0(prop-types@15.8.1)(react@17.0.2)
|
||||
|
||||
language-subtag-registry@0.3.23: {}
|
||||
|
||||
language-tags@1.0.9:
|
||||
@@ -11787,16 +11668,6 @@ snapshots:
|
||||
|
||||
normalize-range@0.1.2: {}
|
||||
|
||||
notistack@0.8.9(@material-ui/core@4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2):
|
||||
dependencies:
|
||||
'@material-ui/core': 4.12.4(@types/react@17.0.80)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)
|
||||
classnames: 2.5.1
|
||||
hoist-non-react-statics: 3.3.2
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
react-is: 16.13.1
|
||||
|
||||
now-and-later@3.0.0:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
@@ -11860,14 +11731,6 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
oidc-client@1.11.5:
|
||||
dependencies:
|
||||
acorn: 7.4.1
|
||||
base64-js: 1.5.1
|
||||
core-js: 3.43.0
|
||||
crypto-js: 4.2.0
|
||||
serialize-javascript: 4.0.0
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
@@ -12599,16 +12462,6 @@ snapshots:
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
typescript: 5.8.3
|
||||
|
||||
react-intl@2.9.0(prop-types@15.8.1)(react@17.0.2):
|
||||
dependencies:
|
||||
hoist-non-react-statics: 3.3.2
|
||||
intl-format-cache: 2.2.9
|
||||
intl-messageformat: 2.2.0
|
||||
intl-relativeformat: 2.2.0
|
||||
invariant: 2.2.4
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@17.0.2: {}
|
||||
@@ -12975,10 +12828,6 @@ snapshots:
|
||||
chainsaw: 0.0.9
|
||||
hashish: 0.0.4
|
||||
|
||||
serialize-javascript@4.0.0:
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
|
||||
serialize-javascript@6.0.2:
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
|
||||
@@ -2,10 +2,7 @@ import React, {ReactElement, Suspense, lazy, useState, useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {MuiThemeProvider} from '@material-ui/core/styles';
|
||||
import {defaultTheme} from 'kpop/es/theme';
|
||||
|
||||
import 'kpop/static/css/base.css';
|
||||
import 'kpop/static/css/scrollbar.css';
|
||||
import muiTheme from './theme';
|
||||
|
||||
import Spinner from './components/Spinner';
|
||||
import * as version from './version';
|
||||
@@ -52,7 +49,7 @@ const App = ({ bgImg }): ReactElement => {
|
||||
className={`oc-login-bg ${bgImg ? 'oc-login-bg-image' : ''}`}
|
||||
style={{backgroundImage: bgImg ? `url(${bgImg})` : undefined}}
|
||||
>
|
||||
<MuiThemeProvider theme={defaultTheme}>
|
||||
<MuiThemeProvider theme={muiTheme}>
|
||||
<Suspense fallback={<Spinner/>}>
|
||||
<LazyMain/>
|
||||
</Suspense>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* additional css on top of kpop */
|
||||
@font-face {
|
||||
font-family: OpenCloud;
|
||||
src: url('./fonts/OpenCloud500-Regular.woff2') format('woff2');
|
||||
@@ -17,16 +16,25 @@
|
||||
html {
|
||||
font-feature-settings: "cv11";
|
||||
color: #20434f !important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: OpenCloud, sans-serif;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.oc-font-weight-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@ -12,3 +12,8 @@ packages:
|
||||
github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles:
|
||||
interfaces:
|
||||
UserRoleAssigner: {}
|
||||
go-micro.dev/v4/store:
|
||||
config:
|
||||
dir: pkg/staticroutes/internal/backchannellogout/mocks
|
||||
interfaces:
|
||||
Store: {}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/justinas/alice"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
|
||||
"github.com/opencloud-eu/opencloud/pkg/generators"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -73,6 +74,7 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
microstore.Nodes(cfg.PreSignedURL.SigningKeys.Nodes...),
|
||||
microstore.Database("proxy"),
|
||||
microstore.Table("signing-keys"),
|
||||
store.DisablePersistence(cfg.PreSignedURL.SigningKeys.DisablePersistence),
|
||||
store.Authentication(cfg.PreSignedURL.SigningKeys.AuthUsername, cfg.PreSignedURL.SigningKeys.AuthPassword),
|
||||
)
|
||||
|
||||
|
||||
@@ -8,14 +8,16 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/oidc"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
store "go-micro.dev/v4/store"
|
||||
"go-micro.dev/v4/store"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"golang.org/x/oauth2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/oidc"
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/staticroutes"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -114,16 +116,25 @@ func (m *OIDCAuthenticator) getClaims(token string, req *http.Request) (map[stri
|
||||
m.Logger.Error().Err(err).Msg("failed to write to userinfo cache")
|
||||
}
|
||||
|
||||
if sid := aClaims.SessionID; sid != "" {
|
||||
// reuse user cache for session id lookup
|
||||
err = m.userInfoCache.Write(&store.Record{
|
||||
Key: sid,
|
||||
Value: []byte(encodedHash),
|
||||
Expiry: time.Until(expiration),
|
||||
})
|
||||
if err != nil {
|
||||
m.Logger.Error().Err(err).Msg("failed to write session lookup cache")
|
||||
}
|
||||
// fail if creating the storage key fails,
|
||||
// it means there is no subject and no session.
|
||||
//
|
||||
// ok: {key: ".sessionId"}
|
||||
// ok: {key: "subject."}
|
||||
// ok: {key: "subject.sessionId"}
|
||||
// fail: {key: "."}
|
||||
subjectSessionKey, err := staticroutes.NewRecordKey(aClaims.Subject, aClaims.SessionID)
|
||||
if err != nil {
|
||||
m.Logger.Error().Err(err).Msg("failed to build subject.session")
|
||||
return
|
||||
}
|
||||
|
||||
if err := m.userInfoCache.Write(&store.Record{
|
||||
Key: subjectSessionKey,
|
||||
Value: []byte(encodedHash),
|
||||
Expiry: time.Until(expiration),
|
||||
}); err != nil {
|
||||
m.Logger.Error().Err(err).Msg("failed to write session lookup cache")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -6,17 +6,40 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/opencloud-eu/opencloud/pkg/oidc"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
bcl "github.com/opencloud-eu/opencloud/services/proxy/pkg/staticroutes/internal/backchannellogout"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
// handle backchannel logout requests as per https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRequest
|
||||
// NewRecordKey converts the subject and session to a base64 encoded key
|
||||
var NewRecordKey = bcl.NewKey
|
||||
|
||||
// backchannelLogout handles backchannel logout requests from the identity provider and invalidates the related sessions in the cache
|
||||
// spec: https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRequest
|
||||
//
|
||||
// known side effects of backchannel logout in keycloak:
|
||||
//
|
||||
// - keyCloak "Sign out all active sessions" does not send a backchannel logout request,
|
||||
// as the devs mention, this may lead to thousands of backchannel logout requests,
|
||||
// therefore, they recommend a short token lifetime.
|
||||
// https://github.com/keycloak/keycloak/issues/27342#issuecomment-2408461913
|
||||
//
|
||||
// - keyCloak user self-service portal, "Sign out all devices" may not send a backchannel
|
||||
// logout request for each session, it's not mentionex explicitly,
|
||||
// but maybe the reason for that is the same as for "Sign out all active sessions"
|
||||
// to prevent a flood of backchannel logout requests.
|
||||
//
|
||||
// - if the keycloak setting "Backchannel logout session required" is disabled (or the token has no session id),
|
||||
// we resolve the session by the subject which can lead to multiple session records (subject.*),
|
||||
// we then send a logout event (sse) to each connected client and delete our stored cache record (subject.session & claim).
|
||||
// all sessions besides the one that triggered the backchannel logout continue to exist in the identity provider,
|
||||
// so the user will not be fully logged out until all sessions are logged out or expired.
|
||||
// this leads to the situation that web renders the logout view even if the instance is not fully logged out yet.
|
||||
func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Request) {
|
||||
// parse the application/x-www-form-urlencoded POST request
|
||||
logger := s.Logger.SubloggerWithRequestID(r.Context())
|
||||
if err := r.ParseForm(); err != nil {
|
||||
logger.Warn().Err(err).Msg("ParseForm failed")
|
||||
@@ -27,45 +50,86 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
|
||||
|
||||
logoutToken, err := s.OidcClient.VerifyLogoutToken(r.Context(), r.PostFormValue("logout_token"))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("VerifyLogoutToken failed")
|
||||
msg := "failed to verify logout token"
|
||||
logger.Warn().Err(err).Msg(msg)
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: msg})
|
||||
return
|
||||
}
|
||||
|
||||
records, err := s.UserInfoCache.Read(logoutToken.SessionId)
|
||||
if errors.Is(err, microstore.ErrNotFound) || len(records) == 0 {
|
||||
lookupKey, err := bcl.NewKey(logoutToken.Subject, logoutToken.SessionId)
|
||||
if err != nil {
|
||||
msg := "failed to build key from logout token"
|
||||
logger.Warn().Err(err).Msg(msg)
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: msg})
|
||||
return
|
||||
}
|
||||
|
||||
requestSubjectAndSession, err := bcl.NewSuSe(lookupKey)
|
||||
if err != nil {
|
||||
msg := "failed to build subjec.session from lookupKey"
|
||||
logger.Error().Err(err).Msg(msg)
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: msg})
|
||||
return
|
||||
}
|
||||
|
||||
lookupRecords, err := bcl.GetLogoutRecords(requestSubjectAndSession, s.UserInfoCache)
|
||||
if errors.Is(err, microstore.ErrNotFound) || len(lookupRecords) == 0 {
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, nil)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error reading userinfo cache")
|
||||
msg := "failed to read userinfo cache"
|
||||
logger.Error().Err(err).Msg(msg)
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: msg})
|
||||
return
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err := s.publishBackchannelLogoutEvent(r.Context(), record, logoutToken)
|
||||
for _, record := range lookupRecords {
|
||||
// the record key is in the format "subject.session" or ".session"
|
||||
// the record value is the key of the record that contains the claim in its value
|
||||
key, value := record.Key, string(record.Value)
|
||||
|
||||
subjectSession, err := bcl.NewSuSe(key)
|
||||
if err != nil {
|
||||
s.Logger.Warn().Err(err).Msg("could not publish backchannel logout event")
|
||||
// never leak any key-related information
|
||||
logger.Warn().Err(err).Msgf("failed to parse key: %s", key)
|
||||
continue
|
||||
}
|
||||
err = s.UserInfoCache.Delete(string(record.Value))
|
||||
|
||||
session, err := subjectSession.Session()
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msgf("failed to read session for: %s", key)
|
||||
continue
|
||||
}
|
||||
|
||||
if requestSubjectAndSession.Mode() == bcl.LogoutModeSession {
|
||||
if err := s.publishBackchannelLogoutEvent(r.Context(), session, value); err != nil {
|
||||
s.Logger.Warn().Err(err).Msgf("failed to publish backchannel logout event for: %s", key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err = s.UserInfoCache.Delete(value)
|
||||
if err != nil && !errors.Is(err, microstore.ErrNotFound) {
|
||||
// Spec requires us to return a 400 BadRequest when the session could not be destroyed
|
||||
logger.Err(err).Msg("could not delete user info from cache")
|
||||
// we have to return a 400 BadRequest when we fail to delete the session
|
||||
// https://openid.net/specs/openid-connect-backchannel-1_0.html#rfc.section.2.8
|
||||
msg := "failed to delete record"
|
||||
s.Logger.Warn().Err(err).Msgf("%s for: %s", msg, key)
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: msg})
|
||||
return
|
||||
}
|
||||
logger.Debug().Msg("Deleted userinfo from cache")
|
||||
}
|
||||
|
||||
// we can ignore errors when cleaning up the lookup table
|
||||
err = s.UserInfoCache.Delete(logoutToken.SessionId)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("Failed to cleanup sessionid lookup entry")
|
||||
// we can ignore errors when deleting the lookup record
|
||||
err = s.UserInfoCache.Delete(key)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msgf("failed to delete record for: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
@@ -73,41 +137,42 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// publishBackchannelLogoutEvent publishes a backchannel logout event when the callback revived from the identity provider
|
||||
func (s StaticRouteHandler) publishBackchannelLogoutEvent(ctx context.Context, record *microstore.Record, logoutToken *oidc.LogoutToken) error {
|
||||
func (s *StaticRouteHandler) publishBackchannelLogoutEvent(ctx context.Context, sessionId, claimKey string) error {
|
||||
if s.EventsPublisher == nil {
|
||||
return fmt.Errorf("the events publisher is not set")
|
||||
return errors.New("events publisher not set")
|
||||
}
|
||||
urecords, err := s.UserInfoCache.Read(string(record.Value))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading userinfo cache: %w", err)
|
||||
}
|
||||
if len(urecords) == 0 {
|
||||
return fmt.Errorf("userinfo not found")
|
||||
|
||||
claimRecords, err := s.UserInfoCache.Read(claimKey)
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("failed to read userinfo cache: %w", err)
|
||||
case len(claimRecords) == 0:
|
||||
return fmt.Errorf("no claim found for key: %s", claimKey)
|
||||
}
|
||||
|
||||
var claims map[string]interface{}
|
||||
if err = msgpack.Unmarshal(urecords[0].Value, &claims); err != nil {
|
||||
return fmt.Errorf("could not unmarshal userinfo: %w", err)
|
||||
if err = msgpack.Unmarshal(claimRecords[0].Value, &claims); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal claims: %w", err)
|
||||
}
|
||||
|
||||
oidcClaim, ok := claims[s.Config.UserOIDCClaim].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not get claim %w", err)
|
||||
return fmt.Errorf("failed to get claim %w", err)
|
||||
}
|
||||
|
||||
user, _, err := s.UserProvider.GetUserByClaims(ctx, s.Config.UserCS3Claim, oidcClaim)
|
||||
if err != nil || user.GetId() == nil {
|
||||
return fmt.Errorf("could not get user by claims: %w", err)
|
||||
return fmt.Errorf("failed to get user by claims: %w", err)
|
||||
}
|
||||
|
||||
e := events.BackchannelLogout{
|
||||
Executant: user.GetId(),
|
||||
SessionId: logoutToken.SessionId,
|
||||
SessionId: sessionId,
|
||||
Timestamp: utils.TSNow(),
|
||||
}
|
||||
|
||||
if err := events.Publish(ctx, s.EventsPublisher, e); err != nil {
|
||||
return fmt.Errorf("could not publish user created event %w", err)
|
||||
return fmt.Errorf("failed to publish user logout event %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// package backchannellogout provides functions to classify and lookup
|
||||
// backchannel logout records from the cache store.
|
||||
|
||||
package backchannellogout
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
microstore "go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
// keyEncoding is the base64 encoding used for session and subject keys
|
||||
var keyEncoding = base64.URLEncoding
|
||||
|
||||
// ErrInvalidKey indicates that the provided key does not conform to the expected format.
|
||||
var ErrInvalidKey = errors.New("invalid key format")
|
||||
|
||||
// NewKey converts the subject and session to a base64 encoded key
|
||||
func NewKey(subject, session string) (string, error) {
|
||||
subjectSession := strings.Join([]string{
|
||||
keyEncoding.EncodeToString([]byte(subject)),
|
||||
keyEncoding.EncodeToString([]byte(session)),
|
||||
}, ".")
|
||||
|
||||
if subjectSession == "." {
|
||||
return "", ErrInvalidKey
|
||||
}
|
||||
|
||||
return subjectSession, nil
|
||||
}
|
||||
|
||||
// LogoutMode defines the mode of backchannel logout, either by session or by subject
|
||||
type LogoutMode int
|
||||
|
||||
const (
|
||||
// LogoutModeUndefined is used when the logout mode cannot be determined
|
||||
LogoutModeUndefined LogoutMode = iota
|
||||
// LogoutModeSubject is used when the logout mode is determined by the subject
|
||||
LogoutModeSubject
|
||||
// LogoutModeSession is used when the logout mode is determined by the session id
|
||||
LogoutModeSession
|
||||
)
|
||||
|
||||
// ErrDecoding is returned when decoding fails
|
||||
var ErrDecoding = errors.New("failed to decode")
|
||||
|
||||
// SuSe 🦎 ;) is a struct that groups the subject and session together
|
||||
// to prevent mix-ups for ('session, subject' || 'subject, session')
|
||||
// return values.
|
||||
type SuSe struct {
|
||||
encodedSubject string
|
||||
encodedSession string
|
||||
}
|
||||
|
||||
// Subject decodes and returns the subject or an error
|
||||
func (suse SuSe) Subject() (string, error) {
|
||||
subject, err := keyEncoding.DecodeString(suse.encodedSubject)
|
||||
if err != nil {
|
||||
return "", errors.Join(errors.New("failed to decode subject"), ErrDecoding, err)
|
||||
}
|
||||
|
||||
return string(subject), nil
|
||||
}
|
||||
|
||||
// Session decodes and returns the session or an error
|
||||
func (suse SuSe) Session() (string, error) {
|
||||
subject, err := keyEncoding.DecodeString(suse.encodedSession)
|
||||
if err != nil {
|
||||
return "", errors.Join(errors.New("failed to decode session"), ErrDecoding, err)
|
||||
}
|
||||
|
||||
return string(subject), nil
|
||||
}
|
||||
|
||||
// Mode determines the backchannel logout mode based on the presence of subject and session
|
||||
func (suse SuSe) Mode() LogoutMode {
|
||||
switch {
|
||||
case suse.encodedSession == "" && suse.encodedSubject != "":
|
||||
return LogoutModeSubject
|
||||
case suse.encodedSession != "":
|
||||
return LogoutModeSession
|
||||
default:
|
||||
return LogoutModeUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidSubjectOrSession is returned when the provided key does not match the expected key format
|
||||
var ErrInvalidSubjectOrSession = errors.New("invalid subject or session")
|
||||
|
||||
// NewSuSe parses the subject and session id from the given key and returns a SuSe struct
|
||||
func NewSuSe(key string) (SuSe, error) {
|
||||
suse := SuSe{}
|
||||
keys := strings.Split(key, ".")
|
||||
switch len(keys) {
|
||||
case 1:
|
||||
suse.encodedSession = keys[0]
|
||||
case 2:
|
||||
suse.encodedSubject = keys[0]
|
||||
suse.encodedSession = keys[1]
|
||||
default:
|
||||
return suse, ErrInvalidSubjectOrSession
|
||||
}
|
||||
|
||||
if suse.encodedSubject == "" && suse.encodedSession == "" {
|
||||
return suse, ErrInvalidSubjectOrSession
|
||||
}
|
||||
|
||||
if _, err := suse.Subject(); err != nil {
|
||||
return suse, errors.Join(ErrInvalidSubjectOrSession, err)
|
||||
}
|
||||
|
||||
if _, err := suse.Session(); err != nil {
|
||||
return suse, errors.Join(ErrInvalidSubjectOrSession, err)
|
||||
}
|
||||
|
||||
if mode := suse.Mode(); mode == LogoutModeUndefined {
|
||||
return suse, ErrInvalidSubjectOrSession
|
||||
}
|
||||
|
||||
return suse, nil
|
||||
}
|
||||
|
||||
// ErrSuspiciousCacheResult is returned when the cache result is suspicious
|
||||
var ErrSuspiciousCacheResult = errors.New("suspicious cache result")
|
||||
|
||||
// GetLogoutRecords retrieves the records from the user info cache based on the backchannel
|
||||
// logout mode and the provided SuSe struct.
|
||||
// it uses a seperator to prevent sufix and prefix exploration in the cache and checks
|
||||
// if the retrieved records match the requested subject and or session id as well, to prevent false positives.
|
||||
func GetLogoutRecords(suse SuSe, store microstore.Store) ([]*microstore.Record, error) {
|
||||
var key string
|
||||
var opts []microstore.ReadOption
|
||||
switch {
|
||||
case suse.Mode() == LogoutModeSubject && suse.encodedSubject != "":
|
||||
// the dot at the end prevents prefix exploration in the cache,
|
||||
// so only keys that start with 'subject.*' will be returned, but not 'sub*'.
|
||||
key = suse.encodedSubject + "."
|
||||
opts = append(opts, microstore.ReadPrefix())
|
||||
case suse.Mode() == LogoutModeSession && suse.encodedSession != "":
|
||||
// the dot at the beginning prevents sufix exploration in the cache,
|
||||
// so only keys that end with '*.session' will be returned, but not '*sion'.
|
||||
key = "." + suse.encodedSession
|
||||
opts = append(opts, microstore.ReadSuffix())
|
||||
default:
|
||||
return nil, errors.Join(errors.New("cannot determine logout mode"), ErrSuspiciousCacheResult)
|
||||
}
|
||||
|
||||
// the go micro memory store requires a limit to work, why???
|
||||
records, err := store.Read(key, append(opts, microstore.ReadLimit(1000))...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return nil, microstore.ErrNotFound
|
||||
}
|
||||
|
||||
if suse.Mode() == LogoutModeSession && len(records) > 1 {
|
||||
return nil, errors.Join(errors.New("multiple session records found"), ErrSuspiciousCacheResult)
|
||||
}
|
||||
|
||||
// double-check if the found records match the requested subject and or session id as well,
|
||||
// to prevent false positives.
|
||||
for _, record := range records {
|
||||
recordSuSe, err := NewSuSe(record.Key)
|
||||
if err != nil {
|
||||
// never leak any key-related information
|
||||
return nil, errors.Join(errors.New("failed to parse key"), ErrSuspiciousCacheResult, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
// in subject mode, the subject must match, but the session id can be different
|
||||
case suse.Mode() == LogoutModeSubject && suse.encodedSubject == recordSuSe.encodedSubject:
|
||||
continue
|
||||
// in session mode, the session id must match, but the subject can be different
|
||||
case suse.Mode() == LogoutModeSession && suse.encodedSession == recordSuSe.encodedSession:
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, errors.Join(errors.New("key does not match the requested subject or session"), ErrSuspiciousCacheResult)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package backchannellogout
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go-micro.dev/v4/store"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/staticroutes/internal/backchannellogout/mocks"
|
||||
)
|
||||
|
||||
func mustNewKey(t *testing.T, subject, session string) string {
|
||||
key, err := NewKey(subject, session)
|
||||
require.NoError(t, err)
|
||||
return key
|
||||
}
|
||||
|
||||
func mustNewSuSe(t *testing.T, subject, session string) SuSe {
|
||||
suse, err := NewSuSe(mustNewKey(t, subject, session))
|
||||
require.NoError(t, err)
|
||||
return suse
|
||||
}
|
||||
|
||||
func TestNewKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
subject string
|
||||
session string
|
||||
wantKey string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "key variation: 'subject.session'",
|
||||
subject: "subject",
|
||||
session: "session",
|
||||
wantKey: "c3ViamVjdA==.c2Vzc2lvbg==",
|
||||
},
|
||||
{
|
||||
name: "key variation: 'subject.'",
|
||||
subject: "subject",
|
||||
wantKey: "c3ViamVjdA==.",
|
||||
},
|
||||
{
|
||||
name: "key variation: '.session'",
|
||||
session: "session",
|
||||
wantKey: ".c2Vzc2lvbg==",
|
||||
},
|
||||
{
|
||||
name: "key variation: '.'",
|
||||
wantErr: ErrInvalidKey,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
key, err := NewKey(tt.subject, tt.session)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
require.Equal(t, tt.wantKey, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSuSe(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
wantSubject string
|
||||
wantSession string
|
||||
wantMode LogoutMode
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "key variation: '.session'",
|
||||
key: mustNewKey(t, "", "session"),
|
||||
wantSession: "session",
|
||||
wantMode: LogoutModeSession,
|
||||
},
|
||||
{
|
||||
name: "key variation: 'session'",
|
||||
key: mustNewKey(t, "", "session"),
|
||||
wantSession: "session",
|
||||
wantMode: LogoutModeSession,
|
||||
},
|
||||
{
|
||||
name: "key variation: 'subject.'",
|
||||
key: mustNewKey(t, "subject", ""),
|
||||
wantSubject: "subject",
|
||||
wantMode: LogoutModeSubject,
|
||||
},
|
||||
{
|
||||
name: "key variation: 'subject.session'",
|
||||
key: mustNewKey(t, "subject", "session"),
|
||||
wantSubject: "subject",
|
||||
wantSession: "session",
|
||||
wantMode: LogoutModeSession,
|
||||
},
|
||||
{
|
||||
name: "key variation: 'dot'",
|
||||
key: ".",
|
||||
wantErr: ErrInvalidSubjectOrSession,
|
||||
},
|
||||
{
|
||||
name: "key variation: 'empty'",
|
||||
key: "",
|
||||
wantErr: ErrInvalidSubjectOrSession,
|
||||
},
|
||||
{
|
||||
name: "key variation: string('subject.session')",
|
||||
key: "subject.session",
|
||||
wantErr: ErrInvalidSubjectOrSession,
|
||||
wantMode: LogoutModeSession,
|
||||
},
|
||||
{
|
||||
name: "key variation: string('subject.')",
|
||||
key: "subject.",
|
||||
wantErr: ErrInvalidSubjectOrSession,
|
||||
wantMode: LogoutModeSubject,
|
||||
},
|
||||
{
|
||||
name: "key variation: string('.session')",
|
||||
key: ".session",
|
||||
wantErr: ErrInvalidSubjectOrSession,
|
||||
wantMode: LogoutModeSession,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
suSe, err := NewSuSe(tt.key)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
||||
mode := suSe.Mode()
|
||||
require.Equal(t, tt.wantMode, mode)
|
||||
|
||||
subject, _ := suSe.Subject()
|
||||
require.Equal(t, tt.wantSubject, subject)
|
||||
|
||||
session, _ := suSe.Session()
|
||||
require.Equal(t, tt.wantSession, session)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLogoutRecords(t *testing.T) {
|
||||
sessionStore := store.NewMemoryStore()
|
||||
|
||||
recordClaimA := &store.Record{Key: "claim-a", Value: []byte("claim-a-data")}
|
||||
recordClaimB := &store.Record{Key: "claim-b", Value: []byte("claim-b-data")}
|
||||
recordClaimC := &store.Record{Key: "claim-c", Value: []byte("claim-c-data")}
|
||||
recordClaimD := &store.Record{Key: "claim-d", Value: []byte("claim-d-data")}
|
||||
recordSessionA := &store.Record{Key: mustNewKey(t, "", "session-a"), Value: []byte(recordClaimA.Key)}
|
||||
recordSessionB := &store.Record{Key: mustNewKey(t, "", "session-b"), Value: []byte(recordClaimB.Key)}
|
||||
recordSubjectASessionC := &store.Record{Key: mustNewKey(t, "subject-a", "session-c"), Value: []byte(recordSessionA.Key)}
|
||||
recordSubjectASessionD := &store.Record{Key: mustNewKey(t, "subject-a", "session-d"), Value: []byte(recordSessionA.Key)}
|
||||
|
||||
for _, r := range []*store.Record{
|
||||
recordClaimA,
|
||||
recordClaimB,
|
||||
recordClaimC,
|
||||
recordClaimD,
|
||||
recordSessionA,
|
||||
recordSessionB,
|
||||
recordSubjectASessionC,
|
||||
recordSubjectASessionD,
|
||||
} {
|
||||
require.NoError(t, sessionStore.Write(r))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
suSe SuSe
|
||||
store func(t *testing.T) store.Store
|
||||
wantRecords []*store.Record
|
||||
wantErrs []error
|
||||
}{
|
||||
{
|
||||
name: "fails if multiple session records are found",
|
||||
suSe: mustNewSuSe(t, "", "session-a"),
|
||||
store: func(t *testing.T) store.Store {
|
||||
s := mocks.NewStore(t)
|
||||
s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{
|
||||
recordSessionA,
|
||||
recordSessionB,
|
||||
}, nil)
|
||||
return s
|
||||
},
|
||||
wantRecords: []*store.Record{},
|
||||
wantErrs: []error{ErrSuspiciousCacheResult}},
|
||||
{
|
||||
name: "fails if the record key is not ok",
|
||||
suSe: mustNewSuSe(t, "", "session-a"),
|
||||
store: func(t *testing.T) store.Store {
|
||||
s := mocks.NewStore(t)
|
||||
s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{
|
||||
{Key: "invalid.record.key"},
|
||||
}, nil)
|
||||
return s
|
||||
},
|
||||
wantRecords: []*store.Record{},
|
||||
wantErrs: []error{ErrInvalidSubjectOrSession, ErrSuspiciousCacheResult},
|
||||
},
|
||||
{
|
||||
name: "fails if the session does not match the retrieved record",
|
||||
suSe: mustNewSuSe(t, "", "session-a"),
|
||||
store: func(t *testing.T) store.Store {
|
||||
s := mocks.NewStore(t)
|
||||
s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{
|
||||
recordSessionB,
|
||||
}, nil)
|
||||
return s
|
||||
},
|
||||
wantRecords: []*store.Record{},
|
||||
wantErrs: []error{ErrSuspiciousCacheResult}},
|
||||
{
|
||||
name: "fails if the subject does not match the retrieved record",
|
||||
suSe: mustNewSuSe(t, "subject-a", ""),
|
||||
store: func(t *testing.T) store.Store {
|
||||
s := mocks.NewStore(t)
|
||||
s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{
|
||||
recordSessionB,
|
||||
}, nil)
|
||||
return s
|
||||
},
|
||||
wantRecords: []*store.Record{},
|
||||
wantErrs: []error{ErrSuspiciousCacheResult}},
|
||||
// key variation tests
|
||||
{
|
||||
name: "key variation: 'session-a'",
|
||||
suSe: mustNewSuSe(t, "", "session-a"),
|
||||
store: func(*testing.T) store.Store {
|
||||
return sessionStore
|
||||
},
|
||||
wantRecords: []*store.Record{recordSessionA},
|
||||
},
|
||||
{
|
||||
name: "key variation: 'session-b'",
|
||||
suSe: mustNewSuSe(t, "", "session-b"),
|
||||
store: func(*testing.T) store.Store {
|
||||
return sessionStore
|
||||
},
|
||||
wantRecords: []*store.Record{recordSessionB},
|
||||
},
|
||||
{
|
||||
name: "key variation: 'session-c'",
|
||||
suSe: mustNewSuSe(t, "", "session-c"),
|
||||
store: func(*testing.T) store.Store {
|
||||
return sessionStore
|
||||
},
|
||||
wantRecords: []*store.Record{recordSubjectASessionC},
|
||||
},
|
||||
{
|
||||
name: "key variation: 'ession-c'",
|
||||
suSe: mustNewSuSe(t, "", "ession-c"),
|
||||
store: func(*testing.T) store.Store {
|
||||
return sessionStore
|
||||
},
|
||||
wantRecords: []*store.Record{},
|
||||
wantErrs: []error{store.ErrNotFound},
|
||||
},
|
||||
{
|
||||
name: "key variation: 'subject-a'",
|
||||
suSe: mustNewSuSe(t, "subject-a", ""),
|
||||
store: func(*testing.T) store.Store {
|
||||
return sessionStore
|
||||
},
|
||||
wantRecords: []*store.Record{recordSubjectASessionC, recordSubjectASessionD},
|
||||
},
|
||||
{
|
||||
name: "key variation: 'subject-'",
|
||||
suSe: mustNewSuSe(t, "subject-", ""),
|
||||
store: func(*testing.T) store.Store {
|
||||
return sessionStore
|
||||
},
|
||||
wantRecords: []*store.Record{},
|
||||
wantErrs: []error{store.ErrNotFound},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
records, err := GetLogoutRecords(tt.suSe, tt.store(t))
|
||||
for _, wantErr := range tt.wantErrs {
|
||||
require.ErrorIs(t, err, wantErr)
|
||||
}
|
||||
require.Len(t, records, len(tt.wantRecords))
|
||||
|
||||
sortRecords := func(r []*store.Record) []*store.Record {
|
||||
slices.SortFunc(r, func(a, b *store.Record) int {
|
||||
return strings.Compare(a.Key, b.Key)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
records = sortRecords(records)
|
||||
for i, wantRecords := range sortRecords(tt.wantRecords) {
|
||||
require.True(t, len(records) >= i+1)
|
||||
require.Equal(t, wantRecords.Key, records[i].Key)
|
||||
require.Equal(t, wantRecords.Value, records[i].Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
"go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
// NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Store {
|
||||
mock := &Store{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Store is an autogenerated mock type for the Store type
|
||||
type Store struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Store_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Store) EXPECT() *Store_Expecter {
|
||||
return &Store_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Close provides a mock function for the type Store
|
||||
func (_mock *Store) Close() error {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Close")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
||||
type Store_Close_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Close is a helper method to define mock.On call
|
||||
func (_e *Store_Expecter) Close() *Store_Close_Call {
|
||||
return &Store_Close_Call{Call: _e.mock.On("Close")}
|
||||
}
|
||||
|
||||
func (_c *Store_Close_Call) Run(run func()) *Store_Close_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Close_Call) Return(err error) *Store_Close_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Close_Call) RunAndReturn(run func() error) *Store_Close_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Delete provides a mock function for the type Store
|
||||
func (_mock *Store) Delete(key string, opts ...store.DeleteOption) error {
|
||||
var tmpRet mock.Arguments
|
||||
if len(opts) > 0 {
|
||||
tmpRet = _mock.Called(key, opts)
|
||||
} else {
|
||||
tmpRet = _mock.Called(key)
|
||||
}
|
||||
ret := tmpRet
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string, ...store.DeleteOption) error); ok {
|
||||
r0 = returnFunc(key, opts...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||
type Store_Delete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Delete is a helper method to define mock.On call
|
||||
// - key string
|
||||
// - opts ...store.DeleteOption
|
||||
func (_e *Store_Expecter) Delete(key interface{}, opts ...interface{}) *Store_Delete_Call {
|
||||
return &Store_Delete_Call{Call: _e.mock.On("Delete",
|
||||
append([]interface{}{key}, opts...)...)}
|
||||
}
|
||||
|
||||
func (_c *Store_Delete_Call) Run(run func(key string, opts ...store.DeleteOption)) *Store_Delete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 []store.DeleteOption
|
||||
var variadicArgs []store.DeleteOption
|
||||
if len(args) > 1 {
|
||||
variadicArgs = args[1].([]store.DeleteOption)
|
||||
}
|
||||
arg1 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Delete_Call) Return(err error) *Store_Delete_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Delete_Call) RunAndReturn(run func(key string, opts ...store.DeleteOption) error) *Store_Delete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Init provides a mock function for the type Store
|
||||
func (_mock *Store) Init(options ...store.Option) error {
|
||||
var tmpRet mock.Arguments
|
||||
if len(options) > 0 {
|
||||
tmpRet = _mock.Called(options)
|
||||
} else {
|
||||
tmpRet = _mock.Called()
|
||||
}
|
||||
ret := tmpRet
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Init")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(...store.Option) error); ok {
|
||||
r0 = returnFunc(options...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init'
|
||||
type Store_Init_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Init is a helper method to define mock.On call
|
||||
// - options ...store.Option
|
||||
func (_e *Store_Expecter) Init(options ...interface{}) *Store_Init_Call {
|
||||
return &Store_Init_Call{Call: _e.mock.On("Init",
|
||||
append([]interface{}{}, options...)...)}
|
||||
}
|
||||
|
||||
func (_c *Store_Init_Call) Run(run func(options ...store.Option)) *Store_Init_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 []store.Option
|
||||
var variadicArgs []store.Option
|
||||
if len(args) > 0 {
|
||||
variadicArgs = args[0].([]store.Option)
|
||||
}
|
||||
arg0 = variadicArgs
|
||||
run(
|
||||
arg0...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Init_Call) Return(err error) *Store_Init_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Init_Call) RunAndReturn(run func(options ...store.Option) error) *Store_Init_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// List provides a mock function for the type Store
|
||||
func (_mock *Store) List(opts ...store.ListOption) ([]string, error) {
|
||||
var tmpRet mock.Arguments
|
||||
if len(opts) > 0 {
|
||||
tmpRet = _mock.Called(opts)
|
||||
} else {
|
||||
tmpRet = _mock.Called()
|
||||
}
|
||||
ret := tmpRet
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for List")
|
||||
}
|
||||
|
||||
var r0 []string
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(...store.ListOption) ([]string, error)); ok {
|
||||
return returnFunc(opts...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(...store.ListOption) []string); ok {
|
||||
r0 = returnFunc(opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(...store.ListOption) error); ok {
|
||||
r1 = returnFunc(opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
|
||||
type Store_List_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// List is a helper method to define mock.On call
|
||||
// - opts ...store.ListOption
|
||||
func (_e *Store_Expecter) List(opts ...interface{}) *Store_List_Call {
|
||||
return &Store_List_Call{Call: _e.mock.On("List",
|
||||
append([]interface{}{}, opts...)...)}
|
||||
}
|
||||
|
||||
func (_c *Store_List_Call) Run(run func(opts ...store.ListOption)) *Store_List_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 []store.ListOption
|
||||
var variadicArgs []store.ListOption
|
||||
if len(args) > 0 {
|
||||
variadicArgs = args[0].([]store.ListOption)
|
||||
}
|
||||
arg0 = variadicArgs
|
||||
run(
|
||||
arg0...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_List_Call) Return(strings []string, err error) *Store_List_Call {
|
||||
_c.Call.Return(strings, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_List_Call) RunAndReturn(run func(opts ...store.ListOption) ([]string, error)) *Store_List_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Options provides a mock function for the type Store
|
||||
func (_mock *Store) Options() store.Options {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Options")
|
||||
}
|
||||
|
||||
var r0 store.Options
|
||||
if returnFunc, ok := ret.Get(0).(func() store.Options); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(store.Options)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_Options_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Options'
|
||||
type Store_Options_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Options is a helper method to define mock.On call
|
||||
func (_e *Store_Expecter) Options() *Store_Options_Call {
|
||||
return &Store_Options_Call{Call: _e.mock.On("Options")}
|
||||
}
|
||||
|
||||
func (_c *Store_Options_Call) Run(run func()) *Store_Options_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Options_Call) Return(options store.Options) *Store_Options_Call {
|
||||
_c.Call.Return(options)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Options_Call) RunAndReturn(run func() store.Options) *Store_Options_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Read provides a mock function for the type Store
|
||||
func (_mock *Store) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
|
||||
var tmpRet mock.Arguments
|
||||
if len(opts) > 0 {
|
||||
tmpRet = _mock.Called(key, opts)
|
||||
} else {
|
||||
tmpRet = _mock.Called(key)
|
||||
}
|
||||
ret := tmpRet
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Read")
|
||||
}
|
||||
|
||||
var r0 []*store.Record
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string, ...store.ReadOption) ([]*store.Record, error)); ok {
|
||||
return returnFunc(key, opts...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(string, ...store.ReadOption) []*store.Record); ok {
|
||||
r0 = returnFunc(key, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*store.Record)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(string, ...store.ReadOption) error); ok {
|
||||
r1 = returnFunc(key, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||
type Store_Read_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Read is a helper method to define mock.On call
|
||||
// - key string
|
||||
// - opts ...store.ReadOption
|
||||
func (_e *Store_Expecter) Read(key interface{}, opts ...interface{}) *Store_Read_Call {
|
||||
return &Store_Read_Call{Call: _e.mock.On("Read",
|
||||
append([]interface{}{key}, opts...)...)}
|
||||
}
|
||||
|
||||
func (_c *Store_Read_Call) Run(run func(key string, opts ...store.ReadOption)) *Store_Read_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
var arg1 []store.ReadOption
|
||||
var variadicArgs []store.ReadOption
|
||||
if len(args) > 1 {
|
||||
variadicArgs = args[1].([]store.ReadOption)
|
||||
}
|
||||
arg1 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Read_Call) Return(records []*store.Record, err error) *Store_Read_Call {
|
||||
_c.Call.Return(records, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Read_Call) RunAndReturn(run func(key string, opts ...store.ReadOption) ([]*store.Record, error)) *Store_Read_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// String provides a mock function for the type Store
|
||||
func (_mock *Store) String() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for String")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String'
|
||||
type Store_String_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// String is a helper method to define mock.On call
|
||||
func (_e *Store_Expecter) String() *Store_String_Call {
|
||||
return &Store_String_Call{Call: _e.mock.On("String")}
|
||||
}
|
||||
|
||||
func (_c *Store_String_Call) Run(run func()) *Store_String_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_String_Call) Return(s string) *Store_String_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_String_Call) RunAndReturn(run func() string) *Store_String_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Write provides a mock function for the type Store
|
||||
func (_mock *Store) Write(r *store.Record, opts ...store.WriteOption) error {
|
||||
var tmpRet mock.Arguments
|
||||
if len(opts) > 0 {
|
||||
tmpRet = _mock.Called(r, opts)
|
||||
} else {
|
||||
tmpRet = _mock.Called(r)
|
||||
}
|
||||
ret := tmpRet
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Write")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(*store.Record, ...store.WriteOption) error); ok {
|
||||
r0 = returnFunc(r, opts...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||
type Store_Write_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Write is a helper method to define mock.On call
|
||||
// - r *store.Record
|
||||
// - opts ...store.WriteOption
|
||||
func (_e *Store_Expecter) Write(r interface{}, opts ...interface{}) *Store_Write_Call {
|
||||
return &Store_Write_Call{Call: _e.mock.On("Write",
|
||||
append([]interface{}{r}, opts...)...)}
|
||||
}
|
||||
|
||||
func (_c *Store_Write_Call) Run(run func(r *store.Record, opts ...store.WriteOption)) *Store_Write_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *store.Record
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*store.Record)
|
||||
}
|
||||
var arg1 []store.WriteOption
|
||||
var variadicArgs []store.WriteOption
|
||||
if len(args) > 1 {
|
||||
variadicArgs = args[1].([]store.WriteOption)
|
||||
}
|
||||
arg1 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Write_Call) Return(err error) *Store_Write_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_Write_Call) RunAndReturn(run func(r *store.Record, opts ...store.WriteOption) error) *Store_Write_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
Reference in New Issue
Block a user