forked from mystiq/dex
Compare commits
589 commits
Author | SHA1 | Date | |
---|---|---|---|
2081f7d057 | |||
b9d88c723f | |||
e74acdff6c | |||
b479d26137 | |||
adb5454913 | |||
4bcdcf8e1e | |||
3df7c489ce | |||
ce11154529 | |||
e1a407830d | |||
83e2df821e | |||
454122ca22 | |||
4a0218e87c | |||
27c25d00be | |||
367487d7c5 | |||
3b7e56035a | |||
f53fab6b06 | |||
100246328b | |||
d564cc7200 | |||
a3e2946cfc | |||
f49e7bc218 | |||
9ebcd651ff | |||
1aaa7fa0b7 | |||
c561318baa | |||
5066414735 | |||
731d0d7d9d | |||
1cc26fab2f | |||
f34529b13f | |||
ffec99287b | |||
ea46fc39ca | |||
e253fa8efb | |||
c538f3d6a2 | |||
33483aa179 | |||
b6c4112c88 | |||
60228d8fd8 | |||
b07c8b1d8d | |||
9079c31637 | |||
a51d12056f | |||
6c99a9b99d | |||
3836196af2 | |||
b578e4d8e5 | |||
8360cbfbde | |||
465be883a3 | |||
870395971e | |||
ebb27418c4 | |||
15a516684b | |||
dcb25d0c3d | |||
59b69352e0 | |||
89d1c51e9b | |||
a0fd469e47 | |||
b6cc099305 | |||
6eeba947f1 | |||
a858ffbcf2 | |||
65592d0b5a | |||
cbf158bcc0 | |||
6da5187b47 | |||
957def7928 | |||
ec4ac04c41 | |||
bdfb10137a | |||
e9d17888d8 | |||
b4ccd92d65 | |||
505726e7d5 | |||
70e6cc2205 | |||
3df9cf2cb9 | |||
a02f2e8fac | |||
55d963ac77 | |||
c2f3bea207 | |||
1736f95024 | |||
ab02a2d714 | |||
a3dfe30a12 | |||
1884705b87 | |||
8e6d123772 | |||
999d3855c1 | |||
81818b9afe | |||
2baf728d1f | |||
7071480c2a | |||
071969f172 | |||
f881fb4b2e | |||
bbb3bba01a | |||
97c7f2491b | |||
6c5286cbfe | |||
a3880c7371 | |||
dc0dfa771f | |||
6759369e16 | |||
a7ca81f03a | |||
51f1ec441d | |||
a4fb0a089f | |||
c98646f004 | |||
9da59ce5dd | |||
502a2d0d4a | |||
f09af6102c | |||
3d5a3befb4 | |||
97254db62a | |||
0270536a2e | |||
861ad968c5 | |||
d26d4e15bc | |||
96e0229205 | |||
866f3e0c76 | |||
47411e9a75 | |||
f26181558c | |||
574650abe3 | |||
3a83b6ce39 | |||
a232af7f28 | |||
c74ad3bb66 | |||
a98ab893c2 | |||
2571ae9096 | |||
38fe0f5319 | |||
92161abfdc | |||
5fe1647fc7 | |||
7c335e9337 | |||
35f58dca73 | |||
9cd29bdee0 | |||
997ec94a4a | |||
f07a58a7f1 | |||
691f8be785 | |||
453504c450 | |||
fd15dd2248 | |||
ebe1c8b14a | |||
5c70f1227f | |||
0b5a9581cd | |||
7b75e1e0cc | |||
6f07a27fad | |||
42f8f91ebf | |||
27fb1cf3bd | |||
a9fb4ae7ef | |||
b8f2186593 | |||
ec9a57ee4b | |||
7c60f79f10 | |||
1067641e53 | |||
e9a43bf3cd | |||
75d198bd85 | |||
c5c88a688b | |||
b26e639515 | |||
ad89e01676 | |||
c8ff7ed40a | |||
3702525c86 | |||
8b2ce6252d | |||
6038af5044 | |||
95e81a925f | |||
4a5f2dbb4d | |||
aa35fa6580 | |||
0f5481a00a | |||
783a7621e0 | |||
169b5a59cc | |||
4088d4f897 | |||
2b262ff5d6 | |||
6822ad950f | |||
a2089dd8e7 | |||
fdc43a0c36 | |||
d8289d3429 | |||
23de36d721 | |||
a51ed2c4db | |||
9b1a8409f1 | |||
b51e73bc2e | |||
0c3c577b52 | |||
20b03b3f6d | |||
07a43f2d66 | |||
493c0eb8b7 | |||
50dc2f5518 | |||
ff68ca1aae | |||
cf78e741ca | |||
e462d69353 | |||
b163944ee5 | |||
a136c0141e | |||
2ebcd70d30 | |||
6692759586 | |||
897ae8d2a3 | |||
863416f0a3 | |||
090593b7f9 | |||
df1cb1cdbf | |||
20e2e429b3 | |||
c98636457b | |||
f1cc7133da | |||
111ce66bd0 | |||
57e9611ff6 | |||
cb9f0b5d5e | |||
a322f42a10 | |||
22a7d3acd3 | |||
5d9d68106a | |||
b83ba01c40 | |||
5f9abc5be8 | |||
98ed9b70a4 | |||
a190bba9e6 | |||
5b0cb0704a | |||
616e20b334 | |||
fd545e0493 | |||
575d935792 | |||
e22c24dba2 | |||
333b1d1971 | |||
d9535b8dc1 | |||
364f7954fd | |||
dde621980d | |||
2e2471b21f | |||
102762062b | |||
5f58d8e7d2 | |||
b97732f353 | |||
f8685d2e83 | |||
592a9f603f | |||
727b0101f7 | |||
75c27c8dba | |||
470327e002 | |||
8cee3927b4 | |||
238c07ac33 | |||
bf034906fd | |||
d03d229ddc | |||
f8fcae5f07 | |||
71d95d7aa2 | |||
6275eba9ce | |||
bf0025fbd3 | |||
2eedc5897c | |||
13f93cb785 | |||
254165d665 | |||
4ee9658dfe | |||
0f89054634 | |||
8519219dae | |||
2bc4ad6b56 | |||
cd44a3e4f3 | |||
49e15945a2 | |||
12a904afdd | |||
373bddaf73 | |||
593d03789d | |||
deac802c73 | |||
b434058f19 | |||
c205b49189 | |||
22d27c60e4 | |||
73ce1eb110 | |||
419db81c67 | |||
55605751f5 | |||
b28098dde8 | |||
1608b473eb | |||
2b6bb1997c | |||
14a0aecc81 | |||
45143c98b3 | |||
0394bf8cea | |||
a672ff9288 | |||
764ce711b6 | |||
ba1bd65c10 | |||
3e0f7c42b8 | |||
f44af5c8e9 | |||
eb26422bdc | |||
716eef83bc | |||
e8d8967a5b | |||
adaa31c0a5 | |||
97abc800fb | |||
cb916cdf43 | |||
84802f247f | |||
3bc6a45ee1 | |||
d112627564 | |||
7f17aae35d | |||
c8d55ce016 | |||
39ddadcd8e | |||
74dc922703 | |||
25f5b47272 | |||
79721196a8 | |||
243661155e | |||
3fa53bbc3d | |||
a407b5861b | |||
93b32c3500 | |||
4a42e80a7a | |||
a941593b8b | |||
9d3471e39b | |||
79233f41ef | |||
a413d9b383 | |||
b14b0fd127 | |||
ae1b50c26b | |||
ac02fb04cf | |||
ca615f7ad7 | |||
7ebc76b79e | |||
6256b863b0 | |||
131bf83699 | |||
578cb05f7b | |||
6d55fe1c80 | |||
40e21f14ca | |||
1f30080e6a | |||
e7c287a00d | |||
539e08ba50 | |||
e00e75b773 | |||
528ef18c2f | |||
f70015dfed | |||
f717c71d66 | |||
8b865169bd | |||
7c80e44caf | |||
45932bd38a | |||
f980d3e0a7 | |||
8ea121b45a | |||
49cb30af26 | |||
02860da8b6 | |||
60b8875780 | |||
9952851cc4 | |||
fdf19e8014 | |||
930b331a5b | |||
a087c05ebf | |||
9284ffb8c0 | |||
1cb4b32fcb | |||
98e7d7a99d | |||
bc9322ff44 | |||
d3c4a170e3 | |||
0aad109b6f | |||
e875745ee0 | |||
49f9853a89 | |||
1ecc17292b | |||
f45fe6d0c1 | |||
054e397177 | |||
ee76923443 | |||
1bfb1ab757 | |||
f92fc54b7a | |||
21a8ac6d3c | |||
12a2c2b104 | |||
a86beb8952 | |||
d6cf1704ea | |||
14fe699dcf | |||
d2eec79e48 | |||
588910468a | |||
aebe808162 | |||
71d5c3415f | |||
5854dd192d | |||
a1c1076137 | |||
84b241721e | |||
18311aa44d | |||
30bfb924c2 | |||
58cac422f5 | |||
5210e758d2 | |||
514c2f29c6 | |||
e2c40f8f71 | |||
66aba9c32b | |||
b9046ce566 | |||
ed5315bb2e | |||
f7d2bf38b2 | |||
9fad0602ec | |||
c319983ecc | |||
a48c8ea9a4 | |||
94597d8dc8 | |||
4aa7e6846f | |||
0dd5d65cc7 | |||
ee5b5b25bd | |||
a15cd8788f | |||
6bb627f3e3 | |||
2e0041f95f | |||
67ba7a1c70 | |||
7bc966217d | |||
8fd69c16f5 | |||
8593933883 | |||
ff6e7c7688 | |||
d4e82e3315 | |||
f92a6f4457 | |||
3456c3315d | |||
a417f5d1b0 | |||
5169c4317d | |||
551022046c | |||
f0186ff265 | |||
40b426b276 | |||
8a7c2b47f1 | |||
c6f6dd69e9 | |||
575742b137 | |||
096e229562 | |||
4d4edaf540 | |||
fb38e1235d | |||
eae3219e4d | |||
79ce4fdbad | |||
c9b4e8db3b | |||
0d5b2ac060 | |||
b4b74955bd | |||
1497e70225 | |||
1b8f544873 | |||
e412369851 | |||
b74af809fc | |||
656f5548d4 | |||
53c6eb6675 | |||
b11a2a5604 | |||
6dcf7a042d | |||
33ba1d3b74 | |||
b3b9c26e5a | |||
4488af24b6 | |||
ad13fdc523 | |||
a950a24811 | |||
691ecbbd9e | |||
e1c88b2598 | |||
22db25ef94 | |||
e472fe668e | |||
d5727600ae | |||
10a3ab9c4c | |||
fdc46d2bd3 | |||
4f3e410e33 | |||
091d9eae83 | |||
2a54eb4e4f | |||
6c61425e36 | |||
dc859e1ca9 | |||
27d4075f54 | |||
46a7f81f6d | |||
7c0fd3f804 | |||
c110f12441 | |||
f9f48016f4 | |||
532bc88a65 | |||
f02415a83d | |||
c4066b2153 | |||
fa18b14437 | |||
11a9476bef | |||
3009ae3b5d | |||
6865d84ae4 | |||
050339df86 | |||
0e2459c230 | |||
53c2dc30b5 | |||
3c1763a14f | |||
fd67978363 | |||
d6f14bd2d5 | |||
2109211d09 | |||
3fac2ab6bc | |||
766fc7ad99 | |||
1f7ddac4e2 | |||
3f5f6172af | |||
65edeff231 | |||
a6fbdc3ec8 | |||
f1d4fec45e | |||
e650aef331 | |||
e1f3bfe418 | |||
4ffaa60d21 | |||
138364ceeb | |||
ff10f84e42 | |||
d4bd37156d | |||
30c6ddd556 | |||
a9942794e5 | |||
1e61e9b1b4 | |||
5b7ec77538 | |||
3d3f275efb | |||
d413870f6e | |||
823484f024 | |||
033a8d89f2 | |||
2211c515a6 | |||
39f0e0e0b0 | |||
be492e97ce | |||
731e53913a | |||
9bb764b63c | |||
176a6738fd | |||
6cac94f720 | |||
eec8ed6182 | |||
f72602c3bd | |||
0780edbcbe | |||
0d3d787511 | |||
27cc11b4d6 | |||
ceb4324c18 | |||
e1ae7240f4 | |||
61881d751b | |||
2e5e1488e6 | |||
452d466481 | |||
59c7e20c3c | |||
81e884e903 | |||
cfc8d198ac | |||
215c3160f8 | |||
f7c09760f2 | |||
79e1f25f21 | |||
f6904c38ef | |||
5e6a8362c6 | |||
3981ac8aa6 | |||
76cb5f521c | |||
e51704e41a | |||
9fe031776e | |||
cba7d69577 | |||
5c315a3a4e | |||
0417789626 | |||
ddd19bf91d | |||
a207238491 | |||
7043d944cf | |||
831c0efe9c | |||
0754c30ac2 | |||
7a2472555a | |||
baec4f79ce | |||
fd2c86d36e | |||
6c8c336e9a | |||
753cff1764 | |||
aece0ce873 | |||
245a46e743 | |||
f45a89f6b3 | |||
21a01ee811 | |||
93ded5c406 | |||
97591861b2 | |||
00950eedd6 | |||
bc5371e730 | |||
5a48d8a82d | |||
6384af06e4 | |||
cdcf7a4694 | |||
5d996661ea | |||
4b54433ec2 | |||
95941506f5 | |||
8dbd0c6536 | |||
aef61cea8d | |||
0bef10ef80 | |||
5451188e29 | |||
ca02fc16bd | |||
dea1d3383c | |||
13a83d9bba | |||
cd0c24ec4d | |||
030a6459d6 | |||
88025b3d7c | |||
0284a4c3c9 | |||
cdbb5dd94d | |||
4a874cce89 | |||
461c5f687d | |||
4e4dad023c | |||
1220017f6c | |||
20875c972e | |||
47d029a51b | |||
18d1f70cee | |||
fe8085b886 | |||
283dd89f4d | |||
c65652ed8f | |||
49adc4e5bb | |||
19884d92ac | |||
ecea593ddd | |||
47bdbdb1a2 | |||
81c4dc7994 | |||
ba2cec3f72 | |||
fcca5f4b4f | |||
b1292bd630 | |||
8553309db3 | |||
94a2b3ed87 | |||
24fa4def5b | |||
2e61860d5a | |||
11859166d0 | |||
674631c9ab | |||
47b0a2bdf9 | |||
e2b56d0a09 | |||
4561214ab2 | |||
afa6f1e03e | |||
df9fc78d2d | |||
bf8c35ad2d | |||
59fcab281e | |||
05b61a3d86 | |||
551229a986 | |||
b1ac799073 | |||
31c18e557a | |||
5bc3cb2ad3 | |||
efd9839fd2 | |||
fa3a4d7f6b | |||
0b9b588c96 | |||
026d979073 | |||
e4065013a4 | |||
d4a2a362ab | |||
de6d1bea56 | |||
8fbbd4cec9 | |||
b79d9a84bc | |||
03db309337 | |||
c7549cce5b | |||
656798c8bd | |||
beb8911cf7 | |||
b73c406d21 | |||
4b924f1d86 | |||
55352575b8 | |||
d2eb1b04dc | |||
0f4ad150ce | |||
b57c8fa75b | |||
1076081f79 | |||
f5a29bcdbb | |||
e18510b16e | |||
95796b04a3 | |||
2bf728c6ec | |||
356ccecc24 | |||
8e7ce6353f | |||
d25051c867 | |||
41712bcbfa | |||
e3f8b0f2f6 | |||
8cba308b0e | |||
3adb4e74df | |||
1ec5cf07f2 | |||
3e12618f0c | |||
bbd8b3b3cd | |||
3ecdd57282 | |||
a050f3228a | |||
3b80d480e5 | |||
d1e8b085e2 | |||
78fcac7568 | |||
3f0ca9b361 | |||
2f28fc7451 | |||
4e569024fd | |||
7b50cbf0ac | |||
1eab25f89f | |||
10e9054811 | |||
d658c24e8f | |||
ec6f3a2f19 | |||
6be747142a | |||
568fc06520 | |||
3bd0e91a68 | |||
9ed5cc00cf | |||
1211a86d58 | |||
9340fee011 | |||
89295a5b4a | |||
4e73f39f57 | |||
0c75ed12e2 | |||
06c8ab5aa7 | |||
91de99d57e | |||
ba47aaba86 |
6
.envrc
Normal file
6
.envrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
if ! has nix_direnv_version || ! nix_direnv_version 1.5.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/1.5.0/direnvrc" "sha256-carKk9aUFHMuHt+IWh74hFj58nY4K3uywpZbwXX0BTI="
|
||||
fi
|
||||
use flake
|
||||
|
||||
dotenv_if_exists
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
name: 🐛 Bug report
|
||||
about: Report a bug to help us improve Dex
|
||||
---
|
||||
<!--
|
||||
Thank you for sending a bug report! Here are some tips:
|
||||
|
||||
1. Please fill out the template below to make it easier to debug your problem.
|
||||
2. If you are not sure if it is a bug or not, you can ask in the Kubernetes slack channel `#dexidp`.
|
||||
-->
|
||||
|
||||
#### Expected behavior (what you expected to happen)
|
||||
|
||||
#### Actual behavior (what actually happened)
|
||||
|
||||
#### Steps to reproduce
|
||||
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
#### Environment
|
||||
|
||||
- Dex version:
|
||||
- Storage type:
|
||||
- Installation type (official docker image, helm chart, etc.):
|
||||
|
||||
#### Anything else we should know?
|
||||
|
||||
#### Additional information for debugging (if necessary)
|
||||
|
||||
<details><summary>Configuration</summary><br><pre>
|
||||
|
||||
</pre></details>
|
||||
|
||||
<details><summary>Logs</summary><br><pre>
|
||||
|
||||
</pre></details>
|
102
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
102
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
name: 🐛 Bug report
|
||||
description: Report a bug to help us improve Dex
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a bug report!
|
||||
|
||||
Please fill out the template below to make it easier to debug your problem.
|
||||
|
||||
If you are not sure if it is a bug or not, you can contact us via the available [support channels](https://github.com/dexidp/dex/issues/new/choose).
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/dexidp/dex/blob/master/.github/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- label: I have searched the [issue tracker](https://www.github.com/dexidp/dex/issues) for an issue that matches the one I want to file, without success.
|
||||
required: true
|
||||
- label: I am not looking for support or already pursued the available [support channels](https://github.com/dexidp/dex/issues/new/choose) without success.
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of Dex are you running?
|
||||
placeholder: 2.29.0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Storage Type
|
||||
description: Which persistent storage type are you using?
|
||||
options:
|
||||
- etcd
|
||||
- Kubernetes
|
||||
- In-memory
|
||||
- Postgres
|
||||
- MySQL
|
||||
- SQLite
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Installation Type
|
||||
description: How did you install Dex?
|
||||
options:
|
||||
- Binary
|
||||
- Official container image
|
||||
- Official Helm chart
|
||||
- Custom container image
|
||||
- Custom Helm chart
|
||||
- Other (specify below)
|
||||
multiple: true
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: A clear description of what actually happens.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior if it is not self-explanatory.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Links? References? Anything that will give us more context about the issue that you are encountering!
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: Contents of your configuration file (if relevant).
|
||||
render: yaml
|
||||
placeholder: |
|
||||
issuer: http://127.0.0.1:5556/dex
|
||||
|
||||
storage:
|
||||
# ...
|
||||
|
||||
connectors:
|
||||
# ...
|
||||
|
||||
staticClients:
|
||||
# ...
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs
|
||||
description: Dex application logs (if relevant).
|
||||
render: shell
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -2,7 +2,7 @@ blank_issues_enabled: false
|
|||
contact_links:
|
||||
- name: ❓ Ask a question
|
||||
url: https://github.com/dexidp/dex/discussions/new?category=q-a
|
||||
about: Ask and discuss questions with other Dex community members
|
||||
about: Ask and discuss questions with other Dex community members
|
||||
|
||||
- name: 📚 Documentation
|
||||
url: https://dexidp.io/docs/
|
||||
|
@ -11,3 +11,7 @@ contact_links:
|
|||
- name: 💬 Slack channel
|
||||
url: https://cloud-native.slack.com/messages/dexidp
|
||||
about: Please ask and answer questions here
|
||||
|
||||
- name: 💡 Dex Enhancement Proposal
|
||||
url: https://github.com/dexidp/dex/tree/master/enhancements/README.md
|
||||
about: Open a proposal for significant architectural change
|
||||
|
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: 🚀 Feature request
|
||||
about: Suggest an idea for Dex
|
||||
---
|
||||
<!--
|
||||
Thank you for sending a feature request!
|
||||
Please describe what you would like to change/add and why in detail by filling out the template below.
|
||||
-->
|
||||
|
||||
#### Is your feature request related to a problem?
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
#### Describe the solution you'd like to see
|
||||
<!-- A clear and concise description of what would you like to happen. -->
|
||||
|
||||
#### Describe alternatives you've considered
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
#### Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
40
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: 🎉 Feature request
|
||||
description: Suggest an idea for Dex
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a feature request!
|
||||
|
||||
Please describe what you would like to change/add and why in detail by filling out the template below.
|
||||
|
||||
If you are not sure if your request fits into Dex, you can contact us via the available [support channels](https://github.com/dexidp/dex/issues/new/choose).
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/dexidp/dex/blob/master/.github/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- label: I have searched the [issue tracker](https://www.github.com/dexidp/dex/issues) for an issue that matches the one I want to file, without success.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem Description
|
||||
description: A clear and concise description of the problem you are seeking to solve with this feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: A clear and concise description of what would you like to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives Considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Add any other context about the problem here.
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,5 +1,5 @@
|
|||
<!--
|
||||
Thank you for sending a pull request! Here some tips for contributors:
|
||||
Thank you for sending a pull request! Here are some tips for contributors:
|
||||
|
||||
1. Fill the description template below.
|
||||
2. Sign a DCO (if you haven't already signed it).
|
||||
|
|
24
.github/SECURITY.md
vendored
Normal file
24
.github/SECURITY.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Security Policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
To report a vulnerability, send an email to [cncf-dex-maintainers@lists.cncf.io](mailto:cncf-dex-maintainers@lists.cncf.io)
|
||||
detailing the issue and steps to reproduce. The reporter(s) can expect a
|
||||
response within 48 hours acknowledging the issue was received. If a response is
|
||||
not received within 48 hours, please reach out to any maintainer directly
|
||||
to confirm receipt of the issue.
|
||||
|
||||
## Review Process
|
||||
|
||||
Once a maintainer has confirmed the relevance of the report, a draft security
|
||||
advisory will be created on Github. The draft advisory will be used to discuss
|
||||
the issue with maintainers, the reporter(s).
|
||||
If the reporter(s) wishes to participate in this discussion, then provide
|
||||
reporter Github username(s) to be invited to the discussion. If the reporter(s)
|
||||
does not wish to participate directly in the discussion, then the reporter(s)
|
||||
can request to be updated regularly via email.
|
||||
|
||||
If the vulnerability is accepted, a timeline for developing a patch, public
|
||||
disclosure, and patch release will be determined. The reporter(s) are expected
|
||||
to participate in the discussion of the timeline and abide by agreed upon dates
|
||||
for public disclosure.
|
|
@ -8,6 +8,13 @@ updates:
|
|||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/api/v2"
|
||||
labels:
|
||||
- "area/dependencies"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
labels:
|
30
.github/release.yml
vendored
Normal file
30
.github/release.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- release-note/ignore
|
||||
categories:
|
||||
- title: Exciting New Features 🎉
|
||||
labels:
|
||||
- kind/feature
|
||||
- release-note/new-feature
|
||||
- title: Enhancements 🚀
|
||||
labels:
|
||||
- kind/enhancement
|
||||
- release-note/enhancement
|
||||
- title: Bug Fixes 🐛
|
||||
labels:
|
||||
- kind/bug
|
||||
- release-note/bug-fix
|
||||
- title: Breaking Changes 🛠
|
||||
labels:
|
||||
- release-note/breaking-change
|
||||
- title: Deprecations ❌
|
||||
labels:
|
||||
- release-note/deprecation
|
||||
- title: Dependency Updates ⬆️
|
||||
labels:
|
||||
- area/dependencies
|
||||
- release-note/dependency-update
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
97
.github/workflows/artifacts.yaml
vendored
Normal file
97
.github/workflows/artifacts.yaml
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
name: Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
container-images:
|
||||
name: Container images
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
variant:
|
||||
- alpine
|
||||
- distroless
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Gather metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/dexidp/dex
|
||||
dexidp/dex
|
||||
flavor: |
|
||||
latest = false
|
||||
tags: |
|
||||
type=ref,event=branch,enable=${{ matrix.variant == 'alpine' }}
|
||||
type=ref,event=pr,enable=${{ matrix.variant == 'alpine' }}
|
||||
type=semver,pattern={{raw}},enable=${{ matrix.variant == 'alpine' }}
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && matrix.variant == 'alpine' }}
|
||||
type=ref,event=branch,suffix=-${{ matrix.variant }}
|
||||
type=ref,event=pr,suffix=-${{ matrix.variant }}
|
||||
type=semver,pattern={{raw}},suffix=-${{ matrix.variant }}
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }},suffix=-${{ matrix.variant }}
|
||||
labels: |
|
||||
org.opencontainers.image.documentation=https://dexidp.io/docs/
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ github.token }}
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ matrix.variant }}
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
COMMIT_HASH=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@0.6.1
|
||||
with:
|
||||
image-ref: "ghcr.io/dexidp/dex:${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}"
|
||||
format: "sarif"
|
||||
output: "trivy-results.sarif"
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: "trivy-results.sarif"
|
||||
if: github.event_name == 'push'
|
18
.github/workflows/checks.yaml
vendored
Normal file
18
.github/workflows/checks.yaml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, labeled, unlabeled, synchronize]
|
||||
|
||||
jobs:
|
||||
release-label:
|
||||
name: Release note label
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check minimum labels
|
||||
uses: mheap/github-action-required-labels@v2
|
||||
with:
|
||||
mode: minimum
|
||||
count: 1
|
||||
labels: "release-note/ignore, kind/feature, release-note/new-feature, kind/enhancement, release-note/enhancement, kind/bug, release-note/bug-fix, release-note/breaking-change, release-note/deprecation, area/dependencies, release-note/dependency-update"
|
56
.github/workflows/ci.yaml
vendored
56
.github/workflows/ci.yaml
vendored
|
@ -20,17 +20,32 @@ jobs:
|
|||
- 5432
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
postgres-ent:
|
||||
image: postgres:10.8
|
||||
ports:
|
||||
- 5432
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: dex
|
||||
ports:
|
||||
- 3306:3306
|
||||
- 3306
|
||||
options: --health-cmd "mysql -proot -e \"show databases;\"" --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
mysql-ent:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: dex
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd "mysql -proot -e \"show databases;\"" --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
etcd:
|
||||
image: gcr.io/etcd-development/etcd:v3.2.9
|
||||
image: gcr.io/etcd-development/etcd:v3.5.0
|
||||
ports:
|
||||
- 2379
|
||||
env:
|
||||
|
@ -39,7 +54,7 @@ jobs:
|
|||
options: --health-cmd "ETCDCTL_API=3 etcdctl --endpoints http://localhost:2379 endpoint health" --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
|
||||
keystone:
|
||||
image: openio/openstack-keystone:pike
|
||||
image: openio/openstack-keystone:rocky
|
||||
ports:
|
||||
- 5000
|
||||
- 35357
|
||||
|
@ -47,16 +62,25 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.18
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Start services
|
||||
run: docker-compose -f docker-compose.test.yaml up -d
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.3.0
|
||||
with:
|
||||
version: v0.11.1
|
||||
node_image: kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729
|
||||
|
||||
- name: Download tool dependencies
|
||||
run: make deps
|
||||
|
||||
- name: Test
|
||||
run: make testall
|
||||
env:
|
||||
|
@ -64,21 +88,39 @@ jobs:
|
|||
DEX_MYSQL_USER: root
|
||||
DEX_MYSQL_PASSWORD: root
|
||||
DEX_MYSQL_HOST: 127.0.0.1
|
||||
DEX_MYSQL_PORT: 3306
|
||||
DEX_MYSQL_PORT: ${{ job.services.mysql.ports[3306] }}
|
||||
|
||||
DEX_MYSQL_ENT_DATABASE: dex
|
||||
DEX_MYSQL_ENT_USER: root
|
||||
DEX_MYSQL_ENT_PASSWORD: root
|
||||
DEX_MYSQL_ENT_HOST: 127.0.0.1
|
||||
DEX_MYSQL_ENT_PORT: ${{ job.services.mysql-ent.ports[3306] }}
|
||||
|
||||
DEX_POSTGRES_DATABASE: postgres
|
||||
DEX_POSTGRES_USER: postgres
|
||||
DEX_POSTGRES_PASSWORD: postgres
|
||||
DEX_POSTGRES_HOST: localhost
|
||||
DEX_POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
|
||||
|
||||
DEX_POSTGRES_ENT_DATABASE: postgres
|
||||
DEX_POSTGRES_ENT_USER: postgres
|
||||
DEX_POSTGRES_ENT_PASSWORD: postgres
|
||||
DEX_POSTGRES_ENT_HOST: localhost
|
||||
DEX_POSTGRES_ENT_PORT: ${{ job.services.postgres-ent.ports[5432] }}
|
||||
|
||||
DEX_ETCD_ENDPOINTS: http://localhost:${{ job.services.etcd.ports[2379] }}
|
||||
|
||||
DEX_LDAP_HOST: localhost
|
||||
DEX_LDAP_PORT: 389
|
||||
DEX_LDAP_TLS_PORT: 636
|
||||
|
||||
DEX_KEYSTONE_URL: http://localhost:${{ job.services.keystone.ports[5000] }}
|
||||
DEX_KEYSTONE_ADMIN_URL: http://localhost:${{ job.services.keystone.ports[35357] }}
|
||||
DEX_KEYSTONE_ADMIN_USER: demo
|
||||
DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS
|
||||
|
||||
DEX_KUBERNETES_CONFIG_PATH: ~/.kube/config
|
||||
|
||||
- name: Lint
|
||||
run: make lint
|
||||
|
||||
|
|
8
.github/workflows/codeql-analysis.yaml
vendored
8
.github/workflows/codeql-analysis.yaml
vendored
|
@ -35,11 +35,11 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -50,7 +50,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -64,4 +64,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
43
.github/workflows/docker.yaml
vendored
43
.github/workflows/docker.yaml
vendored
|
@ -1,11 +1,11 @@
|
|||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
# tags:
|
||||
# - v[0-9]+.[0-9]+.[0-9]+
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
@ -15,7 +15,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Calculate Docker image tags
|
||||
id: tags
|
||||
|
@ -44,12 +44,12 @@ jobs:
|
|||
echo ::set-output name=build_date::$(git show -s --format=%cI)
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
install: true
|
||||
version: latest
|
||||
|
@ -57,25 +57,27 @@ jobs:
|
|||
driver-opts: image=moby/buildkit:master
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
password: ${{ github.token }}
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
build-args: |
|
||||
|
@ -92,3 +94,18 @@ jobs:
|
|||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.licenses=${{ github.event.repository.license.spdx_id }}
|
||||
org.opencontainers.image.documentation=https://dexidp.io/docs/
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@0.6.1
|
||||
with:
|
||||
image-ref: "ghcr.io/dexidp/dex:${{ steps.tags.outputs.version }}"
|
||||
format: "template"
|
||||
template: "@/contrib/sarif.tpl"
|
||||
output: "trivy-results.sarif"
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: "trivy-results.sarif"
|
||||
if: github.event_name == 'push'
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,7 @@
|
|||
/.direnv/
|
||||
/.idea/
|
||||
/bin/
|
||||
/config.yaml
|
||||
/docker-compose.override.yaml
|
||||
/var/
|
||||
/vendor/
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
run:
|
||||
timeout: 2m
|
||||
timeout: 4m
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
include-go-root: true
|
||||
packages:
|
||||
- io/ioutil
|
||||
packages-with-error-message:
|
||||
- io/ioutil: "The 'io/ioutil' package is deprecated. Use corresponding 'os' or 'io' functions instead."
|
||||
gci:
|
||||
local-prefixes: github.com/dexidp/dex
|
||||
goimports:
|
||||
local-prefixes: github.com/dexidp/dex
|
||||
golint:
|
||||
min-confidence: 0
|
||||
|
||||
|
||||
linters:
|
||||
|
@ -15,6 +20,7 @@ linters:
|
|||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
|
@ -24,7 +30,6 @@ linters:
|
|||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- gosimple
|
||||
- govet
|
||||
|
@ -33,19 +38,22 @@ linters:
|
|||
- nakedret
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- tparallel
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
# Disable temporarily until everything works with Go 1.18
|
||||
# - typecheck
|
||||
|
||||
# TODO: fix linter errors before enabling
|
||||
# - exhaustivestruct
|
||||
# - gochecknoglobals
|
||||
|
@ -66,7 +74,6 @@ linters:
|
|||
# - scopelint
|
||||
|
||||
# unused
|
||||
# - depguard
|
||||
# - goheader
|
||||
# - gomodguard
|
||||
|
||||
|
|
|
@ -12,3 +12,4 @@ This is a list of production adopters of Dex (in alphabetical order):
|
|||
- [Kyma](https://kyma-project.io) is using Dex to authenticate access to Kubernetes API server (even for managed Kubernetes like Google Kubernetes Engine or Azure Kubernetes Service) and for protecting web UI of [Kyma Console](https://github.com/kyma-project/console) and other UIs integrated in Kyma ([Grafana](https://github.com/grafana/grafana), [Loki](https://github.com/grafana/loki), and [Jaeger](https://github.com/jaegertracing/jaeger)). Kyma is an open-source project ([`github.com/kyma-project`](https://github.com/kyma-project/kyma)) designed natively on Kubernetes, that allows you to extend and customize your applications in a quick and modern way, using serverless computing or microservice architecture.
|
||||
- [Pusher](https://pusher.com) uses Dex for authenticating users across their Kubernetes infrastructure (using Kubernetes OIDC support) in conjunction with the [OAuth2 Proxy](https://github.com/pusher/oauth2_proxy) for protecting web UIs.
|
||||
- [Pydio](https://pydio.com/) Pydio Cells is an open source sync & share platform written in Go. Cells is using Dex as an OIDC service for authentication and authorizations. Check out [Pydio Cells repository](https://github.com/pydio/cells) for more information and/or to contribute.
|
||||
- [sigstore](https://sigstore.dev) uses Dex for authentication in their public Fulcio instance, which is a certificate authority for code signing certificates bound to OIDC-based identities.
|
||||
|
|
50
Dockerfile
50
Dockerfile
|
@ -1,8 +1,10 @@
|
|||
FROM golang:1.16.2-alpine3.13 AS builder
|
||||
ARG BASE_IMAGE=alpine
|
||||
|
||||
FROM golang:1.18.4-alpine3.15 AS builder
|
||||
|
||||
WORKDIR /usr/local/src/dex
|
||||
|
||||
RUN apk add --no-cache --update alpine-sdk
|
||||
RUN apk add --no-cache --update alpine-sdk ca-certificates openssl
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
@ -20,51 +22,51 @@ COPY . .
|
|||
|
||||
RUN make release-binary
|
||||
|
||||
FROM alpine:3.13.2 AS gomplate
|
||||
FROM alpine:3.16.2 AS stager
|
||||
|
||||
RUN mkdir -p /var/dex
|
||||
RUN mkdir -p /etc/dex
|
||||
COPY config.docker.yaml /etc/dex/
|
||||
|
||||
FROM alpine:3.16.2 AS gomplate
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
ENV GOMPLATE_VERSION=v3.9.0
|
||||
ENV GOMPLATE_VERSION=v3.11.2
|
||||
|
||||
RUN wget -O /usr/local/bin/gomplate \
|
||||
"https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}" \
|
||||
&& chmod +x /usr/local/bin/gomplate
|
||||
"https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}" \
|
||||
&& chmod +x /usr/local/bin/gomplate
|
||||
|
||||
# For Dependabot to detect base image versions
|
||||
FROM alpine:3.16.2 AS alpine
|
||||
FROM gcr.io/distroless/static:latest AS distroless
|
||||
|
||||
FROM alpine:3.13.2
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
# Dex connectors, such as GitHub and Google logins require root certificates.
|
||||
# Proper installations should manage those certificates, but it's a bad user
|
||||
# experience when this doesn't work out of the box.
|
||||
#
|
||||
# OpenSSL is required so wget can query HTTPS endpoints for health checking.
|
||||
RUN apk add --no-cache --update ca-certificates openssl
|
||||
# See https://go.dev/src/crypto/x509/root_linux.go for Go root CA bundle locations.
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
RUN mkdir -p /var/dex
|
||||
RUN chown -R 1001:1001 /var/dex
|
||||
|
||||
RUN mkdir -p /etc/dex
|
||||
COPY config.docker.yaml /etc/dex/
|
||||
RUN chown -R 1001:1001 /etc/dex
|
||||
COPY --from=stager --chown=1001:1001 /var/dex /var/dex
|
||||
COPY --from=stager --chown=1001:1001 /etc/dex /etc/dex
|
||||
|
||||
# Copy module files for CVE scanning / dependency analysis.
|
||||
COPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/local/src/dex/
|
||||
COPY --from=builder /usr/local/src/dex/api/v2/go.mod /usr/local/src/dex/api/v2/go.sum /usr/local/src/dex/api/v2/
|
||||
|
||||
COPY --from=builder /go/bin/dex /usr/local/bin/dex
|
||||
COPY --from=builder /go/bin/docker-entrypoint /usr/local/bin/docker-entrypoint
|
||||
COPY --from=builder /usr/local/src/dex/web /srv/dex/web
|
||||
|
||||
COPY --from=gomplate /usr/local/bin/gomplate /usr/local/bin/gomplate
|
||||
|
||||
USER 1001:1001
|
||||
|
||||
# Import frontend assets and set the correct CWD directory so the assets
|
||||
# are in the default path.
|
||||
COPY --from=builder /usr/local/src/dex/web /web
|
||||
|
||||
USER 1001:1001
|
||||
|
||||
COPY docker-entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint"]
|
||||
CMD ["dex", "serve", "/etc/dex/config.docker.yaml"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Joel Speed <Joel.speed@hotmail.co.uk> (@JoelSpeed)
|
||||
Maksim Nabokikh <max.nabokih@gmail.com> (@nabokihms)
|
||||
Mark Sagi-Kazar <mark.sagikazar@gmail.com> (@sagikazarmark)
|
||||
Nandor Kracser <bonifaido@gmail.com> (@bonifaido)
|
||||
Rithu John <rithujohn191@gmail.com> (@rithujohn191)
|
||||
|
|
126
Makefile
126
Makefile
|
@ -1,7 +1,10 @@
|
|||
OS = $(shell uname | tr A-Z a-z)
|
||||
|
||||
export PATH := $(abspath bin/protoc/bin/):$(abspath bin/):${PATH}
|
||||
|
||||
PROJ=dex
|
||||
ORG_PATH=github.com/dexidp
|
||||
REPO_PATH=$(ORG_PATH)/$(PROJ)
|
||||
export PATH := $(PWD)/bin:$(PATH)
|
||||
|
||||
VERSION ?= $(shell ./scripts/git-version)
|
||||
|
||||
|
@ -15,12 +18,18 @@ group=$(shell id -g -n)
|
|||
|
||||
export GOBIN=$(PWD)/bin
|
||||
|
||||
LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)"
|
||||
LD_FLAGS="-w -X main.version=$(VERSION)"
|
||||
|
||||
# Dependency versions
|
||||
GOLANGCI_VERSION = 1.32.2
|
||||
|
||||
build: bin/dex
|
||||
KIND_NODE_IMAGE = "kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729"
|
||||
KIND_TMP_DIR = "$(PWD)/bin/test/dex-kind-kubeconfig"
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
@go generate $(REPO_PATH)/storage/ent/
|
||||
|
||||
build: generate bin/dex
|
||||
|
||||
bin/dex:
|
||||
@mkdir -p bin/
|
||||
|
@ -37,8 +46,10 @@ bin/example-app:
|
|||
@cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/example-app
|
||||
|
||||
.PHONY: release-binary
|
||||
release-binary:
|
||||
release-binary: LD_FLAGS = "-w -X main.version=$(VERSION) -extldflags \"-static\""
|
||||
release-binary: generate
|
||||
@go build -o /go/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
|
||||
@go build -o /go/bin/docker-entrypoint -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/docker-entrypoint
|
||||
|
||||
docker-compose.override.yaml:
|
||||
cp docker-compose.override.yaml.dist docker-compose.override.yaml
|
||||
|
@ -52,60 +63,40 @@ up: docker-compose.override.yaml ## Launch the development environment
|
|||
down: clear ## Destroy the development environment
|
||||
docker-compose down --volumes --remove-orphans --rmi local
|
||||
|
||||
test: bin/test/kube-apiserver bin/test/etcd
|
||||
test:
|
||||
@go test -v ./...
|
||||
|
||||
testrace: bin/test/kube-apiserver bin/test/etcd
|
||||
testrace:
|
||||
@go test -v --race ./...
|
||||
|
||||
export TEST_ASSET_KUBE_APISERVER=$(abspath bin/test/kube-apiserver)
|
||||
export TEST_ASSET_ETCD=$(abspath bin/test/etcd)
|
||||
|
||||
bin/test/kube-apiserver:
|
||||
.PHONY: kind-up kind-down kind-tests
|
||||
kind-up:
|
||||
@mkdir -p bin/test
|
||||
curl -L https://storage.googleapis.com/k8s-c10s-test-binaries/kube-apiserver-$(shell uname)-x86_64 > bin/test/kube-apiserver
|
||||
chmod +x bin/test/kube-apiserver
|
||||
@kind create cluster --image ${KIND_NODE_IMAGE} --kubeconfig ${KIND_TMP_DIR}
|
||||
|
||||
bin/test/etcd:
|
||||
@mkdir -p bin/test
|
||||
curl -L https://storage.googleapis.com/k8s-c10s-test-binaries/etcd-$(shell uname)-x86_64 > bin/test/etcd
|
||||
chmod +x bin/test/etcd
|
||||
kind-down:
|
||||
@kind delete cluster
|
||||
rm ${KIND_TMP_DIR}
|
||||
|
||||
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
||||
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
||||
bin/golangci-lint-${GOLANGCI_VERSION}:
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
|
||||
@mv bin/golangci-lint $@
|
||||
kind-tests: export DEX_KUBERNETES_CONFIG_PATH=${KIND_TMP_DIR}
|
||||
kind-tests: testall
|
||||
|
||||
.PHONY: lint
|
||||
lint: bin/golangci-lint ## Run linter
|
||||
bin/golangci-lint run
|
||||
.PHONY: lint lint-fix
|
||||
lint: ## Run linter
|
||||
golangci-lint run
|
||||
|
||||
.PHONY: fix
|
||||
fix: bin/golangci-lint ## Fix lint violations
|
||||
bin/golangci-lint run --fix
|
||||
fix: ## Fix lint violations
|
||||
golangci-lint run --fix
|
||||
|
||||
.PHONY: docker-image
|
||||
docker-image:
|
||||
@sudo docker build -t $(DOCKER_IMAGE) .
|
||||
|
||||
.PHONY: proto
|
||||
proto: bin/protoc bin/protoc-gen-go
|
||||
@./bin/protoc --go_out=plugins=grpc:. --plugin=protoc-gen-go=./bin/protoc-gen-go api/v2/*.proto
|
||||
@cp api/v2/*.proto api/
|
||||
@./bin/protoc --go_out=plugins=grpc:. --plugin=protoc-gen-go=./bin/protoc-gen-go api/*.proto
|
||||
@./bin/protoc --go_out=. --plugin=protoc-gen-go=./bin/protoc-gen-go server/internal/*.proto
|
||||
|
||||
.PHONY: verify-proto
|
||||
verify-proto: proto
|
||||
@./scripts/git-diff
|
||||
|
||||
bin/protoc: scripts/get-protoc
|
||||
@./scripts/get-protoc bin/protoc
|
||||
|
||||
bin/protoc-gen-go:
|
||||
@go install -v github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
clean:
|
||||
@rm -rf bin/
|
||||
|
||||
|
@ -114,3 +105,58 @@ testall: testrace
|
|||
FORCE:
|
||||
|
||||
.PHONY: test testrace testall
|
||||
|
||||
.PHONY: proto
|
||||
proto:
|
||||
@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/v2/*.proto
|
||||
@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/*.proto
|
||||
#@cp api/v2/*.proto api/
|
||||
|
||||
.PHONY: proto-internal
|
||||
proto-internal:
|
||||
@protoc --go_out=paths=source_relative:. server/internal/*.proto
|
||||
|
||||
# Dependency versions
|
||||
GOLANGCI_VERSION = 1.46.0
|
||||
GOTESTSUM_VERSION ?= 1.7.0
|
||||
PROTOC_VERSION = 3.15.6
|
||||
PROTOC_GEN_GO_VERSION = 1.26.0
|
||||
PROTOC_GEN_GO_GRPC_VERSION = 1.1.0
|
||||
KIND_VERSION = 0.11.1
|
||||
|
||||
deps: bin/gotestsum bin/golangci-lint bin/protoc bin/protoc-gen-go bin/protoc-gen-go-grpc bin/kind
|
||||
|
||||
bin/gotestsum:
|
||||
@mkdir -p bin
|
||||
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_$(shell uname | tr A-Z a-z)_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum
|
||||
@chmod +x ./bin/gotestsum
|
||||
|
||||
bin/golangci-lint:
|
||||
@mkdir -p bin
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
|
||||
|
||||
bin/protoc:
|
||||
@mkdir -p bin/protoc
|
||||
ifeq ($(shell uname | tr A-Z a-z), darwin)
|
||||
curl -L https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-osx-x86_64.zip > bin/protoc.zip
|
||||
endif
|
||||
ifeq ($(shell uname | tr A-Z a-z), linux)
|
||||
curl -L https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip > bin/protoc.zip
|
||||
endif
|
||||
unzip bin/protoc.zip -d bin/protoc
|
||||
rm bin/protoc.zip
|
||||
|
||||
bin/protoc-gen-go:
|
||||
@mkdir -p bin
|
||||
curl -L https://github.com/protocolbuffers/protobuf-go/releases/download/v${PROTOC_GEN_GO_VERSION}/protoc-gen-go.v${PROTOC_GEN_GO_VERSION}.$(shell uname | tr A-Z a-z).amd64.tar.gz | tar -zOxf - protoc-gen-go > ./bin/protoc-gen-go
|
||||
@chmod +x ./bin/protoc-gen-go
|
||||
|
||||
bin/protoc-gen-go-grpc:
|
||||
@mkdir -p bin
|
||||
curl -L https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTOC_GEN_GO_GRPC_VERSION}/protoc-gen-go-grpc.v${PROTOC_GEN_GO_GRPC_VERSION}.$(shell uname | tr A-Z a-z).amd64.tar.gz | tar -zOxf - ./protoc-gen-go-grpc > ./bin/protoc-gen-go-grpc
|
||||
@chmod +x ./bin/protoc-gen-go-grpc
|
||||
|
||||
bin/kind:
|
||||
@mkdir -p bin
|
||||
curl -L https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-$(shell uname | tr A-Z a-z)-amd64 > ./bin/kind
|
||||
@chmod +x ./bin/kind
|
||||
|
|
31
README.md
31
README.md
|
@ -2,7 +2,6 @@
|
|||
|
||||
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/dexidp/dex/CI?style=flat-square)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/dexidp/dex?style=flat-square)](https://goreportcard.com/report/github.com/dexidp/dex)
|
||||
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/dexidp/dex)
|
||||
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/dexidp/dex)
|
||||
|
||||
![logo](docs/logos/dex-horizontal-color.png)
|
||||
|
@ -72,14 +71,16 @@ Dex implements the following connectors:
|
|||
| [SAML 2.0](https://dexidp.io/docs/connectors/saml/) | no | yes | no | stable | WARNING: Unmaintained and likely vulnerable to auth bypasses ([#1884](https://github.com/dexidp/dex/discussions/1884)) |
|
||||
| [GitLab](https://dexidp.io/docs/connectors/gitlab/) | yes | yes | yes | beta | |
|
||||
| [OpenID Connect](https://dexidp.io/docs/connectors/oidc/) | yes | yes | yes | beta | Includes Salesforce, Azure, etc. |
|
||||
| [OAuth 2.0](https://dexidp.io/docs/connectors/oauth/) | no | yes | yes | alpha | |
|
||||
| [Google](https://dexidp.io/docs/connectors/google/) | yes | yes | yes | alpha | |
|
||||
| [LinkedIn](https://dexidp.io/docs/connectors/linkedin/) | yes | no | no | beta | |
|
||||
| [Microsoft](https://dexidp.io/docs/connectors/microsoft/) | yes | yes | no | beta | |
|
||||
| [AuthProxy](https://dexidp.io/docs/connectors/authproxy/) | no | no | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
|
||||
| [AuthProxy](https://dexidp.io/docs/connectors/authproxy/) | no | yes | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
|
||||
| [Bitbucket Cloud](https://dexidp.io/docs/connectors/bitbucketcloud/) | yes | yes | no | alpha | |
|
||||
| [OpenShift](https://dexidp.io/docs/connectors/openshift/) | no | yes | no | alpha | |
|
||||
| [Atlassian Crowd](https://dexidp.io/docs/connectors/atlassiancrowd/) | yes | yes | yes * | beta | preferred_username claim must be configured through config |
|
||||
| [Gitea](https://dexidp.io/docs/connectors/gitea/) | yes | no | yes | alpha | |
|
||||
| [Gitea](https://dexidp.io/docs/connectors/gitea/) | yes | no | yes | beta | |
|
||||
| [OpenStack Keystone](https://dexidp.io/docs/connectors/keystone/) | yes | yes | no | alpha | |
|
||||
|
||||
Stable, beta, and alpha are defined as:
|
||||
|
||||
|
@ -102,13 +103,9 @@ All changes or deprecations of connector features will be announced in the [rele
|
|||
* Client libraries
|
||||
* [Go][go-oidc]
|
||||
|
||||
## Reporting a security vulnerability
|
||||
## Reporting a vulnerability
|
||||
|
||||
Due to their public nature, GitHub and mailing lists are NOT appropriate places
|
||||
for reporting vulnerabilities.
|
||||
|
||||
Please email the [maintainers list](mailto:cncf-dex-maintainers@lists.cncf.io) to report issues that may
|
||||
be security-related.
|
||||
Please see our [security policy](.github/SECURITY.md) for details about reporting vulnerabilities.
|
||||
|
||||
## Getting help
|
||||
|
||||
|
@ -128,3 +125,19 @@ be security-related.
|
|||
[go-oidc]: https://github.com/coreos/go-oidc
|
||||
[issue-1065]: https://github.com/dexidp/dex/issues/1065
|
||||
[release-notes]: https://github.com/dexidp/dex/releases
|
||||
|
||||
## Development
|
||||
|
||||
When all coding and testing is done, please run the test suite:
|
||||
|
||||
```shell
|
||||
make testall
|
||||
```
|
||||
|
||||
For the best developer experience, install [Nix](https://builtwithnix.org/) and [direnv](https://direnv.net/).
|
||||
|
||||
Alternatively, install Go and Docker manually or using a package manager. Install the rest of the dependencies by running `make deps`.
|
||||
|
||||
## License
|
||||
|
||||
The project is licensed under the [Apache License, Version 2.0](LICENSE).
|
||||
|
|
2944
api/api.pb.go
2944
api/api.pb.go
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,10 @@
|
|||
syntax = "proto3";
|
||||
option java_package = "com.coreos.dex.api";
|
||||
|
||||
package api;
|
||||
|
||||
option java_package = "com.coreos.dex.api";
|
||||
option go_package = "github.com/dexidp/dex/api";
|
||||
|
||||
// Client represents an OAuth2 client.
|
||||
message Client {
|
||||
string id = 1;
|
||||
|
@ -22,7 +24,7 @@ message CreateClientReq {
|
|||
// CreateClientResp returns the response from creating a client.
|
||||
message CreateClientResp {
|
||||
bool already_exists = 1;
|
||||
Client client = 2;
|
||||
Client client = 2;
|
||||
}
|
||||
|
||||
// DeleteClientReq is a request to delete a client.
|
||||
|
@ -31,7 +33,7 @@ message DeleteClientReq {
|
|||
string id = 1;
|
||||
}
|
||||
|
||||
// DeleteClientResp determines if the client is deleted successfully.
|
||||
// DeleteClientResp determines if the client is deleted successfully.
|
||||
message DeleteClientResp {
|
||||
bool not_found = 1;
|
||||
}
|
||||
|
@ -80,7 +82,7 @@ message UpdatePasswordReq {
|
|||
string new_username = 3;
|
||||
}
|
||||
|
||||
// UpdatePasswordResp returns the response from modifying an existing password.
|
||||
// UpdatePasswordResp returns the response from modifying an existing password.
|
||||
message UpdatePasswordResp {
|
||||
bool not_found = 1;
|
||||
}
|
||||
|
@ -90,7 +92,7 @@ message DeletePasswordReq {
|
|||
string email = 1;
|
||||
}
|
||||
|
||||
// DeletePasswordResp returns the response from deleting a password.
|
||||
// DeletePasswordResp returns the response from deleting a password.
|
||||
message DeletePasswordResp {
|
||||
bool not_found = 1;
|
||||
}
|
||||
|
@ -142,7 +144,7 @@ message RevokeRefreshReq {
|
|||
string client_id = 2;
|
||||
}
|
||||
|
||||
// RevokeRefreshResp determines if the refresh token is revoked successfully.
|
||||
// RevokeRefreshResp determines if the refresh token is revoked successfully.
|
||||
message RevokeRefreshResp {
|
||||
// Set to true is refresh token was not found and token could not be revoked.
|
||||
bool not_found = 1;
|
||||
|
|
487
api/api_grpc.pb.go
Normal file
487
api/api_grpc.pb.go
Normal file
|
@ -0,0 +1,487 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// DexClient is the client API for Dex service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type DexClient interface {
|
||||
// CreateClient creates a client.
|
||||
CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error)
|
||||
// UpdateClient updates an existing client
|
||||
UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error)
|
||||
// DeleteClient deletes the provided client.
|
||||
DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error)
|
||||
// CreatePassword creates a password.
|
||||
CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error)
|
||||
// UpdatePassword modifies existing password.
|
||||
UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error)
|
||||
// DeletePassword deletes the password.
|
||||
DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error)
|
||||
// ListPassword lists all password entries.
|
||||
ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error)
|
||||
// GetVersion returns version information of the server.
|
||||
GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error)
|
||||
// ListRefresh lists all the refresh token entries for a particular user.
|
||||
ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error)
|
||||
// RevokeRefresh revokes the refresh token for the provided user-client pair.
|
||||
//
|
||||
// Note that each user-client pair can have only one refresh token at a time.
|
||||
RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error)
|
||||
// VerifyPassword returns whether a password matches a hash for a specific email or not.
|
||||
VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error)
|
||||
}
|
||||
|
||||
type dexClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewDexClient(cc grpc.ClientConnInterface) DexClient {
|
||||
return &dexClient{cc}
|
||||
}
|
||||
|
||||
func (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) {
|
||||
out := new(CreateClientResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/CreateClient", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) {
|
||||
out := new(UpdateClientResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/UpdateClient", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) {
|
||||
out := new(DeleteClientResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/DeleteClient", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) {
|
||||
out := new(CreatePasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/CreatePassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) {
|
||||
out := new(UpdatePasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/UpdatePassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) {
|
||||
out := new(DeletePasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/DeletePassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) {
|
||||
out := new(ListPasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/ListPasswords", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) {
|
||||
out := new(VersionResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/GetVersion", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) {
|
||||
out := new(ListRefreshResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/ListRefresh", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) {
|
||||
out := new(RevokeRefreshResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/RevokeRefresh", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) {
|
||||
out := new(VerifyPasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/VerifyPassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DexServer is the server API for Dex service.
|
||||
// All implementations must embed UnimplementedDexServer
|
||||
// for forward compatibility
|
||||
type DexServer interface {
|
||||
// CreateClient creates a client.
|
||||
CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error)
|
||||
// UpdateClient updates an existing client
|
||||
UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error)
|
||||
// DeleteClient deletes the provided client.
|
||||
DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error)
|
||||
// CreatePassword creates a password.
|
||||
CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error)
|
||||
// UpdatePassword modifies existing password.
|
||||
UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error)
|
||||
// DeletePassword deletes the password.
|
||||
DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error)
|
||||
// ListPassword lists all password entries.
|
||||
ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error)
|
||||
// GetVersion returns version information of the server.
|
||||
GetVersion(context.Context, *VersionReq) (*VersionResp, error)
|
||||
// ListRefresh lists all the refresh token entries for a particular user.
|
||||
ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error)
|
||||
// RevokeRefresh revokes the refresh token for the provided user-client pair.
|
||||
//
|
||||
// Note that each user-client pair can have only one refresh token at a time.
|
||||
RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error)
|
||||
// VerifyPassword returns whether a password matches a hash for a specific email or not.
|
||||
VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error)
|
||||
mustEmbedUnimplementedDexServer()
|
||||
}
|
||||
|
||||
// UnimplementedDexServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedDexServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedDexServer) CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteClient not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreatePassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdatePassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListPasswords not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) GetVersion(context.Context, *VersionReq) (*VersionResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListRefresh not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeRefresh not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method VerifyPassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) mustEmbedUnimplementedDexServer() {}
|
||||
|
||||
// UnsafeDexServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to DexServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeDexServer interface {
|
||||
mustEmbedUnimplementedDexServer()
|
||||
}
|
||||
|
||||
func RegisterDexServer(s grpc.ServiceRegistrar, srv DexServer) {
|
||||
s.RegisterService(&Dex_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreateClientReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).CreateClient(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/CreateClient",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateClientReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).UpdateClient(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/UpdateClient",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteClientReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).DeleteClient(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/DeleteClient",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreatePasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).CreatePassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/CreatePassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdatePasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).UpdatePassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/UpdatePassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeletePasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).DeletePassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/DeletePassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListPasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).ListPasswords(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/ListPasswords",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(VersionReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).GetVersion(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/GetVersion",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).GetVersion(ctx, req.(*VersionReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListRefreshReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).ListRefresh(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/ListRefresh",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RevokeRefreshReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).RevokeRefresh(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/RevokeRefresh",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(VerifyPasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).VerifyPassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/VerifyPassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Dex_ServiceDesc is the grpc.ServiceDesc for Dex service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Dex_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "api.Dex",
|
||||
HandlerType: (*DexServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateClient",
|
||||
Handler: _Dex_CreateClient_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateClient",
|
||||
Handler: _Dex_UpdateClient_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteClient",
|
||||
Handler: _Dex_DeleteClient_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreatePassword",
|
||||
Handler: _Dex_CreatePassword_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdatePassword",
|
||||
Handler: _Dex_UpdatePassword_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeletePassword",
|
||||
Handler: _Dex_DeletePassword_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListPasswords",
|
||||
Handler: _Dex_ListPasswords_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetVersion",
|
||||
Handler: _Dex_GetVersion_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListRefresh",
|
||||
Handler: _Dex_ListRefresh_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RevokeRefresh",
|
||||
Handler: _Dex_RevokeRefresh_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "VerifyPassword",
|
||||
Handler: _Dex_VerifyPassword_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "api/api.proto",
|
||||
}
|
2945
api/v2/api.pb.go
2945
api/v2/api.pb.go
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,10 @@
|
|||
syntax = "proto3";
|
||||
option java_package = "com.coreos.dex.api";
|
||||
|
||||
package api;
|
||||
|
||||
option java_package = "com.coreos.dex.api";
|
||||
option go_package = "github.com/dexidp/dex/api/v2;api";
|
||||
|
||||
// Client represents an OAuth2 client.
|
||||
message Client {
|
||||
string id = 1;
|
||||
|
@ -22,7 +24,7 @@ message CreateClientReq {
|
|||
// CreateClientResp returns the response from creating a client.
|
||||
message CreateClientResp {
|
||||
bool already_exists = 1;
|
||||
Client client = 2;
|
||||
Client client = 2;
|
||||
}
|
||||
|
||||
// DeleteClientReq is a request to delete a client.
|
||||
|
@ -31,7 +33,7 @@ message DeleteClientReq {
|
|||
string id = 1;
|
||||
}
|
||||
|
||||
// DeleteClientResp determines if the client is deleted successfully.
|
||||
// DeleteClientResp determines if the client is deleted successfully.
|
||||
message DeleteClientResp {
|
||||
bool not_found = 1;
|
||||
}
|
||||
|
@ -80,7 +82,7 @@ message UpdatePasswordReq {
|
|||
string new_username = 3;
|
||||
}
|
||||
|
||||
// UpdatePasswordResp returns the response from modifying an existing password.
|
||||
// UpdatePasswordResp returns the response from modifying an existing password.
|
||||
message UpdatePasswordResp {
|
||||
bool not_found = 1;
|
||||
}
|
||||
|
@ -90,7 +92,7 @@ message DeletePasswordReq {
|
|||
string email = 1;
|
||||
}
|
||||
|
||||
// DeletePasswordResp returns the response from deleting a password.
|
||||
// DeletePasswordResp returns the response from deleting a password.
|
||||
message DeletePasswordResp {
|
||||
bool not_found = 1;
|
||||
}
|
||||
|
@ -142,7 +144,7 @@ message RevokeRefreshReq {
|
|||
string client_id = 2;
|
||||
}
|
||||
|
||||
// RevokeRefreshResp determines if the refresh token is revoked successfully.
|
||||
// RevokeRefreshResp determines if the refresh token is revoked successfully.
|
||||
message RevokeRefreshResp {
|
||||
// Set to true is refresh token was not found and token could not be revoked.
|
||||
bool not_found = 1;
|
||||
|
|
487
api/v2/api_grpc.pb.go
Normal file
487
api/v2/api_grpc.pb.go
Normal file
|
@ -0,0 +1,487 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// DexClient is the client API for Dex service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type DexClient interface {
|
||||
// CreateClient creates a client.
|
||||
CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error)
|
||||
// UpdateClient updates an existing client
|
||||
UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error)
|
||||
// DeleteClient deletes the provided client.
|
||||
DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error)
|
||||
// CreatePassword creates a password.
|
||||
CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error)
|
||||
// UpdatePassword modifies existing password.
|
||||
UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error)
|
||||
// DeletePassword deletes the password.
|
||||
DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error)
|
||||
// ListPassword lists all password entries.
|
||||
ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error)
|
||||
// GetVersion returns version information of the server.
|
||||
GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error)
|
||||
// ListRefresh lists all the refresh token entries for a particular user.
|
||||
ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error)
|
||||
// RevokeRefresh revokes the refresh token for the provided user-client pair.
|
||||
//
|
||||
// Note that each user-client pair can have only one refresh token at a time.
|
||||
RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error)
|
||||
// VerifyPassword returns whether a password matches a hash for a specific email or not.
|
||||
VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error)
|
||||
}
|
||||
|
||||
type dexClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewDexClient(cc grpc.ClientConnInterface) DexClient {
|
||||
return &dexClient{cc}
|
||||
}
|
||||
|
||||
func (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) {
|
||||
out := new(CreateClientResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/CreateClient", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) {
|
||||
out := new(UpdateClientResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/UpdateClient", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) {
|
||||
out := new(DeleteClientResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/DeleteClient", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) {
|
||||
out := new(CreatePasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/CreatePassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) {
|
||||
out := new(UpdatePasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/UpdatePassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) {
|
||||
out := new(DeletePasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/DeletePassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) {
|
||||
out := new(ListPasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/ListPasswords", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) {
|
||||
out := new(VersionResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/GetVersion", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) {
|
||||
out := new(ListRefreshResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/ListRefresh", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) {
|
||||
out := new(RevokeRefreshResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/RevokeRefresh", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) {
|
||||
out := new(VerifyPasswordResp)
|
||||
err := c.cc.Invoke(ctx, "/api.Dex/VerifyPassword", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DexServer is the server API for Dex service.
|
||||
// All implementations must embed UnimplementedDexServer
|
||||
// for forward compatibility
|
||||
type DexServer interface {
|
||||
// CreateClient creates a client.
|
||||
CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error)
|
||||
// UpdateClient updates an existing client
|
||||
UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error)
|
||||
// DeleteClient deletes the provided client.
|
||||
DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error)
|
||||
// CreatePassword creates a password.
|
||||
CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error)
|
||||
// UpdatePassword modifies existing password.
|
||||
UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error)
|
||||
// DeletePassword deletes the password.
|
||||
DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error)
|
||||
// ListPassword lists all password entries.
|
||||
ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error)
|
||||
// GetVersion returns version information of the server.
|
||||
GetVersion(context.Context, *VersionReq) (*VersionResp, error)
|
||||
// ListRefresh lists all the refresh token entries for a particular user.
|
||||
ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error)
|
||||
// RevokeRefresh revokes the refresh token for the provided user-client pair.
|
||||
//
|
||||
// Note that each user-client pair can have only one refresh token at a time.
|
||||
RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error)
|
||||
// VerifyPassword returns whether a password matches a hash for a specific email or not.
|
||||
VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error)
|
||||
mustEmbedUnimplementedDexServer()
|
||||
}
|
||||
|
||||
// UnimplementedDexServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedDexServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedDexServer) CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteClient not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreatePassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdatePassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListPasswords not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) GetVersion(context.Context, *VersionReq) (*VersionResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListRefresh not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeRefresh not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method VerifyPassword not implemented")
|
||||
}
|
||||
func (UnimplementedDexServer) mustEmbedUnimplementedDexServer() {}
|
||||
|
||||
// UnsafeDexServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to DexServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeDexServer interface {
|
||||
mustEmbedUnimplementedDexServer()
|
||||
}
|
||||
|
||||
func RegisterDexServer(s grpc.ServiceRegistrar, srv DexServer) {
|
||||
s.RegisterService(&Dex_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreateClientReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).CreateClient(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/CreateClient",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateClientReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).UpdateClient(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/UpdateClient",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteClientReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).DeleteClient(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/DeleteClient",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreatePasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).CreatePassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/CreatePassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdatePasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).UpdatePassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/UpdatePassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeletePasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).DeletePassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/DeletePassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListPasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).ListPasswords(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/ListPasswords",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(VersionReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).GetVersion(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/GetVersion",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).GetVersion(ctx, req.(*VersionReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListRefreshReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).ListRefresh(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/ListRefresh",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RevokeRefreshReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).RevokeRefresh(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/RevokeRefresh",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(VerifyPasswordReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DexServer).VerifyPassword(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Dex/VerifyPassword",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Dex_ServiceDesc is the grpc.ServiceDesc for Dex service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Dex_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "api.Dex",
|
||||
HandlerType: (*DexServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateClient",
|
||||
Handler: _Dex_CreateClient_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateClient",
|
||||
Handler: _Dex_UpdateClient_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteClient",
|
||||
Handler: _Dex_DeleteClient_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreatePassword",
|
||||
Handler: _Dex_CreatePassword_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdatePassword",
|
||||
Handler: _Dex_UpdatePassword_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeletePassword",
|
||||
Handler: _Dex_DeletePassword_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListPasswords",
|
||||
Handler: _Dex_ListPasswords_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetVersion",
|
||||
Handler: _Dex_GetVersion_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListRefresh",
|
||||
Handler: _Dex_ListRefresh_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RevokeRefresh",
|
||||
Handler: _Dex_RevokeRefresh_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "VerifyPassword",
|
||||
Handler: _Dex_VerifyPassword_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "api/v2/api.proto",
|
||||
}
|
|
@ -1,8 +1,16 @@
|
|||
module github.com/dexidp/dex/api/v2
|
||||
|
||||
go 1.14
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2
|
||||
google.golang.org/grpc v1.26.0
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect
|
||||
)
|
||||
|
|
110
api/v2/go.sum
110
api/v2/go.sum
|
@ -1,49 +1,141 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 h1:qRu95HZ148xXw+XeZ3dvqe85PxH4X8+jIo0iRPKcEnM=
|
||||
google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/dexidp/dex/pkg/log"
|
||||
"github.com/dexidp/dex/server"
|
||||
"github.com/dexidp/dex/storage"
|
||||
"github.com/dexidp/dex/storage/ent"
|
||||
"github.com/dexidp/dex/storage/etcd"
|
||||
"github.com/dexidp/dex/storage/kubernetes"
|
||||
"github.com/dexidp/dex/storage/memory"
|
||||
|
@ -150,6 +151,8 @@ type Web struct {
|
|||
// Telemetry is the config format for telemetry including the HTTP server config.
|
||||
type Telemetry struct {
|
||||
HTTP string `json:"http"`
|
||||
// EnableProfiling makes profiling endpoints available via web interface host:port/debug/pprof/
|
||||
EnableProfiling bool `json:"enableProfiling"`
|
||||
}
|
||||
|
||||
// GRPC is the config for the gRPC API.
|
||||
|
@ -173,13 +176,36 @@ type StorageConfig interface {
|
|||
Open(logger log.Logger) (storage.Storage, error)
|
||||
}
|
||||
|
||||
var (
|
||||
_ StorageConfig = (*etcd.Etcd)(nil)
|
||||
_ StorageConfig = (*kubernetes.Config)(nil)
|
||||
_ StorageConfig = (*memory.Config)(nil)
|
||||
_ StorageConfig = (*sql.SQLite3)(nil)
|
||||
_ StorageConfig = (*sql.Postgres)(nil)
|
||||
_ StorageConfig = (*sql.MySQL)(nil)
|
||||
_ StorageConfig = (*ent.SQLite3)(nil)
|
||||
_ StorageConfig = (*ent.Postgres)(nil)
|
||||
_ StorageConfig = (*ent.MySQL)(nil)
|
||||
)
|
||||
|
||||
func getORMBasedSQLStorage(normal, entBased StorageConfig) func() StorageConfig {
|
||||
return func() StorageConfig {
|
||||
switch os.Getenv("DEX_ENT_ENABLED") {
|
||||
case "true", "yes":
|
||||
return entBased
|
||||
default:
|
||||
return normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var storages = map[string]func() StorageConfig{
|
||||
"etcd": func() StorageConfig { return new(etcd.Etcd) },
|
||||
"kubernetes": func() StorageConfig { return new(kubernetes.Config) },
|
||||
"memory": func() StorageConfig { return new(memory.Config) },
|
||||
"sqlite3": func() StorageConfig { return new(sql.SQLite3) },
|
||||
"postgres": func() StorageConfig { return new(sql.Postgres) },
|
||||
"mysql": func() StorageConfig { return new(sql.MySQL) },
|
||||
"sqlite3": getORMBasedSQLStorage(&sql.SQLite3{}, &ent.SQLite3{}),
|
||||
"postgres": getORMBasedSQLStorage(&sql.Postgres{}, &ent.Postgres{}),
|
||||
"mysql": getORMBasedSQLStorage(&sql.MySQL{}, &ent.MySQL{}),
|
||||
}
|
||||
|
||||
// isExpandEnvEnabled returns if os.ExpandEnv should be used for each storage and connector config.
|
||||
|
@ -304,6 +330,9 @@ type Expiry struct {
|
|||
|
||||
// DeviceRequests defines the duration of time for which the DeviceRequests will be valid.
|
||||
DeviceRequests string `json:"deviceRequests"`
|
||||
|
||||
// RefreshTokens defines refresh tokens expiry policy
|
||||
RefreshTokens RefreshToken `json:"refreshTokens"`
|
||||
}
|
||||
|
||||
// Logger holds configuration required to customize logging for dex.
|
||||
|
@ -314,3 +343,10 @@ type Logger struct {
|
|||
// Format specifies the format to be used for logging.
|
||||
Format string `json:"format"`
|
||||
}
|
||||
|
||||
type RefreshToken struct {
|
||||
DisableRotation bool `json:"disableRotation"`
|
||||
ReuseInterval string `json:"reuseInterval"`
|
||||
AbsoluteLifetime string `json:"absoluteLifetime"`
|
||||
ValidIfNotUsedFor string `json:"validIfNotUsedFor"`
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ import (
|
|||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"github.com/oklog/run"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -75,7 +77,7 @@ func commandServe() *cobra.Command {
|
|||
|
||||
func runServe(options serveOptions) error {
|
||||
configFile := options.config
|
||||
configData, err := ioutil.ReadFile(configFile)
|
||||
configData, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read config file %s: %v", configFile, err)
|
||||
}
|
||||
|
@ -91,6 +93,15 @@ func runServe(options serveOptions) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("invalid config: %v", err)
|
||||
}
|
||||
|
||||
logger.Infof(
|
||||
"Dex Version: %s, Go Version: %s, Go OS/ARCH: %s %s",
|
||||
version,
|
||||
runtime.Version(),
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
)
|
||||
|
||||
if c.Logger.Level != "" {
|
||||
logger.Infof("config using log level: %s", c.Logger.Level)
|
||||
}
|
||||
|
@ -101,12 +112,12 @@ func runServe(options serveOptions) error {
|
|||
logger.Infof("config issuer: %s", c.Issuer)
|
||||
|
||||
prometheusRegistry := prometheus.NewRegistry()
|
||||
err = prometheusRegistry.Register(prometheus.NewGoCollector())
|
||||
err = prometheusRegistry.Register(collectors.NewGoCollector())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register Go runtime metrics: %v", err)
|
||||
}
|
||||
|
||||
err = prometheusRegistry.Register(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
|
||||
err = prometheusRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register process metrics: %v", err)
|
||||
}
|
||||
|
@ -147,7 +158,7 @@ func runServe(options serveOptions) error {
|
|||
if c.GRPC.TLSClientCA != "" {
|
||||
// Parse certificates from client CA file to a new CertPool.
|
||||
cPool := x509.NewCertPool()
|
||||
clientCert, err := ioutil.ReadFile(c.GRPC.TLSClientCA)
|
||||
clientCert, err := os.ReadFile(c.GRPC.TLSClientCA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config: reading from client CA file: %v", err)
|
||||
}
|
||||
|
@ -304,6 +315,18 @@ func runServe(options serveOptions) error {
|
|||
logger.Infof("config device requests valid for: %v", deviceRequests)
|
||||
serverConfig.DeviceRequestsValidFor = deviceRequests
|
||||
}
|
||||
refreshTokenPolicy, err := server.NewRefreshTokenPolicy(
|
||||
logger,
|
||||
c.Expiry.RefreshTokens.DisableRotation,
|
||||
c.Expiry.RefreshTokens.ValidIfNotUsedFor,
|
||||
c.Expiry.RefreshTokens.AbsoluteLifetime,
|
||||
c.Expiry.RefreshTokens.ReuseInterval,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid refresh token expiration policy config: %v", err)
|
||||
}
|
||||
|
||||
serverConfig.RefreshTokenPolicy = refreshTokenPolicy
|
||||
serv, err := server.NewServer(context.Background(), serverConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize server: %v", err)
|
||||
|
@ -324,14 +347,14 @@ func runServe(options serveOptions) error {
|
|||
telemetryRouter.Handle("/healthz/ready", handler)
|
||||
}
|
||||
|
||||
healthChecker.RegisterCheck(&gosundheit.Config{
|
||||
Check: &checks.CustomCheck{
|
||||
healthChecker.RegisterCheck(
|
||||
&checks.CustomCheck{
|
||||
CheckName: "storage",
|
||||
CheckFunc: storage.NewCustomHealthCheckFunc(serverConfig.Storage, serverConfig.Now),
|
||||
},
|
||||
ExecutionPeriod: 15 * time.Second,
|
||||
InitiallyPassing: true,
|
||||
})
|
||||
gosundheit.ExecutionPeriod(15*time.Second),
|
||||
gosundheit.InitiallyPassing(true),
|
||||
)
|
||||
|
||||
var group run.Group
|
||||
|
||||
|
@ -346,6 +369,10 @@ func runServe(options serveOptions) error {
|
|||
return fmt.Errorf("listening (%s) on %s: %v", name, c.Telemetry.HTTP, err)
|
||||
}
|
||||
|
||||
if c.Telemetry.EnableProfiling {
|
||||
pprofHandler(telemetryRouter)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Handler: telemetryRouter,
|
||||
}
|
||||
|
@ -437,7 +464,7 @@ func runServe(options serveOptions) error {
|
|||
}
|
||||
|
||||
grpcSrv := grpc.NewServer(grpcOptions...)
|
||||
api.RegisterDexServer(grpcSrv, server.NewAPI(serverConfig.Storage, logger))
|
||||
api.RegisterDexServer(grpcSrv, server.NewAPI(serverConfig.Storage, logger, version))
|
||||
|
||||
grpcMetrics.InitializeMetrics(grpcSrv)
|
||||
if c.GRPC.Reflection {
|
||||
|
@ -523,4 +550,16 @@ func applyConfigOverrides(options serveOptions, config *Config) {
|
|||
if options.grpcAddr != "" {
|
||||
config.GRPC.Addr = options.grpcAddr
|
||||
}
|
||||
|
||||
if config.Frontend.Dir == "" {
|
||||
config.Frontend.Dir = os.Getenv("DEX_FRONTEND_DIR")
|
||||
}
|
||||
}
|
||||
|
||||
func pprofHandler(router *http.ServeMux) {
|
||||
router.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/dexidp/dex/version"
|
||||
)
|
||||
|
||||
var version = "DEV"
|
||||
|
||||
func commandVersion() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
|
@ -16,7 +16,7 @@ func commandVersion() *cobra.Command {
|
|||
Run: func(_ *cobra.Command, _ []string) {
|
||||
fmt.Printf(
|
||||
"Dex Version: %s\nGo Version: %s\nGo OS/ARCH: %s %s\n",
|
||||
version.Version,
|
||||
version,
|
||||
runtime.Version(),
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
|
|
92
cmd/docker-entrypoint/main.go
Normal file
92
cmd/docker-entrypoint/main.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Package main provides a utility program to launch the Dex container process with an optional
|
||||
// templating step (provided by gomplate).
|
||||
//
|
||||
// This was originally written as a shell script, but we rewrote it as a Go program so that it could
|
||||
// run as a raw binary in a distroless container.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note that this docker-entrypoint program is args[0], and it is provided with the true process
|
||||
// args.
|
||||
args := os.Args[1:]
|
||||
|
||||
if err := run(args, realExec, realWhich); err != nil {
|
||||
fmt.Println("error:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func realExec(fork bool, args ...string) error {
|
||||
if fork {
|
||||
if output, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("cannot fork/exec command %s: %w (output: %q)", args, err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
argv0, err := exec.LookPath(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot lookup path for command %s: %w", args[0], err)
|
||||
}
|
||||
|
||||
if err := syscall.Exec(argv0, args, os.Environ()); err != nil {
|
||||
return fmt.Errorf("cannot exec command %s (%q): %w", args, argv0, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func realWhich(path string) string {
|
||||
fullPath, err := exec.LookPath(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
func run(args []string, execFunc func(bool, ...string) error, whichFunc func(string) string) error {
|
||||
if args[0] != "dex" && args[0] != whichFunc("dex") {
|
||||
return execFunc(false, args...)
|
||||
}
|
||||
|
||||
if args[1] != "serve" {
|
||||
return execFunc(false, args...)
|
||||
}
|
||||
|
||||
newArgs := []string{}
|
||||
for _, tplCandidate := range args {
|
||||
if hasSuffixes(tplCandidate, ".tpl", ".tmpl", ".yaml") {
|
||||
tmpFile, err := os.CreateTemp("/tmp", "dex.config.yaml-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create temp file: %w", err)
|
||||
}
|
||||
|
||||
if err := execFunc(true, "gomplate", "-f", tplCandidate, "-o", tmpFile.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newArgs = append(newArgs, tmpFile.Name())
|
||||
} else {
|
||||
newArgs = append(newArgs, tplCandidate)
|
||||
}
|
||||
}
|
||||
|
||||
return execFunc(false, newArgs...)
|
||||
}
|
||||
|
||||
func hasSuffixes(s string, suffixes ...string) bool {
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(s, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
113
cmd/docker-entrypoint/main_test.go
Normal file
113
cmd/docker-entrypoint/main_test.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type execArgs struct {
|
||||
fork bool
|
||||
argPrefixes []string
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
execReturns error
|
||||
whichReturns string
|
||||
wantExecArgs []execArgs
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "executable not dex",
|
||||
args: []string{"tuna", "fish"},
|
||||
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"tuna", "fish"}}},
|
||||
},
|
||||
{
|
||||
name: "executable is full path to dex",
|
||||
args: []string{"/usr/local/bin/dex", "marshmallow", "zelda"},
|
||||
whichReturns: "/usr/local/bin/dex",
|
||||
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"/usr/local/bin/dex", "marshmallow", "zelda"}}},
|
||||
},
|
||||
{
|
||||
name: "command is not serve",
|
||||
args: []string{"dex", "marshmallow", "zelda"},
|
||||
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"dex", "marshmallow", "zelda"}}},
|
||||
},
|
||||
{
|
||||
name: "no templates",
|
||||
args: []string{"dex", "serve", "config.yaml.not-a-template"},
|
||||
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"dex", "serve", "config.yaml.not-a-template"}}},
|
||||
},
|
||||
{
|
||||
name: "no templates",
|
||||
args: []string{"dex", "serve", "config.yaml.not-a-template"},
|
||||
wantExecArgs: []execArgs{{fork: false, argPrefixes: []string{"dex", "serve", "config.yaml.not-a-template"}}},
|
||||
},
|
||||
{
|
||||
name: ".tpl template",
|
||||
args: []string{"dex", "serve", "config.tpl"},
|
||||
wantExecArgs: []execArgs{
|
||||
{fork: true, argPrefixes: []string{"gomplate", "-f", "config.tpl", "-o", "/tmp/dex.config.yaml-"}},
|
||||
{fork: false, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".tmpl template",
|
||||
args: []string{"dex", "serve", "config.tmpl"},
|
||||
wantExecArgs: []execArgs{
|
||||
{fork: true, argPrefixes: []string{"gomplate", "-f", "config.tmpl", "-o", "/tmp/dex.config.yaml-"}},
|
||||
{fork: false, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".yaml template",
|
||||
args: []string{"dex", "serve", "some/path/config.yaml"},
|
||||
wantExecArgs: []execArgs{
|
||||
{fork: true, argPrefixes: []string{"gomplate", "-f", "some/path/config.yaml", "-o", "/tmp/dex.config.yaml-"}},
|
||||
{fork: false, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var gotExecForks []bool
|
||||
var gotExecArgs [][]string
|
||||
fakeExec := func(fork bool, args ...string) error {
|
||||
gotExecForks = append(gotExecForks, fork)
|
||||
gotExecArgs = append(gotExecArgs, args)
|
||||
return test.execReturns
|
||||
}
|
||||
|
||||
fakeWhich := func(_ string) string { return test.whichReturns }
|
||||
|
||||
gotErr := run(test.args, fakeExec, fakeWhich)
|
||||
if (test.wantErr == nil) != (gotErr == nil) {
|
||||
t.Errorf("wanted error %s, got %s", test.wantErr, gotErr)
|
||||
}
|
||||
if !execArgsMatch(test.wantExecArgs, gotExecForks, gotExecArgs) {
|
||||
t.Errorf("wanted exec args %+v, got %+v %+v", test.wantExecArgs, gotExecForks, gotExecArgs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func execArgsMatch(wantExecArgs []execArgs, gotForks []bool, gotExecArgs [][]string) bool {
|
||||
if len(wantExecArgs) != len(gotForks) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range wantExecArgs {
|
||||
if wantExecArgs[i].fork != gotForks[i] {
|
||||
return false
|
||||
}
|
||||
for j := range wantExecArgs[i].argPrefixes {
|
||||
if !strings.HasPrefix(gotExecArgs[i][j], wantExecArgs[i].argPrefixes[j]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -60,7 +60,7 @@ web:
|
|||
# frontend:
|
||||
# issuer: dex
|
||||
# logoURL: theme/logo.png
|
||||
# dir: web/
|
||||
# dir: ""
|
||||
# theme: light
|
||||
|
||||
# Telemetry configuration
|
||||
|
@ -85,6 +85,11 @@ web:
|
|||
# deviceRequests: "5m"
|
||||
# signingKeys: "6h"
|
||||
# idTokens: "24h"
|
||||
# refreshTokens:
|
||||
# disableRotation: false
|
||||
# reuseInterval: "3s"
|
||||
# validIfNotUsedFor: "2160h" # 90 days
|
||||
# absoluteLifetime: "3960h" # 165 days
|
||||
|
||||
# OAuth2 configuration
|
||||
# oauth2:
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -432,7 +431,7 @@ func (c *crowdConnector) crowdUserManagementRequest(ctx context.Context, method
|
|||
|
||||
// validateCrowdResponse validates unique not JSON responses from API
|
||||
func (c *crowdConnector) validateCrowdResponse(resp *http.Response) ([]byte, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("crowd: read user body: %v", err)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
|
@ -152,7 +152,7 @@ func newTestCrowdConnector(baseURL string) crowdConnector {
|
|||
connector := crowdConnector{}
|
||||
connector.BaseURL = baseURL
|
||||
connector.logger = &logrus.Logger{
|
||||
Out: ioutil.Discard,
|
||||
Out: io.Discard,
|
||||
Level: logrus.DebugLevel,
|
||||
Formatter: &logrus.TextFormatter{DisableColors: true},
|
||||
}
|
||||
|
|
|
@ -13,9 +13,14 @@ import (
|
|||
)
|
||||
|
||||
// Config holds the configuration parameters for a connector which returns an
|
||||
// identity with the HTTP header X-Remote-User as verified email.
|
||||
// identity with the HTTP header X-Remote-User as verified email,
|
||||
// X-Remote-Group and configured staticGroups as user's group.
|
||||
// Headers retrieved to fetch user's email and group can be configured
|
||||
// with userHeader and groupHeader.
|
||||
type Config struct {
|
||||
UserHeader string `json:"userHeader"`
|
||||
UserHeader string `json:"userHeader"`
|
||||
GroupHeader string `json:"groupHeader"`
|
||||
Groups []string `json:"staticGroups"`
|
||||
}
|
||||
|
||||
// Open returns an authentication strategy which requires no user interaction.
|
||||
|
@ -24,16 +29,22 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
|
|||
if userHeader == "" {
|
||||
userHeader = "X-Remote-User"
|
||||
}
|
||||
groupHeader := c.GroupHeader
|
||||
if groupHeader == "" {
|
||||
groupHeader = "X-Remote-Group"
|
||||
}
|
||||
|
||||
return &callback{userHeader: userHeader, logger: logger, pathSuffix: "/" + id}, nil
|
||||
return &callback{userHeader: userHeader, groupHeader: groupHeader, logger: logger, pathSuffix: "/" + id, groups: c.Groups}, nil
|
||||
}
|
||||
|
||||
// Callback is a connector which returns an identity with the HTTP header
|
||||
// X-Remote-User as verified email.
|
||||
type callback struct {
|
||||
userHeader string
|
||||
logger log.Logger
|
||||
pathSuffix string
|
||||
userHeader string
|
||||
groupHeader string
|
||||
groups []string
|
||||
logger log.Logger
|
||||
pathSuffix string
|
||||
}
|
||||
|
||||
// LoginURL returns the URL to redirect the user to login with.
|
||||
|
@ -55,11 +66,15 @@ func (m *callback) HandleCallback(s connector.Scopes, r *http.Request) (connecto
|
|||
if remoteUser == "" {
|
||||
return connector.Identity{}, fmt.Errorf("required HTTP header %s is not set", m.userHeader)
|
||||
}
|
||||
// TODO: add support for X-Remote-Group, see
|
||||
// https://kubernetes.io/docs/admin/authentication/#authenticating-proxy
|
||||
groups := m.groups
|
||||
headerGroup := r.Header.Get(m.groupHeader)
|
||||
if headerGroup != "" {
|
||||
groups = append(groups, headerGroup)
|
||||
}
|
||||
return connector.Identity{
|
||||
UserID: remoteUser, // TODO: figure out if this is a bad ID value.
|
||||
Email: remoteUser,
|
||||
EmailVerified: true,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -351,7 +351,7 @@ func (b *bitbucketConnector) userEmail(ctx context.Context, client *http.Client)
|
|||
|
||||
// getGroups retrieves Bitbucket teams a user is in, if any.
|
||||
func (b *bitbucketConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) {
|
||||
bitbucketTeams, err := b.userTeams(ctx, client)
|
||||
bitbucketTeams, err := b.userWorkspaces(ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -369,33 +369,33 @@ func (b *bitbucketConnector) getGroups(ctx context.Context, client *http.Client,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
type teamName struct {
|
||||
Name string `json:"username"` // The "username" from Bitbucket Cloud is actually the team name here
|
||||
type workspaceSlug struct {
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
type team struct {
|
||||
Team teamName `json:"team"`
|
||||
type workspace struct {
|
||||
Workspace workspaceSlug `json:"workspace"`
|
||||
}
|
||||
|
||||
type userTeamsResponse struct {
|
||||
type userWorkspacesResponse struct {
|
||||
pagedResponse
|
||||
Values []team
|
||||
Values []workspace `json:"values"`
|
||||
}
|
||||
|
||||
func (b *bitbucketConnector) userTeams(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
func (b *bitbucketConnector) userWorkspaces(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
var teams []string
|
||||
apiURL := b.apiURL + "/user/permissions/teams"
|
||||
apiURL := b.apiURL + "/user/permissions/workspaces"
|
||||
|
||||
for {
|
||||
// https://developer.atlassian.com/bitbucket/api/2/reference/resource/user/permissions/teams
|
||||
var response userTeamsResponse
|
||||
// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get
|
||||
var response userWorkspacesResponse
|
||||
|
||||
if err := get(ctx, client, apiURL, &response); err != nil {
|
||||
return nil, fmt.Errorf("bitbucket: get user teams: %v", err)
|
||||
}
|
||||
|
||||
for _, value := range response.Values {
|
||||
teams = append(teams, value.Team.Name)
|
||||
teams = append(teams, value.Workspace.Slug)
|
||||
}
|
||||
|
||||
if response.Next == nil {
|
||||
|
@ -453,7 +453,7 @@ func get(ctx context.Context, client *http.Client, apiURL string, v interface{})
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitbucket: read body: %s: %v", resp.Status, err)
|
||||
}
|
||||
|
|
|
@ -14,28 +14,28 @@ import (
|
|||
)
|
||||
|
||||
func TestUserGroups(t *testing.T) {
|
||||
teamsResponse := userTeamsResponse{
|
||||
teamsResponse := userWorkspacesResponse{
|
||||
pagedResponse: pagedResponse{
|
||||
Size: 3,
|
||||
Page: 1,
|
||||
PageLen: 10,
|
||||
},
|
||||
Values: []team{
|
||||
{Team: teamName{Name: "team-1"}},
|
||||
{Team: teamName{Name: "team-2"}},
|
||||
{Team: teamName{Name: "team-3"}},
|
||||
Values: []workspace{
|
||||
{Workspace: workspaceSlug{Slug: "team-1"}},
|
||||
{Workspace: workspaceSlug{Slug: "team-2"}},
|
||||
{Workspace: workspaceSlug{Slug: "team-3"}},
|
||||
},
|
||||
}
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/user/permissions/teams": teamsResponse,
|
||||
"/groups/team-1": []group{{Slug: "administrators"}, {Slug: "members"}},
|
||||
"/groups/team-2": []group{{Slug: "everyone"}},
|
||||
"/groups/team-3": []group{},
|
||||
"/user/permissions/workspaces": teamsResponse,
|
||||
"/groups/team-1": []group{{Slug: "administrators"}, {Slug: "members"}},
|
||||
"/groups/team-2": []group{{Slug: "everyone"}},
|
||||
"/groups/team-3": []group{},
|
||||
})
|
||||
|
||||
connector := bitbucketConnector{apiURL: s.URL, legacyAPIURL: s.URL}
|
||||
groups, err := connector.userTeams(context.Background(), newClient())
|
||||
groups, err := connector.userWorkspaces(context.Background(), newClient())
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, groups, []string{
|
||||
|
@ -45,7 +45,7 @@ func TestUserGroups(t *testing.T) {
|
|||
})
|
||||
|
||||
connector.includeTeamGroups = true
|
||||
groups, err = connector.userTeams(context.Background(), newClient())
|
||||
groups, err = connector.userWorkspaces(context.Background(), newClient())
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, groups, []string{
|
||||
|
@ -62,11 +62,11 @@ func TestUserGroups(t *testing.T) {
|
|||
|
||||
func TestUserWithoutTeams(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/user/permissions/teams": userTeamsResponse{},
|
||||
"/user/permissions/workspaces": userWorkspacesResponse{},
|
||||
})
|
||||
|
||||
connector := bitbucketConnector{apiURL: s.URL}
|
||||
groups, err := connector.userTeams(context.Background(), newClient())
|
||||
groups, err := connector.userWorkspaces(context.Background(), newClient())
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, len(groups), 0)
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
@ -20,11 +20,26 @@ import (
|
|||
|
||||
// Config holds configuration options for gitea logins.
|
||||
type Config struct {
|
||||
BaseURL string `json:"baseURL"`
|
||||
ClientID string `json:"clientID"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
RedirectURI string `json:"redirectURI"`
|
||||
UseLoginAsID bool `json:"useLoginAsID"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
ClientID string `json:"clientID"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
RedirectURI string `json:"redirectURI"`
|
||||
Orgs []Org `json:"orgs"`
|
||||
LoadAllGroups bool `json:"loadAllGroups"`
|
||||
UseLoginAsID bool `json:"useLoginAsID"`
|
||||
}
|
||||
|
||||
// Org holds org-team filters, in which teams are optional.
|
||||
type Org struct {
|
||||
// Organization name in gitea (not slug, full name). Only users in this gitea
|
||||
// organization can authenticate.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Names of teams in a gitea organization. A user will be able to
|
||||
// authenticate if they are members of at least one of these teams. Users
|
||||
// in the organization can authenticate if this field is omitted from the
|
||||
// config file.
|
||||
Teams []string `json:"teams,omitempty"`
|
||||
}
|
||||
|
||||
type giteaUser struct {
|
||||
|
@ -35,18 +50,20 @@ type giteaUser struct {
|
|||
IsAdmin bool `json:"is_admin"`
|
||||
}
|
||||
|
||||
// Open returns a strategy for logging in through GitLab.
|
||||
// Open returns a strategy for logging in through Gitea
|
||||
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
|
||||
if c.BaseURL == "" {
|
||||
c.BaseURL = "https://gitea.com"
|
||||
}
|
||||
return &giteaConnector{
|
||||
baseURL: c.BaseURL,
|
||||
redirectURI: c.RedirectURI,
|
||||
clientID: c.ClientID,
|
||||
clientSecret: c.ClientSecret,
|
||||
logger: logger,
|
||||
useLoginAsID: c.UseLoginAsID,
|
||||
baseURL: c.BaseURL,
|
||||
redirectURI: c.RedirectURI,
|
||||
orgs: c.Orgs,
|
||||
clientID: c.ClientID,
|
||||
clientSecret: c.ClientSecret,
|
||||
logger: logger,
|
||||
loadAllGroups: c.LoadAllGroups,
|
||||
useLoginAsID: c.UseLoginAsID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -64,10 +81,13 @@ var (
|
|||
type giteaConnector struct {
|
||||
baseURL string
|
||||
redirectURI string
|
||||
orgs []Org
|
||||
clientID string
|
||||
clientSecret string
|
||||
logger log.Logger
|
||||
httpClient *http.Client
|
||||
// if set to true and no orgs are configured then connector loads all user claims (all orgs and team)
|
||||
loadAllGroups bool
|
||||
// if set to true will use the user's handle rather than their numeric id as the ID
|
||||
useLoginAsID bool
|
||||
}
|
||||
|
@ -130,6 +150,7 @@ func (c *giteaConnector) HandleCallback(s connector.Scopes, r *http.Request) (id
|
|||
if username == "" {
|
||||
username = user.Email
|
||||
}
|
||||
|
||||
identity = connector.Identity{
|
||||
UserID: strconv.Itoa(user.ID),
|
||||
Username: username,
|
||||
|
@ -141,6 +162,15 @@ func (c *giteaConnector) HandleCallback(s connector.Scopes, r *http.Request) (id
|
|||
identity.UserID = user.Username
|
||||
}
|
||||
|
||||
// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.
|
||||
if c.groupsRequired() {
|
||||
groups, err := c.getGroups(ctx, client)
|
||||
if err != nil {
|
||||
return identity, err
|
||||
}
|
||||
identity.Groups = groups
|
||||
}
|
||||
|
||||
if s.OfflineAccess {
|
||||
data := connectorData{
|
||||
AccessToken: token.AccessToken,
|
||||
|
@ -232,9 +262,132 @@ func (c *giteaConnector) Refresh(ctx context.Context, s connector.Scopes, ident
|
|||
ident.PreferredUsername = user.Username
|
||||
ident.Email = user.Email
|
||||
|
||||
// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.
|
||||
if c.groupsRequired() {
|
||||
groups, err := c.getGroups(ctx, client)
|
||||
if err != nil {
|
||||
return ident, err
|
||||
}
|
||||
ident.Groups = groups
|
||||
}
|
||||
|
||||
return ident, nil
|
||||
}
|
||||
|
||||
// getGroups retrieves Gitea orgs and teams a user is in, if any.
|
||||
func (c *giteaConnector) getGroups(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
if len(c.orgs) > 0 {
|
||||
return c.groupsForOrgs(ctx, client)
|
||||
} else if c.loadAllGroups {
|
||||
return c.userGroups(ctx, client)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// formatTeamName returns unique team name.
|
||||
// Orgs might have the same team names. To make team name unique it should be prefixed with the org name.
|
||||
func formatTeamName(org string, team string) string {
|
||||
return fmt.Sprintf("%s:%s", org, team)
|
||||
}
|
||||
|
||||
// groupsForOrgs returns list of groups that user belongs to in approved list
|
||||
func (c *giteaConnector) groupsForOrgs(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
groups, err := c.userGroups(ctx, client)
|
||||
if err != nil {
|
||||
return groups, err
|
||||
}
|
||||
|
||||
keys := make(map[string]bool)
|
||||
for _, o := range c.orgs {
|
||||
keys[o.Name] = true
|
||||
if o.Teams != nil {
|
||||
for _, t := range o.Teams {
|
||||
keys[formatTeamName(o.Name, t)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
atLeastOne := false
|
||||
filteredGroups := make([]string, 0)
|
||||
for _, g := range groups {
|
||||
if _, value := keys[g]; value {
|
||||
filteredGroups = append(filteredGroups, g)
|
||||
atLeastOne = true
|
||||
}
|
||||
}
|
||||
|
||||
if !atLeastOne {
|
||||
return []string{}, fmt.Errorf("gitea: User does not belong to any of the approved groups")
|
||||
}
|
||||
return filteredGroups, nil
|
||||
}
|
||||
|
||||
type organization struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"username"`
|
||||
}
|
||||
|
||||
type team struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Organization *organization `json:"organization"`
|
||||
}
|
||||
|
||||
func (c *giteaConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
apiURL := c.baseURL + "/api/v1/user/teams"
|
||||
groups := make([]string, 0)
|
||||
page := 1
|
||||
limit := 20
|
||||
for {
|
||||
var teams []team
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s?page=%d&limit=%d", apiURL, page, limit), nil)
|
||||
if err != nil {
|
||||
return groups, fmt.Errorf("gitea: new req: %v", err)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return groups, fmt.Errorf("gitea: get URL %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return groups, fmt.Errorf("gitea: read body: %v", err)
|
||||
}
|
||||
return groups, fmt.Errorf("%s: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&teams); err != nil {
|
||||
return groups, fmt.Errorf("failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if len(teams) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, t := range teams {
|
||||
groups = append(groups, t.Organization.Name)
|
||||
groups = append(groups, formatTeamName(t.Organization.Name, t.Name))
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
// remove duplicate slice variables
|
||||
keys := make(map[string]struct{})
|
||||
list := []string{}
|
||||
for _, group := range groups {
|
||||
if _, exists := keys[group]; !exists {
|
||||
keys[group] = struct{}{}
|
||||
list = append(list, group)
|
||||
}
|
||||
}
|
||||
groups = list
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// user queries the Gitea API for profile information using the provided client. The HTTP
|
||||
// client is expected to be constructed by the golang.org/x/oauth2 package, which inserts
|
||||
// a bearer token as part of the request.
|
||||
|
@ -252,7 +405,7 @@ func (c *giteaConnector) user(ctx context.Context, client *http.Client) (giteaUs
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("gitea: read body: %v", err)
|
||||
}
|
||||
|
@ -264,3 +417,8 @@ func (c *giteaConnector) user(ctx context.Context, client *http.Client) (giteaUs
|
|||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// groupsRequired returns whether dex needs to request groups from Gitea.
|
||||
func (c *giteaConnector) groupsRequired() bool {
|
||||
return len(c.orgs) > 0 || c.loadAllGroups
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -210,7 +211,7 @@ func (e *oauth2Error) Error() string {
|
|||
// newHTTPClient returns a new HTTP client that trusts the custom declared rootCA cert.
|
||||
func newHTTPClient(rootCA string) (*http.Client, error) {
|
||||
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
|
||||
rootCABytes, err := ioutil.ReadFile(rootCA)
|
||||
rootCABytes, err := os.ReadFile(rootCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read root-ca: %v", err)
|
||||
}
|
||||
|
@ -488,7 +489,7 @@ func get(ctx context.Context, client *http.Client, apiURL string, v interface{})
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("github: read body: %v", err)
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
|
@ -61,8 +62,9 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
|
|||
}
|
||||
|
||||
type connectorData struct {
|
||||
// GitLab's OAuth2 tokens never expire. We don't need a refresh token.
|
||||
AccessToken string `json:"accessToken"`
|
||||
// Support GitLab's Access Tokens and Refresh tokens.
|
||||
AccessToken string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -135,6 +137,11 @@ func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (i
|
|||
return identity, fmt.Errorf("gitlab: failed to get token: %v", err)
|
||||
}
|
||||
|
||||
return c.identity(ctx, s, token)
|
||||
}
|
||||
|
||||
func (c *gitlabConnector) identity(ctx context.Context, s connector.Scopes, token *oauth2.Token) (identity connector.Identity, err error) {
|
||||
oauth2Config := c.oauth2Config(s)
|
||||
client := oauth2Config.Client(ctx, token)
|
||||
|
||||
user, err := c.user(ctx, client)
|
||||
|
@ -146,6 +153,7 @@ func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (i
|
|||
if username == "" {
|
||||
username = user.Email
|
||||
}
|
||||
|
||||
identity = connector.Identity{
|
||||
UserID: strconv.Itoa(user.ID),
|
||||
Username: username,
|
||||
|
@ -166,10 +174,10 @@ func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (i
|
|||
}
|
||||
|
||||
if s.OfflineAccess {
|
||||
data := connectorData{AccessToken: token.AccessToken}
|
||||
data := connectorData{RefreshToken: token.RefreshToken, AccessToken: token.AccessToken}
|
||||
connData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("marshal connector data: %v", err)
|
||||
return identity, fmt.Errorf("gitlab: marshal connector data: %v", err)
|
||||
}
|
||||
identity.ConnectorData = connData
|
||||
}
|
||||
|
@ -178,37 +186,39 @@ func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (i
|
|||
}
|
||||
|
||||
func (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {
|
||||
if len(ident.ConnectorData) == 0 {
|
||||
return ident, errors.New("no upstream access token found")
|
||||
}
|
||||
|
||||
var data connectorData
|
||||
if err := json.Unmarshal(ident.ConnectorData, &data); err != nil {
|
||||
return ident, fmt.Errorf("gitlab: unmarshal access token: %v", err)
|
||||
return ident, fmt.Errorf("gitlab: unmarshal connector data: %v", err)
|
||||
}
|
||||
oauth2Config := c.oauth2Config(s)
|
||||
|
||||
if c.httpClient != nil {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
|
||||
}
|
||||
|
||||
client := c.oauth2Config(s).Client(ctx, &oauth2.Token{AccessToken: data.AccessToken})
|
||||
user, err := c.user(ctx, client)
|
||||
if err != nil {
|
||||
return ident, fmt.Errorf("gitlab: get user: %v", err)
|
||||
}
|
||||
|
||||
username := user.Name
|
||||
if username == "" {
|
||||
username = user.Email
|
||||
}
|
||||
ident.Username = username
|
||||
ident.PreferredUsername = user.Username
|
||||
ident.Email = user.Email
|
||||
|
||||
if c.groupsRequired(s.Groups) {
|
||||
groups, err := c.getGroups(ctx, client, s.Groups, user.Username)
|
||||
if err != nil {
|
||||
return ident, fmt.Errorf("gitlab: get groups: %v", err)
|
||||
switch {
|
||||
case data.RefreshToken != "":
|
||||
{
|
||||
t := &oauth2.Token{
|
||||
RefreshToken: data.RefreshToken,
|
||||
Expiry: time.Now().Add(-time.Hour),
|
||||
}
|
||||
token, err := oauth2Config.TokenSource(ctx, t).Token()
|
||||
if err != nil {
|
||||
return ident, fmt.Errorf("gitlab: failed to get refresh token: %v", err)
|
||||
}
|
||||
return c.identity(ctx, s, token)
|
||||
}
|
||||
ident.Groups = groups
|
||||
case data.AccessToken != "":
|
||||
{
|
||||
token := &oauth2.Token{
|
||||
AccessToken: data.AccessToken,
|
||||
}
|
||||
return c.identity(ctx, s, token)
|
||||
}
|
||||
default:
|
||||
return ident, errors.New("no refresh or access token found")
|
||||
}
|
||||
return ident, nil
|
||||
}
|
||||
|
||||
func (c *gitlabConnector) groupsRequired(groupScope bool) bool {
|
||||
|
@ -232,7 +242,7 @@ func (c *gitlabConnector) user(ctx context.Context, client *http.Client) (gitlab
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("gitlab: read body: %v", err)
|
||||
}
|
||||
|
@ -266,7 +276,7 @@ func (c *gitlabConnector) userGroups(ctx context.Context, client *http.Client) (
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gitlab: read body: %v", err)
|
||||
}
|
||||
|
|
|
@ -180,6 +180,75 @@ func TestLoginWithTeamNonWhitelisted(t *testing.T) {
|
|||
expectEquals(t, err.Error(), "gitlab: get groups: gitlab: user \"joebloggs\" is not in any of the required groups")
|
||||
}
|
||||
|
||||
func TestRefresh(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
"refresh_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC",
|
||||
"expires_in": "30",
|
||||
},
|
||||
"/oauth/userinfo": userInfo{
|
||||
Groups: []string{"team-1"},
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
hostURL, err := url.Parse(s.URL)
|
||||
expectNil(t, err)
|
||||
|
||||
req, err := http.NewRequest("GET", hostURL.String(), nil)
|
||||
expectNil(t, err)
|
||||
|
||||
c := gitlabConnector{baseURL: s.URL, httpClient: newClient()}
|
||||
|
||||
expectedConnectorData, err := json.Marshal(connectorData{
|
||||
RefreshToken: "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC",
|
||||
AccessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
})
|
||||
expectNil(t, err)
|
||||
|
||||
identity, err := c.HandleCallback(connector.Scopes{OfflineAccess: true}, req)
|
||||
expectNil(t, err)
|
||||
expectEquals(t, identity.Username, "some@email.com")
|
||||
expectEquals(t, identity.UserID, "12345678")
|
||||
expectEquals(t, identity.ConnectorData, expectedConnectorData)
|
||||
|
||||
identity, err = c.Refresh(context.Background(), connector.Scopes{OfflineAccess: true}, identity)
|
||||
expectNil(t, err)
|
||||
expectEquals(t, identity.Username, "some@email.com")
|
||||
expectEquals(t, identity.UserID, "12345678")
|
||||
expectEquals(t, identity.ConnectorData, expectedConnectorData)
|
||||
}
|
||||
|
||||
func TestRefreshWithEmptyConnectorData(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
"refresh_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC",
|
||||
"expires_in": "30",
|
||||
},
|
||||
"/oauth/userinfo": userInfo{
|
||||
Groups: []string{"team-1"},
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
emptyConnectorData, err := json.Marshal(connectorData{
|
||||
RefreshToken: "",
|
||||
AccessToken: "",
|
||||
})
|
||||
expectNil(t, err)
|
||||
|
||||
c := gitlabConnector{baseURL: s.URL, httpClient: newClient()}
|
||||
emptyIdentity := connector.Identity{ConnectorData: emptyConnectorData}
|
||||
|
||||
identity, err := c.Refresh(context.Background(), connector.Scopes{OfflineAccess: true}, emptyIdentity)
|
||||
expectNotNil(t, err, "Refresh error")
|
||||
expectEquals(t, emptyIdentity, identity)
|
||||
}
|
||||
|
||||
func newTestServer(responses map[string]interface{}) *httptest.Server {
|
||||
return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
response := responses[r.RequestURI]
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
|
@ -49,6 +49,9 @@ type Config struct {
|
|||
// The email of a GSuite super user which the service account will impersonate
|
||||
// when listing groups
|
||||
AdminEmail string
|
||||
|
||||
// If this field is true, fetch direct group membership and transitive group membership
|
||||
FetchTransitiveGroupMembership bool `json:"fetchTransitiveGroupMembership"`
|
||||
}
|
||||
|
||||
// Open returns a connector which can be used to login users through Google.
|
||||
|
@ -87,13 +90,14 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
|
|||
verifier: provider.Verifier(
|
||||
&oidc.Config{ClientID: clientID},
|
||||
),
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
hostedDomains: c.HostedDomains,
|
||||
groups: c.Groups,
|
||||
serviceAccountFilePath: c.ServiceAccountFilePath,
|
||||
adminEmail: c.AdminEmail,
|
||||
adminSrv: srv,
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
hostedDomains: c.HostedDomains,
|
||||
groups: c.Groups,
|
||||
serviceAccountFilePath: c.ServiceAccountFilePath,
|
||||
adminEmail: c.AdminEmail,
|
||||
fetchTransitiveGroupMembership: c.FetchTransitiveGroupMembership,
|
||||
adminSrv: srv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -103,16 +107,17 @@ var (
|
|||
)
|
||||
|
||||
type googleConnector struct {
|
||||
redirectURI string
|
||||
oauth2Config *oauth2.Config
|
||||
verifier *oidc.IDTokenVerifier
|
||||
cancel context.CancelFunc
|
||||
logger log.Logger
|
||||
hostedDomains []string
|
||||
groups []string
|
||||
serviceAccountFilePath string
|
||||
adminEmail string
|
||||
adminSrv *admin.Service
|
||||
redirectURI string
|
||||
oauth2Config *oauth2.Config
|
||||
verifier *oidc.IDTokenVerifier
|
||||
cancel context.CancelFunc
|
||||
logger log.Logger
|
||||
hostedDomains []string
|
||||
groups []string
|
||||
serviceAccountFilePath string
|
||||
adminEmail string
|
||||
fetchTransitiveGroupMembership bool
|
||||
adminSrv *admin.Service
|
||||
}
|
||||
|
||||
func (c *googleConnector) Close() error {
|
||||
|
@ -214,7 +219,7 @@ func (c *googleConnector) createIdentity(ctx context.Context, identity connector
|
|||
|
||||
var groups []string
|
||||
if s.Groups && c.adminSrv != nil {
|
||||
groups, err = c.getGroups(claims.Email)
|
||||
groups, err = c.getGroups(claims.Email, c.fetchTransitiveGroupMembership)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("google: could not retrieve groups: %v", err)
|
||||
}
|
||||
|
@ -240,7 +245,7 @@ func (c *googleConnector) createIdentity(ctx context.Context, identity connector
|
|||
|
||||
// getGroups creates a connection to the admin directory service and lists
|
||||
// all groups the user is a member of
|
||||
func (c *googleConnector) getGroups(email string) ([]string, error) {
|
||||
func (c *googleConnector) getGroups(email string, fetchTransitiveGroupMembership bool) ([]string, error) {
|
||||
var userGroups []string
|
||||
var err error
|
||||
groupsList := &admin.Groups{}
|
||||
|
@ -254,6 +259,16 @@ func (c *googleConnector) getGroups(email string) ([]string, error) {
|
|||
for _, group := range groupsList.Groups {
|
||||
// TODO (joelspeed): Make desired group key configurable
|
||||
userGroups = append(userGroups, group.Email)
|
||||
|
||||
// getGroups takes a user's email/alias as well as a group's email/alias
|
||||
if fetchTransitiveGroupMembership {
|
||||
transitiveGroups, err := c.getGroups(group.Email, fetchTransitiveGroupMembership)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not list transitive groups: %v", err)
|
||||
}
|
||||
|
||||
userGroups = append(userGroups, transitiveGroups...)
|
||||
}
|
||||
}
|
||||
|
||||
if groupsList.NextPageToken == "" {
|
||||
|
@ -261,7 +276,7 @@ func (c *googleConnector) getGroups(email string) ([]string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return userGroups, nil
|
||||
return uniqueGroups(userGroups), nil
|
||||
}
|
||||
|
||||
// createDirectoryService loads a google service account credentials file,
|
||||
|
@ -274,7 +289,7 @@ func createDirectoryService(serviceAccountFilePath string, email string) (*admin
|
|||
if serviceAccountFilePath == "" || email == "" {
|
||||
return nil, fmt.Errorf("directory service requires both serviceAccountFilePath and adminEmail")
|
||||
}
|
||||
jsonCredentials, err := ioutil.ReadFile(serviceAccountFilePath)
|
||||
jsonCredentials, err := os.ReadFile(serviceAccountFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading credentials from file: %v", err)
|
||||
}
|
||||
|
@ -296,3 +311,16 @@ func createDirectoryService(serviceAccountFilePath string, email string) (*admin
|
|||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// uniqueGroups returns the unique groups of a slice
|
||||
func uniqueGroups(groups []string) []string {
|
||||
keys := make(map[string]struct{})
|
||||
unique := []string{}
|
||||
for _, group := range groups {
|
||||
if _, exists := keys[group]; !exists {
|
||||
keys[group] = struct{}{}
|
||||
unique = append(unique, group)
|
||||
}
|
||||
}
|
||||
return unique
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
|
@ -42,8 +42,8 @@ type domainKeystone struct {
|
|||
// config:
|
||||
// keystoneHost: http://example:5000
|
||||
// domain: default
|
||||
// keystoneUsername: demo
|
||||
// keystonePassword: DEMO_PASS
|
||||
// keystoneUsername: demo
|
||||
// keystonePassword: DEMO_PASS
|
||||
type Config struct {
|
||||
Domain string `json:"domain"`
|
||||
Host string `json:"keystoneHost"`
|
||||
|
@ -133,7 +133,7 @@ func (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, pas
|
|||
return identity, false, nil
|
||||
}
|
||||
token := resp.Header.Get("X-Subject-Token")
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return identity, false, err
|
||||
}
|
||||
|
@ -168,7 +168,8 @@ func (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, pas
|
|||
func (p *conn) Prompt() string { return "username" }
|
||||
|
||||
func (p *conn) Refresh(
|
||||
ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) {
|
||||
ctx context.Context, scopes connector.Scopes, identity connector.Identity,
|
||||
) (connector.Identity, error) {
|
||||
token, err := p.getAdminToken(ctx)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err)
|
||||
|
@ -260,7 +261,7 @@ func (p *conn) getUser(ctx context.Context, userID string, token string) (*userR
|
|||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -290,7 +291,7 @@ func (p *conn) getUserGroups(ctx context.Context, userID string, token string) (
|
|||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
|
@ -78,7 +77,7 @@ func getAdminToken(t *testing.T, adminName, adminPass string) (token, id string)
|
|||
|
||||
token = resp.Header.Get("X-Subject-Token")
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -122,7 +121,7 @@ func createUser(t *testing.T, token, userName, userEmail, userPass string) strin
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -183,7 +182,7 @@ func createGroup(t *testing.T, token, description, name string) string {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -454,22 +453,22 @@ func setupVariables(t *testing.T) {
|
|||
keystoneAdminPassEnv := "DEX_KEYSTONE_ADMIN_PASS"
|
||||
keystoneURL = os.Getenv(keystoneURLEnv)
|
||||
if keystoneURL == "" {
|
||||
t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv))
|
||||
t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv)
|
||||
return
|
||||
}
|
||||
keystoneAdminURL = os.Getenv(keystoneAdminURLEnv)
|
||||
if keystoneAdminURL == "" {
|
||||
t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv))
|
||||
t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv)
|
||||
return
|
||||
}
|
||||
adminUser = os.Getenv(keystoneAdminUserEnv)
|
||||
if adminUser == "" {
|
||||
t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneAdminUserEnv))
|
||||
t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneAdminUserEnv)
|
||||
return
|
||||
}
|
||||
adminPass = os.Getenv(keystoneAdminPassEnv)
|
||||
if adminPass == "" {
|
||||
t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneAdminPassEnv))
|
||||
t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneAdminPassEnv)
|
||||
return
|
||||
}
|
||||
authTokenURL = keystoneURL + "/v3/auth/tokens/"
|
||||
|
|
|
@ -7,10 +7,10 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"gopkg.in/ldap.v2"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
|
@ -187,11 +187,12 @@ func parseScope(s string) (int, bool) {
|
|||
// Function exists here to allow backward compatibility between old and new
|
||||
// group to user matching implementations.
|
||||
// See "Config.GroupSearch.UserMatchers" comments for the details
|
||||
func (c *ldapConnector) userMatchers() []UserMatcher {
|
||||
func userMatchers(c *Config, logger log.Logger) []UserMatcher {
|
||||
if len(c.GroupSearch.UserMatchers) > 0 && c.GroupSearch.UserMatchers[0].UserAttr != "" {
|
||||
return c.GroupSearch.UserMatchers
|
||||
}
|
||||
|
||||
log.Deprecated(logger, `LDAP: use groupSearch.userMatchers option instead of "userAttr/groupAttr" fields.`)
|
||||
return []UserMatcher{
|
||||
{
|
||||
UserAttr: c.GroupSearch.UserAttr,
|
||||
|
@ -219,7 +220,8 @@ func (c *Config) OpenConnector(logger log.Logger) (interface {
|
|||
connector.Connector
|
||||
connector.PasswordConnector
|
||||
connector.RefreshConnector
|
||||
}, error) {
|
||||
}, error,
|
||||
) {
|
||||
return c.openConnector(logger)
|
||||
}
|
||||
|
||||
|
@ -257,7 +259,7 @@ func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) {
|
|||
data := c.RootCAData
|
||||
if len(data) == 0 {
|
||||
var err error
|
||||
if data, err = ioutil.ReadFile(c.RootCA); err != nil {
|
||||
if data, err = os.ReadFile(c.RootCA); err != nil {
|
||||
return nil, fmt.Errorf("ldap: read ca file: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -283,6 +285,9 @@ func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) {
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("groupSearch.Scope unknown value %q", c.GroupSearch.Scope)
|
||||
}
|
||||
|
||||
// TODO(nabokihms): remove it after deleting deprecated groupSearch options
|
||||
c.GroupSearch.UserMatchers = userMatchers(c, logger)
|
||||
return &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig, logger}, nil
|
||||
}
|
||||
|
||||
|
@ -331,10 +336,11 @@ func (c *ldapConnector) do(_ context.Context, f func(c *ldap.Conn) error) error
|
|||
defer conn.Close()
|
||||
|
||||
// If bindDN and bindPW are empty this will default to an anonymous bind.
|
||||
if err := conn.Bind(c.BindDN, c.BindPW); err != nil {
|
||||
if c.BindDN == "" && c.BindPW == "" {
|
||||
if c.BindDN == "" && c.BindPW == "" {
|
||||
if err := conn.UnauthenticatedBind(""); err != nil {
|
||||
return fmt.Errorf("ldap: initial anonymous bind failed: %v", err)
|
||||
}
|
||||
} else if err := conn.Bind(c.BindDN, c.BindPW); err != nil {
|
||||
return fmt.Errorf("ldap: initial bind for user %q failed: %v", c.BindDN, err)
|
||||
}
|
||||
|
||||
|
@ -417,7 +423,7 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E
|
|||
},
|
||||
}
|
||||
|
||||
for _, matcher := range c.userMatchers() {
|
||||
for _, matcher := range c.GroupSearch.UserMatchers {
|
||||
req.Attributes = append(req.Attributes, matcher.UserAttr)
|
||||
}
|
||||
|
||||
|
@ -574,7 +580,7 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string,
|
|||
}
|
||||
|
||||
var groups []*ldap.Entry
|
||||
for _, matcher := range c.userMatchers() {
|
||||
for _, matcher := range c.GroupSearch.UserMatchers {
|
||||
for _, attr := range getAttrs(user, matcher.UserAttr) {
|
||||
filter := fmt.Sprintf("(%s=%s)", matcher.GroupAttr, ldap.EscapeFilter(attr))
|
||||
if c.GroupSearch.Filter != "" {
|
||||
|
|
|
@ -3,7 +3,7 @@ package ldap
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
@ -555,7 +555,7 @@ func runTests(t *testing.T, connMethod connectionMethod, config *Config, tests [
|
|||
c.BindDN = "cn=admin,dc=example,dc=org"
|
||||
c.BindPW = "admin"
|
||||
|
||||
l := &logrus.Logger{Out: ioutil.Discard, Formatter: &logrus.TextFormatter{}}
|
||||
l := &logrus.Logger{Out: io.Discard, Formatter: &logrus.TextFormatter{}}
|
||||
|
||||
conn, err := c.openConnector(l)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
@ -169,7 +169,7 @@ func (c *linkedInConnector) primaryEmail(ctx context.Context, client *http.Clien
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return email, fmt.Errorf("read body: %v", err)
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ func (c *linkedInConnector) profile(ctx context.Context, client *http.Client) (p
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("read body: %v", err)
|
||||
}
|
||||
|
|
|
@ -53,6 +53,13 @@ type Config struct {
|
|||
GroupNameFormat GroupNameFormat `json:"groupNameFormat"`
|
||||
UseGroupsAsWhitelist bool `json:"useGroupsAsWhitelist"`
|
||||
EmailToLowercase bool `json:"emailToLowercase"`
|
||||
|
||||
// PromptType is used for the prompt query parameter.
|
||||
// For valid values, see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code.
|
||||
PromptType string `json:"promptType"`
|
||||
DomainHint string `json:"domainHint"`
|
||||
|
||||
Scopes []string `json:"scopes"` // defaults to scopeUser (user.read)
|
||||
}
|
||||
|
||||
// Open returns a strategy for logging in through Microsoft.
|
||||
|
@ -70,6 +77,9 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
|
|||
useGroupsAsWhitelist: c.UseGroupsAsWhitelist,
|
||||
logger: logger,
|
||||
emailToLowercase: c.EmailToLowercase,
|
||||
promptType: c.PromptType,
|
||||
domainHint: c.DomainHint,
|
||||
scopes: c.Scopes,
|
||||
}
|
||||
// By default allow logins from both personal and business/school
|
||||
// accounts.
|
||||
|
@ -113,6 +123,9 @@ type microsoftConnector struct {
|
|||
useGroupsAsWhitelist bool
|
||||
logger log.Logger
|
||||
emailToLowercase bool
|
||||
promptType string
|
||||
domainHint string
|
||||
scopes []string
|
||||
}
|
||||
|
||||
func (c *microsoftConnector) isOrgTenant() bool {
|
||||
|
@ -124,7 +137,12 @@ func (c *microsoftConnector) groupsRequired(groupScope bool) bool {
|
|||
}
|
||||
|
||||
func (c *microsoftConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {
|
||||
microsoftScopes := []string{scopeUser}
|
||||
var microsoftScopes []string
|
||||
if len(c.scopes) > 0 {
|
||||
microsoftScopes = c.scopes
|
||||
} else {
|
||||
microsoftScopes = append(microsoftScopes, scopeUser)
|
||||
}
|
||||
if c.groupsRequired(scopes.Groups) {
|
||||
microsoftScopes = append(microsoftScopes, scopeGroups)
|
||||
}
|
||||
|
@ -150,7 +168,15 @@ func (c *microsoftConnector) LoginURL(scopes connector.Scopes, callbackURL, stat
|
|||
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
|
||||
}
|
||||
|
||||
return c.oauth2Config(scopes).AuthCodeURL(state), nil
|
||||
var options []oauth2.AuthCodeOption
|
||||
if c.promptType != "" {
|
||||
options = append(options, oauth2.SetAuthURLParam("prompt", c.promptType))
|
||||
}
|
||||
if c.domainHint != "" {
|
||||
options = append(options, oauth2.SetAuthURLParam("domain_hint", c.domainHint))
|
||||
}
|
||||
|
||||
return c.oauth2Config(scopes).AuthCodeURL(state, options...), nil
|
||||
}
|
||||
|
||||
func (c *microsoftConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
@ -16,13 +17,68 @@ type testResponse struct {
|
|||
data interface{}
|
||||
}
|
||||
|
||||
const tenant = "9b1c3439-a67e-4e92-bb0d-0571d44ca965"
|
||||
const (
|
||||
tenant = "9b1c3439-a67e-4e92-bb0d-0571d44ca965"
|
||||
clientID = "a115ebf3-6020-4384-8eb1-c0c42e667b6f"
|
||||
)
|
||||
|
||||
var dummyToken = testResponse{data: map[string]interface{}{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
"expires_in": "30",
|
||||
}}
|
||||
|
||||
func TestLoginURL(t *testing.T) {
|
||||
testURL := "https://test.com"
|
||||
testState := "some-state"
|
||||
|
||||
conn := microsoftConnector{
|
||||
apiURL: testURL,
|
||||
graphURL: testURL,
|
||||
redirectURI: testURL,
|
||||
clientID: clientID,
|
||||
tenant: tenant,
|
||||
}
|
||||
|
||||
loginURL, _ := conn.LoginURL(connector.Scopes{}, conn.redirectURI, testState)
|
||||
|
||||
parsedLoginURL, _ := url.Parse(loginURL)
|
||||
queryParams := parsedLoginURL.Query()
|
||||
|
||||
expectEquals(t, parsedLoginURL.Path, "/"+tenant+"/oauth2/v2.0/authorize")
|
||||
expectEquals(t, queryParams.Get("client_id"), clientID)
|
||||
expectEquals(t, queryParams.Get("redirect_uri"), testURL)
|
||||
expectEquals(t, queryParams.Get("response_type"), "code")
|
||||
expectEquals(t, queryParams.Get("scope"), "user.read")
|
||||
expectEquals(t, queryParams.Get("state"), testState)
|
||||
expectEquals(t, queryParams.Get("prompt"), "")
|
||||
expectEquals(t, queryParams.Get("domain_hint"), "")
|
||||
}
|
||||
|
||||
func TestLoginURLWithOptions(t *testing.T) {
|
||||
testURL := "https://test.com"
|
||||
promptType := "consent"
|
||||
domainHint := "domain.hint"
|
||||
|
||||
conn := microsoftConnector{
|
||||
apiURL: testURL,
|
||||
graphURL: testURL,
|
||||
redirectURI: testURL,
|
||||
clientID: clientID,
|
||||
tenant: tenant,
|
||||
|
||||
promptType: promptType,
|
||||
domainHint: domainHint,
|
||||
}
|
||||
|
||||
loginURL, _ := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state")
|
||||
|
||||
parsedLoginURL, _ := url.Parse(loginURL)
|
||||
queryParams := parsedLoginURL.Query()
|
||||
|
||||
expectEquals(t, queryParams.Get("prompt"), promptType)
|
||||
expectEquals(t, queryParams.Get("domain_hint"), domainHint)
|
||||
}
|
||||
|
||||
func TestUserIdentityFromGraphAPI(t *testing.T) {
|
||||
s := newTestServer(map[string]testResponse{
|
||||
"/v1.0/me?$select=id,displayName,userPrincipalName": {
|
||||
|
|
|
@ -107,6 +107,7 @@ func (p passwordConnector) Login(ctx context.Context, s connector.Scopes, userna
|
|||
Username: "Kilgore Trout",
|
||||
Email: "kilgore@kilgore.trout",
|
||||
EmailVerified: true,
|
||||
ConnectorData: []byte(`{"test": "true"}`),
|
||||
}, true, nil
|
||||
}
|
||||
return identity, false, nil
|
||||
|
|
298
connector/oauth/oauth.go
Normal file
298
connector/oauth/oauth.go
Normal file
|
@ -0,0 +1,298 @@
|
|||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
)
|
||||
|
||||
type oauthConnector struct {
|
||||
clientID string
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
tokenURL string
|
||||
authorizationURL string
|
||||
userInfoURL string
|
||||
scopes []string
|
||||
userIDKey string
|
||||
userNameKey string
|
||||
preferredUsernameKey string
|
||||
emailKey string
|
||||
emailVerifiedKey string
|
||||
groupsKey string
|
||||
httpClient *http.Client
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
type connectorData struct {
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ClientID string `json:"clientID"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
RedirectURI string `json:"redirectURI"`
|
||||
TokenURL string `json:"tokenURL"`
|
||||
AuthorizationURL string `json:"authorizationURL"`
|
||||
UserInfoURL string `json:"userInfoURL"`
|
||||
Scopes []string `json:"scopes"`
|
||||
RootCAs []string `json:"rootCAs"`
|
||||
InsecureSkipVerify bool `json:"insecureSkipVerify"`
|
||||
UserIDKey string `json:"userIDKey"` // defaults to "id"
|
||||
ClaimMapping struct {
|
||||
UserNameKey string `json:"userNameKey"` // defaults to "user_name"
|
||||
PreferredUsernameKey string `json:"preferredUsernameKey"` // defaults to "preferred_username"
|
||||
GroupsKey string `json:"groupsKey"` // defaults to "groups"
|
||||
EmailKey string `json:"emailKey"` // defaults to "email"
|
||||
EmailVerifiedKey string `json:"emailVerifiedKey"` // defaults to "email_verified"
|
||||
} `json:"claimMapping"`
|
||||
}
|
||||
|
||||
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
|
||||
var err error
|
||||
|
||||
userIDKey := c.UserIDKey
|
||||
if userIDKey == "" {
|
||||
userIDKey = "id"
|
||||
}
|
||||
|
||||
userNameKey := c.ClaimMapping.UserNameKey
|
||||
if userNameKey == "" {
|
||||
userNameKey = "user_name"
|
||||
}
|
||||
|
||||
preferredUsernameKey := c.ClaimMapping.PreferredUsernameKey
|
||||
if preferredUsernameKey == "" {
|
||||
preferredUsernameKey = "preferred_username"
|
||||
}
|
||||
|
||||
groupsKey := c.ClaimMapping.GroupsKey
|
||||
if groupsKey == "" {
|
||||
groupsKey = "groups"
|
||||
}
|
||||
|
||||
emailKey := c.ClaimMapping.EmailKey
|
||||
if emailKey == "" {
|
||||
emailKey = "email"
|
||||
}
|
||||
|
||||
emailVerifiedKey := c.ClaimMapping.EmailVerifiedKey
|
||||
if emailVerifiedKey == "" {
|
||||
emailVerifiedKey = "email_verified"
|
||||
}
|
||||
|
||||
oauthConn := &oauthConnector{
|
||||
clientID: c.ClientID,
|
||||
clientSecret: c.ClientSecret,
|
||||
tokenURL: c.TokenURL,
|
||||
authorizationURL: c.AuthorizationURL,
|
||||
userInfoURL: c.UserInfoURL,
|
||||
scopes: c.Scopes,
|
||||
redirectURI: c.RedirectURI,
|
||||
logger: logger,
|
||||
userIDKey: userIDKey,
|
||||
userNameKey: userNameKey,
|
||||
preferredUsernameKey: preferredUsernameKey,
|
||||
groupsKey: groupsKey,
|
||||
emailKey: emailKey,
|
||||
emailVerifiedKey: emailVerifiedKey,
|
||||
}
|
||||
|
||||
oauthConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return oauthConn, err
|
||||
}
|
||||
|
||||
func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) {
|
||||
pool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify}
|
||||
for _, rootCA := range rootCAs {
|
||||
rootCABytes, err := os.ReadFile(rootCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read root-ca: %v", err)
|
||||
}
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {
|
||||
return nil, fmt.Errorf("no certs found in root CA file %q", rootCA)
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tlsConfig,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *oauthConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
|
||||
if c.redirectURI != callbackURL {
|
||||
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
|
||||
}
|
||||
|
||||
oauth2Config := &oauth2.Config{
|
||||
ClientID: c.clientID,
|
||||
ClientSecret: c.clientSecret,
|
||||
Endpoint: oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL},
|
||||
RedirectURL: c.redirectURI,
|
||||
Scopes: c.scopes,
|
||||
}
|
||||
|
||||
return oauth2Config.AuthCodeURL(state), nil
|
||||
}
|
||||
|
||||
func (c *oauthConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
|
||||
q := r.URL.Query()
|
||||
if errType := q.Get("error"); errType != "" {
|
||||
return identity, errors.New(q.Get("error_description"))
|
||||
}
|
||||
|
||||
oauth2Config := &oauth2.Config{
|
||||
ClientID: c.clientID,
|
||||
ClientSecret: c.clientSecret,
|
||||
Endpoint: oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL},
|
||||
RedirectURL: c.redirectURI,
|
||||
Scopes: c.scopes,
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)
|
||||
|
||||
token, err := oauth2Config.Exchange(ctx, q.Get("code"))
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("OAuth connector: failed to get token: %v", err)
|
||||
}
|
||||
|
||||
client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token))
|
||||
|
||||
userInfoResp, err := client.Get(c.userInfoURL)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("OAuth Connector: failed to execute request to userinfo: %v", err)
|
||||
}
|
||||
defer userInfoResp.Body.Close()
|
||||
|
||||
if userInfoResp.StatusCode != http.StatusOK {
|
||||
return identity, fmt.Errorf("OAuth Connector: failed to execute request to userinfo: status %d", userInfoResp.StatusCode)
|
||||
}
|
||||
|
||||
var userInfoResult map[string]interface{}
|
||||
err = json.NewDecoder(userInfoResp.Body).Decode(&userInfoResult)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("OAuth Connector: failed to parse userinfo: %v", err)
|
||||
}
|
||||
|
||||
userID, found := userInfoResult[c.userIDKey]
|
||||
if !found {
|
||||
return identity, fmt.Errorf("OAuth Connector: not found %v claim", c.userIDKey)
|
||||
}
|
||||
|
||||
switch userID.(type) {
|
||||
case float64, int64, string:
|
||||
identity.UserID = fmt.Sprintf("%v", userID)
|
||||
default:
|
||||
return identity, fmt.Errorf("OAuth Connector: %v claim should be string or number, got %T", c.userIDKey, userID)
|
||||
}
|
||||
|
||||
identity.Username, _ = userInfoResult[c.userNameKey].(string)
|
||||
identity.PreferredUsername, _ = userInfoResult[c.preferredUsernameKey].(string)
|
||||
identity.Email, _ = userInfoResult[c.emailKey].(string)
|
||||
identity.EmailVerified, _ = userInfoResult[c.emailVerifiedKey].(bool)
|
||||
|
||||
if s.Groups {
|
||||
groups := map[string]struct{}{}
|
||||
|
||||
c.addGroupsFromMap(groups, userInfoResult)
|
||||
c.addGroupsFromToken(groups, token.AccessToken)
|
||||
|
||||
for groupName := range groups {
|
||||
identity.Groups = append(identity.Groups, groupName)
|
||||
}
|
||||
}
|
||||
|
||||
if s.OfflineAccess {
|
||||
data := connectorData{AccessToken: token.AccessToken}
|
||||
connData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("OAuth Connector: failed to parse connector data for offline access: %v", err)
|
||||
}
|
||||
identity.ConnectorData = connData
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (c *oauthConnector) addGroupsFromMap(groups map[string]struct{}, result map[string]interface{}) error {
|
||||
groupsClaim, ok := result[c.groupsKey].([]interface{})
|
||||
if !ok {
|
||||
return errors.New("cannot convert to slice")
|
||||
}
|
||||
|
||||
for _, group := range groupsClaim {
|
||||
if groupString, ok := group.(string); ok {
|
||||
groups[groupString] = struct{}{}
|
||||
}
|
||||
if groupMap, ok := group.(map[string]interface{}); ok {
|
||||
if groupName, ok := groupMap["name"].(string); ok {
|
||||
groups[groupName] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *oauthConnector) addGroupsFromToken(groups map[string]struct{}, token string) error {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) < 2 {
|
||||
return errors.New("invalid token")
|
||||
}
|
||||
|
||||
decoded, err := decode(parts[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var claimsMap map[string]interface{}
|
||||
err = json.Unmarshal(decoded, &claimsMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.addGroupsFromMap(groups, claimsMap)
|
||||
}
|
||||
|
||||
func decode(seg string) ([]byte, error) {
|
||||
if l := len(seg) % 4; l > 0 {
|
||||
seg += strings.Repeat("=", 4-l)
|
||||
}
|
||||
|
||||
return base64.URLEncoding.DecodeString(seg)
|
||||
}
|
299
connector/oauth/oauth_test.go
Normal file
299
connector/oauth/oauth_test.go
Normal file
|
@ -0,0 +1,299 @@
|
|||
package oauth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
tokenClaims := map[string]interface{}{}
|
||||
userInfoClaims := map[string]interface{}{}
|
||||
|
||||
testServer := testSetup(t, tokenClaims, userInfoClaims)
|
||||
defer testServer.Close()
|
||||
|
||||
conn := newConnector(t, testServer.URL)
|
||||
|
||||
sort.Strings(conn.scopes)
|
||||
|
||||
assert.Equal(t, conn.clientID, "testClient")
|
||||
assert.Equal(t, conn.clientSecret, "testSecret")
|
||||
assert.Equal(t, conn.redirectURI, testServer.URL+"/callback")
|
||||
assert.Equal(t, conn.tokenURL, testServer.URL+"/token")
|
||||
assert.Equal(t, conn.authorizationURL, testServer.URL+"/authorize")
|
||||
assert.Equal(t, conn.userInfoURL, testServer.URL+"/userinfo")
|
||||
assert.Equal(t, len(conn.scopes), 2)
|
||||
assert.Equal(t, conn.scopes[0], "groups")
|
||||
assert.Equal(t, conn.scopes[1], "openid")
|
||||
}
|
||||
|
||||
func TestLoginURL(t *testing.T) {
|
||||
tokenClaims := map[string]interface{}{}
|
||||
userInfoClaims := map[string]interface{}{}
|
||||
|
||||
testServer := testSetup(t, tokenClaims, userInfoClaims)
|
||||
defer testServer.Close()
|
||||
|
||||
conn := newConnector(t, testServer.URL)
|
||||
|
||||
loginURL, err := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state")
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
expectedURL, err := url.Parse(testServer.URL + "/authorize")
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
values := url.Values{}
|
||||
values.Add("client_id", "testClient")
|
||||
values.Add("redirect_uri", conn.redirectURI)
|
||||
values.Add("response_type", "code")
|
||||
values.Add("scope", "openid groups")
|
||||
values.Add("state", "some-state")
|
||||
expectedURL.RawQuery = values.Encode()
|
||||
|
||||
assert.Equal(t, loginURL, expectedURL.String())
|
||||
}
|
||||
|
||||
func TestHandleCallBackForGroupsInUserInfo(t *testing.T) {
|
||||
tokenClaims := map[string]interface{}{}
|
||||
|
||||
userInfoClaims := map[string]interface{}{
|
||||
"name": "test-name",
|
||||
"user_id_key": "test-user-id",
|
||||
"user_name_key": "test-username",
|
||||
"preferred_username": "test-preferred-username",
|
||||
"mail": "mod_mail",
|
||||
"has_verified_email": false,
|
||||
"groups_key": []string{"admin-group", "user-group"},
|
||||
}
|
||||
|
||||
testServer := testSetup(t, tokenClaims, userInfoClaims)
|
||||
defer testServer.Close()
|
||||
|
||||
conn := newConnector(t, testServer.URL)
|
||||
req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupsInUserInfo")
|
||||
|
||||
identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
sort.Strings(identity.Groups)
|
||||
assert.Equal(t, len(identity.Groups), 2)
|
||||
assert.Equal(t, identity.Groups[0], "admin-group")
|
||||
assert.Equal(t, identity.Groups[1], "user-group")
|
||||
assert.Equal(t, identity.UserID, "test-user-id")
|
||||
assert.Equal(t, identity.Username, "test-username")
|
||||
assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
|
||||
assert.Equal(t, identity.Email, "mod_mail")
|
||||
assert.Equal(t, identity.EmailVerified, false)
|
||||
}
|
||||
|
||||
func TestHandleCallBackForGroupMapsInUserInfo(t *testing.T) {
|
||||
tokenClaims := map[string]interface{}{}
|
||||
|
||||
userInfoClaims := map[string]interface{}{
|
||||
"name": "test-name",
|
||||
"user_id_key": "test-user-id",
|
||||
"user_name_key": "test-username",
|
||||
"preferred_username": "test-preferred-username",
|
||||
"mail": "mod_mail",
|
||||
"has_verified_email": false,
|
||||
"groups_key": []interface{}{
|
||||
map[string]string{"name": "admin-group", "id": "111"},
|
||||
map[string]string{"name": "user-group", "id": "222"},
|
||||
},
|
||||
}
|
||||
|
||||
testServer := testSetup(t, tokenClaims, userInfoClaims)
|
||||
defer testServer.Close()
|
||||
|
||||
conn := newConnector(t, testServer.URL)
|
||||
req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupMapsInUserInfo")
|
||||
|
||||
identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
sort.Strings(identity.Groups)
|
||||
assert.Equal(t, len(identity.Groups), 2)
|
||||
assert.Equal(t, identity.Groups[0], "admin-group")
|
||||
assert.Equal(t, identity.Groups[1], "user-group")
|
||||
assert.Equal(t, identity.UserID, "test-user-id")
|
||||
assert.Equal(t, identity.Username, "test-username")
|
||||
assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
|
||||
assert.Equal(t, identity.Email, "mod_mail")
|
||||
assert.Equal(t, identity.EmailVerified, false)
|
||||
}
|
||||
|
||||
func TestHandleCallBackForGroupsInToken(t *testing.T) {
|
||||
tokenClaims := map[string]interface{}{
|
||||
"groups_key": []string{"test-group"},
|
||||
}
|
||||
|
||||
userInfoClaims := map[string]interface{}{
|
||||
"name": "test-name",
|
||||
"user_id_key": "test-user-id",
|
||||
"user_name_key": "test-username",
|
||||
"preferred_username": "test-preferred-username",
|
||||
"email": "test-email",
|
||||
"email_verified": true,
|
||||
}
|
||||
|
||||
testServer := testSetup(t, tokenClaims, userInfoClaims)
|
||||
defer testServer.Close()
|
||||
|
||||
conn := newConnector(t, testServer.URL)
|
||||
req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupsInToken")
|
||||
|
||||
identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
assert.Equal(t, len(identity.Groups), 1)
|
||||
assert.Equal(t, identity.Groups[0], "test-group")
|
||||
assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
|
||||
assert.Equal(t, identity.UserID, "test-user-id")
|
||||
assert.Equal(t, identity.Username, "test-username")
|
||||
assert.Equal(t, identity.Email, "")
|
||||
assert.Equal(t, identity.EmailVerified, false)
|
||||
}
|
||||
|
||||
func TestHandleCallbackForNumericUserID(t *testing.T) {
|
||||
tokenClaims := map[string]interface{}{}
|
||||
|
||||
userInfoClaims := map[string]interface{}{
|
||||
"name": "test-name",
|
||||
"user_id_key": 1000,
|
||||
"user_name_key": "test-username",
|
||||
"preferred_username": "test-preferred-username",
|
||||
"mail": "mod_mail",
|
||||
"has_verified_email": false,
|
||||
}
|
||||
|
||||
testServer := testSetup(t, tokenClaims, userInfoClaims)
|
||||
defer testServer.Close()
|
||||
|
||||
conn := newConnector(t, testServer.URL)
|
||||
req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallbackForNumericUserID")
|
||||
|
||||
identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
assert.Equal(t, identity.UserID, "1000")
|
||||
assert.Equal(t, identity.Username, "test-username")
|
||||
assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
|
||||
assert.Equal(t, identity.Email, "mod_mail")
|
||||
assert.Equal(t, identity.EmailVerified, false)
|
||||
}
|
||||
|
||||
func testSetup(t *testing.T, tokenClaims map[string]interface{}, userInfoClaims map[string]interface{}) *httptest.Server {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to generate rsa key", err)
|
||||
}
|
||||
|
||||
jwk := jose.JSONWebKey{
|
||||
Key: key,
|
||||
KeyID: "some-key",
|
||||
Algorithm: "RSA",
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
|
||||
token, err := newToken(&jwk, tokenClaims)
|
||||
if err != nil {
|
||||
t.Fatal("unable to generate token", err)
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(&map[string]string{
|
||||
"access_token": token,
|
||||
"id_token": token,
|
||||
"token_type": "Bearer",
|
||||
})
|
||||
})
|
||||
|
||||
mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(userInfoClaims)
|
||||
})
|
||||
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, error) {
|
||||
signingKey := jose.SigningKey{Key: key, Algorithm: jose.RS256}
|
||||
|
||||
signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new signer: %v", err)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(claims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshaling claims: %v", err)
|
||||
}
|
||||
|
||||
signature, err := signer.Sign(payload)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("signing payload: %v", err)
|
||||
}
|
||||
|
||||
return signature.CompactSerialize()
|
||||
}
|
||||
|
||||
func newConnector(t *testing.T, serverURL string) *oauthConnector {
|
||||
testConfig := Config{
|
||||
ClientID: "testClient",
|
||||
ClientSecret: "testSecret",
|
||||
RedirectURI: serverURL + "/callback",
|
||||
TokenURL: serverURL + "/token",
|
||||
AuthorizationURL: serverURL + "/authorize",
|
||||
UserInfoURL: serverURL + "/userinfo",
|
||||
Scopes: []string{"openid", "groups"},
|
||||
UserIDKey: "user_id_key",
|
||||
}
|
||||
|
||||
testConfig.ClaimMapping.UserNameKey = "user_name_key"
|
||||
testConfig.ClaimMapping.GroupsKey = "groups_key"
|
||||
testConfig.ClaimMapping.EmailKey = "mail"
|
||||
testConfig.ClaimMapping.EmailVerifiedKey = "has_verified_email"
|
||||
|
||||
log := logrus.New()
|
||||
|
||||
conn, err := testConfig.Open("id", log)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
oauthConn, ok := conn.(*oauthConnector)
|
||||
if !ok {
|
||||
t.Fatal(errors.New("failed to convert to oauthConnector"))
|
||||
}
|
||||
|
||||
return oauthConn
|
||||
}
|
||||
|
||||
func newRequestWithAuthCode(t *testing.T, serverURL string, code string) *http.Request {
|
||||
req, err := http.NewRequest("GET", serverURL, nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
|
||||
values := req.URL.Query()
|
||||
values.Add("code", code)
|
||||
req.URL.RawQuery = values.Encode()
|
||||
|
||||
return req
|
||||
}
|
|
@ -34,16 +34,17 @@ type Config struct {
|
|||
|
||||
Scopes []string `json:"scopes"` // defaults to "profile" and "email"
|
||||
|
||||
// Optional list of whitelisted domains when using Google
|
||||
// If this field is nonempty, only users from a listed domain will be allowed to log in
|
||||
HostedDomains []string `json:"hostedDomains"`
|
||||
|
||||
// Override the value of email_verified to true in the returned claims
|
||||
InsecureSkipEmailVerified bool `json:"insecureSkipEmailVerified"`
|
||||
|
||||
// InsecureEnableGroups enables groups claims. This is disabled by default until https://github.com/dexidp/dex/issues/1065 is resolved
|
||||
InsecureEnableGroups bool `json:"insecureEnableGroups"`
|
||||
|
||||
// AcrValues (Authentication Context Class Reference Values) that specifies the Authentication Context Class Values
|
||||
// within the Authentication Request that the Authorization Server is being requested to use for
|
||||
// processing requests from this Client, with the values appearing in order of preference.
|
||||
AcrValues []string `json:"acrValues"`
|
||||
|
||||
// GetUserInfo uses the userinfo endpoint to get additional claims for
|
||||
// the token. This is especially useful where upstreams return "thin"
|
||||
// id tokens
|
||||
|
@ -56,6 +57,11 @@ type Config struct {
|
|||
// PromptType will be used fot the prompt parameter (when offline_access, by default prompt=consent)
|
||||
PromptType string `json:"promptType"`
|
||||
|
||||
// OverrideClaimMapping will be used to override the options defined in claimMappings.
|
||||
// i.e. if there are 'email' and `preferred_email` claims available, by default Dex will always use the `email` claim independent of the ClaimMapping.EmailKey.
|
||||
// This setting allows you to override the default behavior of Dex and enforce the mappings defined in `claimMapping`.
|
||||
OverrideClaimMapping bool `json:"overrideClaimMapping"` // defaults to false
|
||||
|
||||
ClaimMapping struct {
|
||||
// Configurable key which contains the preferred username claims
|
||||
PreferredUsernameKey string `json:"preferred_username"` // defaults to "preferred_username"
|
||||
|
@ -146,13 +152,14 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
|
|||
),
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
hostedDomains: c.HostedDomains,
|
||||
insecureSkipEmailVerified: c.InsecureSkipEmailVerified,
|
||||
insecureEnableGroups: c.InsecureEnableGroups,
|
||||
acrValues: c.AcrValues,
|
||||
getUserInfo: c.GetUserInfo,
|
||||
promptType: c.PromptType,
|
||||
userIDKey: c.UserIDKey,
|
||||
userNameKey: c.UserNameKey,
|
||||
overrideClaimMapping: c.OverrideClaimMapping,
|
||||
preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey,
|
||||
emailKey: c.ClaimMapping.EmailKey,
|
||||
groupsKey: c.ClaimMapping.GroupsKey,
|
||||
|
@ -171,13 +178,14 @@ type oidcConnector struct {
|
|||
verifier *oidc.IDTokenVerifier
|
||||
cancel context.CancelFunc
|
||||
logger log.Logger
|
||||
hostedDomains []string
|
||||
insecureSkipEmailVerified bool
|
||||
insecureEnableGroups bool
|
||||
acrValues []string
|
||||
getUserInfo bool
|
||||
promptType string
|
||||
userIDKey string
|
||||
userNameKey string
|
||||
overrideClaimMapping bool
|
||||
preferredUsernameKey string
|
||||
emailKey string
|
||||
groupsKey string
|
||||
|
@ -194,12 +202,10 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string)
|
|||
}
|
||||
|
||||
var opts []oauth2.AuthCodeOption
|
||||
if len(c.hostedDomains) > 0 {
|
||||
preferredDomain := c.hostedDomains[0]
|
||||
if len(c.hostedDomains) > 1 {
|
||||
preferredDomain = "*"
|
||||
}
|
||||
opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain))
|
||||
|
||||
if len(c.acrValues) > 0 {
|
||||
acrValues := strings.Join(c.acrValues, " ")
|
||||
opts = append(opts, oauth2.SetAuthURLParam("acr_values", acrValues))
|
||||
}
|
||||
|
||||
if s.OfflineAccess {
|
||||
|
@ -220,6 +226,13 @@ func (e *oauth2Error) Error() string {
|
|||
return e.error + ": " + e.errorDescription
|
||||
}
|
||||
|
||||
type caller uint
|
||||
|
||||
const (
|
||||
createCaller caller = iota
|
||||
refreshCaller
|
||||
)
|
||||
|
||||
func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
|
||||
q := r.URL.Query()
|
||||
if errType := q.Get("error"); errType != "" {
|
||||
|
@ -229,8 +242,7 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide
|
|||
if err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to get token: %v", err)
|
||||
}
|
||||
|
||||
return c.createIdentity(r.Context(), identity, token)
|
||||
return c.createIdentity(r.Context(), identity, token, createCaller)
|
||||
}
|
||||
|
||||
// Refresh is used to refresh a session with the refresh token provided by the IdP
|
||||
|
@ -249,23 +261,25 @@ func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identit
|
|||
if err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to get refresh token: %v", err)
|
||||
}
|
||||
|
||||
return c.createIdentity(ctx, identity, token)
|
||||
return c.createIdentity(ctx, identity, token, refreshCaller)
|
||||
}
|
||||
|
||||
func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token) (connector.Identity, error) {
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return identity, errors.New("oidc: no id_token in token response")
|
||||
}
|
||||
idToken, err := c.verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err)
|
||||
}
|
||||
|
||||
func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token, caller caller) (connector.Identity, error) {
|
||||
var claims map[string]interface{}
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
|
||||
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if ok {
|
||||
idToken, err := c.verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err)
|
||||
}
|
||||
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
|
||||
}
|
||||
} else if caller != refreshCaller {
|
||||
// ID tokens aren't mandatory in the reply when using a refresh_token grant
|
||||
return identity, errors.New("oidc: no id_token in token response")
|
||||
}
|
||||
|
||||
// We immediately want to run getUserInfo if configured before we validate the claims
|
||||
|
@ -279,6 +293,12 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
}
|
||||
}
|
||||
|
||||
const subjectClaimKey = "sub"
|
||||
subject, found := claims[subjectClaimKey].(string)
|
||||
if !found {
|
||||
return identity, fmt.Errorf("missing \"%s\" claim", subjectClaimKey)
|
||||
}
|
||||
|
||||
userNameKey := "name"
|
||||
if c.userNameKey != "" {
|
||||
userNameKey = c.userNameKey
|
||||
|
@ -289,7 +309,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
}
|
||||
|
||||
preferredUsername, found := claims["preferred_username"].(string)
|
||||
if !found {
|
||||
if (!found || c.overrideClaimMapping) && c.preferredUsernameKey != "" {
|
||||
preferredUsername, _ = claims[c.preferredUsernameKey].(string)
|
||||
}
|
||||
|
||||
|
@ -304,7 +324,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
var email string
|
||||
emailKey := "email"
|
||||
email, found = claims[emailKey].(string)
|
||||
if !found && c.emailKey != "" {
|
||||
if (!found || c.overrideClaimMapping) && c.emailKey != "" {
|
||||
emailKey = c.emailKey
|
||||
email, found = claims[emailKey].(string)
|
||||
}
|
||||
|
@ -326,7 +346,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
if c.insecureEnableGroups {
|
||||
groupsKey := "groups"
|
||||
vs, found := claims[groupsKey].([]interface{})
|
||||
if !found {
|
||||
if (!found || c.overrideClaimMapping) && c.groupsKey != "" {
|
||||
groupsKey = c.groupsKey
|
||||
vs, found = claims[groupsKey].([]interface{})
|
||||
}
|
||||
|
@ -342,21 +362,6 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
}
|
||||
}
|
||||
|
||||
hostedDomain, _ := claims["hd"].(string)
|
||||
if len(c.hostedDomains) > 0 {
|
||||
found := false
|
||||
for _, domain := range c.hostedDomains {
|
||||
if hostedDomain == domain {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return identity, fmt.Errorf("oidc: unexpected hd claim %v", hostedDomain)
|
||||
}
|
||||
}
|
||||
|
||||
cd := connectorData{
|
||||
RefreshToken: []byte(token.RefreshToken),
|
||||
}
|
||||
|
@ -367,7 +372,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
}
|
||||
|
||||
identity = connector.Identity{
|
||||
UserID: idToken.Subject,
|
||||
UserID: subject,
|
||||
Username: name,
|
||||
PreferredUsername: preferredUsername,
|
||||
Email: email,
|
||||
|
|
|
@ -49,6 +49,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
name string
|
||||
userIDKey string
|
||||
userNameKey string
|
||||
overrideClaimMapping bool
|
||||
preferredUsernameKey string
|
||||
emailKey string
|
||||
groupsKey string
|
||||
|
@ -92,6 +93,23 @@ func TestHandleCallback(t *testing.T) {
|
|||
"email_verified": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "overrideWithCustomEmailClaim",
|
||||
userIDKey: "", // not configured
|
||||
userNameKey: "", // not configured
|
||||
overrideClaimMapping: true,
|
||||
emailKey: "custommail",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "customemailvalue",
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
"email": "emailvalue",
|
||||
"custommail": "customemailvalue",
|
||||
"email_verified": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email_verified not in claims, configured to be skipped",
|
||||
insecureSkipEmailVerified: true,
|
||||
|
@ -234,11 +252,31 @@ func TestHandleCallback(t *testing.T) {
|
|||
"cognito:groups": []string{"group3", "group4"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "customGroupsKeyDespiteGroupsProvidedButOverride",
|
||||
overrideClaimMapping: true,
|
||||
groupsKey: "cognito:groups",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "emailvalue",
|
||||
expectGroups: []string{"group3", "group4"},
|
||||
scopes: []string{"groups"},
|
||||
insecureSkipEmailVerified: true,
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
"user_name": "username",
|
||||
"email": "emailvalue",
|
||||
"groups": []string{"group1", "group2"},
|
||||
"cognito:groups": []string{"group3", "group4"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testServer, err := setupServer(tc.token)
|
||||
idTokenDesired := true
|
||||
testServer, err := setupServer(tc.token, idTokenDesired)
|
||||
if err != nil {
|
||||
t.Fatal("failed to setup test server", err)
|
||||
}
|
||||
|
@ -263,6 +301,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
InsecureSkipEmailVerified: tc.insecureSkipEmailVerified,
|
||||
InsecureEnableGroups: true,
|
||||
BasicAuthUnsupported: &basicAuth,
|
||||
OverrideClaimMapping: tc.overrideClaimMapping,
|
||||
}
|
||||
config.ClaimMapping.PreferredUsernameKey = tc.preferredUsernameKey
|
||||
config.ClaimMapping.EmailKey = tc.emailKey
|
||||
|
@ -293,7 +332,87 @@ func TestHandleCallback(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func setupServer(tok map[string]interface{}) (*httptest.Server, error) {
|
||||
func TestRefresh(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectUserID string
|
||||
expectUserName string
|
||||
idTokenDesired bool
|
||||
token map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "IDTokenOnRefresh",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
idTokenDesired: true,
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NoIDTokenOnRefresh",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
idTokenDesired: false,
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testServer, err := setupServer(tc.token, tc.idTokenDesired)
|
||||
if err != nil {
|
||||
t.Fatal("failed to setup test server", err)
|
||||
}
|
||||
defer testServer.Close()
|
||||
|
||||
scopes := []string{"openid", "offline_access"}
|
||||
serverURL := testServer.URL
|
||||
config := Config{
|
||||
Issuer: serverURL,
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Scopes: scopes,
|
||||
RedirectURI: fmt.Sprintf("%s/callback", serverURL),
|
||||
GetUserInfo: true,
|
||||
}
|
||||
|
||||
conn, err := newConnector(config)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create new connector", err)
|
||||
}
|
||||
|
||||
req, err := newRequestWithAuthCode(testServer.URL, "someCode")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
|
||||
refreshTokenStr := "{\"RefreshToken\":\"asdf\"}"
|
||||
refreshToken := []byte(refreshTokenStr)
|
||||
|
||||
identity := connector.Identity{
|
||||
UserID: tc.expectUserID,
|
||||
Username: tc.expectUserName,
|
||||
ConnectorData: refreshToken,
|
||||
}
|
||||
|
||||
refreshIdentity, err := conn.Refresh(req.Context(), connector.Scopes{OfflineAccess: true}, identity)
|
||||
if err != nil {
|
||||
t.Fatal("Refresh failed", err)
|
||||
}
|
||||
|
||||
expectEquals(t, refreshIdentity.UserID, tc.expectUserID)
|
||||
expectEquals(t, refreshIdentity.Username, tc.expectUserName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupServer(tok map[string]interface{}, idTokenDesired bool) (*httptest.Server, error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate rsa key: %v", err)
|
||||
|
@ -330,11 +449,23 @@ func setupServer(tok map[string]interface{}) (*httptest.Server, error) {
|
|||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(&map[string]string{
|
||||
"access_token": token,
|
||||
"id_token": token,
|
||||
"token_type": "Bearer",
|
||||
})
|
||||
if idTokenDesired {
|
||||
json.NewEncoder(w).Encode(&map[string]string{
|
||||
"access_token": token,
|
||||
"id_token": token,
|
||||
"token_type": "Bearer",
|
||||
})
|
||||
} else {
|
||||
json.NewEncoder(w).Encode(&map[string]string{
|
||||
"access_token": token,
|
||||
"token_type": "Bearer",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(tok)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -6,9 +6,10 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -20,6 +21,11 @@ import (
|
|||
"github.com/dexidp/dex/storage/kubernetes/k8sapi"
|
||||
)
|
||||
|
||||
const (
|
||||
wellKnownURLPath = "/.well-known/oauth-authorization-server"
|
||||
usersURLPath = "/apis/user.openshift.io/v1/users/~"
|
||||
)
|
||||
|
||||
// Config holds configuration options for OpenShift login
|
||||
type Config struct {
|
||||
Issuer string `json:"issuer"`
|
||||
|
@ -31,7 +37,10 @@ type Config struct {
|
|||
RootCA string `json:"rootCA"`
|
||||
}
|
||||
|
||||
var _ connector.CallbackConnector = (*openshiftConnector)(nil)
|
||||
var (
|
||||
_ connector.CallbackConnector = (*openshiftConnector)(nil)
|
||||
_ connector.RefreshConnector = (*openshiftConnector)(nil)
|
||||
)
|
||||
|
||||
type openshiftConnector struct {
|
||||
apiURL string
|
||||
|
@ -58,9 +67,22 @@ type user struct {
|
|||
// Open returns a connector which can be used to login users through an upstream
|
||||
// OpenShift OAuth2 provider.
|
||||
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
|
||||
httpClient, err := newHTTPClient(c.InsecureCA, c.RootCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP client: %w", err)
|
||||
}
|
||||
|
||||
return c.OpenWithHTTPClient(id, logger, httpClient)
|
||||
}
|
||||
|
||||
// OpenWithHTTPClient returns a connector which can be used to login users through an upstream
|
||||
// OpenShift OAuth2 provider. It provides the ability to inject a http.Client.
|
||||
func (c *Config) OpenWithHTTPClient(id string, logger log.Logger,
|
||||
httpClient *http.Client,
|
||||
) (conn connector.Connector, err error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
wellKnownURL := strings.TrimSuffix(c.Issuer, "/") + "/.well-known/oauth-authorization-server"
|
||||
wellKnownURL := strings.TrimSuffix(c.Issuer, "/") + wellKnownURLPath
|
||||
req, err := http.NewRequest(http.MethodGet, wellKnownURL, nil)
|
||||
|
||||
openshiftConnector := openshiftConnector{
|
||||
|
@ -73,11 +95,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
|
|||
redirectURI: c.RedirectURI,
|
||||
rootCA: c.RootCA,
|
||||
groups: c.Groups,
|
||||
}
|
||||
|
||||
if openshiftConnector.httpClient, err = newHTTPClient(c.InsecureCA, c.RootCA); err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to create HTTP client: %v", err)
|
||||
httpClient: httpClient,
|
||||
}
|
||||
|
||||
var metadata struct {
|
||||
|
@ -88,14 +106,14 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
|
|||
resp, err := openshiftConnector.httpClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to query OpenShift endpoint %v", err)
|
||||
return nil, fmt.Errorf("failed to query OpenShift endpoint %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("discovery through endpoint %s failed to decode body: %v",
|
||||
return nil, fmt.Errorf("discovery through endpoint %s failed to decode body: %w",
|
||||
wellKnownURL, err)
|
||||
}
|
||||
|
||||
|
@ -119,7 +137,8 @@ func (c *openshiftConnector) Close() error {
|
|||
// LoginURL returns the URL to redirect the user to login with.
|
||||
func (c *openshiftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
|
||||
if c.redirectURI != callbackURL {
|
||||
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
|
||||
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q",
|
||||
callbackURL, c.redirectURI)
|
||||
}
|
||||
return c.oauth2Config.AuthCodeURL(state), nil
|
||||
}
|
||||
|
@ -137,7 +156,9 @@ func (e *oauth2Error) Error() string {
|
|||
}
|
||||
|
||||
// HandleCallback parses the request and returns the user's identity
|
||||
func (c *openshiftConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
|
||||
func (c *openshiftConnector) HandleCallback(s connector.Scopes,
|
||||
r *http.Request,
|
||||
) (identity connector.Identity, err error) {
|
||||
q := r.URL.Query()
|
||||
if errType := q.Get("error"); errType != "" {
|
||||
return identity, &oauth2Error{errType, q.Get("error_description")}
|
||||
|
@ -153,8 +174,27 @@ func (c *openshiftConnector) HandleCallback(s connector.Scopes, r *http.Request)
|
|||
return identity, fmt.Errorf("oidc: failed to get token: %v", err)
|
||||
}
|
||||
|
||||
client := c.oauth2Config.Client(ctx, token)
|
||||
return c.identity(ctx, s, token)
|
||||
}
|
||||
|
||||
func (c *openshiftConnector) Refresh(ctx context.Context, s connector.Scopes,
|
||||
oldID connector.Identity,
|
||||
) (connector.Identity, error) {
|
||||
var token oauth2.Token
|
||||
err := json.Unmarshal(oldID.ConnectorData, &token)
|
||||
if err != nil {
|
||||
return connector.Identity{}, fmt.Errorf("parsing token: %w", err)
|
||||
}
|
||||
if c.httpClient != nil {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
|
||||
}
|
||||
return c.identity(ctx, s, &token)
|
||||
}
|
||||
|
||||
func (c *openshiftConnector) identity(ctx context.Context, s connector.Scopes,
|
||||
token *oauth2.Token,
|
||||
) (identity connector.Identity, err error) {
|
||||
client := c.oauth2Config.Client(ctx, token)
|
||||
user, err := c.user(ctx, client)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("openshift: get user: %v", err)
|
||||
|
@ -176,12 +216,20 @@ func (c *openshiftConnector) HandleCallback(s connector.Scopes, r *http.Request)
|
|||
Groups: user.Groups,
|
||||
}
|
||||
|
||||
if s.OfflineAccess {
|
||||
connData, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("marshal connector data: %v", err)
|
||||
}
|
||||
identity.ConnectorData = connData
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// user function returns the OpenShift user associated with the authenticated user
|
||||
func (c *openshiftConnector) user(ctx context.Context, client *http.Client) (u user, err error) {
|
||||
url := c.apiURL + "/apis/user.openshift.io/v1/users/~"
|
||||
url := c.apiURL + usersURLPath
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
|
@ -195,7 +243,7 @@ func (c *openshiftConnector) user(ctx context.Context, client *http.Client) (u u
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("read body: %v", err)
|
||||
}
|
||||
|
@ -218,14 +266,13 @@ func validateAllowedGroups(userGroups, allowedGroups []string) bool {
|
|||
// newHTTPClient returns a new HTTP client
|
||||
func newHTTPClient(insecureCA bool, rootCA string) (*http.Client, error) {
|
||||
tlsConfig := tls.Config{}
|
||||
|
||||
if insecureCA {
|
||||
tlsConfig = tls.Config{InsecureSkipVerify: true}
|
||||
} else if rootCA != "" {
|
||||
tlsConfig = tls.Config{RootCAs: x509.NewCertPool()}
|
||||
rootCABytes, err := ioutil.ReadFile(rootCA)
|
||||
rootCABytes, err := os.ReadFile(rootCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read root-ca: %v", err)
|
||||
return nil, fmt.Errorf("failed to read root-ca: %w", err)
|
||||
}
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {
|
||||
return nil, fmt.Errorf("no certs found in root CA file %q", rootCA)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
|
@ -184,6 +185,78 @@ func TestCallbackIdentity(t *testing.T) {
|
|||
expectEquals(t, identity.Groups[0], "users")
|
||||
}
|
||||
|
||||
func TestRefreshIdentity(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
usersURLPath: user{
|
||||
ObjectMeta: k8sapi.ObjectMeta{
|
||||
Name: "jdoe",
|
||||
UID: "12345",
|
||||
},
|
||||
FullName: "John Doe",
|
||||
Groups: []string{"users"},
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
h, err := newHTTPClient(true, "")
|
||||
expectNil(t, err)
|
||||
|
||||
oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/oauth/authorize", s.URL),
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", s.URL),
|
||||
},
|
||||
}}
|
||||
|
||||
data, err := json.Marshal(oauth2.Token{AccessToken: "fFAGRNJru1FTz70BzhT3Zg"})
|
||||
expectNil(t, err)
|
||||
|
||||
oldID := connector.Identity{ConnectorData: data}
|
||||
|
||||
identity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID)
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, identity.UserID, "12345")
|
||||
expectEquals(t, identity.Username, "jdoe")
|
||||
expectEquals(t, identity.PreferredUsername, "jdoe")
|
||||
expectEquals(t, identity.Email, "jdoe")
|
||||
expectEquals(t, len(identity.Groups), 1)
|
||||
expectEquals(t, identity.Groups[0], "users")
|
||||
}
|
||||
|
||||
func TestRefreshIdentityFailure(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
usersURLPath: user{
|
||||
ObjectMeta: k8sapi.ObjectMeta{
|
||||
Name: "jdoe",
|
||||
UID: "12345",
|
||||
},
|
||||
FullName: "John Doe",
|
||||
Groups: []string{"users"},
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
h, err := newHTTPClient(true, "")
|
||||
expectNil(t, err)
|
||||
|
||||
oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/oauth/authorize", s.URL),
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", s.URL),
|
||||
},
|
||||
}}
|
||||
|
||||
data, err := json.Marshal(oauth2.Token{AccessToken: "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", Expiry: time.Now().Add(-time.Hour)})
|
||||
expectNil(t, err)
|
||||
|
||||
oldID := connector.Identity{ConnectorData: data}
|
||||
|
||||
identity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID)
|
||||
expectNotNil(t, err)
|
||||
expectEquals(t, connector.Identity{}, identity)
|
||||
}
|
||||
|
||||
func newTestServer(responses map[string]interface{}) *httptest.Server {
|
||||
var s *httptest.Server
|
||||
s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -216,3 +289,9 @@ func expectEquals(t *testing.T, a interface{}, b interface{}) {
|
|||
t.Errorf("Expected %+v to equal %+v", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func expectNotNil(t *testing.T, a interface{}) {
|
||||
if a == nil {
|
||||
t.Errorf("Expected %+v to not equal nil", a)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"encoding/pem"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -194,7 +194,7 @@ func (c *Config) openConnector(logger log.Logger) (*provider, error) {
|
|||
|
||||
var caData []byte
|
||||
if c.CA != "" {
|
||||
data, err := ioutil.ReadFile(c.CA)
|
||||
data, err := os.ReadFile(c.CA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read ca file: %v", err)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -392,7 +392,7 @@ func TestTamperedResponseNameID(t *testing.T) {
|
|||
}
|
||||
|
||||
func loadCert(ca string) (*x509.Certificate, error) {
|
||||
data, err := ioutil.ReadFile(ca)
|
||||
data, err := os.ReadFile(ca)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -426,7 +426,7 @@ func (r responseTest) run(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
conn.now = func() time.Time { return now }
|
||||
resp, err := ioutil.ReadFile(r.respFile)
|
||||
resp, err := os.ReadFile(r.respFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -456,11 +456,11 @@ func (r responseTest) run(t *testing.T) {
|
|||
|
||||
func TestConfigCAData(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
validPEM, err := ioutil.ReadFile("testdata/ca.crt")
|
||||
validPEM, err := os.ReadFile("testdata/ca.crt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
valid2ndPEM, err := ioutil.ReadFile("testdata/okta-ca.pem")
|
||||
valid2ndPEM, err := os.ReadFile("testdata/okta-ca.pem")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -551,7 +551,7 @@ func runVerify(t *testing.T, ca string, resp string, shouldSucceed bool) {
|
|||
|
||||
validator := dsig.NewDefaultValidationContext(s)
|
||||
|
||||
data, err := ioutil.ReadFile(resp)
|
||||
data, err := os.ReadFile(resp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ services:
|
|||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
etcd:
|
||||
image: gcr.io/etcd-development/etcd:v3.4.9
|
||||
image: gcr.io/etcd-development/etcd:v3.5.0
|
||||
environment:
|
||||
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
|
||||
ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
### Usage: /docker-entrypoint.sh <command> <args>
|
||||
function main() {
|
||||
executable=$1
|
||||
command=$2
|
||||
|
||||
if [[ "$executable" != "dex" ]] && [[ "$executable" != "$(which dex)" ]]; then
|
||||
exec $@
|
||||
fi
|
||||
|
||||
if [[ "$command" != "serve" ]]; then
|
||||
exec $@
|
||||
fi
|
||||
|
||||
for tpl_candidate in $@ ; do
|
||||
case "$tpl_candidate" in
|
||||
*.tpl|*.tmpl|*.yaml)
|
||||
tmp_file=$(mktemp /tmp/dex.config.yaml-XXXXXX)
|
||||
gomplate -f "$tpl_candidate" -o "$tmp_file"
|
||||
|
||||
args="${args} ${tmp_file}"
|
||||
;;
|
||||
*)
|
||||
args="${args} ${tpl_candidate}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
exec $args
|
||||
}
|
||||
|
||||
main $@
|
23
docs/enhancements/README.md
Normal file
23
docs/enhancements/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Dex Enhancement Proposal
|
||||
|
||||
## Why do we need it?
|
||||
|
||||
Dex Enhancement Proposal (DEP) is a design document providing information to the community, or describing a new feature for Dex.
|
||||
|
||||
We intend DEPs to be the primary mechanisms for proposing major new features or significant changes to existing ones.
|
||||
This will make it easier for the community to describe, track, and look through the history of changes that affected the development of the project.
|
||||
|
||||
## Process
|
||||
|
||||
### Before starting
|
||||
1. Search GitHub for previous [issues](https://github.com/dexidp/dex/issues), [discussions](https://github.com/dexidp/dex/discussions) and [DEPs](https://github.com/dexidp/dex/tree/master/docs/enhancements).
|
||||
2. If a discussion does not exist, [open it](https://github.com/dexidp/dex/discussions/new?category=Ideas).
|
||||
3. Ensure that writing enhancement proposal is necessary for you change by discussing it with a community.
|
||||
|
||||
### Writing an enhancement proposal
|
||||
|
||||
1. Fork the repo.
|
||||
2. Copy the [`docs/enhancements/_title-YYYY-MM-DD-#issue.md`](docs/enhancements/_title-YYYY-MM-DD-#issue.md) template with the appropriate
|
||||
name.
|
||||
3. Fill all sections according to hints in them. Provide as much information as you can.
|
||||
4. Submit your PR and discuss it with the Dex team.
|
62
docs/enhancements/_title-YYYY-MM-DD-#issue.md
Normal file
62
docs/enhancements/_title-YYYY-MM-DD-#issue.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Dex Enhancement Proposal (DEP) <issue#> - <YYYY-MM-DD> - <title>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Summary](#summary)
|
||||
- [Motivation](#motivation)
|
||||
- [Goals/Pain](#goals)
|
||||
- [Non-Goals](#non-goals)
|
||||
- [Proposal](#proposal)
|
||||
- [User Experience](#user-experience)
|
||||
- [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)
|
||||
- [Risks and Mitigations](#risks-and-mitigations)
|
||||
- [Alternatives](#alternatives)
|
||||
- [Future Improvements](#future-improvements)
|
||||
|
||||
## Summary
|
||||
|
||||
- Provide a one-paragraph description of the expected change here.
|
||||
|
||||
## Context
|
||||
|
||||
- Link to any previous issues, RFCs, discussions, or briefs.
|
||||
- Link to any ongoing or future work relevant to this change.
|
||||
|
||||
## Motivation
|
||||
|
||||
### Goals/Pain
|
||||
|
||||
- List work that is assumed to be done in the scope of this enhancement.
|
||||
- Mention problems solve by this enhancement.
|
||||
|
||||
### Non-goals
|
||||
|
||||
- List work that is entirely out of the scope of this enhancement. Use this to define DEP borders to keep work focused.
|
||||
- All planned future enhancements should be listed in one of the following blocks - Future Improvements.
|
||||
|
||||
## Proposal
|
||||
|
||||
### User Experience
|
||||
|
||||
- Explain your change as if you were describing it to end-users.
|
||||
- Explain the way users are supposed to use Dex with the proposed enhancement.
|
||||
|
||||
### Implementation Details/Notes/Constraints
|
||||
|
||||
- Explain your change as if you were at a development team meeting (give more technical and implementation details).
|
||||
- When possible, demonstrate with pseudo-code, not text.
|
||||
- Be specific. Be opinionated. Avoid ambiguity.
|
||||
|
||||
### Risks and Mitigations
|
||||
|
||||
- Mention all expected risks and migrations in detail here.
|
||||
- Do not forget to mention if the proposed enhancement is a breaking change.
|
||||
|
||||
### Alternatives
|
||||
|
||||
- What other approaches have been considered, and why did you not choose them?
|
||||
- What happens if this enhancement will never be accepted and implemented?
|
||||
|
||||
## Future Improvements
|
||||
|
||||
- List any future improvements.
|
|
@ -63,6 +63,7 @@ web:
|
|||
# Configuration for telemetry
|
||||
telemetry:
|
||||
http: 0.0.0.0:5558
|
||||
# enableProfiling: true
|
||||
|
||||
# Uncomment this block to enable the gRPC API. This values MUST be different
|
||||
# from the HTTP endpoints.
|
||||
|
@ -73,10 +74,15 @@ telemetry:
|
|||
# tlsClientCA: examples/grpc-client/ca.crt
|
||||
|
||||
# Uncomment this block to enable configuration for the expiration time durations.
|
||||
# Is possible to specify units using only s, m and h suffixes.
|
||||
# expiry:
|
||||
# deviceRequests: "5m"
|
||||
# signingKeys: "6h"
|
||||
# idTokens: "24h"
|
||||
# refreshTokens:
|
||||
# reuseInterval: "3s"
|
||||
# validIfNotUsedFor: "2160h" # 90 days
|
||||
# absoluteLifetime: "3960h" # 165 days
|
||||
|
||||
# Options for controlling the logger.
|
||||
# logger:
|
||||
|
@ -136,7 +142,7 @@ enablePasswordDB: true
|
|||
# If this option isn't chosen users may be added through the gRPC API.
|
||||
staticPasswords:
|
||||
- email: "admin@example.com"
|
||||
# bcrypt hash of the string "password"
|
||||
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
|
||||
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
|
||||
username: "admin"
|
||||
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -43,7 +42,7 @@ type app struct {
|
|||
// return an HTTP client which trusts the provided root CAs.
|
||||
func httpClientForRootCAs(rootCAs string) (*http.Client, error) {
|
||||
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
|
||||
rootCABytes, err := ioutil.ReadFile(rootCAs)
|
||||
rootCABytes, err := os.ReadFile(rootCAs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read root-ca: %v", err)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
module github.com/dexidp/dex/examples
|
||||
|
||||
go 1.14
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc/v3 v3.0.0
|
||||
github.com/dexidp/dex/api/v2 v2.0.0-20200715135117-cb46a28c3ce2
|
||||
github.com/spf13/cobra v1.0.0
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
google.golang.org/grpc v1.26.0
|
||||
github.com/coreos/go-oidc/v3 v3.1.0
|
||||
github.com/dexidp/dex/api/v2 v2.0.0
|
||||
github.com/spf13/cobra v1.3.0
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||
google.golang.org/grpc v1.43.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
)
|
||||
|
|
763
examples/go.sum
763
examples/go.sum
|
@ -1,183 +1,788 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ=
|
||||
github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
|
||||
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dexidp/dex/api/v2 v2.0.0-20200715135117-cb46a28c3ce2 h1:7dhZfOv4rPQobAFxxWLnZB2bV9QxSHGnufXeCjzHEos=
|
||||
github.com/dexidp/dex/api/v2 v2.0.0-20200715135117-cb46a28c3ce2/go.mod h1:k5arBJT1QYvpsEY3sEd0NXJp3hKWKuUUfzJ3BlcqPdM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dexidp/dex/api/v2 v2.0.0 h1:bvge1sRmzVzWPWp4WlMzS04lcNQA+jFzHqKV3066bRw=
|
||||
github.com/dexidp/dex/api/v2 v2.0.0/go.mod h1:k5arBJT1QYvpsEY3sEd0NXJp3hKWKuUUfzJ3BlcqPdM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c h1:zJ0mtu4jCalhKg6Oaukv6iIkb+cOvDrajDH9DH46Q4M=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 h1:aCsSLXylHWFno0r4S3joLpiaWayvqd2Mn4iSvx4WZZc=
|
||||
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
@ -17,7 +17,7 @@ import (
|
|||
|
||||
func newDexClient(hostAndPort, caPath, clientCrt, clientKey string) (api.DexClient, error) {
|
||||
cPool := x509.NewCertPool()
|
||||
caCert, err := ioutil.ReadFile(caPath)
|
||||
caCert, err := os.ReadFile(caPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid CA crt file: %s", caPath)
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func createPassword(cli api.DexClient) error {
|
|||
|
||||
// Create password.
|
||||
if resp, err := cli.CreatePassword(context.TODO(), createReq); err != nil || resp.AlreadyExists {
|
||||
if resp.AlreadyExists {
|
||||
if resp != nil && resp.AlreadyExists {
|
||||
return fmt.Errorf("Password %s already exists", createReq.Password.Email)
|
||||
}
|
||||
return fmt.Errorf("failed to create password: %v", err)
|
||||
|
@ -115,7 +115,7 @@ func createPassword(cli api.DexClient) error {
|
|||
|
||||
// Delete password with email = test@example.com.
|
||||
if resp, err := cli.DeletePassword(context.TODO(), deleteReq); err != nil || resp.NotFound {
|
||||
if resp.NotFound {
|
||||
if resp != nil && resp.NotFound {
|
||||
return fmt.Errorf("Password %s not found", deleteReq.Email)
|
||||
}
|
||||
return fmt.Errorf("failed to delete password: %v", err)
|
||||
|
|
|
@ -23,7 +23,7 @@ spec:
|
|||
spec:
|
||||
serviceAccountName: dex # This is created below
|
||||
containers:
|
||||
- image: dexidp/dex:v2.27.0 #or quay.io/dexidp/dex:v2.26.0
|
||||
- image: ghcr.io/dexidp/dex:v2.30.0
|
||||
name: dex
|
||||
command: ["/usr/local/bin/dex", "serve", "/etc/dex/cfg/config.yaml"]
|
||||
|
||||
|
@ -53,6 +53,7 @@ spec:
|
|||
httpGet:
|
||||
path: /healthz
|
||||
port: 5556
|
||||
scheme: HTTPS
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
|
@ -102,7 +103,7 @@ data:
|
|||
enablePasswordDB: true
|
||||
staticPasswords:
|
||||
- email: "admin@example.com"
|
||||
# bcrypt hash of the string "password"
|
||||
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
|
||||
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
|
||||
username: "admin"
|
||||
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
|
||||
|
|
42
flake.lock
Normal file
42
flake.lock
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1648297722,
|
||||
"narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1649225869,
|
||||
"narHash": "sha256-u1zLtPmQzhT9mNXyM8Ey9pk7orDrIKdwooeGDEXm5xM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b6966d911da89e5a7301aaef8b4f0a44c77e103c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
25
flake.nix
Normal file
25
flake.nix
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
description = "OpenID Connect (OIDC) identity and OAuth 2.0 provider with pluggable connectors";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
buildDeps = with pkgs; [ git go_1_18 gnumake ];
|
||||
devDeps = with pkgs;
|
||||
buildDeps ++ [
|
||||
golangci-lint
|
||||
gotestsum
|
||||
protobuf
|
||||
protoc-gen-go
|
||||
protoc-gen-go-grpc
|
||||
kind
|
||||
];
|
||||
in
|
||||
{ devShell = pkgs.mkShell { buildInputs = devDeps; }; });
|
||||
}
|
109
go.mod
109
go.mod
|
@ -1,44 +1,95 @@
|
|||
module github.com/dexidp/dex
|
||||
|
||||
go 1.16
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/AppsFlyer/go-sundheit v0.3.1
|
||||
entgo.io/ent v0.10.1
|
||||
github.com/AppsFlyer/go-sundheit v0.5.0
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.0.0
|
||||
github.com/dexidp/dex/api/v2 v2.0.0
|
||||
github.com/felixge/httpsnoop v1.0.1
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/dexidp/dex/api/v2 v2.1.0
|
||||
github.com/felixge/httpsnoop v1.0.3
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gogo/protobuf v1.3.1 // indirect
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/go-ldap/ldap/v3 v3.4.4
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/lib/pq v1.10.0
|
||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201219040909-8fd2afad43d1
|
||||
github.com/mattn/go-sqlite3 v1.14.6
|
||||
github.com/lib/pq v1.10.4
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0
|
||||
github.com/mattn/go-sqlite3 v1.14.11
|
||||
github.com/oklog/run v1.1.0
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.4.0
|
||||
github.com/russellhaering/goxmldsig v1.1.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 // indirect
|
||||
google.golang.org/api v0.15.0
|
||||
google.golang.org/grpc v1.26.0
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/ldap.v2 v2.5.1
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
sigs.k8s.io/testing_frameworks v0.1.2
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/russellhaering/goxmldsig v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4
|
||||
go.etcd.io/etcd/client/v3 v3.5.4
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
|
||||
google.golang.org/api v0.89.0
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3 // indirect
|
||||
cloud.google.com/go/compute v1.7.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.10.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.1 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/zclconf/go-cty v1.8.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/dexidp/dex/api/v2 => ./api/v2
|
||||
|
|
5
pkg/log/deprecated.go
Normal file
5
pkg/log/deprecated.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package log
|
||||
|
||||
func Deprecated(logger Logger, f string, args ...interface{}) {
|
||||
logger.Warnf("Deprecated: "+f, args...)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
# This is a script to download protoc. Rather than depending on the version on
|
||||
# a developer's machine, always download a specific version.
|
||||
|
||||
VERSION="3.1.0"
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: ./get-protoc [dest]"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Use the go tool to determine OS.
|
||||
OS=$( go env GOOS )
|
||||
|
||||
if [ "$OS" = "darwin" ]; then
|
||||
OS="osx"
|
||||
fi
|
||||
|
||||
mkdir -p bin
|
||||
|
||||
# TODO(ericchiang): Architectures other than amd64?
|
||||
ZIP="protoc-${VERSION}-${OS}-x86_64.zip"
|
||||
URL="https://github.com/google/protobuf/releases/download/v${VERSION}/${ZIP}"
|
||||
|
||||
wget ${URL}
|
||||
# Unpack the protoc binary. Later we might want to grab additional data.
|
||||
unzip -p ${ZIP} bin/protoc > $1
|
||||
chmod +x $1
|
||||
rm ${ZIP}
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: authcodes.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: AuthCodeList
|
||||
plural: authcodes
|
||||
singular: authcode
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: authrequests.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: AuthRequestList
|
||||
plural: authrequests
|
||||
singular: authrequest
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: connectors.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: ConnectorList
|
||||
plural: connectors
|
||||
singular: connector
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: devicerequests.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: DeviceRequestList
|
||||
plural: devicerequests
|
||||
singular: devicerequest
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: devicetokens.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: DeviceTokenList
|
||||
plural: devicetokens
|
||||
singular: devicetoken
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: oauth2clients.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: OAuth2ClientList
|
||||
plural: oauth2clients
|
||||
singular: oauth2client
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: offlinesessionses.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: OfflineSessionsList
|
||||
plural: offlinesessionses
|
||||
singular: offlinesessions
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: passwords.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: PasswordList
|
||||
plural: passwords
|
||||
singular: password
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: refreshtokens.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: RefreshTokenList
|
||||
plural: refreshtokens
|
||||
singular: refreshtoken
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: signingkeies.dex.coreos.com
|
||||
|
@ -9,4 +9,12 @@ spec:
|
|||
listKind: SigningKeyList
|
||||
plural: signingkeies
|
||||
singular: signingkey
|
||||
version: v1
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/dexidp/dex/pkg/log"
|
||||
"github.com/dexidp/dex/server/internal"
|
||||
"github.com/dexidp/dex/storage"
|
||||
"github.com/dexidp/dex/version"
|
||||
)
|
||||
|
||||
// apiVersion increases every time a new call is added to the API. Clients should use this info
|
||||
|
@ -30,16 +29,20 @@ const (
|
|||
)
|
||||
|
||||
// NewAPI returns a server which implements the gRPC API interface.
|
||||
func NewAPI(s storage.Storage, logger log.Logger) api.DexServer {
|
||||
func NewAPI(s storage.Storage, logger log.Logger, version string) api.DexServer {
|
||||
return dexAPI{
|
||||
s: s,
|
||||
logger: logger,
|
||||
s: s,
|
||||
logger: logger,
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
type dexAPI struct {
|
||||
s storage.Storage
|
||||
logger log.Logger
|
||||
api.UnimplementedDexServer
|
||||
|
||||
s storage.Storage
|
||||
logger log.Logger
|
||||
version string
|
||||
}
|
||||
|
||||
func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*api.CreateClientResp, error) {
|
||||
|
@ -221,7 +224,7 @@ func (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq)
|
|||
|
||||
func (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.VersionResp, error) {
|
||||
return &api.VersionResp{
|
||||
Server: version.Version,
|
||||
Server: d.version,
|
||||
Api: apiVersion,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/dexidp/dex/api/v2"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
|
@ -36,12 +37,12 @@ func newAPI(s storage.Storage, logger log.Logger, t *testing.T) *apiClient {
|
|||
}
|
||||
|
||||
serv := grpc.NewServer()
|
||||
api.RegisterDexServer(serv, NewAPI(s, logger))
|
||||
api.RegisterDexServer(serv, NewAPI(s, logger, "test"))
|
||||
go serv.Serve(l)
|
||||
|
||||
// Dial will retry automatically if the serv.Serve() goroutine
|
||||
// hasn't started yet.
|
||||
conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
|
||||
conn, err := grpc.Dial(l.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
"github.com/dexidp/dex/storage"
|
||||
)
|
||||
|
||||
|
@ -70,6 +73,17 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
|
|||
clientID := r.Form.Get("client_id")
|
||||
clientSecret := r.Form.Get("client_secret")
|
||||
scopes := strings.Fields(r.Form.Get("scope"))
|
||||
codeChallenge := r.Form.Get("code_challenge")
|
||||
codeChallengeMethod := r.Form.Get("code_challenge_method")
|
||||
|
||||
if codeChallengeMethod == "" {
|
||||
codeChallengeMethod = codeChallengeMethodPlain
|
||||
}
|
||||
if codeChallengeMethod != codeChallengeMethodS256 && codeChallengeMethod != codeChallengeMethodPlain {
|
||||
description := fmt.Sprintf("Unsupported PKCE challenge method (%q).", codeChallengeMethod)
|
||||
s.tokenErrHelper(w, errInvalidRequest, description, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Infof("Received device request for client %v with scopes %v", clientID, scopes)
|
||||
|
||||
|
@ -105,6 +119,10 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
|
|||
Expiry: expireTime,
|
||||
LastRequestTime: s.now(),
|
||||
PollIntervalSeconds: 0,
|
||||
PKCE: storage.PKCE{
|
||||
CodeChallenge: codeChallenge,
|
||||
CodeChallengeMethod: codeChallengeMethod,
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.storage.CreateDeviceToken(deviceToken); err != nil {
|
||||
|
@ -140,6 +158,10 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
|
|||
// https://tools.ietf.org/html/rfc8628#section-3.2
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
|
||||
// Response type should be application/json according to
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
|
@ -151,7 +173,9 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleDeviceTokenDeprecated(w http.ResponseWriter, r *http.Request) {
|
||||
log.Deprecated(s.logger, `The /device/token endpoint was called. It will be removed, use /token instead.`)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
|
@ -162,71 +186,99 @@ func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
deviceCode := r.Form.Get("device_code")
|
||||
if deviceCode == "" {
|
||||
s.tokenErrHelper(w, errInvalidRequest, "No device code received", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
grantType := r.PostFormValue("grant_type")
|
||||
if grantType != grantTypeDeviceCode {
|
||||
s.tokenErrHelper(w, errInvalidGrant, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
now := s.now()
|
||||
|
||||
// Grab the device token, check validity
|
||||
deviceToken, err := s.storage.GetDeviceToken(deviceCode)
|
||||
if err != nil {
|
||||
if err != storage.ErrNotFound {
|
||||
s.logger.Errorf("failed to get device code: %v", err)
|
||||
}
|
||||
s.tokenErrHelper(w, errInvalidRequest, "Invalid Device code.", http.StatusBadRequest)
|
||||
return
|
||||
} else if now.After(deviceToken.Expiry) {
|
||||
s.tokenErrHelper(w, deviceTokenExpired, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Rate Limiting check
|
||||
slowDown := false
|
||||
pollInterval := deviceToken.PollIntervalSeconds
|
||||
minRequestTime := deviceToken.LastRequestTime.Add(time.Second * time.Duration(pollInterval))
|
||||
if now.Before(minRequestTime) {
|
||||
slowDown = true
|
||||
// Continually increase the poll interval until the user waits the proper time
|
||||
pollInterval += 5
|
||||
} else {
|
||||
pollInterval = 5
|
||||
}
|
||||
|
||||
switch deviceToken.Status {
|
||||
case deviceTokenPending:
|
||||
updater := func(old storage.DeviceToken) (storage.DeviceToken, error) {
|
||||
old.PollIntervalSeconds = pollInterval
|
||||
old.LastRequestTime = now
|
||||
return old, nil
|
||||
}
|
||||
// Update device token last request time in storage
|
||||
if err := s.storage.UpdateDeviceToken(deviceCode, updater); err != nil {
|
||||
s.logger.Errorf("failed to update device token: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
if slowDown {
|
||||
s.tokenErrHelper(w, deviceTokenSlowDown, "", http.StatusBadRequest)
|
||||
} else {
|
||||
s.tokenErrHelper(w, deviceTokenPending, "", http.StatusUnauthorized)
|
||||
}
|
||||
case deviceTokenComplete:
|
||||
w.Write([]byte(deviceToken.Token))
|
||||
}
|
||||
s.handleDeviceToken(w, r)
|
||||
default:
|
||||
s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) {
|
||||
deviceCode := r.Form.Get("device_code")
|
||||
if deviceCode == "" {
|
||||
s.tokenErrHelper(w, errInvalidRequest, "No device code received", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
now := s.now()
|
||||
|
||||
// Grab the device token, check validity
|
||||
deviceToken, err := s.storage.GetDeviceToken(deviceCode)
|
||||
if err != nil {
|
||||
if err != storage.ErrNotFound {
|
||||
s.logger.Errorf("failed to get device code: %v", err)
|
||||
}
|
||||
s.tokenErrHelper(w, errInvalidRequest, "Invalid Device code.", http.StatusBadRequest)
|
||||
return
|
||||
} else if now.After(deviceToken.Expiry) {
|
||||
s.tokenErrHelper(w, deviceTokenExpired, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Rate Limiting check
|
||||
slowDown := false
|
||||
pollInterval := deviceToken.PollIntervalSeconds
|
||||
minRequestTime := deviceToken.LastRequestTime.Add(time.Second * time.Duration(pollInterval))
|
||||
if now.Before(minRequestTime) {
|
||||
slowDown = true
|
||||
// Continually increase the poll interval until the user waits the proper time
|
||||
pollInterval += 5
|
||||
} else {
|
||||
pollInterval = 5
|
||||
}
|
||||
|
||||
switch deviceToken.Status {
|
||||
case deviceTokenPending:
|
||||
updater := func(old storage.DeviceToken) (storage.DeviceToken, error) {
|
||||
old.PollIntervalSeconds = pollInterval
|
||||
old.LastRequestTime = now
|
||||
return old, nil
|
||||
}
|
||||
// Update device token last request time in storage
|
||||
if err := s.storage.UpdateDeviceToken(deviceCode, updater); err != nil {
|
||||
s.logger.Errorf("failed to update device token: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
if slowDown {
|
||||
s.tokenErrHelper(w, deviceTokenSlowDown, "", http.StatusBadRequest)
|
||||
} else {
|
||||
s.tokenErrHelper(w, deviceTokenPending, "", http.StatusUnauthorized)
|
||||
}
|
||||
case deviceTokenComplete:
|
||||
codeChallengeFromStorage := deviceToken.PKCE.CodeChallenge
|
||||
providedCodeVerifier := r.Form.Get("code_verifier")
|
||||
|
||||
switch {
|
||||
case providedCodeVerifier != "" && codeChallengeFromStorage != "":
|
||||
calculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, deviceToken.PKCE.CodeChallengeMethod)
|
||||
if err != nil {
|
||||
s.logger.Error(err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if codeChallengeFromStorage != calculatedCodeChallenge {
|
||||
s.tokenErrHelper(w, errInvalidGrant, "Invalid code_verifier.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case providedCodeVerifier != "":
|
||||
// Received no code_challenge on /auth, but a code_verifier on /token
|
||||
s.tokenErrHelper(w, errInvalidRequest, "No PKCE flow started. Cannot check code_verifier.", http.StatusBadRequest)
|
||||
return
|
||||
case codeChallengeFromStorage != "":
|
||||
// Received PKCE request on /auth, but no code_verifier on /token
|
||||
s.tokenErrHelper(w, errInvalidGrant, "Expecting parameter code_verifier in PKCE flow.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Write([]byte(deviceToken.Token))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
|
@ -240,7 +292,9 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Authorization redirect callback from OAuth2 auth flow.
|
||||
if errMsg := r.FormValue("error"); errMsg != "" {
|
||||
http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest)
|
||||
// escape the message to prevent cross-site scripting
|
||||
msg := html.EscapeString(errMsg + ": " + r.FormValue("error_description"))
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -49,9 +49,11 @@ func TestHandleDeviceCode(t *testing.T) {
|
|||
tests := []struct {
|
||||
testName string
|
||||
clientID string
|
||||
codeChallengeMethod string
|
||||
requestType string
|
||||
scopes []string
|
||||
expectedResponseCode int
|
||||
expectedContentType string
|
||||
expectedServerResponse string
|
||||
}{
|
||||
{
|
||||
|
@ -60,6 +62,7 @@ func TestHandleDeviceCode(t *testing.T) {
|
|||
requestType: "POST",
|
||||
scopes: []string{"openid", "profile", "email"},
|
||||
expectedResponseCode: http.StatusOK,
|
||||
expectedContentType: "application/json",
|
||||
},
|
||||
{
|
||||
testName: "Invalid request Type (GET)",
|
||||
|
@ -67,6 +70,25 @@ func TestHandleDeviceCode(t *testing.T) {
|
|||
requestType: "GET",
|
||||
scopes: []string{"openid", "profile", "email"},
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
expectedContentType: "application/json",
|
||||
},
|
||||
{
|
||||
testName: "New Code with valid PKCE",
|
||||
clientID: "test",
|
||||
requestType: "POST",
|
||||
scopes: []string{"openid", "profile", "email"},
|
||||
codeChallengeMethod: "S256",
|
||||
expectedResponseCode: http.StatusOK,
|
||||
expectedContentType: "application/json",
|
||||
},
|
||||
{
|
||||
testName: "Invalid code challenge method",
|
||||
clientID: "test",
|
||||
requestType: "POST",
|
||||
codeChallengeMethod: "invalid",
|
||||
scopes: []string{"openid", "profile", "email"},
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
expectedContentType: "application/json",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
|
@ -89,6 +111,7 @@ func TestHandleDeviceCode(t *testing.T) {
|
|||
|
||||
data := url.Values{}
|
||||
data.Set("client_id", tc.clientID)
|
||||
data.Set("code_challenge_method", tc.codeChallengeMethod)
|
||||
for _, scope := range tc.scopes {
|
||||
data.Add("scope", scope)
|
||||
}
|
||||
|
@ -101,7 +124,11 @@ func TestHandleDeviceCode(t *testing.T) {
|
|||
t.Errorf("Unexpected Response Type. Expected %v got %v", tc.expectedResponseCode, rr.Code)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(rr.Body)
|
||||
if rr.Header().Get("content-type") != tc.expectedContentType {
|
||||
t.Errorf("Unexpected Response Content Type. Expected %v got %v", tc.expectedContentType, rr.Header().Get("content-type"))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Could read token response %v", err)
|
||||
}
|
||||
|
@ -160,12 +187,13 @@ func TestDeviceCallback(t *testing.T) {
|
|||
}
|
||||
|
||||
tests := []struct {
|
||||
testName string
|
||||
expectedResponseCode int
|
||||
values formValues
|
||||
testAuthCode storage.AuthCode
|
||||
testDeviceRequest storage.DeviceRequest
|
||||
testDeviceToken storage.DeviceToken
|
||||
testName string
|
||||
expectedResponseCode int
|
||||
expectedServerResponse string
|
||||
values formValues
|
||||
testAuthCode storage.AuthCode
|
||||
testDeviceRequest storage.DeviceRequest
|
||||
testDeviceToken storage.DeviceToken
|
||||
}{
|
||||
{
|
||||
testName: "Missing State",
|
||||
|
@ -192,7 +220,8 @@ func TestDeviceCallback(t *testing.T) {
|
|||
code: "somecode",
|
||||
error: "Error Condition",
|
||||
},
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
expectedServerResponse: "Error Condition: \n",
|
||||
},
|
||||
{
|
||||
testName: "Expired Auth Code",
|
||||
|
@ -314,6 +343,16 @@ func TestDeviceCallback(t *testing.T) {
|
|||
testDeviceToken: baseDeviceToken,
|
||||
expectedResponseCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
testName: "Prevent cross-site scripting",
|
||||
values: formValues{
|
||||
state: "XXXX-XXXX",
|
||||
code: "somecode",
|
||||
error: "<script>console.log(window);</script>",
|
||||
},
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
expectedServerResponse: "<script>console.log(window);</script>: \n",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
|
@ -366,6 +405,13 @@ func TestDeviceCallback(t *testing.T) {
|
|||
if rr.Code != tc.expectedResponseCode {
|
||||
t.Errorf("%s: Unexpected Response Type. Expected %v got %v", tc.testName, tc.expectedResponseCode, rr.Code)
|
||||
}
|
||||
|
||||
if len(tc.expectedServerResponse) > 0 {
|
||||
result, _ := io.ReadAll(rr.Body)
|
||||
if string(result) != tc.expectedServerResponse {
|
||||
t.Errorf("%s: Unexpected Response. Expected %q got %q", tc.testName, tc.expectedServerResponse, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -375,6 +421,13 @@ func TestDeviceTokenResponse(t *testing.T) {
|
|||
|
||||
now := func() time.Time { return t0 }
|
||||
|
||||
// Base PKCE values
|
||||
// base64-urlencoded, sha256 digest of code_verifier
|
||||
codeChallenge := "L7ZqsT_zNwvrH8E7J0CqPHx1wgBaFiaE-fAZcKUUAbc"
|
||||
codeChallengeMethod := "S256"
|
||||
// "random" string between 43 & 128 ASCII characters
|
||||
codeVerifier := "66114650f56cc45dee7ee03c49f048ddf9aa53cbf5b09985832fa4f790ff2604"
|
||||
|
||||
baseDeviceRequest := storage.DeviceRequest{
|
||||
UserCode: "ABCD-WXYZ",
|
||||
DeviceCode: "foo",
|
||||
|
@ -389,6 +442,7 @@ func TestDeviceTokenResponse(t *testing.T) {
|
|||
testDeviceToken storage.DeviceToken
|
||||
testGrantType string
|
||||
testDeviceCode string
|
||||
testCodeVerifier string
|
||||
expectedServerResponse string
|
||||
expectedResponseCode int
|
||||
}{
|
||||
|
@ -498,6 +552,101 @@ func TestDeviceTokenResponse(t *testing.T) {
|
|||
expectedServerResponse: "{\"access_token\": \"foobar\"}",
|
||||
expectedResponseCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
testName: "Successful Exchange with PKCE",
|
||||
testDeviceToken: storage.DeviceToken{
|
||||
DeviceCode: "foo",
|
||||
Status: deviceTokenComplete,
|
||||
Token: "{\"access_token\": \"foobar\"}",
|
||||
Expiry: now().Add(5 * time.Minute),
|
||||
LastRequestTime: time.Time{},
|
||||
PollIntervalSeconds: 0,
|
||||
PKCE: storage.PKCE{
|
||||
CodeChallenge: codeChallenge,
|
||||
CodeChallengeMethod: codeChallengeMethod,
|
||||
},
|
||||
},
|
||||
testDeviceCode: "foo",
|
||||
testCodeVerifier: codeVerifier,
|
||||
testDeviceRequest: baseDeviceRequest,
|
||||
expectedServerResponse: "{\"access_token\": \"foobar\"}",
|
||||
expectedResponseCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
testName: "Test Exchange started with PKCE but without verifier provided",
|
||||
testDeviceToken: storage.DeviceToken{
|
||||
DeviceCode: "foo",
|
||||
Status: deviceTokenComplete,
|
||||
Token: "{\"access_token\": \"foobar\"}",
|
||||
Expiry: now().Add(5 * time.Minute),
|
||||
LastRequestTime: time.Time{},
|
||||
PollIntervalSeconds: 0,
|
||||
PKCE: storage.PKCE{
|
||||
CodeChallenge: codeChallenge,
|
||||
CodeChallengeMethod: codeChallengeMethod,
|
||||
},
|
||||
},
|
||||
testDeviceCode: "foo",
|
||||
testDeviceRequest: baseDeviceRequest,
|
||||
expectedServerResponse: errInvalidGrant,
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
testName: "Test Exchange not started with PKCE but verifier provided",
|
||||
testDeviceToken: storage.DeviceToken{
|
||||
DeviceCode: "foo",
|
||||
Status: deviceTokenComplete,
|
||||
Token: "{\"access_token\": \"foobar\"}",
|
||||
Expiry: now().Add(5 * time.Minute),
|
||||
LastRequestTime: time.Time{},
|
||||
PollIntervalSeconds: 0,
|
||||
},
|
||||
testDeviceCode: "foo",
|
||||
testCodeVerifier: codeVerifier,
|
||||
testDeviceRequest: baseDeviceRequest,
|
||||
expectedServerResponse: errInvalidRequest,
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
testName: "Test with PKCE but incorrect verifier provided",
|
||||
testDeviceToken: storage.DeviceToken{
|
||||
DeviceCode: "foo",
|
||||
Status: deviceTokenComplete,
|
||||
Token: "{\"access_token\": \"foobar\"}",
|
||||
Expiry: now().Add(5 * time.Minute),
|
||||
LastRequestTime: time.Time{},
|
||||
PollIntervalSeconds: 0,
|
||||
PKCE: storage.PKCE{
|
||||
CodeChallenge: codeChallenge,
|
||||
CodeChallengeMethod: codeChallengeMethod,
|
||||
},
|
||||
},
|
||||
testDeviceCode: "foo",
|
||||
testCodeVerifier: "invalid",
|
||||
testDeviceRequest: baseDeviceRequest,
|
||||
expectedServerResponse: errInvalidGrant,
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
testName: "Test with PKCE but incorrect challenge provided",
|
||||
testDeviceToken: storage.DeviceToken{
|
||||
DeviceCode: "foo",
|
||||
Status: deviceTokenComplete,
|
||||
Token: "{\"access_token\": \"foobar\"}",
|
||||
Expiry: now().Add(5 * time.Minute),
|
||||
LastRequestTime: time.Time{},
|
||||
PollIntervalSeconds: 0,
|
||||
PKCE: storage.PKCE{
|
||||
CodeChallenge: "invalid",
|
||||
CodeChallengeMethod: codeChallengeMethod,
|
||||
},
|
||||
},
|
||||
testDeviceCode: "foo",
|
||||
testCodeVerifier: codeVerifier,
|
||||
testDeviceRequest: baseDeviceRequest,
|
||||
expectedServerResponse: errInvalidGrant,
|
||||
expectedResponseCode: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
|
@ -532,6 +681,9 @@ func TestDeviceTokenResponse(t *testing.T) {
|
|||
}
|
||||
data.Set("grant_type", grantType)
|
||||
data.Set("device_code", tc.testDeviceCode)
|
||||
if tc.testCodeVerifier != "" {
|
||||
data.Set("code_verifier", tc.testCodeVerifier)
|
||||
}
|
||||
req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(data.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||||
|
||||
|
@ -541,7 +693,7 @@ func TestDeviceTokenResponse(t *testing.T) {
|
|||
t.Errorf("Unexpected Response Type. Expected %v got %v", tc.expectedResponseCode, rr.Code)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(rr.Body)
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Could read token response %v", err)
|
||||
}
|
||||
|
|
486
server/handlers.go
Normal file → Executable file
486
server/handlers.go
Normal file → Executable file
|
@ -2,10 +2,11 @@ package server
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
@ -24,8 +25,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
CodeChallengeMethodPlain = "plain"
|
||||
CodeChallengeMethodS256 = "S256"
|
||||
codeChallengeMethodPlain = "plain"
|
||||
codeChallengeMethodS256 = "S256"
|
||||
)
|
||||
|
||||
func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -94,9 +95,8 @@ func (s *Server) discoveryHandler() (http.HandlerFunc, error) {
|
|||
UserInfo: s.absURL("/userinfo"),
|
||||
DeviceEndpoint: s.absURL("/device/code"),
|
||||
Subjects: []string{"public"},
|
||||
GrantTypes: []string{grantTypeAuthorizationCode, grantTypeRefreshToken, grantTypeDeviceCode},
|
||||
IDTokenAlgs: []string{string(jose.RS256)},
|
||||
CodeChallengeAlgs: []string{CodeChallengeMethodS256, CodeChallengeMethodPlain},
|
||||
CodeChallengeAlgs: []string{codeChallengeMethodS256, codeChallengeMethodPlain},
|
||||
Scopes: []string{"openid", "email", "groups", "profile", "offline_access"},
|
||||
AuthMethods: []string{"client_secret_basic", "client_secret_post"},
|
||||
Claims: []string{
|
||||
|
@ -110,6 +110,8 @@ func (s *Server) discoveryHandler() (http.HandlerFunc, error) {
|
|||
}
|
||||
sort.Strings(d.ResponseTypes)
|
||||
|
||||
d.GrantTypes = s.supportedGrantTypes
|
||||
|
||||
data, err := json.MarshalIndent(d, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal discovery data: %v", err)
|
||||
|
@ -124,38 +126,15 @@ func (s *Server) discoveryHandler() (http.HandlerFunc, error) {
|
|||
|
||||
// handleAuthorization handles the OAuth2 auth endpoint.
|
||||
func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
|
||||
authReq, err := s.parseAuthorizationRequest(r)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to parse authorization request: %v", err)
|
||||
status := http.StatusInternalServerError
|
||||
// Extract the arguments
|
||||
if err := r.ParseForm(); err != nil {
|
||||
s.logger.Errorf("Failed to parse arguments: %v", err)
|
||||
|
||||
// If this is an authErr, let's let it handle the error, or update the HTTP
|
||||
// status code
|
||||
if err, ok := err.(*authErr); ok {
|
||||
if handler, ok := err.Handle(); ok {
|
||||
// client_id and redirect_uri checked out and we can redirect back to
|
||||
// the client with the error.
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
status = err.Status()
|
||||
}
|
||||
|
||||
s.renderError(r, w, status, err.Error())
|
||||
s.renderError(r, w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(ericchiang): Create this authorization request later in the login flow
|
||||
// so users don't hit "not found" database errors if they wait at the login
|
||||
// screen too long.
|
||||
//
|
||||
// See: https://github.com/dexidp/dex/issues/646
|
||||
authReq.Expiry = s.now().Add(s.authRequestsValidFor)
|
||||
if err := s.storage.CreateAuthRequest(*authReq); err != nil {
|
||||
s.logger.Errorf("Failed to create authorization request: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Failed to connect to the database.")
|
||||
return
|
||||
}
|
||||
connectorID := r.Form.Get("connector_id")
|
||||
|
||||
connectors, err := s.storage.ListConnectors()
|
||||
if err != nil {
|
||||
|
@ -164,36 +143,40 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// We don't need connector_id any more
|
||||
r.Form.Del("connector_id")
|
||||
|
||||
// Construct a URL with all of the arguments in its query
|
||||
connURL := url.URL{
|
||||
RawQuery: r.Form.Encode(),
|
||||
}
|
||||
|
||||
// Redirect if a client chooses a specific connector_id
|
||||
if authReq.ConnectorID != "" {
|
||||
if connectorID != "" {
|
||||
for _, c := range connectors {
|
||||
if c.ID == authReq.ConnectorID {
|
||||
http.Redirect(w, r, s.absPath("/auth", c.ID)+"?req="+authReq.ID, http.StatusFound)
|
||||
if c.ID == connectorID {
|
||||
connURL.Path = s.absPath("/auth", url.PathEscape(c.ID))
|
||||
http.Redirect(w, r, connURL.String(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.tokenErrHelper(w, errInvalidConnectorID, "Connector ID does not match a valid Connector", http.StatusNotFound)
|
||||
s.renderError(r, w, http.StatusBadRequest, "Connector ID does not match a valid Connector")
|
||||
return
|
||||
}
|
||||
|
||||
if len(connectors) == 1 && !s.alwaysShowLogin {
|
||||
for _, c := range connectors {
|
||||
// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
|
||||
// on create the auth request.
|
||||
http.Redirect(w, r, s.absPath("/auth", c.ID)+"?req="+authReq.ID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
connURL.Path = s.absPath("/auth", url.PathEscape(connectors[0].ID))
|
||||
http.Redirect(w, r, connURL.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
connectorInfos := make([]connectorInfo, len(connectors))
|
||||
for index, conn := range connectors {
|
||||
connURL.Path = s.absPath("/auth", url.PathEscape(conn.ID))
|
||||
connectorInfos[index] = connectorInfo{
|
||||
ID: conn.ID,
|
||||
Name: conn.Name,
|
||||
Type: conn.Type,
|
||||
// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
|
||||
// on create the auth request.
|
||||
URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID,
|
||||
URL: template.URL(connURL.String()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +186,29 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
|
||||
connID := mux.Vars(r)["connector"]
|
||||
authReq, err := s.parseAuthorizationRequest(r)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to parse authorization request: %v", err)
|
||||
|
||||
switch authErr := err.(type) {
|
||||
case *redirectedAuthErr:
|
||||
authErr.Handler().ServeHTTP(w, r)
|
||||
case *displayedAuthErr:
|
||||
s.renderError(r, w, authErr.Status, err.Error())
|
||||
default:
|
||||
panic("unsupported error type")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
connID, err := url.PathUnescape(mux.Vars(r)["connector"])
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to parse connector: %v", err)
|
||||
s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := s.getConnector(connID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to get connector: %v", err)
|
||||
|
@ -211,37 +216,35 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
authReqID := r.FormValue("req")
|
||||
|
||||
authReq, err := s.storage.GetAuthRequest(authReqID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to get auth request: %v", err)
|
||||
if err == storage.ErrNotFound {
|
||||
s.renderError(r, w, http.StatusBadRequest, "Login session expired.")
|
||||
} else {
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Database error.")
|
||||
}
|
||||
// Set the connector being used for the login.
|
||||
if authReq.ConnectorID != "" && authReq.ConnectorID != connID {
|
||||
s.logger.Errorf("Mismatched connector ID in auth request: %s vs %s",
|
||||
authReq.ConnectorID, connID)
|
||||
s.renderError(r, w, http.StatusBadRequest, "Bad connector ID")
|
||||
return
|
||||
}
|
||||
|
||||
// Set the connector being used for the login.
|
||||
if authReq.ConnectorID != connID {
|
||||
updater := func(a storage.AuthRequest) (storage.AuthRequest, error) {
|
||||
if a.ConnectorID != "" {
|
||||
return a, fmt.Errorf("connector is already set for this auth request")
|
||||
}
|
||||
a.ConnectorID = connID
|
||||
return a, nil
|
||||
}
|
||||
if err := s.storage.UpdateAuthRequest(authReqID, updater); err != nil {
|
||||
s.logger.Errorf("Failed to set connector ID on auth request: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Database error.")
|
||||
return
|
||||
}
|
||||
authReq.ConnectorID = connID
|
||||
|
||||
// Actually create the auth request
|
||||
authReq.Expiry = s.now().Add(s.authRequestsValidFor)
|
||||
if err := s.storage.CreateAuthRequest(*authReq); err != nil {
|
||||
s.logger.Errorf("Failed to create authorization request: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Failed to connect to the database.")
|
||||
return
|
||||
}
|
||||
|
||||
scopes := parseScopes(authReq.Scopes)
|
||||
showBacklink := len(s.connectors) > 1
|
||||
|
||||
// Work out where the "Select another login method" link should go.
|
||||
backLink := ""
|
||||
if len(s.connectors) > 1 {
|
||||
backLinkURL := url.URL{
|
||||
Path: s.absPath("/auth"),
|
||||
RawQuery: r.Form.Encode(),
|
||||
}
|
||||
backLink = backLinkURL.String()
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
|
@ -250,7 +253,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
|
|||
// Use the auth request ID as the "state" token.
|
||||
//
|
||||
// TODO(ericchiang): Is this appropriate or should we also be using a nonce?
|
||||
callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReqID)
|
||||
callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReq.ID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Connector %q returned error when creating callback: %v", connID, err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Login error.")
|
||||
|
@ -258,11 +261,17 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
http.Redirect(w, r, callbackURL, http.StatusFound)
|
||||
case connector.PasswordConnector:
|
||||
if err := s.templates.password(r, w, r.URL.String(), "", usernamePrompt(conn), false, showBacklink); err != nil {
|
||||
s.logger.Errorf("Server template error: %v", err)
|
||||
loginURL := url.URL{
|
||||
Path: s.absPath("/auth", connID, "login"),
|
||||
}
|
||||
q := loginURL.Query()
|
||||
q.Set("state", authReq.ID)
|
||||
q.Set("back", backLink)
|
||||
loginURL.RawQuery = q.Encode()
|
||||
|
||||
http.Redirect(w, r, loginURL.String(), http.StatusFound)
|
||||
case connector.SAMLConnector:
|
||||
action, value, err := conn.POSTData(scopes, authReqID)
|
||||
action, value, err := conn.POSTData(scopes, authReq.ID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Creating SAML data: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Connector Login Error")
|
||||
|
@ -285,28 +294,79 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
|
|||
document.forms[0].submit();
|
||||
</script>
|
||||
</body>
|
||||
</html>`, action, value, authReqID)
|
||||
</html>`, action, value, authReq.ID)
|
||||
default:
|
||||
s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.")
|
||||
}
|
||||
case http.MethodPost:
|
||||
passwordConnector, ok := conn.Connector.(connector.PasswordConnector)
|
||||
if !ok {
|
||||
default:
|
||||
s.renderError(r, w, http.StatusBadRequest, "Unsupported request method.")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) {
|
||||
authID := r.URL.Query().Get("state")
|
||||
if authID == "" {
|
||||
s.renderError(r, w, http.StatusBadRequest, "User session error.")
|
||||
return
|
||||
}
|
||||
|
||||
backLink := r.URL.Query().Get("back")
|
||||
|
||||
authReq, err := s.storage.GetAuthRequest(authID)
|
||||
if err != nil {
|
||||
if err == storage.ErrNotFound {
|
||||
s.logger.Errorf("Invalid 'state' parameter provided: %v", err)
|
||||
s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.")
|
||||
return
|
||||
}
|
||||
s.logger.Errorf("Failed to get auth request: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Database error.")
|
||||
return
|
||||
}
|
||||
|
||||
connID, err := url.PathUnescape(mux.Vars(r)["connector"])
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to parse connector: %v", err)
|
||||
s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist")
|
||||
return
|
||||
} else if connID != "" && connID != authReq.ConnectorID {
|
||||
s.logger.Errorf("Connector mismatch: authentication started with id %q, but password login for id %q was triggered", authReq.ConnectorID, connID)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := s.getConnector(authReq.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
pwConn, ok := conn.Connector.(connector.PasswordConnector)
|
||||
if !ok {
|
||||
s.logger.Errorf("Expected password connector in handlePasswordLogin(), but got %v", pwConn)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
if err := s.templates.password(r, w, r.URL.String(), "", usernamePrompt(pwConn), false, backLink); err != nil {
|
||||
s.logger.Errorf("Server template error: %v", err)
|
||||
}
|
||||
case http.MethodPost:
|
||||
username := r.FormValue("login")
|
||||
password := r.FormValue("password")
|
||||
scopes := parseScopes(authReq.Scopes)
|
||||
|
||||
identity, ok, err := passwordConnector.Login(r.Context(), scopes, username, password)
|
||||
identity, ok, err := pwConn.Login(r.Context(), scopes, username, password)
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to login user: %v", err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, fmt.Sprintf("Login error: %v", err))
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
if err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(passwordConnector), true, showBacklink); err != nil {
|
||||
if err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(pwConn), true, backLink); err != nil {
|
||||
s.logger.Errorf("Server template error: %v", err)
|
||||
}
|
||||
return
|
||||
|
@ -354,7 +414,12 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
if connID := mux.Vars(r)["connector"]; connID != "" && connID != authReq.ConnectorID {
|
||||
connID, err := url.PathUnescape(mux.Vars(r)["connector"])
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.")
|
||||
return
|
||||
} else if connID != "" && connID != authReq.ConnectorID {
|
||||
s.logger.Errorf("Connector mismatch: authentication started with id %q, but callback for id %q was triggered", authReq.ConnectorID, connID)
|
||||
s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.")
|
||||
return
|
||||
|
@ -652,7 +717,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
|
|||
http.Redirect(w, r, u.String(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) withClientFromStorage(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request, storage.Client)) {
|
||||
clientID, clientSecret, ok := r.BasicAuth()
|
||||
if ok {
|
||||
var err error
|
||||
|
@ -679,7 +744,8 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
return
|
||||
}
|
||||
if client.Secret != clientSecret {
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(client.Secret), []byte(clientSecret)) != 1 {
|
||||
if clientSecret == "" {
|
||||
s.logger.Infof("missing client_secret on token request for client: %s", client.ID)
|
||||
} else {
|
||||
|
@ -689,24 +755,43 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
handler(w, r, client)
|
||||
}
|
||||
|
||||
func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method != http.MethodPost {
|
||||
s.tokenErrHelper(w, errInvalidRequest, "method not allowed", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
s.logger.Errorf("Could not parse request body: %v", err)
|
||||
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
grantType := r.PostFormValue("grant_type")
|
||||
switch grantType {
|
||||
case grantTypeDeviceCode:
|
||||
s.handleDeviceToken(w, r)
|
||||
case grantTypeAuthorizationCode:
|
||||
s.handleAuthCode(w, r, client)
|
||||
s.withClientFromStorage(w, r, s.handleAuthCode)
|
||||
case grantTypeRefreshToken:
|
||||
s.handleRefreshToken(w, r, client)
|
||||
s.withClientFromStorage(w, r, s.handleRefreshToken)
|
||||
case grantTypePassword:
|
||||
s.handlePasswordGrant(w, r, client)
|
||||
s.withClientFromStorage(w, r, s.handlePasswordGrant)
|
||||
default:
|
||||
s.tokenErrHelper(w, errInvalidGrant, "", http.StatusBadRequest)
|
||||
s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) calculateCodeChallenge(codeVerifier, codeChallengeMethod string) (string, error) {
|
||||
switch codeChallengeMethod {
|
||||
case CodeChallengeMethodPlain:
|
||||
case codeChallengeMethodPlain:
|
||||
return codeVerifier, nil
|
||||
case CodeChallengeMethodS256:
|
||||
case codeChallengeMethodS256:
|
||||
shaSum := sha256.Sum256([]byte(codeVerifier))
|
||||
return base64.RawURLEncoding.EncodeToString(shaSum[:]), nil
|
||||
default:
|
||||
|
@ -919,206 +1004,6 @@ func (s *Server) exchangeAuthCode(w http.ResponseWriter, authCode storage.AuthCo
|
|||
return s.toAccessTokenResponse(idToken, accessToken, refreshToken, expiry), nil
|
||||
}
|
||||
|
||||
// handle a refresh token request https://tools.ietf.org/html/rfc6749#section-6
|
||||
func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, client storage.Client) {
|
||||
code := r.PostFormValue("refresh_token")
|
||||
scope := r.PostFormValue("scope")
|
||||
if code == "" {
|
||||
s.tokenErrHelper(w, errInvalidRequest, "No refresh token in request.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
token := new(internal.RefreshToken)
|
||||
if err := internal.Unmarshal(code, token); err != nil {
|
||||
// For backward compatibility, assume the refresh_token is a raw refresh token ID
|
||||
// if it fails to decode.
|
||||
//
|
||||
// Because refresh_token values that aren't unmarshable were generated by servers
|
||||
// that don't have a Token value, we'll still reject any attempts to claim a
|
||||
// refresh_token twice.
|
||||
token = &internal.RefreshToken{RefreshId: code, Token: ""}
|
||||
}
|
||||
|
||||
refresh, err := s.storage.GetRefresh(token.RefreshId)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to get refresh token: %v", err)
|
||||
if err == storage.ErrNotFound {
|
||||
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
||||
} else {
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
if refresh.ClientID != client.ID {
|
||||
s.logger.Errorf("client %s trying to claim token for client %s", client.ID, refresh.ClientID)
|
||||
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if refresh.Token != token.Token {
|
||||
s.logger.Errorf("refresh token with id %s claimed twice", refresh.ID)
|
||||
s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Per the OAuth2 spec, if the client has omitted the scopes, default to the original
|
||||
// authorized scopes.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc6749#section-6
|
||||
scopes := refresh.Scopes
|
||||
if scope != "" {
|
||||
requestedScopes := strings.Fields(scope)
|
||||
var unauthorizedScopes []string
|
||||
|
||||
for _, s := range requestedScopes {
|
||||
contains := func() bool {
|
||||
for _, scope := range refresh.Scopes {
|
||||
if s == scope {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}()
|
||||
if !contains {
|
||||
unauthorizedScopes = append(unauthorizedScopes, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unauthorizedScopes) > 0 {
|
||||
msg := fmt.Sprintf("Requested scopes contain unauthorized scope(s): %q.", unauthorizedScopes)
|
||||
s.tokenErrHelper(w, errInvalidRequest, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
scopes = requestedScopes
|
||||
}
|
||||
|
||||
var connectorData []byte
|
||||
|
||||
session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID)
|
||||
switch {
|
||||
case err != nil:
|
||||
if err != storage.ErrNotFound {
|
||||
s.logger.Errorf("failed to get offline session: %v", err)
|
||||
return
|
||||
}
|
||||
case len(refresh.ConnectorData) > 0:
|
||||
// Use the old connector data if it exists, should be deleted once used
|
||||
connectorData = refresh.ConnectorData
|
||||
default:
|
||||
connectorData = session.ConnectorData
|
||||
}
|
||||
|
||||
conn, err := s.getConnector(refresh.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ident := connector.Identity{
|
||||
UserID: refresh.Claims.UserID,
|
||||
Username: refresh.Claims.Username,
|
||||
PreferredUsername: refresh.Claims.PreferredUsername,
|
||||
Email: refresh.Claims.Email,
|
||||
EmailVerified: refresh.Claims.EmailVerified,
|
||||
Groups: refresh.Claims.Groups,
|
||||
ConnectorData: connectorData,
|
||||
}
|
||||
|
||||
// Can the connector refresh the identity? If so, attempt to refresh the data
|
||||
// in the connector.
|
||||
//
|
||||
// TODO(ericchiang): We may want a strict mode where connectors that don't implement
|
||||
// this interface can't perform refreshing.
|
||||
if refreshConn, ok := conn.Connector.(connector.RefreshConnector); ok {
|
||||
newIdent, err := refreshConn.Refresh(r.Context(), parseScopes(scopes), ident)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to refresh identity: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ident = newIdent
|
||||
}
|
||||
|
||||
claims := storage.Claims{
|
||||
UserID: ident.UserID,
|
||||
Username: ident.Username,
|
||||
PreferredUsername: ident.PreferredUsername,
|
||||
Email: ident.Email,
|
||||
EmailVerified: ident.EmailVerified,
|
||||
Groups: ident.Groups,
|
||||
}
|
||||
|
||||
accessToken, err := s.newAccessToken(client.ID, claims, scopes, refresh.Nonce, refresh.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to create new access token: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, refresh.Nonce, accessToken, "", refresh.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to create ID token: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
newToken := &internal.RefreshToken{
|
||||
RefreshId: refresh.ID,
|
||||
Token: storage.NewID(),
|
||||
}
|
||||
rawNewToken, err := internal.Marshal(newToken)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to marshal refresh token: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
lastUsed := s.now()
|
||||
updater := func(old storage.RefreshToken) (storage.RefreshToken, error) {
|
||||
if old.Token != refresh.Token {
|
||||
return old, errors.New("refresh token claimed twice")
|
||||
}
|
||||
old.Token = newToken.Token
|
||||
// Update the claims of the refresh token.
|
||||
//
|
||||
// UserID intentionally ignored for now.
|
||||
old.Claims.Username = ident.Username
|
||||
old.Claims.PreferredUsername = ident.PreferredUsername
|
||||
old.Claims.Email = ident.Email
|
||||
old.Claims.EmailVerified = ident.EmailVerified
|
||||
old.Claims.Groups = ident.Groups
|
||||
old.LastUsed = lastUsed
|
||||
|
||||
// ConnectorData has been moved to OfflineSession
|
||||
old.ConnectorData = []byte{}
|
||||
return old, nil
|
||||
}
|
||||
|
||||
// Update LastUsed time stamp in refresh token reference object
|
||||
// in offline session for the user.
|
||||
if err := s.storage.UpdateOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
|
||||
if old.Refresh[refresh.ClientID].ID != refresh.ID {
|
||||
return old, errors.New("refresh token invalid")
|
||||
}
|
||||
old.Refresh[refresh.ClientID].LastUsed = lastUsed
|
||||
old.ConnectorData = ident.ConnectorData
|
||||
return old, nil
|
||||
}); err != nil {
|
||||
s.logger.Errorf("failed to update offline session: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Update refresh token in the storage.
|
||||
if err := s.storage.UpdateRefreshToken(refresh.ID, updater); err != nil {
|
||||
s.logger.Errorf("failed to update refresh token: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := s.toAccessTokenResponse(idToken, accessToken, rawNewToken, expiry)
|
||||
s.writeAccessToken(w, resp)
|
||||
}
|
||||
|
||||
func (s *Server) handleUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
const prefix = "Bearer "
|
||||
|
||||
|
@ -1238,10 +1123,17 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli
|
|||
Groups: identity.Groups,
|
||||
}
|
||||
|
||||
accessToken := storage.NewID()
|
||||
accessToken, err := s.newAccessToken(client.ID, claims, scopes, nonce, connID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("password grant failed to create new access token: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, "", connID)
|
||||
if err != nil {
|
||||
s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError)
|
||||
s.logger.Errorf("password grant failed to create new ID token: %v", err)
|
||||
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1322,9 +1214,10 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli
|
|||
return
|
||||
}
|
||||
offlineSessions := storage.OfflineSessions{
|
||||
UserID: refresh.Claims.UserID,
|
||||
ConnID: refresh.ConnectorID,
|
||||
Refresh: make(map[string]*storage.RefreshTokenRef),
|
||||
UserID: refresh.Claims.UserID,
|
||||
ConnID: refresh.ConnectorID,
|
||||
Refresh: make(map[string]*storage.RefreshTokenRef),
|
||||
ConnectorData: identity.ConnectorData,
|
||||
}
|
||||
offlineSessions.Refresh[tokenRef.ClientID] = &tokenRef
|
||||
|
||||
|
@ -1354,6 +1247,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli
|
|||
// Update existing OfflineSession obj with new RefreshTokenRef.
|
||||
if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
|
||||
old.Refresh[tokenRef.ClientID] = &tokenRef
|
||||
old.ConnectorData = identity.ConnectorData
|
||||
return old, nil
|
||||
}); err != nil {
|
||||
s.logger.Errorf("failed to update offline session: %v", err)
|
||||
|
|
|
@ -7,18 +7,18 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gosundheit "github.com/AppsFlyer/go-sundheit"
|
||||
"github.com/AppsFlyer/go-sundheit/checks"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/dexidp/dex/storage"
|
||||
"github.com/dexidp/dex/storage/memory"
|
||||
)
|
||||
|
||||
func TestHandleHealth(t *testing.T) {
|
||||
|
@ -42,16 +42,16 @@ func TestHandleHealthFailure(t *testing.T) {
|
|||
httpServer, server := newTestServer(ctx, t, func(c *Config) {
|
||||
c.HealthChecker = gosundheit.New()
|
||||
|
||||
c.HealthChecker.RegisterCheck(&gosundheit.Config{
|
||||
Check: &checks.CustomCheck{
|
||||
c.HealthChecker.RegisterCheck(
|
||||
&checks.CustomCheck{
|
||||
CheckName: "fail",
|
||||
CheckFunc: func() (details interface{}, err error) {
|
||||
CheckFunc: func(_ context.Context) (details interface{}, err error) {
|
||||
return nil, errors.New("error")
|
||||
},
|
||||
},
|
||||
InitiallyPassing: false,
|
||||
ExecutionPeriod: 1 * time.Second,
|
||||
})
|
||||
gosundheit.InitiallyPassing(false),
|
||||
gosundheit.ExecutionPeriod(1*time.Second),
|
||||
)
|
||||
})
|
||||
defer httpServer.Close()
|
||||
|
||||
|
@ -132,87 +132,6 @@ func TestHandleInvalidSAMLCallbacks(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestConnectorLoginDoesNotAllowToChangeConnectorForAuthRequest(t *testing.T) {
|
||||
memStorage := memory.New(logger)
|
||||
|
||||
templates, err := loadTemplates(webConfig{}, "../web/templates")
|
||||
if err != nil {
|
||||
t.Fatal("failed to load templates")
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
storage: memStorage,
|
||||
logger: logger,
|
||||
templates: templates,
|
||||
supportedResponseTypes: map[string]bool{"code": true},
|
||||
now: time.Now,
|
||||
connectors: make(map[string]Connector),
|
||||
}
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/auth/{connector}", s.handleConnectorLogin)
|
||||
s.mux = r
|
||||
|
||||
clientID := "clientID"
|
||||
clientSecret := "secret"
|
||||
redirectURL := "localhost:5555" + "/callback"
|
||||
client := storage.Client{
|
||||
ID: clientID,
|
||||
Secret: clientSecret,
|
||||
RedirectURIs: []string{redirectURL},
|
||||
}
|
||||
if err := memStorage.CreateClient(client); err != nil {
|
||||
t.Fatal("failed to create client")
|
||||
}
|
||||
|
||||
createConnector := func(t *testing.T, id string) storage.Connector {
|
||||
connector := storage.Connector{
|
||||
ID: id,
|
||||
Type: "mockCallback",
|
||||
Name: "Mock",
|
||||
ResourceVersion: "1",
|
||||
}
|
||||
if err := memStorage.CreateConnector(connector); err != nil {
|
||||
t.Fatalf("failed to create connector %v", id)
|
||||
}
|
||||
|
||||
return connector
|
||||
}
|
||||
|
||||
connector1 := createConnector(t, "mock1")
|
||||
connector2 := createConnector(t, "mock2")
|
||||
|
||||
authReq := storage.AuthRequest{
|
||||
ID: storage.NewID(),
|
||||
}
|
||||
if err := memStorage.CreateAuthRequest(authReq); err != nil {
|
||||
t.Fatal("failed to create auth request")
|
||||
}
|
||||
|
||||
createConnectorLoginRequest := func(connID string) *http.Request {
|
||||
req := httptest.NewRequest("GET", "/auth/"+connID, nil)
|
||||
q := req.URL.Query()
|
||||
q.Add("req", authReq.ID)
|
||||
q.Add("redirect_uri", redirectURL)
|
||||
q.Add("scope", "openid")
|
||||
q.Add("response_type", "code")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
return req
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
s.ServeHTTP(recorder, createConnectorLoginRequest(connector1.ID))
|
||||
if recorder.Code != 302 {
|
||||
t.Fatal("failed to process request")
|
||||
}
|
||||
|
||||
recorder2 := httptest.NewRecorder()
|
||||
s.ServeHTTP(recorder2, createConnectorLoginRequest(connector2.ID))
|
||||
if recorder2.Code != 500 {
|
||||
t.Error("attempt to overwrite connector on auth request should fail")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleAuthCode checks that it is forbidden to use same code twice
|
||||
func TestHandleAuthCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
@ -310,3 +229,84 @@ func TestHandleAuthCode(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mockConnectorDataTestStorage(t *testing.T, s storage.Storage) {
|
||||
c := storage.Client{
|
||||
ID: "test",
|
||||
Secret: "barfoo",
|
||||
RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"},
|
||||
Name: "dex client",
|
||||
LogoURL: "https://goo.gl/JIyzIC",
|
||||
}
|
||||
|
||||
err := s.CreateClient(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
c1 := storage.Connector{
|
||||
ID: "test",
|
||||
Type: "mockPassword",
|
||||
Name: "mockPassword",
|
||||
Config: []byte(`{
|
||||
"username": "test",
|
||||
"password": "test"
|
||||
}`),
|
||||
}
|
||||
|
||||
err = s.CreateConnector(c1)
|
||||
require.NoError(t, err)
|
||||
|
||||
c2 := storage.Connector{
|
||||
ID: "http://any.valid.url/",
|
||||
Type: "mock",
|
||||
Name: "mockURLID",
|
||||
}
|
||||
|
||||
err = s.CreateConnector(c2)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPasswordConnectorDataNotEmpty(t *testing.T) {
|
||||
t0 := time.Now()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Setup a dex server.
|
||||
httpServer, s := newTestServer(ctx, t, func(c *Config) {
|
||||
c.PasswordConnector = "test"
|
||||
c.Now = func() time.Time { return t0 }
|
||||
})
|
||||
defer httpServer.Close()
|
||||
|
||||
mockConnectorDataTestStorage(t, s.storage)
|
||||
|
||||
u, err := url.Parse(s.issuerURL.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
u.Path = path.Join(u.Path, "/token")
|
||||
v := url.Values{}
|
||||
v.Add("scope", "openid offline_access email")
|
||||
v.Add("grant_type", "password")
|
||||
v.Add("username", "test")
|
||||
v.Add("password", "test")
|
||||
|
||||
req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||||
req.SetBasicAuth("test", "barfoo")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
s.ServeHTTP(rr, req)
|
||||
|
||||
require.Equal(t, 200, rr.Code)
|
||||
|
||||
// Check that we received expected refresh token
|
||||
var ref struct {
|
||||
Token string `json:"refresh_token"`
|
||||
}
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &ref)
|
||||
require.NoError(t, err)
|
||||
|
||||
newSess, err := s.storage.GetOfflineSessions("0-385-28089-0", "test")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `{"test": "true"}`, string(newSess.ConnectorData))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package internal
|
|||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Marshal converts a protobuf message to a URL legal string.
|
||||
|
|
|
@ -1,71 +1,79 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.6
|
||||
// source: server/internal/types.proto
|
||||
|
||||
// Package internal holds protobuf types used by the server
|
||||
// Package internal holds protobuf types used by the server.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// RefreshToken is a message that holds refresh token data used by dex.
|
||||
type RefreshToken struct {
|
||||
RefreshId string `protobuf:"bytes,1,opt,name=refresh_id,json=refreshId,proto3" json:"refresh_id,omitempty"`
|
||||
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RefreshId string `protobuf:"bytes,1,opt,name=refresh_id,json=refreshId,proto3" json:"refresh_id,omitempty"`
|
||||
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
|
||||
}
|
||||
|
||||
func (m *RefreshToken) Reset() { *m = RefreshToken{} }
|
||||
func (m *RefreshToken) String() string { return proto.CompactTextString(m) }
|
||||
func (*RefreshToken) ProtoMessage() {}
|
||||
func (x *RefreshToken) Reset() {
|
||||
*x = RefreshToken{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_internal_types_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RefreshToken) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RefreshToken) ProtoMessage() {}
|
||||
|
||||
func (x *RefreshToken) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_internal_types_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RefreshToken.ProtoReflect.Descriptor instead.
|
||||
func (*RefreshToken) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c3164bea0c2d595f, []int{0}
|
||||
return file_server_internal_types_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *RefreshToken) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_RefreshToken.Unmarshal(m, b)
|
||||
}
|
||||
func (m *RefreshToken) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_RefreshToken.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *RefreshToken) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_RefreshToken.Merge(m, src)
|
||||
}
|
||||
func (m *RefreshToken) XXX_Size() int {
|
||||
return xxx_messageInfo_RefreshToken.Size(m)
|
||||
}
|
||||
func (m *RefreshToken) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_RefreshToken.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_RefreshToken proto.InternalMessageInfo
|
||||
|
||||
func (m *RefreshToken) GetRefreshId() string {
|
||||
if m != nil {
|
||||
return m.RefreshId
|
||||
func (x *RefreshToken) GetRefreshId() string {
|
||||
if x != nil {
|
||||
return x.RefreshId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RefreshToken) GetToken() string {
|
||||
if m != nil {
|
||||
return m.Token
|
||||
func (x *RefreshToken) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -73,69 +81,152 @@ func (m *RefreshToken) GetToken() string {
|
|||
// IDTokenSubject represents both the userID and connID which is returned
|
||||
// as the "sub" claim in the ID Token.
|
||||
type IDTokenSubject struct {
|
||||
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
ConnId string `protobuf:"bytes,2,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
ConnId string `protobuf:"bytes,2,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty"`
|
||||
}
|
||||
|
||||
func (m *IDTokenSubject) Reset() { *m = IDTokenSubject{} }
|
||||
func (m *IDTokenSubject) String() string { return proto.CompactTextString(m) }
|
||||
func (*IDTokenSubject) ProtoMessage() {}
|
||||
func (x *IDTokenSubject) Reset() {
|
||||
*x = IDTokenSubject{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_internal_types_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *IDTokenSubject) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IDTokenSubject) ProtoMessage() {}
|
||||
|
||||
func (x *IDTokenSubject) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_internal_types_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IDTokenSubject.ProtoReflect.Descriptor instead.
|
||||
func (*IDTokenSubject) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c3164bea0c2d595f, []int{1}
|
||||
return file_server_internal_types_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (m *IDTokenSubject) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_IDTokenSubject.Unmarshal(m, b)
|
||||
}
|
||||
func (m *IDTokenSubject) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_IDTokenSubject.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *IDTokenSubject) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_IDTokenSubject.Merge(m, src)
|
||||
}
|
||||
func (m *IDTokenSubject) XXX_Size() int {
|
||||
return xxx_messageInfo_IDTokenSubject.Size(m)
|
||||
}
|
||||
func (m *IDTokenSubject) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_IDTokenSubject.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_IDTokenSubject proto.InternalMessageInfo
|
||||
|
||||
func (m *IDTokenSubject) GetUserId() string {
|
||||
if m != nil {
|
||||
return m.UserId
|
||||
func (x *IDTokenSubject) GetUserId() string {
|
||||
if x != nil {
|
||||
return x.UserId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *IDTokenSubject) GetConnId() string {
|
||||
if m != nil {
|
||||
return m.ConnId
|
||||
func (x *IDTokenSubject) GetConnId() string {
|
||||
if x != nil {
|
||||
return x.ConnId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*RefreshToken)(nil), "internal.RefreshToken")
|
||||
proto.RegisterType((*IDTokenSubject)(nil), "internal.IDTokenSubject")
|
||||
var File_server_internal_types_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_server_internal_types_proto_rawDesc = []byte{
|
||||
0x0a, 0x1b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x43, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65,
|
||||
0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x72, 0x65,
|
||||
0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x66,
|
||||
0x72, 0x65, 0x73, 0x68, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x42, 0x0a, 0x0e,
|
||||
0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x17,
|
||||
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x64,
|
||||
0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64,
|
||||
0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("server/internal/types.proto", fileDescriptor_c3164bea0c2d595f) }
|
||||
var (
|
||||
file_server_internal_types_proto_rawDescOnce sync.Once
|
||||
file_server_internal_types_proto_rawDescData = file_server_internal_types_proto_rawDesc
|
||||
)
|
||||
|
||||
var fileDescriptor_c3164bea0c2d595f = []byte{
|
||||
// 157 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2e, 0x4e, 0x2d, 0x2a,
|
||||
0x4b, 0x2d, 0xd2, 0xcf, 0xcc, 0x2b, 0x49, 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2f, 0xa9, 0x2c, 0x48,
|
||||
0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0x89, 0x2a, 0x39, 0x73, 0xf1, 0x04,
|
||||
0xa5, 0xa6, 0x15, 0xa5, 0x16, 0x67, 0x84, 0xe4, 0x67, 0xa7, 0xe6, 0x09, 0xc9, 0x72, 0x71, 0x15,
|
||||
0x41, 0xf8, 0xf1, 0x99, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x9c, 0x50, 0x11, 0xcf,
|
||||
0x14, 0x21, 0x11, 0x2e, 0xd6, 0x12, 0x90, 0x3a, 0x09, 0x26, 0xb0, 0x0c, 0x84, 0xa3, 0xe4, 0xc4,
|
||||
0xc5, 0xe7, 0xe9, 0x02, 0xd6, 0x1f, 0x5c, 0x9a, 0x94, 0x95, 0x9a, 0x5c, 0x22, 0x24, 0xce, 0xc5,
|
||||
0x5e, 0x5a, 0x9c, 0x5a, 0x84, 0x30, 0x83, 0x0d, 0xc4, 0xf5, 0x4c, 0x01, 0x49, 0x24, 0xe7, 0xe7,
|
||||
0xe5, 0x81, 0x24, 0x20, 0x46, 0xb0, 0x81, 0xb8, 0x9e, 0x29, 0x49, 0x6c, 0x60, 0x97, 0x19, 0x03,
|
||||
0x02, 0x00, 0x00, 0xff, 0xff, 0x13, 0xfe, 0x01, 0x37, 0xb8, 0x00, 0x00, 0x00,
|
||||
func file_server_internal_types_proto_rawDescGZIP() []byte {
|
||||
file_server_internal_types_proto_rawDescOnce.Do(func() {
|
||||
file_server_internal_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_server_internal_types_proto_rawDescData)
|
||||
})
|
||||
return file_server_internal_types_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_server_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_server_internal_types_proto_goTypes = []interface{}{
|
||||
(*RefreshToken)(nil), // 0: internal.RefreshToken
|
||||
(*IDTokenSubject)(nil), // 1: internal.IDTokenSubject
|
||||
}
|
||||
var file_server_internal_types_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_server_internal_types_proto_init() }
|
||||
func file_server_internal_types_proto_init() {
|
||||
if File_server_internal_types_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_server_internal_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RefreshToken); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_server_internal_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*IDTokenSubject); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_server_internal_types_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_server_internal_types_proto_goTypes,
|
||||
DependencyIndexes: file_server_internal_types_proto_depIdxs,
|
||||
MessageInfos: file_server_internal_types_proto_msgTypes,
|
||||
}.Build()
|
||||
File_server_internal_types_proto = out.File
|
||||
file_server_internal_types_proto_rawDesc = nil
|
||||
file_server_internal_types_proto_goTypes = nil
|
||||
file_server_internal_types_proto_depIdxs = nil
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
syntax = "proto3";
|
||||
|
||||
// Package internal holds protobuf types used by the server
|
||||
// Package internal holds protobuf types used by the server.
|
||||
package internal;
|
||||
|
||||
option go_package = "github.com/dexidp/dex/server/internal";
|
||||
|
||||
// RefreshToken is a message that holds refresh token data used by dex.
|
||||
message RefreshToken {
|
||||
string refresh_id = 1;
|
||||
|
|
104
server/oauth2.go
104
server/oauth2.go
|
@ -29,32 +29,35 @@ import (
|
|||
|
||||
// TODO(ericchiang): clean this file up and figure out more idiomatic error handling.
|
||||
|
||||
// authErr is an error response to an authorization request.
|
||||
// See: https://tools.ietf.org/html/rfc6749#section-4.1.2.1
|
||||
type authErr struct {
|
||||
|
||||
// displayedAuthErr is an error that should be displayed to the user as a web page
|
||||
type displayedAuthErr struct {
|
||||
Status int
|
||||
Description string
|
||||
}
|
||||
|
||||
func (err *displayedAuthErr) Error() string {
|
||||
return err.Description
|
||||
}
|
||||
|
||||
func newDisplayedErr(status int, format string, a ...interface{}) *displayedAuthErr {
|
||||
return &displayedAuthErr{status, fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
// redirectedAuthErr is an error that should be reported back to the client by 302 redirect
|
||||
type redirectedAuthErr struct {
|
||||
State string
|
||||
RedirectURI string
|
||||
Type string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (err *authErr) Status() int {
|
||||
if err.State == errServerError {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
return http.StatusBadRequest
|
||||
}
|
||||
|
||||
func (err *authErr) Error() string {
|
||||
func (err *redirectedAuthErr) Error() string {
|
||||
return err.Description
|
||||
}
|
||||
|
||||
func (err *authErr) Handle() (http.Handler, bool) {
|
||||
// Didn't get a valid redirect URI.
|
||||
if err.RedirectURI == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (err *redirectedAuthErr) Handler() http.Handler {
|
||||
hf := func(w http.ResponseWriter, r *http.Request) {
|
||||
v := url.Values{}
|
||||
v.Add("state", err.State)
|
||||
|
@ -70,7 +73,7 @@ func (err *authErr) Handle() (http.Handler, bool) {
|
|||
}
|
||||
http.Redirect(w, r, redirectURI, http.StatusSeeOther)
|
||||
}
|
||||
return http.HandlerFunc(hf), true
|
||||
return http.HandlerFunc(hf)
|
||||
}
|
||||
|
||||
func tokenErr(w http.ResponseWriter, typ, description string, statusCode int) error {
|
||||
|
@ -102,7 +105,6 @@ const (
|
|||
errUnsupportedGrantType = "unsupported_grant_type"
|
||||
errInvalidGrant = "invalid_grant"
|
||||
errInvalidClient = "invalid_client"
|
||||
errInvalidConnectorID = "invalid_connector_id"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -126,6 +128,7 @@ const (
|
|||
const (
|
||||
grantTypeAuthorizationCode = "authorization_code"
|
||||
grantTypeRefreshToken = "refresh_token"
|
||||
grantTypeImplicit = "implicit"
|
||||
grantTypePassword = "password"
|
||||
grantTypeDeviceCode = "urn:ietf:params:oauth:grant-type:device_code"
|
||||
)
|
||||
|
@ -408,12 +411,12 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str
|
|||
// parse the initial request from the OAuth2 client.
|
||||
func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthRequest, error) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return nil, &authErr{"", "", errInvalidRequest, "Failed to parse request body."}
|
||||
return nil, newDisplayedErr(http.StatusBadRequest, "Failed to parse request.")
|
||||
}
|
||||
q := r.Form
|
||||
redirectURI, err := url.QueryUnescape(q.Get("redirect_uri"))
|
||||
if err != nil {
|
||||
return nil, &authErr{"", "", errInvalidRequest, "No redirect_uri provided."}
|
||||
return nil, newDisplayedErr(http.StatusBadRequest, "No redirect_uri provided.")
|
||||
}
|
||||
|
||||
clientID := q.Get("client_id")
|
||||
|
@ -428,51 +431,50 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques
|
|||
codeChallengeMethod := q.Get("code_challenge_method")
|
||||
|
||||
if codeChallengeMethod == "" {
|
||||
codeChallengeMethod = CodeChallengeMethodPlain
|
||||
codeChallengeMethod = codeChallengeMethodPlain
|
||||
}
|
||||
|
||||
client, err := s.storage.GetClient(clientID)
|
||||
if err != nil {
|
||||
if err == storage.ErrNotFound {
|
||||
description := fmt.Sprintf("Invalid client_id (%q).", clientID)
|
||||
return nil, &authErr{"", "", errUnauthorizedClient, description}
|
||||
return nil, newDisplayedErr(http.StatusNotFound, "Invalid client_id (%q).", clientID)
|
||||
}
|
||||
s.logger.Errorf("Failed to get client: %v", err)
|
||||
return nil, &authErr{"", "", errServerError, ""}
|
||||
}
|
||||
|
||||
if connectorID != "" {
|
||||
connectors, err := s.storage.ListConnectors()
|
||||
if err != nil {
|
||||
return nil, &authErr{"", "", errServerError, "Unable to retrieve connectors"}
|
||||
}
|
||||
if !validateConnectorID(connectors, connectorID) {
|
||||
return nil, &authErr{"", "", errInvalidRequest, "Invalid ConnectorID"}
|
||||
}
|
||||
return nil, newDisplayedErr(http.StatusInternalServerError, "Database error.")
|
||||
}
|
||||
|
||||
if !validateRedirectURI(client, redirectURI) {
|
||||
description := fmt.Sprintf("Unregistered redirect_uri (%q).", redirectURI)
|
||||
return nil, &authErr{"", "", errInvalidRequest, description}
|
||||
return nil, newDisplayedErr(http.StatusBadRequest, "Unregistered redirect_uri (%q).", redirectURI)
|
||||
}
|
||||
if redirectURI == deviceCallbackURI && client.Public {
|
||||
redirectURI = s.issuerURL.Path + deviceCallbackURI
|
||||
}
|
||||
|
||||
// From here on out, we want to redirect back to the client with an error.
|
||||
newErr := func(typ, format string, a ...interface{}) *authErr {
|
||||
return &authErr{state, redirectURI, typ, fmt.Sprintf(format, a...)}
|
||||
newRedirectedErr := func(typ, format string, a ...interface{}) *redirectedAuthErr {
|
||||
return &redirectedAuthErr{state, redirectURI, typ, fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
if connectorID != "" {
|
||||
connectors, err := s.storage.ListConnectors()
|
||||
if err != nil {
|
||||
s.logger.Errorf("Failed to list connectors: %v", err)
|
||||
return nil, newRedirectedErr(errServerError, "Unable to retrieve connectors")
|
||||
}
|
||||
if !validateConnectorID(connectors, connectorID) {
|
||||
return nil, newRedirectedErr(errInvalidRequest, "Invalid ConnectorID")
|
||||
}
|
||||
}
|
||||
|
||||
// dex doesn't support request parameter and must return request_not_supported error
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#6.1
|
||||
if q.Get("request") != "" {
|
||||
return nil, newErr(errRequestNotSupported, "Server does not support request parameter.")
|
||||
return nil, newRedirectedErr(errRequestNotSupported, "Server does not support request parameter.")
|
||||
}
|
||||
|
||||
if codeChallengeMethod != CodeChallengeMethodS256 && codeChallengeMethod != CodeChallengeMethodPlain {
|
||||
if codeChallengeMethod != codeChallengeMethodS256 && codeChallengeMethod != codeChallengeMethodPlain {
|
||||
description := fmt.Sprintf("Unsupported PKCE challenge method (%q).", codeChallengeMethod)
|
||||
return nil, newErr(errInvalidRequest, description)
|
||||
return nil, newRedirectedErr(errInvalidRequest, description)
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -494,7 +496,7 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques
|
|||
|
||||
isTrusted, err := s.validateCrossClientTrust(clientID, peerID)
|
||||
if err != nil {
|
||||
return nil, newErr(errServerError, "Internal server error.")
|
||||
return nil, newRedirectedErr(errServerError, "Internal server error.")
|
||||
}
|
||||
if !isTrusted {
|
||||
invalidScopes = append(invalidScopes, scope)
|
||||
|
@ -502,13 +504,13 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques
|
|||
}
|
||||
}
|
||||
if !hasOpenIDScope {
|
||||
return nil, newErr(errInvalidScope, `Missing required scope(s) ["openid"].`)
|
||||
return nil, newRedirectedErr(errInvalidScope, `Missing required scope(s) ["openid"].`)
|
||||
}
|
||||
if len(unrecognized) > 0 {
|
||||
return nil, newErr(errInvalidScope, "Unrecognized scope(s) %q", unrecognized)
|
||||
return nil, newRedirectedErr(errInvalidScope, "Unrecognized scope(s) %q", unrecognized)
|
||||
}
|
||||
if len(invalidScopes) > 0 {
|
||||
return nil, newErr(errInvalidScope, "Client can't request scope(s) %q", invalidScopes)
|
||||
return nil, newRedirectedErr(errInvalidScope, "Client can't request scope(s) %q", invalidScopes)
|
||||
}
|
||||
|
||||
var rt struct {
|
||||
|
@ -526,23 +528,23 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques
|
|||
case responseTypeToken:
|
||||
rt.token = true
|
||||
default:
|
||||
return nil, newErr(errInvalidRequest, "Invalid response type %q", responseType)
|
||||
return nil, newRedirectedErr(errInvalidRequest, "Invalid response type %q", responseType)
|
||||
}
|
||||
|
||||
if !s.supportedResponseTypes[responseType] {
|
||||
return nil, newErr(errUnsupportedResponseType, "Unsupported response type %q", responseType)
|
||||
return nil, newRedirectedErr(errUnsupportedResponseType, "Unsupported response type %q", responseType)
|
||||
}
|
||||
}
|
||||
|
||||
if len(responseTypes) == 0 {
|
||||
return nil, newErr(errInvalidRequest, "No response_type provided")
|
||||
return nil, newRedirectedErr(errInvalidRequest, "No response_type provided")
|
||||
}
|
||||
|
||||
if rt.token && !rt.code && !rt.idToken {
|
||||
// "token" can't be provided by its own.
|
||||
//
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#Authentication
|
||||
return nil, newErr(errInvalidRequest, "Response type 'token' must be provided with type 'id_token' and/or 'code'")
|
||||
return nil, newRedirectedErr(errInvalidRequest, "Response type 'token' must be provided with type 'id_token' and/or 'code'")
|
||||
}
|
||||
if !rt.code {
|
||||
// Either "id_token token" or "id_token" has been provided which implies the
|
||||
|
@ -550,13 +552,13 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques
|
|||
//
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest
|
||||
if nonce == "" {
|
||||
return nil, newErr(errInvalidRequest, "Response type 'token' requires a 'nonce' value.")
|
||||
return nil, newRedirectedErr(errInvalidRequest, "Response type 'token' requires a 'nonce' value.")
|
||||
}
|
||||
}
|
||||
if rt.token {
|
||||
if redirectURI == redirectURIOOB {
|
||||
err := fmt.Sprintf("Cannot use response type 'token' with redirect_uri '%s'.", redirectURIOOB)
|
||||
return nil, newErr(errInvalidRequest, err)
|
||||
return nil, newRedirectedErr(errInvalidRequest, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/dexidp/dex/storage"
|
||||
|
@ -27,8 +26,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
|
||||
queryParams map[string]string
|
||||
|
||||
wantErr bool
|
||||
exactError *authErr
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "normal request",
|
||||
|
@ -78,7 +76,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"response_type": "code",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
expectedError: &displayedAuthErr{Status: http.StatusNotFound},
|
||||
},
|
||||
{
|
||||
name: "invalid redirect uri",
|
||||
|
@ -95,7 +93,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"response_type": "code",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
expectedError: &displayedAuthErr{Status: http.StatusBadRequest},
|
||||
},
|
||||
{
|
||||
name: "implicit flow",
|
||||
|
@ -128,7 +126,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"response_type": "code id_token",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
expectedError: &redirectedAuthErr{Type: errUnsupportedResponseType},
|
||||
},
|
||||
{
|
||||
name: "only token response type",
|
||||
|
@ -145,7 +143,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"response_type": "token",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
expectedError: &redirectedAuthErr{Type: errInvalidRequest},
|
||||
},
|
||||
{
|
||||
name: "choose connector_id",
|
||||
|
@ -197,7 +195,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"response_type": "code id_token",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
expectedError: &redirectedAuthErr{Type: errInvalidRequest},
|
||||
},
|
||||
{
|
||||
name: "PKCE code_challenge_method plain",
|
||||
|
@ -269,7 +267,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"code_challenge_method": "invalid_method",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
expectedError: &redirectedAuthErr{Type: errInvalidRequest},
|
||||
},
|
||||
{
|
||||
name: "No response type",
|
||||
|
@ -287,12 +285,7 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
"code_challenge_method": "plain",
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
wantErr: true,
|
||||
exactError: &authErr{
|
||||
RedirectURI: "https://example.com/bar",
|
||||
Type: "invalid_request",
|
||||
Description: "No response_type provided",
|
||||
},
|
||||
expectedError: &redirectedAuthErr{Type: errInvalidRequest},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -321,13 +314,34 @@ func TestParseAuthorizationRequest(t *testing.T) {
|
|||
}
|
||||
|
||||
_, err := server.parseAuthorizationRequest(req)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
if tc.exactError != nil {
|
||||
require.Equal(t, tc.exactError, err)
|
||||
if tc.expectedError == nil {
|
||||
if err != nil {
|
||||
t.Errorf("%s: expected no error", tc.name)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
switch expectedErr := tc.expectedError.(type) {
|
||||
case *redirectedAuthErr:
|
||||
e, ok := err.(*redirectedAuthErr)
|
||||
if !ok {
|
||||
t.Fatalf("%s: expected redirectedAuthErr error", tc.name)
|
||||
}
|
||||
if e.Type != expectedErr.Type {
|
||||
t.Errorf("%s: expected error type %v, got %v", tc.name, expectedErr.Type, e.Type)
|
||||
}
|
||||
if e.RedirectURI != tc.queryParams["redirect_uri"] {
|
||||
t.Errorf("%s: expected error to be returned in redirect to %v", tc.name, tc.queryParams["redirect_uri"])
|
||||
}
|
||||
case *displayedAuthErr:
|
||||
e, ok := err.(*displayedAuthErr)
|
||||
if !ok {
|
||||
t.Fatalf("%s: expected displayedAuthErr error", tc.name)
|
||||
}
|
||||
if e.Status != expectedErr.Status {
|
||||
t.Errorf("%s: expected http status %v, got %v", tc.name, expectedErr.Status, e.Status)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("%s: unsupported error type", tc.name)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
342
server/refreshhandlers.go
Normal file
342
server/refreshhandlers.go
Normal file
|
@ -0,0 +1,342 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
"github.com/dexidp/dex/server/internal"
|
||||
"github.com/dexidp/dex/storage"
|
||||
)
|
||||
|
||||
func contains(arr []string, item string) bool {
|
||||
for _, itemFromArray := range arr {
|
||||
if itemFromArray == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type refreshError struct {
|
||||
msg string
|
||||
code int
|
||||
desc string
|
||||
}
|
||||
|
||||
func newInternalServerError() *refreshError {
|
||||
return &refreshError{msg: errInvalidRequest, desc: "", code: http.StatusInternalServerError}
|
||||
}
|
||||
|
||||
func newBadRequestError(desc string) *refreshError {
|
||||
return &refreshError{msg: errInvalidRequest, desc: desc, code: http.StatusBadRequest}
|
||||
}
|
||||
|
||||
func (s *Server) refreshTokenErrHelper(w http.ResponseWriter, err *refreshError) {
|
||||
s.tokenErrHelper(w, err.msg, err.desc, err.code)
|
||||
}
|
||||
|
||||
func (s *Server) extractRefreshTokenFromRequest(r *http.Request) (*internal.RefreshToken, *refreshError) {
|
||||
code := r.PostFormValue("refresh_token")
|
||||
if code == "" {
|
||||
return nil, newBadRequestError("No refresh token is found in request.")
|
||||
}
|
||||
|
||||
token := new(internal.RefreshToken)
|
||||
if err := internal.Unmarshal(code, token); err != nil {
|
||||
// For backward compatibility, assume the refresh_token is a raw refresh token ID
|
||||
// if it fails to decode.
|
||||
//
|
||||
// Because refresh_token values that aren't unmarshable were generated by servers
|
||||
// that don't have a Token value, we'll still reject any attempts to claim a
|
||||
// refresh_token twice.
|
||||
token = &internal.RefreshToken{RefreshId: code, Token: ""}
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// getRefreshTokenFromStorage checks that refresh token is valid and exists in the storage and gets its info
|
||||
func (s *Server) getRefreshTokenFromStorage(clientID string, token *internal.RefreshToken) (*storage.RefreshToken, *refreshError) {
|
||||
invalidErr := newBadRequestError("Refresh token is invalid or has already been claimed by another client.")
|
||||
|
||||
refresh, err := s.storage.GetRefresh(token.RefreshId)
|
||||
if err != nil {
|
||||
if err != storage.ErrNotFound {
|
||||
s.logger.Errorf("failed to get refresh token: %v", err)
|
||||
return nil, newInternalServerError()
|
||||
}
|
||||
return nil, invalidErr
|
||||
}
|
||||
|
||||
if refresh.ClientID != clientID {
|
||||
s.logger.Errorf("client %s trying to claim token for client %s", clientID, refresh.ClientID)
|
||||
// According to https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 Dex should respond with an
|
||||
// invalid grant error if token has already been claimed by another client.
|
||||
return nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest}
|
||||
}
|
||||
|
||||
if refresh.Token != token.Token {
|
||||
switch {
|
||||
case !s.refreshTokenPolicy.AllowedToReuse(refresh.LastUsed):
|
||||
fallthrough
|
||||
case refresh.ObsoleteToken != token.Token:
|
||||
fallthrough
|
||||
case refresh.ObsoleteToken == "":
|
||||
s.logger.Errorf("refresh token with id %s claimed twice", refresh.ID)
|
||||
return nil, invalidErr
|
||||
}
|
||||
}
|
||||
|
||||
expiredErr := newBadRequestError("Refresh token expired.")
|
||||
if s.refreshTokenPolicy.CompletelyExpired(refresh.CreatedAt) {
|
||||
s.logger.Errorf("refresh token with id %s expired", refresh.ID)
|
||||
return nil, expiredErr
|
||||
}
|
||||
|
||||
if s.refreshTokenPolicy.ExpiredBecauseUnused(refresh.LastUsed) {
|
||||
s.logger.Errorf("refresh token with id %s expired due to inactivity", refresh.ID)
|
||||
return nil, expiredErr
|
||||
}
|
||||
|
||||
return &refresh, nil
|
||||
}
|
||||
|
||||
func (s *Server) getRefreshScopes(r *http.Request, refresh *storage.RefreshToken) ([]string, *refreshError) {
|
||||
// Per the OAuth2 spec, if the client has omitted the scopes, default to the original
|
||||
// authorized scopes.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc6749#section-6
|
||||
scope := r.PostFormValue("scope")
|
||||
|
||||
if scope == "" {
|
||||
return refresh.Scopes, nil
|
||||
}
|
||||
|
||||
requestedScopes := strings.Fields(scope)
|
||||
var unauthorizedScopes []string
|
||||
|
||||
// Per the OAuth2 spec, if the client has omitted the scopes, default to the original
|
||||
// authorized scopes.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc6749#section-6
|
||||
for _, requestScope := range requestedScopes {
|
||||
if !contains(refresh.Scopes, requestScope) {
|
||||
unauthorizedScopes = append(unauthorizedScopes, requestScope)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unauthorizedScopes) > 0 {
|
||||
desc := fmt.Sprintf("Requested scopes contain unauthorized scope(s): %q.", unauthorizedScopes)
|
||||
return nil, newBadRequestError(desc)
|
||||
}
|
||||
|
||||
return requestedScopes, nil
|
||||
}
|
||||
|
||||
func (s *Server) refreshWithConnector(ctx context.Context, token *internal.RefreshToken, refresh *storage.RefreshToken, scopes []string) (connector.Identity, *refreshError) {
|
||||
var connectorData []byte
|
||||
|
||||
session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID)
|
||||
switch {
|
||||
case err != nil:
|
||||
if err != storage.ErrNotFound {
|
||||
s.logger.Errorf("failed to get offline session: %v", err)
|
||||
return connector.Identity{}, newInternalServerError()
|
||||
}
|
||||
case len(refresh.ConnectorData) > 0:
|
||||
// Use the old connector data if it exists, should be deleted once used
|
||||
connectorData = refresh.ConnectorData
|
||||
default:
|
||||
connectorData = session.ConnectorData
|
||||
}
|
||||
|
||||
conn, err := s.getConnector(refresh.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err)
|
||||
return connector.Identity{}, newInternalServerError()
|
||||
}
|
||||
|
||||
ident := connector.Identity{
|
||||
UserID: refresh.Claims.UserID,
|
||||
Username: refresh.Claims.Username,
|
||||
PreferredUsername: refresh.Claims.PreferredUsername,
|
||||
Email: refresh.Claims.Email,
|
||||
EmailVerified: refresh.Claims.EmailVerified,
|
||||
Groups: refresh.Claims.Groups,
|
||||
ConnectorData: connectorData,
|
||||
}
|
||||
|
||||
// user's token was previously updated by a connector and is allowed to reuse
|
||||
// it is excessive to refresh identity in upstream
|
||||
if s.refreshTokenPolicy.AllowedToReuse(refresh.LastUsed) && token.Token == refresh.ObsoleteToken {
|
||||
return ident, nil
|
||||
}
|
||||
|
||||
// Can the connector refresh the identity? If so, attempt to refresh the data
|
||||
// in the connector.
|
||||
//
|
||||
// TODO(ericchiang): We may want a strict mode where connectors that don't implement
|
||||
// this interface can't perform refreshing.
|
||||
if refreshConn, ok := conn.Connector.(connector.RefreshConnector); ok {
|
||||
newIdent, err := refreshConn.Refresh(ctx, parseScopes(scopes), ident)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to refresh identity: %v", err)
|
||||
return connector.Identity{}, newInternalServerError()
|
||||
}
|
||||
ident = newIdent
|
||||
}
|
||||
|
||||
return ident, nil
|
||||
}
|
||||
|
||||
// updateOfflineSession updates offline session in the storage
|
||||
func (s *Server) updateOfflineSession(refresh *storage.RefreshToken, ident connector.Identity, lastUsed time.Time) *refreshError {
|
||||
offlineSessionUpdater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
|
||||
if old.Refresh[refresh.ClientID].ID != refresh.ID {
|
||||
return old, errors.New("refresh token invalid")
|
||||
}
|
||||
old.Refresh[refresh.ClientID].LastUsed = lastUsed
|
||||
old.ConnectorData = ident.ConnectorData
|
||||
return old, nil
|
||||
}
|
||||
|
||||
// Update LastUsed time stamp in refresh token reference object
|
||||
// in offline session for the user.
|
||||
err := s.storage.UpdateOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID, offlineSessionUpdater)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to update offline session: %v", err)
|
||||
return newInternalServerError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateRefreshToken updates refresh token and offline session in the storage
|
||||
func (s *Server) updateRefreshToken(token *internal.RefreshToken, refresh *storage.RefreshToken, ident connector.Identity) (*internal.RefreshToken, *refreshError) {
|
||||
newToken := token
|
||||
if s.refreshTokenPolicy.RotationEnabled() {
|
||||
newToken = &internal.RefreshToken{
|
||||
RefreshId: refresh.ID,
|
||||
Token: storage.NewID(),
|
||||
}
|
||||
}
|
||||
|
||||
lastUsed := s.now()
|
||||
|
||||
refreshTokenUpdater := func(old storage.RefreshToken) (storage.RefreshToken, error) {
|
||||
if s.refreshTokenPolicy.RotationEnabled() {
|
||||
if old.Token != token.Token {
|
||||
if s.refreshTokenPolicy.AllowedToReuse(old.LastUsed) && old.ObsoleteToken == token.Token {
|
||||
newToken.Token = old.Token
|
||||
// Do not update last used time for offline session if token is allowed to be reused
|
||||
lastUsed = old.LastUsed
|
||||
return old, nil
|
||||
}
|
||||
return old, errors.New("refresh token claimed twice")
|
||||
}
|
||||
|
||||
old.ObsoleteToken = old.Token
|
||||
}
|
||||
|
||||
old.Token = newToken.Token
|
||||
// Update the claims of the refresh token.
|
||||
//
|
||||
// UserID intentionally ignored for now.
|
||||
old.Claims.Username = ident.Username
|
||||
old.Claims.PreferredUsername = ident.PreferredUsername
|
||||
old.Claims.Email = ident.Email
|
||||
old.Claims.EmailVerified = ident.EmailVerified
|
||||
old.Claims.Groups = ident.Groups
|
||||
old.LastUsed = lastUsed
|
||||
|
||||
// ConnectorData has been moved to OfflineSession
|
||||
old.ConnectorData = []byte{}
|
||||
return old, nil
|
||||
}
|
||||
|
||||
// Update refresh token in the storage.
|
||||
err := s.storage.UpdateRefreshToken(refresh.ID, refreshTokenUpdater)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to update refresh token: %v", err)
|
||||
return nil, newInternalServerError()
|
||||
}
|
||||
|
||||
rerr := s.updateOfflineSession(refresh, ident, lastUsed)
|
||||
if rerr != nil {
|
||||
return nil, rerr
|
||||
}
|
||||
|
||||
return newToken, nil
|
||||
}
|
||||
|
||||
// handleRefreshToken handles a refresh token request https://tools.ietf.org/html/rfc6749#section-6
|
||||
// this method is the entrypoint for refresh tokens handling
|
||||
func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, client storage.Client) {
|
||||
token, rerr := s.extractRefreshTokenFromRequest(r)
|
||||
if rerr != nil {
|
||||
s.refreshTokenErrHelper(w, rerr)
|
||||
return
|
||||
}
|
||||
|
||||
refresh, rerr := s.getRefreshTokenFromStorage(client.ID, token)
|
||||
if rerr != nil {
|
||||
s.refreshTokenErrHelper(w, rerr)
|
||||
return
|
||||
}
|
||||
|
||||
scopes, rerr := s.getRefreshScopes(r, refresh)
|
||||
if rerr != nil {
|
||||
s.refreshTokenErrHelper(w, rerr)
|
||||
return
|
||||
}
|
||||
|
||||
ident, rerr := s.refreshWithConnector(r.Context(), token, refresh, scopes)
|
||||
if rerr != nil {
|
||||
s.refreshTokenErrHelper(w, rerr)
|
||||
return
|
||||
}
|
||||
|
||||
claims := storage.Claims{
|
||||
UserID: ident.UserID,
|
||||
Username: ident.Username,
|
||||
PreferredUsername: ident.PreferredUsername,
|
||||
Email: ident.Email,
|
||||
EmailVerified: ident.EmailVerified,
|
||||
Groups: ident.Groups,
|
||||
}
|
||||
|
||||
accessToken, err := s.newAccessToken(client.ID, claims, scopes, refresh.Nonce, refresh.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to create new access token: %v", err)
|
||||
s.refreshTokenErrHelper(w, newInternalServerError())
|
||||
return
|
||||
}
|
||||
|
||||
idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, refresh.Nonce, accessToken, "", refresh.ConnectorID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to create ID token: %v", err)
|
||||
s.refreshTokenErrHelper(w, newInternalServerError())
|
||||
return
|
||||
}
|
||||
|
||||
newToken, rerr := s.updateRefreshToken(token, refresh, ident)
|
||||
if rerr != nil {
|
||||
s.refreshTokenErrHelper(w, rerr)
|
||||
return
|
||||
}
|
||||
|
||||
rawNewToken, err := internal.Marshal(newToken)
|
||||
if err != nil {
|
||||
s.logger.Errorf("failed to marshal refresh token: %v", err)
|
||||
s.refreshTokenErrHelper(w, newInternalServerError())
|
||||
return
|
||||
}
|
||||
|
||||
resp := s.toAccessTokenResponse(idToken, accessToken, rawNewToken, expiry)
|
||||
s.writeAccessToken(w, resp)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue