Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
14e8529b68 |
136 changed files with 769 additions and 2084 deletions
|
@ -2,7 +2,6 @@ name: Bug Report
|
||||||
description: "Create a bug report to help us improve ZITADEL. Click [here](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#product-management) to see how we process your issue."
|
description: "Create a bug report to help us improve ZITADEL. Click [here](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#product-management) to see how we process your issue."
|
||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
labels: ["bug"]
|
labels: ["bug"]
|
||||||
type: Bug
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
|
@ -1,7 +1,6 @@
|
||||||
name: 📄 Documentation
|
name: 📄 Documentation
|
||||||
description: Create an issue for missing or wrong documentation.
|
description: Create an issue for missing or wrong documentation.
|
||||||
labels: ["docs"]
|
labels: ["docs"]
|
||||||
type: task
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
|
@ -1,12 +1,11 @@
|
||||||
name: 🛠️ Improvement
|
name: 🛠️ Improvement
|
||||||
description: "Create an new issue for an improvment in ZITADEL"
|
description: "Create an new issue for an improvment in ZITADEL"
|
||||||
labels: ["enhancement"]
|
labels: ["improvement"]
|
||||||
type: enhancement
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this proposal / feature reqeust
|
Thanks for taking the time to fill out this improvement request
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: preflight
|
id: preflight
|
||||||
attributes:
|
attributes:
|
44
.github/ISSUE_TEMPLATE/proposal.yaml
vendored
Normal file
44
.github/ISSUE_TEMPLATE/proposal.yaml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
name: 💡 Proposal / Feature request
|
||||||
|
description: "Create an issue for a feature request/proposal."
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this proposal / feature reqeust
|
||||||
|
- type: checkboxes
|
||||||
|
id: preflight
|
||||||
|
attributes:
|
||||||
|
label: Preflight Checklist
|
||||||
|
options:
|
||||||
|
- label:
|
||||||
|
I could not find a solution in the existing issues, docs, nor discussions
|
||||||
|
required: true
|
||||||
|
- label:
|
||||||
|
I have joined the [ZITADEL chat](https://zitadel.com/chat)
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Describe your problem
|
||||||
|
description: Please describe your problem this proposal / feature is supposed to solve.
|
||||||
|
placeholder: Describe the problem you have.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe your ideal solution
|
||||||
|
description: Which solution do you propose?
|
||||||
|
placeholder: As a [type of user], I want [some goal] so that [some reason].
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Which version of the OIDC Library are you using.
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Please add any other infos that could be useful.
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: add issue
|
- name: add issue
|
||||||
uses: actions/add-to-project@v1.0.2
|
uses: actions/add-to-project@v1
|
||||||
if: ${{ github.event_name == 'issues' }}
|
if: ${{ github.event_name == 'issues' }}
|
||||||
with:
|
with:
|
||||||
# You can target a repository in a different organization
|
# You can target a repository in a different organization
|
||||||
|
@ -28,7 +28,7 @@ jobs:
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||||
- name: add pr
|
- name: add pr
|
||||||
uses: actions/add-to-project@v1.0.2
|
uses: actions/add-to-project@v1
|
||||||
if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}}
|
if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}}
|
||||||
with:
|
with:
|
||||||
# You can target a repository in a different organization
|
# You can target a repository in a different organization
|
|
@ -14,11 +14,11 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go: ['1.23', '1.24']
|
go: ['1.21', '1.22']
|
||||||
name: Go ${{ matrix.go }} test
|
name: Go ${{ matrix.go }} test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -27,12 +27,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
- run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/...
|
- run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/...
|
||||||
- uses: codecov/codecov-action@v5.4.3
|
- uses: codecov/codecov-action@v4.1.1
|
||||||
with:
|
with:
|
||||||
file: ./profile.cov
|
file: ./profile.cov
|
||||||
name: codecov-go
|
name: codecov-go
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-20.04
|
||||||
needs: [test]
|
needs: [test]
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }}
|
if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }}
|
||||||
env:
|
env:
|
113
README.md
113
README.md
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
[](https://github.com/semantic-release/semantic-release)
|
||||||
[](https://github.com/zitadel/oidc/actions)
|
[](https://github.com/zitadel/oidc/actions)
|
||||||
[](https://pkg.go.dev/github.com/zitadel/oidc/v3)
|
[](https://pkg.go.dev/github.com/zitadel/oidc/v4)
|
||||||
[](https://github.com/zitadel/oidc/blob/master/LICENSE)
|
[](https://github.com/zitadel/oidc/blob/master/LICENSE)
|
||||||
[](https://github.com/zitadel/oidc/releases)
|
[](https://github.com/zitadel/oidc/releases)
|
||||||
[](https://goreportcard.com/report/github.com/zitadel/oidc/v3)
|
[](https://goreportcard.com/report/github.com/zitadel/oidc/v4)
|
||||||
[](https://codecov.io/gh/zitadel/oidc)
|
[](https://codecov.io/gh/zitadel/oidc)
|
||||||
|
|
||||||
[](https://openid.net/certification/)
|
[](https://openid.net/certification/)
|
||||||
|
@ -21,10 +21,9 @@ Whenever possible we tried to reuse / extend existing packages like `OAuth2 for
|
||||||
## Basic Overview
|
## Basic Overview
|
||||||
|
|
||||||
The most important packages of the library:
|
The most important packages of the library:
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
/pkg
|
/pkg
|
||||||
/client clients using the OP for retrieving, exchanging and verifying tokens
|
/client clients using the OP for retrieving, exchanging and verifying tokens
|
||||||
/rp definition and implementation of an OIDC Relying Party (client)
|
/rp definition and implementation of an OIDC Relying Party (client)
|
||||||
/rs definition and implementation of an OAuth Resource Server (API)
|
/rs definition and implementation of an OAuth Resource Server (API)
|
||||||
/op definition and implementation of an OIDC OpenID Provider (server)
|
/op definition and implementation of an OIDC OpenID Provider (server)
|
||||||
|
@ -38,6 +37,7 @@ The most important packages of the library:
|
||||||
/server examples of an OpenID Provider implementations (including dynamic) with some very basic login UI
|
/server examples of an OpenID Provider implementations (including dynamic) with some very basic login UI
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
### Semver
|
### Semver
|
||||||
|
|
||||||
This package uses [semver](https://semver.org/) for [releases](https://github.com/zitadel/oidc/releases). Major releases ship breaking changes. Starting with the `v2` to `v3` increment we provide an [upgrade guide](UPGRADING.md) to ease migration to a newer version.
|
This package uses [semver](https://semver.org/) for [releases](https://github.com/zitadel/oidc/releases). Major releases ship breaking changes. Starting with the `v2` to `v3` increment we provide an [upgrade guide](UPGRADING.md) to ease migration to a newer version.
|
||||||
|
@ -49,90 +49,54 @@ Check the `/example` folder where example code for different scenarios is locate
|
||||||
```bash
|
```bash
|
||||||
# start oidc op server
|
# start oidc op server
|
||||||
# oidc discovery http://localhost:9998/.well-known/openid-configuration
|
# oidc discovery http://localhost:9998/.well-known/openid-configuration
|
||||||
go run github.com/zitadel/oidc/v3/example/server
|
go run github.com/zitadel/oidc/v4/example/server
|
||||||
# start oidc web client (in a new terminal)
|
# start oidc web client (in a new terminal)
|
||||||
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app
|
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v4/example/client/app
|
||||||
```
|
```
|
||||||
|
|
||||||
- open http://localhost:9999/login in your browser
|
- open http://localhost:9999/login in your browser
|
||||||
- you will be redirected to op server and the login UI
|
- you will be redirected to op server and the login UI
|
||||||
- login with user `test-user@localhost` and password `verysecure`
|
- login with user `test-user@localhost` and password `verysecure`
|
||||||
- the OP will redirect you to the client app, which displays the user info
|
- the OP will redirect you to the client app, which displays the user info
|
||||||
|
|
||||||
for the dynamic issuer, just start it with:
|
for the dynamic issuer, just start it with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go run github.com/zitadel/oidc/v3/example/server/dynamic
|
go run github.com/zitadel/oidc/v4/example/server/dynamic
|
||||||
```
|
```
|
||||||
|
|
||||||
the oidc web client above will still work, but if you add `oidc.local` (pointing to 127.0.0.1) in your hosts file you can also start it with:
|
the oidc web client above will still work, but if you add `oidc.local` (pointing to 127.0.0.1) in your hosts file you can also start it with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app
|
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v4/example/client/app
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`)
|
> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`)
|
||||||
|
|
||||||
### Server configuration
|
|
||||||
|
|
||||||
Example server allows extra configuration using environment variables and could be used for end to
|
|
||||||
end testing of your services.
|
|
||||||
|
|
||||||
| Name | Format | Description |
|
|
||||||
| ------------ | -------------------------------- | ------------------------------------- |
|
|
||||||
| PORT | Number between 1 and 65535 | OIDC listen port |
|
|
||||||
| REDIRECT_URI | Comma-separated URIs | List of allowed redirect URIs |
|
|
||||||
| USERS_FILE | Path to json in local filesystem | Users with their data and credentials |
|
|
||||||
|
|
||||||
Here is json equivalent for one of the default users
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id2": {
|
|
||||||
"ID": "id2",
|
|
||||||
"Username": "test-user2",
|
|
||||||
"Password": "verysecure",
|
|
||||||
"FirstName": "Test",
|
|
||||||
"LastName": "User2",
|
|
||||||
"Email": "test-user2@zitadel.ch",
|
|
||||||
"EmailVerified": true,
|
|
||||||
"Phone": "",
|
|
||||||
"PhoneVerified": false,
|
|
||||||
"PreferredLanguage": "DE",
|
|
||||||
"IsAdmin": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
| | Relying party | OpenID Provider | Specification |
|
| | Relying party | OpenID Provider | Specification |
|
||||||
| -------------------- | ------------- | --------------- | -------------------------------------------- |
|
| -------------------- | ------------- | --------------- | ----------------------------------------- |
|
||||||
| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] |
|
| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] |
|
||||||
| Implicit Flow | no[^1] | yes | OpenID Connect Core 1.0, [Section 3.2][2] |
|
| Implicit Flow | no[^1] | yes | OpenID Connect Core 1.0, [Section 3.2][2] |
|
||||||
| Hybrid Flow | no | not yet | OpenID Connect Core 1.0, [Section 3.3][3] |
|
| Hybrid Flow | no | not yet | OpenID Connect Core 1.0, [Section 3.3][3] |
|
||||||
| Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] |
|
| Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] |
|
||||||
| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] |
|
| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] |
|
||||||
| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 |
|
| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 |
|
||||||
| JWT Profile | yes | yes | [RFC 7523][7] |
|
| JWT Profile | yes | yes | [RFC 7523][7] |
|
||||||
| PKCE | yes | yes | [RFC 7636][8] |
|
| PKCE | yes | yes | [RFC 7636][8] |
|
||||||
| Token Exchange | yes | yes | [RFC 8693][9] |
|
| Token Exchange | yes | yes | [RFC 8693][9] |
|
||||||
| Device Authorization | yes | yes | [RFC 8628][10] |
|
| Device Authorization | yes | yes | [RFC 8628][10] |
|
||||||
| mTLS | not yet | not yet | [RFC 8705][11] |
|
| mTLS | not yet | not yet | [RFC 8705][11] |
|
||||||
| Back-Channel Logout | not yet | yes | OpenID Connect [Back-Channel Logout][12] 1.0 |
|
|
||||||
|
|
||||||
[1]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth "3.1. Authentication using the Authorization Code Flow"
|
[1]: <https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth> "3.1. Authentication using the Authorization Code Flow"
|
||||||
[2]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth "3.2. Authentication using the Implicit Flow"
|
[2]: <https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth> "3.2. Authentication using the Implicit Flow"
|
||||||
[3]: https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth "3.3. Authentication using the Hybrid Flow"
|
[3]: <https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth> "3.3. Authentication using the Hybrid Flow"
|
||||||
[4]: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication "9. Client Authentication"
|
[4]: <https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication> "9. Client Authentication"
|
||||||
[5]: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens "12. Using Refresh Tokens"
|
[5]: <https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens> "12. Using Refresh Tokens"
|
||||||
[6]: https://openid.net/specs/openid-connect-discovery-1_0.html "OpenID Connect Discovery 1.0 incorporating errata set 1"
|
[6]: <https://openid.net/specs/openid-connect-discovery-1_0.html> "OpenID Connect Discovery 1.0 incorporating errata set 1"
|
||||||
[7]: https://www.rfc-editor.org/rfc/rfc7523.html "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants"
|
[7]: <https://www.rfc-editor.org/rfc/rfc7523.html> "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants"
|
||||||
[8]: https://www.rfc-editor.org/rfc/rfc7636.html "Proof Key for Code Exchange by OAuth Public Clients"
|
[8]: <https://www.rfc-editor.org/rfc/rfc7636.html> "Proof Key for Code Exchange by OAuth Public Clients"
|
||||||
[9]: https://www.rfc-editor.org/rfc/rfc8693.html "OAuth 2.0 Token Exchange"
|
[9]: <https://www.rfc-editor.org/rfc/rfc8693.html> "OAuth 2.0 Token Exchange"
|
||||||
[10]: https://www.rfc-editor.org/rfc/rfc8628.html "OAuth 2.0 Device Authorization Grant"
|
[10]: <https://www.rfc-editor.org/rfc/rfc8628.html> "OAuth 2.0 Device Authorization Grant"
|
||||||
[11]: https://www.rfc-editor.org/rfc/rfc8705.html "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens"
|
[11]: <https://www.rfc-editor.org/rfc/rfc8705.html> "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens"
|
||||||
[12]: https://openid.net/specs/openid-connect-backchannel-1_0.html "OpenID Connect Back-Channel Logout 1.0 incorporating errata set 1"
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
|
@ -151,14 +115,14 @@ For your convenience you can find the relevant guides linked below.
|
||||||
|
|
||||||
## Supported Go Versions
|
## Supported Go Versions
|
||||||
|
|
||||||
For security reasons, we only support and recommend the use of one of the latest two Go versions (:white_check_mark:).
|
For security reasons, we only support and recommend the use of one of the latest two Go versions (:white_check_mark:).
|
||||||
Versions that also build are marked with :warning:.
|
Versions that also build are marked with :warning:.
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| <1.23 | :x: |
|
| <1.21 | :x: |
|
||||||
| 1.23 | :white_check_mark: |
|
| 1.21 | :white_check_mark: |
|
||||||
| 1.24 | :white_check_mark: |
|
| 1.22 | :white_check_mark: |
|
||||||
|
|
||||||
## Why another library
|
## Why another library
|
||||||
|
|
||||||
|
@ -189,4 +153,5 @@ Unless required by applicable law or agreed to in writing, software distributed
|
||||||
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||||
language governing permissions and limitations under the License.
|
language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
[^1]: https://github.com/zitadel/oidc/issues/135#issuecomment-950563892
|
[^1]: https://github.com/zitadel/oidc/issues/135#issuecomment-950563892
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs"
|
"github.com/zitadel/oidc/v4/pkg/client/rs"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -14,10 +14,10 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -56,7 +56,6 @@ func main() {
|
||||||
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
|
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
|
||||||
rp.WithHTTPClient(client),
|
rp.WithHTTPClient(client),
|
||||||
rp.WithLogger(logger),
|
rp.WithLogger(logger),
|
||||||
rp.WithSigningAlgsFromDiscovery(),
|
|
||||||
}
|
}
|
||||||
if clientSecret == "" {
|
if clientSecret == "" {
|
||||||
options = append(options, rp.WithPKCE(cookieHandler))
|
options = append(options, rp.WithPKCE(cookieHandler))
|
||||||
|
@ -109,7 +108,6 @@ func main() {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("content-type", "application/json")
|
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,8 @@ import (
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
githubOAuth "golang.org/x/oauth2/github"
|
githubOAuth "golang.org/x/oauth2/github"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp/cli"
|
"github.com/zitadel/oidc/v4/pkg/client/rp/cli"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
"github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/profile"
|
"github.com/zitadel/oidc/v4/pkg/client/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
var client = http.DefaultClient
|
var client = http.DefaultClient
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// default port for the http server to run
|
|
||||||
DefaultIssuerPort = "9998"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Port string
|
|
||||||
RedirectURI []string
|
|
||||||
UsersFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromEnvVars loads configuration parameters from environment variables.
|
|
||||||
// If there is no such variable defined, then use default values.
|
|
||||||
func FromEnvVars(defaults *Config) *Config {
|
|
||||||
if defaults == nil {
|
|
||||||
defaults = &Config{}
|
|
||||||
}
|
|
||||||
cfg := &Config{
|
|
||||||
Port: defaults.Port,
|
|
||||||
RedirectURI: defaults.RedirectURI,
|
|
||||||
UsersFile: defaults.UsersFile,
|
|
||||||
}
|
|
||||||
if value, ok := os.LookupEnv("PORT"); ok {
|
|
||||||
cfg.Port = value
|
|
||||||
}
|
|
||||||
if value, ok := os.LookupEnv("USERS_FILE"); ok {
|
|
||||||
cfg.UsersFile = value
|
|
||||||
}
|
|
||||||
if value, ok := os.LookupEnv("REDIRECT_URI"); ok {
|
|
||||||
cfg.RedirectURI = strings.Split(value, ",")
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFromEnvVars(t *testing.T) {
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
env map[string]string
|
|
||||||
defaults *Config
|
|
||||||
want *Config
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no vars, no default values",
|
|
||||||
env: map[string]string{},
|
|
||||||
want: &Config{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no vars, only defaults",
|
|
||||||
env: map[string]string{},
|
|
||||||
defaults: &Config{
|
|
||||||
Port: "6666",
|
|
||||||
UsersFile: "/default/user/path",
|
|
||||||
RedirectURI: []string{"re", "direct", "uris"},
|
|
||||||
},
|
|
||||||
want: &Config{
|
|
||||||
Port: "6666",
|
|
||||||
UsersFile: "/default/user/path",
|
|
||||||
RedirectURI: []string{"re", "direct", "uris"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "overriding default values",
|
|
||||||
env: map[string]string{
|
|
||||||
"PORT": "1234",
|
|
||||||
"USERS_FILE": "/path/to/users",
|
|
||||||
"REDIRECT_URI": "http://redirect/redirect",
|
|
||||||
},
|
|
||||||
defaults: &Config{
|
|
||||||
Port: "6666",
|
|
||||||
UsersFile: "/default/user/path",
|
|
||||||
RedirectURI: []string{"re", "direct", "uris"},
|
|
||||||
},
|
|
||||||
want: &Config{
|
|
||||||
Port: "1234",
|
|
||||||
UsersFile: "/path/to/users",
|
|
||||||
RedirectURI: []string{"http://redirect/redirect"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple redirect uris",
|
|
||||||
env: map[string]string{
|
|
||||||
"REDIRECT_URI": "http://host_1,http://host_2,http://host_3",
|
|
||||||
},
|
|
||||||
want: &Config{
|
|
||||||
RedirectURI: []string{
|
|
||||||
"http://host_1", "http://host_2", "http://host_3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
os.Clearenv()
|
|
||||||
for k, v := range tc.env {
|
|
||||||
os.Setenv(k, v)
|
|
||||||
}
|
|
||||||
cfg := FromEnvVars(tc.defaults)
|
|
||||||
if fmt.Sprint(cfg) != fmt.Sprint(tc.want) {
|
|
||||||
t.Errorf("Expected FromEnvVars()=%q, but got %q", tc.want, cfg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
|
"github.com/zitadel/oidc/v4/example/server/storage"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
type deviceAuthenticate interface {
|
type deviceAuthenticate interface {
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
type login struct {
|
type login struct {
|
||||||
|
|
|
@ -12,13 +12,22 @@ import (
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/example/server/storage"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
pathLoggedOut = "/logged-out"
|
pathLoggedOut = "/logged-out"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
storage.RegisterClients(
|
||||||
|
storage.NativeClient("native"),
|
||||||
|
storage.WebClient("web", "secret"),
|
||||||
|
storage.WebClient("api", "secret"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
op.Storage
|
op.Storage
|
||||||
authenticate
|
authenticate
|
||||||
|
@ -47,7 +56,7 @@ func SetupServer(issuer string, storage Storage, logger *slog.Logger, wrapServer
|
||||||
// for simplicity, we provide a very small default page for users who have signed out
|
// for simplicity, we provide a very small default page for users who have signed out
|
||||||
router.HandleFunc(pathLoggedOut, func(w http.ResponseWriter, req *http.Request) {
|
router.HandleFunc(pathLoggedOut, func(w http.ResponseWriter, req *http.Request) {
|
||||||
w.Write([]byte("signed out successfully"))
|
w.Write([]byte("signed out successfully"))
|
||||||
// no need to check/log error, this will be handled by the middleware.
|
// no need to check/log error, this will be handeled by the middleware.
|
||||||
})
|
})
|
||||||
|
|
||||||
// creation of the OpenIDProvider with the just created in-memory Storage
|
// creation of the OpenIDProvider with the just created in-memory Storage
|
||||||
|
@ -71,7 +80,7 @@ func SetupServer(issuer string, storage Storage, logger *slog.Logger, wrapServer
|
||||||
|
|
||||||
handler := http.Handler(provider)
|
handler := http.Handler(provider)
|
||||||
if wrapServer {
|
if wrapServer {
|
||||||
handler = op.RegisterLegacyServer(op.NewLegacyServer(provider, *op.DefaultEndpoints), op.AuthorizeCallbackHandler(provider))
|
handler = op.RegisterLegacyServer(op.NewLegacyServer(provider, *op.DefaultEndpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
// we register the http handler of the OP on the root, so that the discovery endpoint (/.well-known/openid-configuration)
|
// we register the http handler of the OP on the root, so that the discovery endpoint (/.well-known/openid-configuration)
|
||||||
|
|
|
@ -6,53 +6,36 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/config"
|
"github.com/zitadel/oidc/v4/example/server/exampleop"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop"
|
"github.com/zitadel/oidc/v4/example/server/storage"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getUserStore(cfg *config.Config) (storage.UserStore, error) {
|
|
||||||
if cfg.UsersFile == "" {
|
|
||||||
return storage.NewUserStore(fmt.Sprintf("http://localhost:%s/", cfg.Port)), nil
|
|
||||||
}
|
|
||||||
return storage.StoreFromFile(cfg.UsersFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg := config.FromEnvVars(&config.Config{Port: "9998"})
|
//we will run on :9998
|
||||||
|
port := "9998"
|
||||||
|
//which gives us the issuer: http://localhost:9998/
|
||||||
|
issuer := fmt.Sprintf("http://localhost:%s/", port)
|
||||||
|
|
||||||
|
// the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations
|
||||||
|
// this might be the layer for accessing your database
|
||||||
|
// in this example it will be handled in-memory
|
||||||
|
storage := storage.NewStorage(storage.NewUserStore(issuer))
|
||||||
|
|
||||||
logger := slog.New(
|
logger := slog.New(
|
||||||
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
AddSource: true,
|
AddSource: true,
|
||||||
Level: slog.LevelDebug,
|
Level: slog.LevelDebug,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
//which gives us the issuer: http://localhost:9998/
|
|
||||||
issuer := fmt.Sprintf("http://localhost:%s/", cfg.Port)
|
|
||||||
|
|
||||||
storage.RegisterClients(
|
|
||||||
storage.NativeClient("native", cfg.RedirectURI...),
|
|
||||||
storage.WebClient("web", "secret", cfg.RedirectURI...),
|
|
||||||
storage.WebClient("api", "secret", cfg.RedirectURI...),
|
|
||||||
)
|
|
||||||
|
|
||||||
// the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations
|
|
||||||
// this might be the layer for accessing your database
|
|
||||||
// in this example it will be handled in-memory
|
|
||||||
store, err := getUserStore(cfg)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("cannot create UserStore", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
storage := storage.NewStorage(store)
|
|
||||||
router := exampleop.SetupServer(issuer, storage, logger, false)
|
router := exampleop.SetupServer(issuer, storage, logger, false)
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ":" + cfg.Port,
|
Addr: ":" + port,
|
||||||
Handler: router,
|
Handler: router,
|
||||||
}
|
}
|
||||||
logger.Info("server listening, press ctrl+c to stop", "addr", issuer)
|
logger.Info("server listening, press ctrl+c to stop", "addr", fmt.Sprintf("http://localhost:%s/", port))
|
||||||
if server.ListenAndServe() != http.ErrServerClosed {
|
err := server.ListenAndServe()
|
||||||
|
if err != http.ErrServerClosed {
|
||||||
logger.Error("server terminated", "error", err)
|
logger.Error("server terminated", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package storage
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -121,7 +121,7 @@ func (a *AuthRequest) Done() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string {
|
func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string {
|
||||||
prompts := make([]string, 0, len(oidcPrompt))
|
prompts := make([]string, len(oidcPrompt))
|
||||||
for _, oidcPrompt := range oidcPrompt {
|
for _, oidcPrompt := range oidcPrompt {
|
||||||
switch oidcPrompt {
|
switch oidcPrompt {
|
||||||
case oidc.PromptNone,
|
case oidc.PromptNone,
|
||||||
|
@ -164,15 +164,6 @@ func authRequestToInternal(authReq *oidc.AuthRequest, userID string) *AuthReques
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthRequestWithSessionState struct {
|
|
||||||
*AuthRequest
|
|
||||||
SessionState string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AuthRequestWithSessionState) GetSessionState() string {
|
|
||||||
return a.SessionState
|
|
||||||
}
|
|
||||||
|
|
||||||
type OIDCCodeChallenge struct {
|
type OIDCCodeChallenge struct {
|
||||||
Challenge string
|
Challenge string
|
||||||
Method string
|
Method string
|
||||||
|
|
|
@ -11,11 +11,11 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
|
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
|
||||||
|
@ -151,9 +151,6 @@ func (s *Storage) CheckUsernamePassword(username, password, id string) error {
|
||||||
// in this example we'll simply check the username / password and set a boolean to true
|
// in this example we'll simply check the username / password and set a boolean to true
|
||||||
// therefore we will also just check this boolean if the request / login has been finished
|
// therefore we will also just check this boolean if the request / login has been finished
|
||||||
request.done = true
|
request.done = true
|
||||||
|
|
||||||
request.authTime = time.Now()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("username or password wrong")
|
return fmt.Errorf("username or password wrong")
|
||||||
|
@ -298,19 +295,15 @@ func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.T
|
||||||
|
|
||||||
// if we get here, the currentRefreshToken was not empty, so the call is a refresh token request
|
// if we get here, the currentRefreshToken was not empty, so the call is a refresh token request
|
||||||
// we therefore will have to check the currentRefreshToken and renew the refresh token
|
// we therefore will have to check the currentRefreshToken and renew the refresh token
|
||||||
|
refreshToken, refreshTokenID, err := s.renewRefreshToken(currentRefreshToken)
|
||||||
newRefreshToken = uuid.NewString()
|
|
||||||
|
|
||||||
accessToken, err := s.accessToken(applicationID, newRefreshToken, request.GetSubject(), request.GetAudience(), request.GetScopes())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", time.Time{}, err
|
return "", "", time.Time{}, err
|
||||||
}
|
}
|
||||||
|
accessToken, err := s.accessToken(applicationID, refreshTokenID, request.GetSubject(), request.GetAudience(), request.GetScopes())
|
||||||
if err := s.renewRefreshToken(currentRefreshToken, newRefreshToken, accessToken.ID); err != nil {
|
if err != nil {
|
||||||
return "", "", time.Time{}, err
|
return "", "", time.Time{}, err
|
||||||
}
|
}
|
||||||
|
return accessToken.ID, refreshToken, accessToken.Expiration, nil
|
||||||
return accessToken.ID, newRefreshToken, accessToken.Expiration, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) exchangeRefreshToken(ctx context.Context, request op.TokenExchangeRequest) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) {
|
func (s *Storage) exchangeRefreshToken(ctx context.Context, request op.TokenExchangeRequest) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) {
|
||||||
|
@ -392,9 +385,14 @@ func (s *Storage) RevokeToken(ctx context.Context, tokenIDOrToken string, userID
|
||||||
if refreshToken.ApplicationID != clientID {
|
if refreshToken.ApplicationID != clientID {
|
||||||
return oidc.ErrInvalidClient().WithDescription("token was not issued for this client")
|
return oidc.ErrInvalidClient().WithDescription("token was not issued for this client")
|
||||||
}
|
}
|
||||||
delete(s.refreshTokens, refreshToken.ID)
|
|
||||||
// if it is a refresh token, you will have to remove the access token as well
|
// if it is a refresh token, you will have to remove the access token as well
|
||||||
delete(s.tokens, refreshToken.AccessToken)
|
delete(s.refreshTokens, refreshToken.ID)
|
||||||
|
for _, accessToken := range s.tokens {
|
||||||
|
if accessToken.RefreshTokenID == refreshToken.ID {
|
||||||
|
delete(s.tokens, accessToken.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,9 +488,6 @@ func (s *Storage) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserI
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
if token.Expiration.Before(time.Now()) {
|
|
||||||
return fmt.Errorf("token is expired")
|
|
||||||
}
|
|
||||||
return s.setUserinfo(ctx, userinfo, token.Subject, token.ApplicationID, token.Scopes)
|
return s.setUserinfo(ctx, userinfo, token.Subject, token.ApplicationID, token.Scopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,41 +594,33 @@ func (s *Storage) createRefreshToken(accessToken *Token, amr []string, authTime
|
||||||
Audience: accessToken.Audience,
|
Audience: accessToken.Audience,
|
||||||
Expiration: time.Now().Add(5 * time.Hour),
|
Expiration: time.Now().Add(5 * time.Hour),
|
||||||
Scopes: accessToken.Scopes,
|
Scopes: accessToken.Scopes,
|
||||||
AccessToken: accessToken.ID,
|
|
||||||
}
|
}
|
||||||
s.refreshTokens[token.ID] = token
|
s.refreshTokens[token.ID] = token
|
||||||
return token.Token, nil
|
return token.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// renewRefreshToken checks the provided refresh_token and creates a new one based on the current
|
// renewRefreshToken checks the provided refresh_token and creates a new one based on the current
|
||||||
//
|
func (s *Storage) renewRefreshToken(currentRefreshToken string) (string, string, error) {
|
||||||
// [Refresh Token Rotation] is implemented.
|
|
||||||
//
|
|
||||||
// [Refresh Token Rotation]: https://www.rfc-editor.org/rfc/rfc6819#section-5.2.2.3
|
|
||||||
func (s *Storage) renewRefreshToken(currentRefreshToken, newRefreshToken, newAccessToken string) error {
|
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
refreshToken, ok := s.refreshTokens[currentRefreshToken]
|
refreshToken, ok := s.refreshTokens[currentRefreshToken]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid refresh token")
|
return "", "", fmt.Errorf("invalid refresh token")
|
||||||
}
|
}
|
||||||
// deletes the refresh token
|
// deletes the refresh token and all access tokens which were issued based on this refresh token
|
||||||
delete(s.refreshTokens, currentRefreshToken)
|
delete(s.refreshTokens, currentRefreshToken)
|
||||||
|
for _, token := range s.tokens {
|
||||||
// delete the access token which was issued based on this refresh token
|
if token.RefreshTokenID == currentRefreshToken {
|
||||||
delete(s.tokens, refreshToken.AccessToken)
|
delete(s.tokens, token.ID)
|
||||||
|
break
|
||||||
if refreshToken.Expiration.Before(time.Now()) {
|
}
|
||||||
return fmt.Errorf("expired refresh token")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new refresh token based on the current one
|
// creates a new refresh token based on the current one
|
||||||
refreshToken.Token = newRefreshToken
|
token := uuid.NewString()
|
||||||
refreshToken.ID = newRefreshToken
|
refreshToken.Token = token
|
||||||
refreshToken.Expiration = time.Now().Add(5 * time.Hour)
|
refreshToken.ID = token
|
||||||
refreshToken.AccessToken = newAccessToken
|
s.refreshTokens[token] = refreshToken
|
||||||
s.refreshTokens[newRefreshToken] = refreshToken
|
return token, refreshToken.ID, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// accessToken will store an access_token in-memory based on the provided information
|
// accessToken will store an access_token in-memory based on the provided information
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
type multiStorage struct {
|
type multiStorage struct {
|
||||||
|
|
|
@ -22,5 +22,4 @@ type RefreshToken struct {
|
||||||
ApplicationID string
|
ApplicationID string
|
||||||
Expiration time.Time
|
Expiration time.Time
|
||||||
Scopes []string
|
Scopes []string
|
||||||
AccessToken string // Token.ID
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
@ -37,18 +35,6 @@ type userStore struct {
|
||||||
users map[string]*User
|
users map[string]*User
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoreFromFile(path string) (UserStore, error) {
|
|
||||||
users := map[string]*User{}
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &users); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return userStore{users}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserStore(issuer string) UserStore {
|
func NewUserStore(issuer string) UserStore {
|
||||||
hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0]
|
hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0]
|
||||||
return userStore{
|
return userStore{
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStoreFromFile(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
pathToFile string
|
|
||||||
content string
|
|
||||||
want UserStore
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "normal user file",
|
|
||||||
pathToFile: "userfile.json",
|
|
||||||
content: `{
|
|
||||||
"id1": {
|
|
||||||
"ID": "id1",
|
|
||||||
"EmailVerified": true,
|
|
||||||
"PreferredLanguage": "DE"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
want: userStore{map[string]*User{
|
|
||||||
"id1": {
|
|
||||||
ID: "id1",
|
|
||||||
EmailVerified: true,
|
|
||||||
PreferredLanguage: language.German,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "malformed file",
|
|
||||||
pathToFile: "whatever",
|
|
||||||
content: "not a json just a text",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not existing file",
|
|
||||||
pathToFile: "what/ever/file",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
actualPath := path.Join(t.TempDir(), tc.pathToFile)
|
|
||||||
|
|
||||||
if tc.content != "" && tc.pathToFile != "" {
|
|
||||||
if err := os.WriteFile(actualPath, []byte(tc.content), 0666); err != nil {
|
|
||||||
t.Fatalf("cannot create file with test content: %q", tc.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err := StoreFromFile(actualPath)
|
|
||||||
if err != nil && !tc.wantErr {
|
|
||||||
t.Errorf("StoreFromFile(%q) returned unexpected error %q", tc.pathToFile, err)
|
|
||||||
} else if err == nil && tc.wantErr {
|
|
||||||
t.Errorf("StoreFromFile(%q) did not return an expected error", tc.pathToFile)
|
|
||||||
}
|
|
||||||
if !tc.wantErr && !reflect.DeepEqual(tc.want, result.(userStore)) {
|
|
||||||
t.Errorf("expected StoreFromFile(%q) = %v, but got %v",
|
|
||||||
tc.pathToFile, tc.want, result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
43
go.mod
43
go.mod
|
@ -1,40 +1,41 @@
|
||||||
module git.christmann.info/LARA/zitadel-oidc/v3
|
module github.com/zitadel/oidc/v4
|
||||||
|
|
||||||
go 1.23.7
|
go 1.21
|
||||||
|
|
||||||
toolchain go1.24.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bmatcuk/doublestar/v4 v4.8.1
|
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||||
github.com/go-chi/chi/v5 v5.2.1
|
github.com/go-chi/chi/v5 v5.0.12
|
||||||
github.com/go-jose/go-jose/v4 v4.0.5
|
github.com/go-jose/go-jose/v3 v3.0.3
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/go-github/v31 v31.0.0
|
github.com/google/go-github/v31 v31.0.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/securecookie v1.1.2
|
github.com/gorilla/securecookie v1.1.2
|
||||||
github.com/jeremija/gosubmit v0.2.8
|
github.com/jeremija/gosubmit v0.2.7
|
||||||
github.com/muhlemmer/gu v0.3.1
|
github.com/muhlemmer/gu v0.3.1
|
||||||
github.com/muhlemmer/httpforwarded v0.1.0
|
github.com/muhlemmer/httpforwarded v0.1.0
|
||||||
github.com/rs/cors v1.11.1
|
github.com/rs/cors v1.10.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/zitadel/logging v0.6.2
|
github.com/zitadel/logging v0.6.0
|
||||||
github.com/zitadel/schema v1.3.1
|
github.com/zitadel/schema v1.3.0
|
||||||
go.opentelemetry.io/otel v1.29.0
|
go.opentelemetry.io/otel v1.24.0
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.18.0
|
||||||
golang.org/x/text v0.26.0
|
golang.org/x/text v0.14.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
golang.org/x/net v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
112
go.sum
112
go.sum
|
@ -1,21 +1,27 @@
|
||||||
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/go-cmp v0.5.2/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo=
|
github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo=
|
||||||
|
@ -29,8 +35,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
|
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
|
||||||
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
@ -41,68 +47,102 @@ github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/
|
||||||
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
|
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
|
||||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
|
github.com/zitadel/logging v0.6.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank=
|
||||||
github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU=
|
github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow=
|
||||||
github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU=
|
github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
|
||||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
|
||||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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-20190404232315-eb5bcb51f2a3/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-20190620200207-3b0461eec859/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-20200202094626-16171245cfb2/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/text v0.3.0/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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
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/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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
|
tu "github.com/zitadel/oidc/v4/internal/testutil"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var custom = map[string]any{
|
var custom = map[string]any{
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
|
||||||
"github.com/muhlemmer/gu"
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeySet implements oidc.Keys
|
// KeySet implements oidc.Keys
|
||||||
|
|
|
@ -10,14 +10,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/crypto"
|
||||||
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -42,7 +41,7 @@ func Discover(ctx context.Context, issuer string, httpClient *http.Client, wellK
|
||||||
discoveryConfig := new(oidc.DiscoveryConfiguration)
|
discoveryConfig := new(oidc.DiscoveryConfiguration)
|
||||||
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
|
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(oidc.ErrDiscoveryFailed, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if logger, ok := logging.FromContext(ctx); ok {
|
if logger, ok := logging.FromContext(ctx); ok {
|
||||||
logger.Debug("discover", "config", discoveryConfig)
|
logger.Debug("discover", "config", discoveryConfig)
|
||||||
|
@ -98,12 +97,7 @@ func CallEndSessionEndpoint(ctx context.Context, request any, authFn any, caller
|
||||||
ctx, span := Tracer.Start(ctx, "CallEndSessionEndpoint")
|
ctx, span := Tracer.Start(ctx, "CallEndSessionEndpoint")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
endpoint := caller.GetEndSessionEndpoint()
|
req, err := httphelper.FormRequest(ctx, caller.GetEndSessionEndpoint(), request, Encoder, authFn)
|
||||||
if endpoint == "" {
|
|
||||||
return nil, fmt.Errorf("end session %w", ErrEndpointNotSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := httphelper.FormRequest(ctx, endpoint, request, Encoder, authFn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -149,12 +143,7 @@ func CallRevokeEndpoint(ctx context.Context, request any, authFn any, caller Rev
|
||||||
ctx, span := Tracer.Start(ctx, "CallRevokeEndpoint")
|
ctx, span := Tracer.Start(ctx, "CallRevokeEndpoint")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
endpoint := caller.GetRevokeEndpoint()
|
req, err := httphelper.FormRequest(ctx, caller.GetRevokeEndpoint(), request, Encoder, authFn)
|
||||||
if endpoint == "" {
|
|
||||||
return fmt.Errorf("revoke %w", ErrEndpointNotSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := httphelper.FormRequest(ctx, endpoint, request, Encoder, authFn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -197,12 +186,12 @@ func CallTokenExchangeEndpoint(ctx context.Context, request any, authFn any, cal
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSignerFromPrivateKeyByte(key []byte, keyID string) (jose.Signer, error) {
|
func NewSignerFromPrivateKeyByte(key []byte, keyID string) (jose.Signer, error) {
|
||||||
privateKey, algorithm, err := crypto.BytesToPrivateKey(key)
|
privateKey, err := crypto.BytesToPrivateKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
signingKey := jose.SigningKey{
|
signingKey := jose.SigningKey{
|
||||||
Algorithm: algorithm,
|
Algorithm: jose.RS256,
|
||||||
Key: &jose.JSONWebKey{Key: privateKey, KeyID: keyID},
|
Key: &jose.JSONWebKey{Key: privateKey, KeyID: keyID},
|
||||||
}
|
}
|
||||||
return jose.NewSigner(signingKey, &jose.SignerOptions{})
|
return jose.NewSigner(signingKey, &jose.SignerOptions{})
|
||||||
|
@ -229,12 +218,7 @@ func CallDeviceAuthorizationEndpoint(ctx context.Context, request *oidc.ClientCr
|
||||||
ctx, span := Tracer.Start(ctx, "CallDeviceAuthorizationEndpoint")
|
ctx, span := Tracer.Start(ctx, "CallDeviceAuthorizationEndpoint")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
endpoint := caller.GetDeviceAuthorizationEndpoint()
|
req, err := httphelper.FormRequest(ctx, caller.GetDeviceAuthorizationEndpoint(), request, Encoder, authFn)
|
||||||
if endpoint == "" {
|
|
||||||
return nil, fmt.Errorf("device authorization %w", ErrEndpointNotSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := httphelper.FormRequest(ctx, endpoint, request, Encoder, authFn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +22,7 @@ func TestDiscover(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
wantFields *wantFields
|
wantFields *wantFields
|
||||||
wantErr error
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "spotify", // https://github.com/zitadel/oidc/issues/406
|
name: "spotify", // https://github.com/zitadel/oidc/issues/406
|
||||||
|
@ -33,20 +32,17 @@ func TestDiscover(t *testing.T) {
|
||||||
wantFields: &wantFields{
|
wantFields: &wantFields{
|
||||||
UILocalesSupported: true,
|
UILocalesSupported: true,
|
||||||
},
|
},
|
||||||
wantErr: nil,
|
wantErr: false,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "discovery failed",
|
|
||||||
args: args{
|
|
||||||
issuer: "https://example.com",
|
|
||||||
},
|
|
||||||
wantErr: oidc.ErrDiscoveryFailed,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := Discover(context.Background(), tt.args.issuer, http.DefaultClient, tt.args.wellKnownUrl...)
|
got, err := Discover(context.Background(), tt.args.issuer, http.DefaultClient, tt.args.wellKnownUrl...)
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
if tt.wantFields == nil {
|
if tt.wantFields == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var ErrEndpointNotSet = errors.New("endpoint not set")
|
|
|
@ -23,14 +23,14 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop"
|
"github.com/zitadel/oidc/v4/example/server/exampleop"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
|
"github.com/zitadel/oidc/v4/example/server/storage"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs"
|
"github.com/zitadel/oidc/v4/pkg/client/rs"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/tokenexchange"
|
"github.com/zitadel/oidc/v4/pkg/client/tokenexchange"
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Logger = slog.New(
|
var Logger = slog.New(
|
||||||
|
@ -111,92 +111,6 @@ func testRelyingPartySession(t *testing.T, wrapServer bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRelyingPartyWithSigningAlgsFromDiscovery(t *testing.T) {
|
|
||||||
targetURL := "http://local-site"
|
|
||||||
localURL, err := url.Parse(targetURL + "/login?requestID=1234")
|
|
||||||
require.NoError(t, err, "local url")
|
|
||||||
|
|
||||||
t.Log("------- start example OP ------")
|
|
||||||
seed := rand.New(rand.NewSource(int64(os.Getpid()) + time.Now().UnixNano()))
|
|
||||||
clientID := t.Name() + "-" + strconv.FormatInt(seed.Int63(), 25)
|
|
||||||
clientSecret := "secret"
|
|
||||||
client := storage.WebClient(clientID, clientSecret, targetURL)
|
|
||||||
storage.RegisterClients(client)
|
|
||||||
exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL))
|
|
||||||
var dh deferredHandler
|
|
||||||
opServer := httptest.NewServer(&dh)
|
|
||||||
defer opServer.Close()
|
|
||||||
dh.Handler = exampleop.SetupServer(opServer.URL, exampleStorage, Logger, true)
|
|
||||||
|
|
||||||
t.Log("------- create RP ------")
|
|
||||||
provider, err := rp.NewRelyingPartyOIDC(
|
|
||||||
CTX,
|
|
||||||
opServer.URL,
|
|
||||||
clientID,
|
|
||||||
clientSecret,
|
|
||||||
targetURL,
|
|
||||||
[]string{"openid"},
|
|
||||||
rp.WithSigningAlgsFromDiscovery(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err, "new rp")
|
|
||||||
|
|
||||||
t.Log("------- run authorization code flow ------")
|
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
require.NoError(t, err, "create cookie jar")
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Timeout: time.Second * 5,
|
|
||||||
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
},
|
|
||||||
Jar: jar,
|
|
||||||
}
|
|
||||||
state := "state-" + strconv.FormatInt(seed.Int63(), 25)
|
|
||||||
capturedW := httptest.NewRecorder()
|
|
||||||
get := httptest.NewRequest("GET", localURL.String(), nil)
|
|
||||||
rp.AuthURLHandler(func() string { return state }, provider,
|
|
||||||
rp.WithPromptURLParam("Hello, World!", "Goodbye, World!"),
|
|
||||||
rp.WithURLParam("custom", "param"),
|
|
||||||
)(capturedW, get)
|
|
||||||
defer func() {
|
|
||||||
if t.Failed() {
|
|
||||||
t.Log("response body (redirect from RP to OP)", capturedW.Body.String())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
resp := capturedW.Result()
|
|
||||||
startAuthURL, err := resp.Location()
|
|
||||||
require.NoError(t, err, "get redirect")
|
|
||||||
loginPageURL := getRedirect(t, "get redirect to login page", httpClient, startAuthURL)
|
|
||||||
form := getForm(t, "get login form", httpClient, loginPageURL)
|
|
||||||
defer func() {
|
|
||||||
if t.Failed() {
|
|
||||||
t.Logf("login form (unfilled): %s", string(form))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
postLoginRedirectURL := fillForm(t, "fill login form", httpClient, form, loginPageURL,
|
|
||||||
gosubmit.Set("username", "test-user@local-site"),
|
|
||||||
gosubmit.Set("password", "verysecure"),
|
|
||||||
)
|
|
||||||
codeBearingURL := getRedirect(t, "get redirect with code", httpClient, postLoginRedirectURL)
|
|
||||||
capturedW = httptest.NewRecorder()
|
|
||||||
get = httptest.NewRequest("GET", codeBearingURL.String(), nil)
|
|
||||||
var idToken string
|
|
||||||
redirect := func(w http.ResponseWriter, r *http.Request, newTokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty, info *oidc.UserInfo) {
|
|
||||||
idToken = newTokens.IDToken
|
|
||||||
http.Redirect(w, r, targetURL, http.StatusFound)
|
|
||||||
}
|
|
||||||
rp.CodeExchangeHandler(rp.UserinfoCallback(redirect), provider)(capturedW, get)
|
|
||||||
defer func() {
|
|
||||||
if t.Failed() {
|
|
||||||
t.Log("token exchange response body", capturedW.Body.String())
|
|
||||||
require.GreaterOrEqual(t, capturedW.Code, 200, "captured response code")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Log("------- verify id token ------")
|
|
||||||
_, err = rp.VerifyIDToken[*oidc.IDTokenClaims](CTX, idToken, provider.IDTokenVerifier())
|
|
||||||
require.NoError(t, err, "verify id token")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceServerTokenExchange(t *testing.T) {
|
func TestResourceServerTokenExchange(t *testing.T) {
|
||||||
for _, wrapServer := range []bool{false, true} {
|
for _, wrapServer := range []bool{false, true} {
|
||||||
t.Run(fmt.Sprint("wrapServer ", wrapServer), func(t *testing.T) {
|
t.Run(fmt.Sprint("wrapServer ", wrapServer), func(t *testing.T) {
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
"github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWTProfileExchange handles the oauth2 jwt profile exchange
|
// JWTProfileExchange handles the oauth2 jwt profile exchange
|
||||||
|
|
|
@ -2,7 +2,7 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -24,7 +24,7 @@ type KeyFile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigFromKeyFile(path string) (*KeyFile, error) {
|
func ConfigFromKeyFile(path string) (*KeyFile, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenSource interface {
|
type TokenSource interface {
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package rp
|
package rp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange"
|
"github.com/zitadel/oidc/v4/pkg/oidc/grants/tokenexchange"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelegationTokenRequest is an implementation of TokenExchangeRequest
|
// DelegationTokenRequest is an implementation of TokenExchangeRequest
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) {
|
func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) {
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet {
|
func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet {
|
||||||
|
@ -217,7 +217,7 @@ func (r *remoteKeySet) fetchRemoteKeys(ctx context.Context) ([]jose.JSONWebKey,
|
||||||
ctx, span := client.Tracer.Start(ctx, "fetchRemoteKeys")
|
ctx, span := client.Tracer.Start(ctx, "fetchRemoteKeys")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", r.jwksURL, nil)
|
req, err := http.NewRequest("GET", r.jwksURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("oidc: can't create request: %v", err)
|
return nil, fmt.Errorf("oidc: can't create request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,15 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v4"
|
"github.com/go-jose/go-jose/v3"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -60,7 +60,7 @@ type RelyingParty interface {
|
||||||
// UserinfoEndpoint returns the userinfo
|
// UserinfoEndpoint returns the userinfo
|
||||||
UserinfoEndpoint() string
|
UserinfoEndpoint() string
|
||||||
|
|
||||||
// GetDeviceAuthorizationEndpoint returns the endpoint which can
|
// GetDeviceAuthorizationEndpoint returns the enpoint which can
|
||||||
// be used to start a DeviceAuthorization flow.
|
// be used to start a DeviceAuthorization flow.
|
||||||
GetDeviceAuthorizationEndpoint() string
|
GetDeviceAuthorizationEndpoint() string
|
||||||
|
|
||||||
|
@ -90,13 +90,12 @@ var DefaultUnauthorizedHandler UnauthorizedHandler = func(w http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
type relyingParty struct {
|
type relyingParty struct {
|
||||||
issuer string
|
issuer string
|
||||||
DiscoveryEndpoint string
|
DiscoveryEndpoint string
|
||||||
endpoints Endpoints
|
endpoints Endpoints
|
||||||
oauthConfig *oauth2.Config
|
oauthConfig *oauth2.Config
|
||||||
oauth2Only bool
|
oauth2Only bool
|
||||||
pkce bool
|
pkce bool
|
||||||
useSigningAlgsFromDiscovery bool
|
|
||||||
|
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
cookieHandler *httphelper.CookieHandler
|
cookieHandler *httphelper.CookieHandler
|
||||||
|
@ -239,9 +238,6 @@ func NewRelyingPartyOIDC(ctx context.Context, issuer, clientID, clientSecret, re
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if rp.useSigningAlgsFromDiscovery {
|
|
||||||
rp.verifierOpts = append(rp.verifierOpts, WithSupportedSigningAlgorithms(discoveryConfiguration.IDTokenSigningAlgValuesSupported...))
|
|
||||||
}
|
|
||||||
endpoints := GetEndpoints(discoveryConfiguration)
|
endpoints := GetEndpoints(discoveryConfiguration)
|
||||||
rp.oauthConfig.Endpoint = endpoints.Endpoint
|
rp.oauthConfig.Endpoint = endpoints.Endpoint
|
||||||
rp.endpoints = endpoints
|
rp.endpoints = endpoints
|
||||||
|
@ -352,15 +348,6 @@ func WithLogger(logger *slog.Logger) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSigningAlgsFromDiscovery appends the [WithSupportedSigningAlgorithms] option to the Verifier Options.
|
|
||||||
// The algorithms returned in the `id_token_signing_alg_values_supported` from the discovery response will be set.
|
|
||||||
func WithSigningAlgsFromDiscovery() Option {
|
|
||||||
return func(rp *relyingParty) error {
|
|
||||||
rp.useSigningAlgsFromDiscovery = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignerFromKey func() (jose.Signer, error)
|
type SignerFromKey func() (jose.Signer, error)
|
||||||
|
|
||||||
func SignerFromKeyPath(path string) SignerFromKey {
|
func SignerFromKeyPath(path string) SignerFromKey {
|
||||||
|
@ -401,7 +388,7 @@ func AuthURL(state string, rp RelyingParty, opts ...AuthURLOpt) string {
|
||||||
|
|
||||||
// AuthURLHandler extends the `AuthURL` method with a http redirect handler
|
// AuthURLHandler extends the `AuthURL` method with a http redirect handler
|
||||||
// including handling setting cookie for secure `state` transfer.
|
// including handling setting cookie for secure `state` transfer.
|
||||||
// Custom parameters can optionally be set to the redirect URL.
|
// Custom paramaters can optionally be set to the redirect URL.
|
||||||
func AuthURLHandler(stateFn func() string, rp RelyingParty, urlParam ...URLParamOpt) http.HandlerFunc {
|
func AuthURLHandler(stateFn func() string, rp RelyingParty, urlParam ...URLParamOpt) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
opts := make([]AuthURLOpt, len(urlParam))
|
opts := make([]AuthURLOpt, len(urlParam))
|
||||||
|
@ -541,7 +528,7 @@ func CodeExchangeHandler[C oidc.IDClaims](callback CodeExchangeCallback[C], rp R
|
||||||
rp.CookieHandler().DeleteCookie(w, pkceCode)
|
rp.CookieHandler().DeleteCookie(w, pkceCode)
|
||||||
}
|
}
|
||||||
if rp.Signer() != nil {
|
if rp.Signer() != nil {
|
||||||
assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer(), rp.OAuthConfig().Endpoint.TokenURL}, time.Hour, rp.Signer())
|
assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer()}, time.Hour, rp.Signer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unauthorizedError(w, r, "failed to build assertion: "+err.Error(), state, rp)
|
unauthorizedError(w, r, "failed to build assertion: "+err.Error(), state, rp)
|
||||||
return
|
return
|
||||||
|
@ -655,7 +642,7 @@ func GetEndpoints(discoveryConfig *oidc.DiscoveryConfiguration) Endpoints {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// withURLParam sets custom url parameters.
|
// withURLParam sets custom url paramaters.
|
||||||
// This is the generalized, unexported, function used by both
|
// This is the generalized, unexported, function used by both
|
||||||
// URLParamOpt and AuthURLOpt.
|
// URLParamOpt and AuthURLOpt.
|
||||||
func withURLParam(key, value string) func() []oauth2.AuthCodeOption {
|
func withURLParam(key, value string) func() []oauth2.AuthCodeOption {
|
||||||
|
@ -734,11 +721,11 @@ func (t tokenEndpointCaller) TokenEndpoint() string {
|
||||||
|
|
||||||
type RefreshTokenRequest struct {
|
type RefreshTokenRequest struct {
|
||||||
RefreshToken string `schema:"refresh_token"`
|
RefreshToken string `schema:"refresh_token"`
|
||||||
Scopes oidc.SpaceDelimitedArray `schema:"scope,omitempty"`
|
Scopes oidc.SpaceDelimitedArray `schema:"scope"`
|
||||||
ClientID string `schema:"client_id,omitempty"`
|
ClientID string `schema:"client_id"`
|
||||||
ClientSecret string `schema:"client_secret,omitempty"`
|
ClientSecret string `schema:"client_secret"`
|
||||||
ClientAssertion string `schema:"client_assertion,omitempty"`
|
ClientAssertion string `schema:"client_assertion"`
|
||||||
ClientAssertionType string `schema:"client_assertion_type,omitempty"`
|
ClientAssertionType string `schema:"client_assertion_type"`
|
||||||
GrantType oidc.GrantType `schema:"grant_type"`
|
GrantType oidc.GrantType `schema:"grant_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,7 +734,7 @@ type RefreshTokenRequest struct {
|
||||||
// the old one should be considered invalid.
|
// the old one should be considered invalid.
|
||||||
//
|
//
|
||||||
// In case the RP is not OAuth2 only and an IDToken was part of the response,
|
// In case the RP is not OAuth2 only and an IDToken was part of the response,
|
||||||
// the IDToken and AccessToken will be verified
|
// the IDToken and AccessToken will be verfied
|
||||||
// and the IDToken and IDTokenClaims fields will be populated in the returned object.
|
// and the IDToken and IDTokenClaims fields will be populated in the returned object.
|
||||||
func RefreshTokens[C oidc.IDClaims](ctx context.Context, rp RelyingParty, refreshToken, clientAssertion, clientAssertionType string) (*oidc.Tokens[C], error) {
|
func RefreshTokens[C oidc.IDClaims](ctx context.Context, rp RelyingParty, refreshToken, clientAssertion, clientAssertionType string) (*oidc.Tokens[C], error) {
|
||||||
ctx, span := client.Tracer.Start(ctx, "RefreshTokens")
|
ctx, span := client.Tracer.Start(ctx, "RefreshTokens")
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
tu "github.com/zitadel/oidc/v4/internal/testutil"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange"
|
"github.com/zitadel/oidc/v4/pkg/oidc/grants/tokenexchange"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`
|
// TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VerifyTokens implement the Token Response Validation as defined in OIDC specification
|
// VerifyTokens implement the Token Response Validation as defined in OIDC specification
|
||||||
|
@ -73,10 +73,8 @@ func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v *IDTokenV
|
||||||
return nilClaims, err
|
return nilClaims, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Nonce != nil {
|
if err = oidc.CheckNonce(claims, v.Nonce(ctx)); err != nil {
|
||||||
if err = oidc.CheckNonce(claims, v.Nonce(ctx)); err != nil {
|
return nilClaims, err
|
||||||
return nilClaims, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
|
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
|
||||||
|
|
|
@ -5,11 +5,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
tu "github.com/zitadel/oidc/v4/internal/testutil"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVerifyTokens(t *testing.T) {
|
func TestVerifyTokens(t *testing.T) {
|
||||||
|
@ -100,21 +100,22 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
MaxAge: 2 * time.Minute,
|
MaxAge: 2 * time.Minute,
|
||||||
ACR: tu.ACRVerify,
|
ACR: tu.ACRVerify,
|
||||||
Nonce: func(context.Context) string { return tu.ValidNonce },
|
Nonce: func(context.Context) string { return tu.ValidNonce },
|
||||||
ClientID: tu.ValidClientID,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
tokenClaims func() (string, *oidc.IDTokenClaims)
|
clientID string
|
||||||
customVerifier func(verifier *IDTokenVerifier)
|
tokenClaims func() (string, *oidc.IDTokenClaims)
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: tu.ValidIDToken,
|
tokenClaims: tu.ValidIDToken,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "custom claims",
|
name: "custom claims",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDTokenCustom(
|
return tu.NewIDTokenCustom(
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -124,31 +125,21 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "skip nonce check",
|
|
||||||
customVerifier: func(verifier *IDTokenVerifier) {
|
|
||||||
verifier.Nonce = nil
|
|
||||||
},
|
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
|
||||||
return tu.NewIDToken(
|
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
|
||||||
tu.ValidExpiration, tu.ValidAuthTime, "foo",
|
|
||||||
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "parse err",
|
name: "parse err",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) { return "~~~~", nil },
|
tokenClaims: func() (string, *oidc.IDTokenClaims) { return "~~~~", nil },
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid signature",
|
name: "invalid signature",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.InvalidSignatureToken, nil },
|
tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.InvalidSignatureToken, nil },
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty subject",
|
name: "empty subject",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
tu.ValidIssuer, "", tu.ValidAudience,
|
tu.ValidIssuer, "", tu.ValidAudience,
|
||||||
|
@ -159,7 +150,8 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong issuer",
|
name: "wrong issuer",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
"foo", tu.ValidSubject, tu.ValidAudience,
|
"foo", tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -170,15 +162,14 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong clientID",
|
name: "wrong clientID",
|
||||||
customVerifier: func(verifier *IDTokenVerifier) {
|
clientID: "foo",
|
||||||
verifier.ClientID = "foo"
|
|
||||||
},
|
|
||||||
tokenClaims: tu.ValidIDToken,
|
tokenClaims: tu.ValidIDToken,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "expired",
|
name: "expired",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -189,7 +180,8 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong IAT",
|
name: "wrong IAT",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -200,7 +192,8 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong acr",
|
name: "wrong acr",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -211,7 +204,8 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "expired auth",
|
name: "expired auth",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -222,7 +216,8 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong nonce",
|
name: "wrong nonce",
|
||||||
|
clientID: tu.ValidClientID,
|
||||||
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
tokenClaims: func() (string, *oidc.IDTokenClaims) {
|
||||||
return tu.NewIDToken(
|
return tu.NewIDToken(
|
||||||
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
|
||||||
|
@ -236,10 +231,7 @@ func TestVerifyIDToken(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
token, want := tt.tokenClaims()
|
token, want := tt.tokenClaims()
|
||||||
if tt.customVerifier != nil {
|
verifier.ClientID = tt.clientID
|
||||||
tt.customVerifier(verifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := VerifyIDToken[*oidc.IDTokenClaims](context.Background(), token, verifier)
|
got, err := VerifyIDToken[*oidc.IDTokenClaims](context.Background(), token, verifier)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
|
tu "github.com/zitadel/oidc/v4/internal/testutil"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
|
"github.com/zitadel/oidc/v4/pkg/client/rp"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MyCustomClaims extends the TokenClaims base,
|
// MyCustomClaims extends the TokenClaims base,
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs"
|
"github.com/zitadel/oidc/v4/pkg/client/rs"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntrospectionResponse struct {
|
type IntrospectionResponse struct {
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceServer interface {
|
type ResourceServer interface {
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewResourceServer(t *testing.T) {
|
func TestNewResourceServer(t *testing.T) {
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
|
"github.com/go-jose/go-jose/v3"
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
"github.com/zitadel/oidc/v4/pkg/client"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"github.com/go-jose/go-jose/v4"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenExchanger interface {
|
type TokenExchanger interface {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedAlgorithm = errors.New("unsupported signing algorithm")
|
var ErrUnsupportedAlgorithm = errors.New("unsupported signing algorithm")
|
||||||
|
@ -21,14 +21,6 @@ func GetHashAlgorithm(sigAlgorithm jose.SignatureAlgorithm) (hash.Hash, error) {
|
||||||
return sha512.New384(), nil
|
return sha512.New384(), nil
|
||||||
case jose.RS512, jose.ES512, jose.PS512:
|
case jose.RS512, jose.ES512, jose.PS512:
|
||||||
return sha512.New(), nil
|
return sha512.New(), nil
|
||||||
|
|
||||||
// There is no published spec for this yet, but we have confirmation it will get published.
|
|
||||||
// There is consensus here: https://bitbucket.org/openid/connect/issues/1125/_hash-algorithm-for-eddsa-id-tokens
|
|
||||||
// Currently Go and go-jose only supports the ed25519 curve key for EdDSA, so we can safely assume sha512 here.
|
|
||||||
// It is unlikely ed448 will ever be supported: https://github.com/golang/go/issues/29390
|
|
||||||
case jose.EdDSA:
|
|
||||||
return sha512.New(), nil
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %q", ErrUnsupportedAlgorithm, sigAlgorithm)
|
return nil, fmt.Errorf("%w: %q", ErrUnsupportedAlgorithm, sigAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,22 @@
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v4"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func BytesToPrivateKey(b []byte) (*rsa.PrivateKey, error) {
|
||||||
ErrPEMDecode = errors.New("PEM decode failed")
|
|
||||||
ErrUnsupportedFormat = errors.New("key is neither in PKCS#1 nor PKCS#8 format")
|
|
||||||
ErrUnsupportedPrivateKey = errors.New("unsupported key type, must be RSA, ECDSA or ED25519 private key")
|
|
||||||
)
|
|
||||||
|
|
||||||
func BytesToPrivateKey(b []byte) (crypto.PublicKey, jose.SignatureAlgorithm, error) {
|
|
||||||
block, _ := pem.Decode(b)
|
block, _ := pem.Decode(b)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, "", ErrPEMDecode
|
return nil, errors.New("PEM decode failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
if err == nil {
|
|
||||||
return privateKey, jose.RS256, nil
|
|
||||||
}
|
|
||||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", ErrUnsupportedFormat
|
return nil, err
|
||||||
}
|
|
||||||
switch privateKey := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return privateKey, jose.RS256, nil
|
|
||||||
case ed25519.PrivateKey:
|
|
||||||
return privateKey, jose.EdDSA, nil
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return privateKey, jose.ES256, nil
|
|
||||||
default:
|
|
||||||
return nil, "", ErrUnsupportedPrivateKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +1,21 @@
|
||||||
package crypto_test
|
package crypto_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rsa"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v4"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
zcrypto "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
|
"github.com/zitadel/oidc/v4/pkg/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBytesToPrivateKey(t *testing.T) {
|
func TestBytesToPrivateKey(tt *testing.T) {
|
||||||
type args struct {
|
tt.Run("PEMDecodeError", func(t *testing.T) {
|
||||||
key []byte
|
_, err := crypto.BytesToPrivateKey([]byte("The non-PEM sequence"))
|
||||||
}
|
assert.EqualError(t, err, "PEM decode failed")
|
||||||
type want struct {
|
})
|
||||||
key crypto.Signer
|
|
||||||
algorithm jose.SignatureAlgorithm
|
tt.Run("InvalidKeyFormat", func(t *testing.T) {
|
||||||
err error
|
_, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN PRIVATE KEY-----
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want want
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "PEMDecodeError",
|
|
||||||
args: args{
|
|
||||||
key: []byte("The non-PEM sequence"),
|
|
||||||
},
|
|
||||||
want: want{
|
|
||||||
err: zcrypto.ErrPEMDecode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PKCS#1 RSA",
|
|
||||||
args: args{
|
|
||||||
key: []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu
|
|
||||||
KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm
|
|
||||||
o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k
|
|
||||||
TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7
|
|
||||||
9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy
|
|
||||||
v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs
|
|
||||||
/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00
|
|
||||||
-----END RSA PRIVATE KEY-----`),
|
|
||||||
},
|
|
||||||
want: want{
|
|
||||||
key: &rsa.PrivateKey{},
|
|
||||||
algorithm: jose.RS256,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PKCS#8 RSA",
|
|
||||||
args: args{
|
|
||||||
key: []byte(`-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfaDB7pK/fmP/I
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfaDB7pK/fmP/I
|
||||||
7IusSK8lTCBnPZghqIbVLt2QHYAMoEF1CaF4F4rxo2vl1Mt8gwsq4T3osQFZMvnL
|
7IusSK8lTCBnPZghqIbVLt2QHYAMoEF1CaF4F4rxo2vl1Mt8gwsq4T3osQFZMvnL
|
||||||
YHb7KNyUoJgTjLxJQADv2u4Q3U38heAzK5Tp4ry4MCnuyJIqAPK1GiruwEq4zQrx
|
YHb7KNyUoJgTjLxJQADv2u4Q3U38heAzK5Tp4ry4MCnuyJIqAPK1GiruwEq4zQrx
|
||||||
|
@ -85,50 +42,21 @@ srJnjF0H8oKmAY6hw+1Tm/n/b08p+RyL48TgVSE2vhUCgYA3BWpkD4PlCcn/FZsq
|
||||||
OrLFyFXI6jIaxskFtsRW1IxxIlAdZmxfB26P/2gx6VjLdxJI/RRPkJyEN2dP7CbR
|
OrLFyFXI6jIaxskFtsRW1IxxIlAdZmxfB26P/2gx6VjLdxJI/RRPkJyEN2dP7CbR
|
||||||
BDjb565dy1O9D6+UrY70Iuwjz+OcALRBBGTaiF2pLn6IhSzNI2sy/tXX8q8dBlg9
|
BDjb565dy1O9D6+UrY70Iuwjz+OcALRBBGTaiF2pLn6IhSzNI2sy/tXX8q8dBlg9
|
||||||
OFCrqT/emes3KytTPfa5NZtYeQ==
|
OFCrqT/emes3KytTPfa5NZtYeQ==
|
||||||
-----END PRIVATE KEY-----`),
|
-----END PRIVATE KEY-----`))
|
||||||
},
|
assert.EqualError(t, err, "x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)")
|
||||||
want: want{
|
})
|
||||||
key: &rsa.PrivateKey{},
|
|
||||||
algorithm: jose.RS256,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PKCS#8 ECDSA",
|
|
||||||
args: args{
|
|
||||||
key: []byte(`-----BEGIN PRIVATE KEY-----
|
|
||||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwwOZSU4GlP7ps/Wp
|
|
||||||
V6o0qRwxultdfYo/uUuj48QZjSuhRANCAATMiI2Han+ABKmrk5CNlxRAGC61w4d3
|
|
||||||
G4TAeuBpyzqJ7x/6NjCxoQzJzZHtNjIfjVATI59XFZWF59GhtSZbShAr
|
|
||||||
-----END PRIVATE KEY-----`),
|
|
||||||
},
|
|
||||||
want: want{
|
|
||||||
key: &ecdsa.PrivateKey{},
|
|
||||||
algorithm: jose.ES256,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PKCS#8 ED25519",
|
|
||||||
args: args{
|
|
||||||
key: []byte(`-----BEGIN PRIVATE KEY-----
|
|
||||||
MC4CAQAwBQYDK2VwBCIEIHu6ZtDsjjauMasBxnS9Fg87UJwKfcT/oiq6S0ktbky8
|
|
||||||
-----END PRIVATE KEY-----`),
|
|
||||||
},
|
|
||||||
want: want{
|
|
||||||
key: ed25519.PrivateKey{},
|
|
||||||
algorithm: jose.EdDSA,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
key, algorithm, err := zcrypto.BytesToPrivateKey(tt.args.key)
|
|
||||||
assert.IsType(t, tt.want.key, key)
|
|
||||||
assert.Equal(t, tt.want.algorithm, algorithm)
|
|
||||||
assert.ErrorIs(t, tt.want.err, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
tt.Run("Ok", func(t *testing.T) {
|
||||||
|
key, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu
|
||||||
|
KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm
|
||||||
|
o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k
|
||||||
|
TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7
|
||||||
|
9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy
|
||||||
|
v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs
|
||||||
|
/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00
|
||||||
|
-----END RSA PRIVATE KEY-----`))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, key)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Sign(object any, signer jose.Signer) (string, error) {
|
func Sign(object any, signer jose.Signer) (string, error) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DefaultHTTPClient = &http.Client{
|
var DefaultHTTPClient = &http.Client{
|
||||||
|
|
|
@ -3,7 +3,7 @@ package oidc
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
|
"github.com/zitadel/oidc/v4/pkg/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
// DeviceAuthorizationRequest implements
|
// DeviceAuthorizationRequest implements
|
||||||
// https://www.rfc-editor.org/rfc/rfc8628#section-3.1,
|
// https://www.rfc-editor.org/rfc/rfc8628#section-3.1,
|
||||||
// 3.1 Device Authorization Request.
|
// 3.1 Device Authorization Request.
|
||||||
|
@ -22,26 +20,6 @@ type DeviceAuthorizationResponse struct {
|
||||||
Interval int `json:"interval,omitempty"`
|
Interval int `json:"interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resp *DeviceAuthorizationResponse) UnmarshalJSON(data []byte) error {
|
|
||||||
type Alias DeviceAuthorizationResponse
|
|
||||||
aux := &struct {
|
|
||||||
// workaround misspelling of verification_uri
|
|
||||||
// https://stackoverflow.com/q/76696956/5690223
|
|
||||||
// https://developers.google.com/identity/protocols/oauth2/limited-input-device?hl=fr#success-response
|
|
||||||
VerificationURL string `json:"verification_url"`
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Alias: (*Alias)(resp),
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.VerificationURI == "" {
|
|
||||||
resp.VerificationURI = aux.VerificationURL
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeviceAccessTokenRequest implements
|
// DeviceAccessTokenRequest implements
|
||||||
// https://www.rfc-editor.org/rfc/rfc8628#section-3.4,
|
// https://www.rfc-editor.org/rfc/rfc8628#section-3.4,
|
||||||
// Device Access Token Request.
|
// Device Access Token Request.
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDeviceAuthorizationResponse_UnmarshalJSON(t *testing.T) {
|
|
||||||
jsonStr := `{
|
|
||||||
"device_code": "deviceCode",
|
|
||||||
"user_code": "userCode",
|
|
||||||
"verification_url": "http://example.com/verify",
|
|
||||||
"expires_in": 3600,
|
|
||||||
"interval": 5
|
|
||||||
}`
|
|
||||||
|
|
||||||
expected := &DeviceAuthorizationResponse{
|
|
||||||
DeviceCode: "deviceCode",
|
|
||||||
UserCode: "userCode",
|
|
||||||
VerificationURI: "http://example.com/verify",
|
|
||||||
ExpiresIn: 3600,
|
|
||||||
Interval: 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp DeviceAuthorizationResponse
|
|
||||||
err := resp.UnmarshalJSON([]byte(jsonStr))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected, &resp)
|
|
||||||
}
|
|
|
@ -145,14 +145,6 @@ type DiscoveryConfiguration struct {
|
||||||
|
|
||||||
// OPTermsOfServiceURI is a URL the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service.
|
// OPTermsOfServiceURI is a URL the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service.
|
||||||
OPTermsOfServiceURI string `json:"op_tos_uri,omitempty"`
|
OPTermsOfServiceURI string `json:"op_tos_uri,omitempty"`
|
||||||
|
|
||||||
// BackChannelLogoutSupported specifies whether the OP supports back-channel logout (https://openid.net/specs/openid-connect-backchannel-1_0.html),
|
|
||||||
// with true indicating support. If omitted, the default value is false.
|
|
||||||
BackChannelLogoutSupported bool `json:"backchannel_logout_supported,omitempty"`
|
|
||||||
|
|
||||||
// BackChannelLogoutSessionSupported specifies whether the OP can pass a sid (session ID) Claim in the Logout Token to identify the RP session with the OP.
|
|
||||||
// If supported, the sid Claim is also included in ID Tokens issued by the OP. If omitted, the default value is false.
|
|
||||||
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthMethod string
|
type AuthMethod string
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
@ -133,28 +132,7 @@ type Error struct {
|
||||||
ErrorType errorType `json:"error" schema:"error"`
|
ErrorType errorType `json:"error" schema:"error"`
|
||||||
Description string `json:"error_description,omitempty" schema:"error_description,omitempty"`
|
Description string `json:"error_description,omitempty" schema:"error_description,omitempty"`
|
||||||
State string `json:"state,omitempty" schema:"state,omitempty"`
|
State string `json:"state,omitempty" schema:"state,omitempty"`
|
||||||
SessionState string `json:"session_state,omitempty" schema:"session_state,omitempty"`
|
|
||||||
redirectDisabled bool `schema:"-"`
|
redirectDisabled bool `schema:"-"`
|
||||||
returnParent bool `schema:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) MarshalJSON() ([]byte, error) {
|
|
||||||
m := struct {
|
|
||||||
Error errorType `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description,omitempty"`
|
|
||||||
State string `json:"state,omitempty"`
|
|
||||||
SessionState string `json:"session_state,omitempty"`
|
|
||||||
Parent string `json:"parent,omitempty"`
|
|
||||||
}{
|
|
||||||
Error: e.ErrorType,
|
|
||||||
ErrorDescription: e.Description,
|
|
||||||
State: e.State,
|
|
||||||
SessionState: e.SessionState,
|
|
||||||
}
|
|
||||||
if e.returnParent {
|
|
||||||
m.Parent = e.Parent.Error()
|
|
||||||
}
|
|
||||||
return json.Marshal(m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
|
@ -179,8 +157,7 @@ func (e *Error) Is(target error) bool {
|
||||||
}
|
}
|
||||||
return e.ErrorType == t.ErrorType &&
|
return e.ErrorType == t.ErrorType &&
|
||||||
(e.Description == t.Description || t.Description == "") &&
|
(e.Description == t.Description || t.Description == "") &&
|
||||||
(e.State == t.State || t.State == "") &&
|
(e.State == t.State || t.State == "")
|
||||||
(e.SessionState == t.SessionState || t.SessionState == "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) WithParent(err error) *Error {
|
func (e *Error) WithParent(err error) *Error {
|
||||||
|
@ -188,18 +165,6 @@ func (e *Error) WithParent(err error) *Error {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithReturnParentToClient allows returning the set parent error to the HTTP client.
|
|
||||||
// Currently it only supports setting the parent inside JSON responses, not redirect URLs.
|
|
||||||
// As Go errors don't unmarshal well, only the marshaller is implemented for the moment.
|
|
||||||
//
|
|
||||||
// Warning: parent errors may contain sensitive data or unwanted details about the server status.
|
|
||||||
// Also, the `parent` field is not a standard error field and might confuse certain clients
|
|
||||||
// that require fully compliant responses.
|
|
||||||
func (e *Error) WithReturnParentToClient(b bool) *Error {
|
|
||||||
e.returnParent = b
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) WithDescription(desc string, args ...any) *Error {
|
func (e *Error) WithDescription(desc string, args ...any) *Error {
|
||||||
e.Description = fmt.Sprintf(desc, args...)
|
e.Description = fmt.Sprintf(desc, args...)
|
||||||
return e
|
return e
|
||||||
|
@ -246,9 +211,6 @@ func (e *Error) LogValue() slog.Value {
|
||||||
if e.State != "" {
|
if e.State != "" {
|
||||||
attrs = append(attrs, slog.String("state", e.State))
|
attrs = append(attrs, slog.String("state", e.State))
|
||||||
}
|
}
|
||||||
if e.SessionState != "" {
|
|
||||||
attrs = append(attrs, slog.String("session_state", e.SessionState))
|
|
||||||
}
|
|
||||||
if e.redirectDisabled {
|
if e.redirectDisabled {
|
||||||
attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled))
|
attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultToServerError(t *testing.T) {
|
func TestDefaultToServerError(t *testing.T) {
|
||||||
|
@ -154,39 +151,3 @@ func TestError_LogValue(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestError_MarshalJSON(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
e *Error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "simple error",
|
|
||||||
e: ErrAccessDenied(),
|
|
||||||
want: `{"error":"access_denied","error_description":"The authorization request was denied."}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with description",
|
|
||||||
e: ErrAccessDenied().WithDescription("oops"),
|
|
||||||
want: `{"error":"access_denied","error_description":"oops"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with parent",
|
|
||||||
e: ErrServerError().WithParent(errors.New("oops")),
|
|
||||||
want: `{"error":"server_error"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with return parent",
|
|
||||||
e: ErrServerError().WithParent(errors.New("oops")).WithReturnParentToClient(true),
|
|
||||||
want: `{"error":"server_error","parent":"oops"}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := json.Marshal(tt.e)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.JSONEq(t, tt.want, string(got))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,9 +6,8 @@ import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -93,17 +92,17 @@ func FindMatchingKey(keyID, use, expectedAlg string, keys ...jose.JSONWebKey) (k
|
||||||
}
|
}
|
||||||
|
|
||||||
func algToKeyType(key any, alg string) bool {
|
func algToKeyType(key any, alg string) bool {
|
||||||
if strings.HasPrefix(alg, "RS") || strings.HasPrefix(alg, "PS") {
|
switch alg[0] {
|
||||||
|
case 'R', 'P':
|
||||||
_, ok := key.(*rsa.PublicKey)
|
_, ok := key.(*rsa.PublicKey)
|
||||||
return ok
|
return ok
|
||||||
}
|
case 'E':
|
||||||
if strings.HasPrefix(alg, "ES") {
|
|
||||||
_, ok := key.(*ecdsa.PublicKey)
|
_, ok := key.(*ecdsa.PublicKey)
|
||||||
return ok
|
return ok
|
||||||
}
|
case 'O':
|
||||||
if alg == string(jose.EdDSA) {
|
_, ok := key.(*ed25519.PublicKey)
|
||||||
_, ok := key.(ed25519.PublicKey)
|
|
||||||
return ok
|
return ok
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindKey(t *testing.T) {
|
func TestFindKey(t *testing.T) {
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
// EndSessionRequest for the RP-Initiated Logout according to:
|
// EndSessionRequest for the RP-Initiated Logout according to:
|
||||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
|
//https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
|
||||||
type EndSessionRequest struct {
|
type EndSessionRequest struct {
|
||||||
IdTokenHint string `schema:"id_token_hint"`
|
IdTokenHint string `schema:"id_token_hint"`
|
||||||
LogoutHint string `schema:"logout_hint"`
|
ClientID string `schema:"client_id"`
|
||||||
ClientID string `schema:"client_id"`
|
PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"`
|
||||||
PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"`
|
State string `schema:"state"`
|
||||||
State string `schema:"state"`
|
|
||||||
UILocales Locales `schema:"ui_locales"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/muhlemmer/gu"
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/crypto"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -117,7 +116,6 @@ func NewAccessTokenClaims(issuer, subject string, audience []string, expiration
|
||||||
Expiration: FromTime(expiration),
|
Expiration: FromTime(expiration),
|
||||||
IssuedAt: FromTime(now),
|
IssuedAt: FromTime(now),
|
||||||
NotBefore: FromTime(now),
|
NotBefore: FromTime(now),
|
||||||
ClientID: clientID,
|
|
||||||
JWTID: jwtid,
|
JWTID: jwtid,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -230,13 +228,12 @@ func (c *ActorClaims) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccessTokenResponse struct {
|
type AccessTokenResponse struct {
|
||||||
AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"`
|
AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"`
|
||||||
TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"`
|
TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"`
|
||||||
RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"`
|
RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"`
|
||||||
ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"`
|
ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"`
|
||||||
IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
|
IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
|
||||||
State string `json:"state,omitempty" schema:"state,omitempty"`
|
State string `json:"state,omitempty" schema:"state,omitempty"`
|
||||||
Scope SpaceDelimitedArray `json:"scope,omitempty" schema:"scope,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type JWTProfileAssertionClaims struct {
|
type JWTProfileAssertionClaims struct {
|
||||||
|
@ -347,12 +344,12 @@ func AppendClientIDToAudience(clientID string, audience []string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateJWTProfileToken(assertion *JWTProfileAssertionClaims) (string, error) {
|
func GenerateJWTProfileToken(assertion *JWTProfileAssertionClaims) (string, error) {
|
||||||
privateKey, algorithm, err := crypto.BytesToPrivateKey(assertion.PrivateKey)
|
privateKey, err := crypto.BytesToPrivateKey(assertion.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
key := jose.SigningKey{
|
key := jose.SigningKey{
|
||||||
Algorithm: algorithm,
|
Algorithm: jose.RS256,
|
||||||
Key: &jose.JSONWebKey{Key: privateKey, KeyID: assertion.PrivateKeyID},
|
Key: &jose.JSONWebKey{Key: privateKey, KeyID: assertion.PrivateKeyID},
|
||||||
}
|
}
|
||||||
signer, err := jose.NewSigner(key, &jose.SignerOptions{})
|
signer, err := jose.NewSigner(key, &jose.SignerOptions{})
|
||||||
|
@ -383,40 +380,3 @@ type TokenExchangeResponse struct {
|
||||||
// if the requested_token_type was Access Token and scope contained openid.
|
// if the requested_token_type was Access Token and scope contained openid.
|
||||||
IDToken string `json:"id_token,omitempty"`
|
IDToken string `json:"id_token,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogoutTokenClaims struct {
|
|
||||||
Issuer string `json:"iss,omitempty"`
|
|
||||||
Subject string `json:"sub,omitempty"`
|
|
||||||
Audience Audience `json:"aud,omitempty"`
|
|
||||||
IssuedAt Time `json:"iat,omitempty"`
|
|
||||||
Expiration Time `json:"exp,omitempty"`
|
|
||||||
JWTID string `json:"jti,omitempty"`
|
|
||||||
Events map[string]any `json:"events,omitempty"`
|
|
||||||
SessionID string `json:"sid,omitempty"`
|
|
||||||
Claims map[string]any `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ltcAlias LogoutTokenClaims
|
|
||||||
|
|
||||||
func (i *LogoutTokenClaims) MarshalJSON() ([]byte, error) {
|
|
||||||
return mergeAndMarshalClaims((*ltcAlias)(i), i.Claims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *LogoutTokenClaims) UnmarshalJSON(data []byte) error {
|
|
||||||
return unmarshalJSONMulti(data, (*ltcAlias)(i), &i.Claims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogoutTokenClaims(issuer, subject string, audience Audience, expiration time.Time, jwtID, sessionID string, skew time.Duration) *LogoutTokenClaims {
|
|
||||||
return &LogoutTokenClaims{
|
|
||||||
Issuer: issuer,
|
|
||||||
Subject: subject,
|
|
||||||
Audience: audience,
|
|
||||||
IssuedAt: FromTime(time.Now().Add(-skew)),
|
|
||||||
Expiration: FromTime(expiration),
|
|
||||||
JWTID: jwtID,
|
|
||||||
Events: map[string]any{
|
|
||||||
"http://schemas.openid.net/event/backchannel-logout": struct{}{},
|
|
||||||
},
|
|
||||||
SessionID: sessionID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -72,10 +72,10 @@ type AccessTokenRequest struct {
|
||||||
Code string `schema:"code"`
|
Code string `schema:"code"`
|
||||||
RedirectURI string `schema:"redirect_uri"`
|
RedirectURI string `schema:"redirect_uri"`
|
||||||
ClientID string `schema:"client_id"`
|
ClientID string `schema:"client_id"`
|
||||||
ClientSecret string `schema:"client_secret,omitempty"`
|
ClientSecret string `schema:"client_secret"`
|
||||||
CodeVerifier string `schema:"code_verifier,omitempty"`
|
CodeVerifier string `schema:"code_verifier"`
|
||||||
ClientAssertion string `schema:"client_assertion,omitempty"`
|
ClientAssertion string `schema:"client_assertion"`
|
||||||
ClientAssertionType string `schema:"client_assertion_type,omitempty"`
|
ClientAssertionType string `schema:"client_assertion_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AccessTokenRequest) GrantType() GrantType {
|
func (a *AccessTokenRequest) GrantType() GrantType {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
@ -145,7 +145,6 @@ func TestNewAccessTokenClaims(t *testing.T) {
|
||||||
Subject: "hello@me.com",
|
Subject: "hello@me.com",
|
||||||
Audience: Audience{"foo"},
|
Audience: Audience{"foo"},
|
||||||
Expiration: 12345,
|
Expiration: 12345,
|
||||||
ClientID: "foo",
|
|
||||||
JWTID: "900",
|
JWTID: "900",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -242,39 +241,3 @@ func TestIDTokenClaims_GetUserInfo(t *testing.T) {
|
||||||
got := idTokenData.GetUserInfo()
|
got := idTokenData.GetUserInfo()
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewLogoutTokenClaims(t *testing.T) {
|
|
||||||
want := &LogoutTokenClaims{
|
|
||||||
Issuer: "zitadel",
|
|
||||||
Subject: "hello@me.com",
|
|
||||||
Audience: Audience{"foo", "just@me.com"},
|
|
||||||
Expiration: 12345,
|
|
||||||
JWTID: "jwtID",
|
|
||||||
Events: map[string]any{
|
|
||||||
"http://schemas.openid.net/event/backchannel-logout": struct{}{},
|
|
||||||
},
|
|
||||||
SessionID: "sessionID",
|
|
||||||
Claims: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
got := NewLogoutTokenClaims(
|
|
||||||
want.Issuer,
|
|
||||||
want.Subject,
|
|
||||||
want.Audience,
|
|
||||||
want.Expiration.AsTime(),
|
|
||||||
want.JWTID,
|
|
||||||
want.SessionID,
|
|
||||||
1*time.Second,
|
|
||||||
)
|
|
||||||
|
|
||||||
// test if the dynamic timestamp is around now,
|
|
||||||
// allowing for a delta of 1, just in case we flip on
|
|
||||||
// either side of a second boundry.
|
|
||||||
nowMinusSkew := NowTime() - 1
|
|
||||||
assert.InDelta(t, int64(nowMinusSkew), int64(got.IssuedAt), 1)
|
|
||||||
|
|
||||||
// Make equal not fail on dynamic timestamp
|
|
||||||
got.IssuedAt = 0
|
|
||||||
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/muhlemmer/gu"
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/zitadel/schema"
|
"github.com/zitadel/schema"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
@ -35,17 +35,6 @@ func (a *Audience) UnmarshalJSON(text []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Audience) MarshalJSON() ([]byte, error) {
|
|
||||||
len := len(*a)
|
|
||||||
if len > 1 {
|
|
||||||
return json.Marshal(*a)
|
|
||||||
} else if len == 1 {
|
|
||||||
return json.Marshal((*a)[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("aud is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Display string
|
type Display string
|
||||||
|
|
||||||
func (d *Display) UnmarshalText(text []byte) error {
|
func (d *Display) UnmarshalText(text []byte) error {
|
||||||
|
@ -93,9 +82,6 @@ func (l *Locale) MarshalJSON() ([]byte, error) {
|
||||||
// to an empty value (language "und") and no error will be returned.
|
// to an empty value (language "und") and no error will be returned.
|
||||||
// This state can be checked with the `l.Tag().IsRoot()` method.
|
// This state can be checked with the `l.Tag().IsRoot()` method.
|
||||||
func (l *Locale) UnmarshalJSON(data []byte) error {
|
func (l *Locale) UnmarshalJSON(data []byte) error {
|
||||||
if len(data) == 0 || string(data) == "\"\"" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(data, &l.tag)
|
err := json.Unmarshal(data, &l.tag)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -126,14 +112,6 @@ func ParseLocales(locales []string) Locales {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Locales) String() string {
|
|
||||||
tags := make([]string, len(l))
|
|
||||||
for i, tag := range l {
|
|
||||||
tags[i] = tag.String()
|
|
||||||
}
|
|
||||||
return strings.Join(tags, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
|
// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
|
||||||
// It decodes an unquoted space seperated string into Locales.
|
// It decodes an unquoted space seperated string into Locales.
|
||||||
// Undefined language tags in the input are ignored and ommited from
|
// Undefined language tags in the input are ignored and ommited from
|
||||||
|
@ -250,9 +228,6 @@ func NewEncoder() *schema.Encoder {
|
||||||
e.RegisterEncoder(SpaceDelimitedArray{}, func(value reflect.Value) string {
|
e.RegisterEncoder(SpaceDelimitedArray{}, func(value reflect.Value) string {
|
||||||
return value.Interface().(SpaceDelimitedArray).String()
|
return value.Interface().(SpaceDelimitedArray).String()
|
||||||
})
|
})
|
||||||
e.RegisterEncoder(Locales{}, func(value reflect.Value) string {
|
|
||||||
return value.Interface().(Locales).String()
|
|
||||||
})
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,30 +217,6 @@ func TestLocale_UnmarshalJSON(t *testing.T) {
|
||||||
want dst
|
want dst
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "value not present",
|
|
||||||
input: `{}`,
|
|
||||||
wantErr: false,
|
|
||||||
want: dst{
|
|
||||||
Locale: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "null",
|
|
||||||
input: `{"locale": null}`,
|
|
||||||
wantErr: false,
|
|
||||||
want: dst{
|
|
||||||
Locale: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty, ignored",
|
|
||||||
input: `{"locale": ""}`,
|
|
||||||
wantErr: false,
|
|
||||||
want: dst{
|
|
||||||
Locale: &Locale{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "afrikaans, ok",
|
name: "afrikaans, ok",
|
||||||
input: `{"locale": "af"}`,
|
input: `{"locale": "af"}`,
|
||||||
|
@ -261,17 +237,16 @@ func TestLocale_UnmarshalJSON(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
var got dst
|
||||||
var got dst
|
err := json.Unmarshal([]byte(tt.input), &got)
|
||||||
err := json.Unmarshal([]byte(tt.input), &got)
|
if tt.wantErr {
|
||||||
if tt.wantErr {
|
require.Error(t, err)
|
||||||
require.Error(t, err)
|
return
|
||||||
return
|
}
|
||||||
}
|
require.NoError(t, err)
|
||||||
require.NoError(t, err)
|
assert.Equal(t, tt.want, got)
|
||||||
assert.Equal(t, tt.want, got)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
|
str "github.com/zitadel/oidc/v4/pkg/strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Claims interface {
|
type Claims interface {
|
||||||
|
@ -40,7 +41,6 @@ type IDClaims interface {
|
||||||
var (
|
var (
|
||||||
ErrParse = errors.New("parsing of request failed")
|
ErrParse = errors.New("parsing of request failed")
|
||||||
ErrIssuerInvalid = errors.New("issuer does not match")
|
ErrIssuerInvalid = errors.New("issuer does not match")
|
||||||
ErrDiscoveryFailed = errors.New("OpenID Provider Configuration Discovery has failed")
|
|
||||||
ErrSubjectMissing = errors.New("subject missing")
|
ErrSubjectMissing = errors.New("subject missing")
|
||||||
ErrAudience = errors.New("audience is not valid")
|
ErrAudience = errors.New("audience is not valid")
|
||||||
ErrAzpMissing = errors.New("authorized party is not set. If Token is valid for multiple audiences, azp must not be empty")
|
ErrAzpMissing = errors.New("authorized party is not set. If Token is valid for multiple audiences, azp must not be empty")
|
||||||
|
@ -83,7 +83,7 @@ type ACRVerifier func(string) error
|
||||||
// if none of the provided values matches the acr claim
|
// if none of the provided values matches the acr claim
|
||||||
func DefaultACRVerifier(possibleValues []string) ACRVerifier {
|
func DefaultACRVerifier(possibleValues []string) ACRVerifier {
|
||||||
return func(acr string) error {
|
return func(acr string) error {
|
||||||
if !slices.Contains(possibleValues, acr) {
|
if !str.Contains(possibleValues, acr) {
|
||||||
return fmt.Errorf("expected one of: %v, got: %q", possibleValues, acr)
|
return fmt.Errorf("expected one of: %v, got: %q", possibleValues, acr)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -122,7 +122,7 @@ func CheckIssuer(claims Claims, issuer string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckAudience(claims Claims, clientID string) error {
|
func CheckAudience(claims Claims, clientID string) error {
|
||||||
if !slices.Contains(claims.GetAudience(), clientID) {
|
if !str.Contains(claims.GetAudience(), clientID) {
|
||||||
return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, clientID)
|
return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, clientID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,13 +148,8 @@ func CheckAuthorizedParty(claims Claims, clientID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckSignature(ctx context.Context, token string, payload []byte, claims ClaimsSignature, supportedSigAlgs []string, set KeySet) error {
|
func CheckSignature(ctx context.Context, token string, payload []byte, claims ClaimsSignature, supportedSigAlgs []string, set KeySet) error {
|
||||||
jws, err := jose.ParseSigned(token, toJoseSignatureAlgorithms(supportedSigAlgs))
|
jws, err := jose.ParseSigned(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "go-jose/go-jose: unexpected signature algorithm") {
|
|
||||||
// TODO(v4): we should wrap errors instead of returning static ones.
|
|
||||||
// This is a workaround so we keep returning the same error for now.
|
|
||||||
return ErrSignatureUnsupportedAlg
|
|
||||||
}
|
|
||||||
return ErrParse
|
return ErrParse
|
||||||
}
|
}
|
||||||
if len(jws.Signatures) == 0 {
|
if len(jws.Signatures) == 0 {
|
||||||
|
@ -164,6 +159,12 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl
|
||||||
return ErrSignatureMultiple
|
return ErrSignatureMultiple
|
||||||
}
|
}
|
||||||
sig := jws.Signatures[0]
|
sig := jws.Signatures[0]
|
||||||
|
if len(supportedSigAlgs) == 0 {
|
||||||
|
supportedSigAlgs = []string{"RS256"}
|
||||||
|
}
|
||||||
|
if !str.Contains(supportedSigAlgs, sig.Header.Algorithm) {
|
||||||
|
return fmt.Errorf("%w: id token signed with unsupported algorithm, expected %q got %q", ErrSignatureUnsupportedAlg, supportedSigAlgs, sig.Header.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
signedPayload, err := set.VerifySignature(ctx, jws)
|
signedPayload, err := set.VerifySignature(ctx, jws)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -179,18 +180,6 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(v4): Use the new jose.SignatureAlgorithm type directly, instead of string.
|
|
||||||
func toJoseSignatureAlgorithms(algorithms []string) []jose.SignatureAlgorithm {
|
|
||||||
out := make([]jose.SignatureAlgorithm, len(algorithms))
|
|
||||||
for i := range algorithms {
|
|
||||||
out[i] = jose.SignatureAlgorithm(algorithms[i])
|
|
||||||
}
|
|
||||||
if len(out) == 0 {
|
|
||||||
out = append(out, jose.RS256, jose.ES256, jose.PS256)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckExpiration(claims Claims, offset time.Duration) error {
|
func CheckExpiration(claims Claims, offset time.Duration) error {
|
||||||
expiration := claims.GetExpiration()
|
expiration := claims.GetExpiration()
|
||||||
if !time.Now().Add(offset).Before(expiration) {
|
if !time.Now().Add(offset).Before(expiration) {
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
tu "github.com/zitadel/oidc/v4/internal/testutil"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseToken(t *testing.T) {
|
func TestParseToken(t *testing.T) {
|
||||||
|
|
|
@ -11,13 +11,13 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/bmatcuk/doublestar/v4"
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
|
str "github.com/zitadel/oidc/v4/pkg/strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthRequest interface {
|
type AuthRequest interface {
|
||||||
|
@ -38,13 +38,6 @@ type AuthRequest interface {
|
||||||
Done() bool
|
Done() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthRequestSessionState should be implemented if [OpenID Connect Session Management](https://openid.net/specs/openid-connect-session-1_0.html) is supported
|
|
||||||
type AuthRequestSessionState interface {
|
|
||||||
// GetSessionState returns session_state.
|
|
||||||
// session_state is related to OpenID Connect Session Management.
|
|
||||||
GetSessionState() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Authorizer interface {
|
type Authorizer interface {
|
||||||
Storage() Storage
|
Storage() Storage
|
||||||
Decoder() httphelper.Decoder
|
Decoder() httphelper.Decoder
|
||||||
|
@ -62,19 +55,13 @@ type AuthorizeValidator interface {
|
||||||
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *IDTokenHintVerifier) (string, error)
|
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *IDTokenHintVerifier) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeResponseType struct {
|
|
||||||
Code string `schema:"code"`
|
|
||||||
State string `schema:"state,omitempty"`
|
|
||||||
SessionState string `schema:"session_state,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
|
func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
Authorize(w, r, authorizer)
|
Authorize(w, r, authorizer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthorizeCallbackHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
|
func authorizeCallbackHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
AuthorizeCallback(w, r, authorizer)
|
AuthorizeCallback(w, r, authorizer)
|
||||||
}
|
}
|
||||||
|
@ -95,29 +82,21 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
|
||||||
if authReq.RequestParam != "" && authorizer.RequestObjectSupported() {
|
if authReq.RequestParam != "" && authorizer.RequestObjectSupported() {
|
||||||
err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx))
|
err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AuthRequestError(w, r, nil, err, authorizer)
|
AuthRequestError(w, r, authReq, err, authorizer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if authReq.ClientID == "" {
|
if authReq.ClientID == "" {
|
||||||
AuthRequestError(w, r, nil, fmt.Errorf("auth request is missing client_id"), authorizer)
|
AuthRequestError(w, r, authReq, fmt.Errorf("auth request is missing client_id"), authorizer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if authReq.RedirectURI == "" {
|
if authReq.RedirectURI == "" {
|
||||||
AuthRequestError(w, r, nil, fmt.Errorf("auth request is missing redirect_uri"), authorizer)
|
AuthRequestError(w, r, authReq, fmt.Errorf("auth request is missing redirect_uri"), authorizer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
validation := ValidateAuthRequest
|
||||||
var client Client
|
if validater, ok := authorizer.(AuthorizeValidator); ok {
|
||||||
validation := func(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
|
validation = validater.ValidateAuthRequest
|
||||||
client, err = authorizer.Storage().GetClientByClientID(ctx, authReq.ClientID)
|
|
||||||
if err != nil {
|
|
||||||
return "", oidc.ErrInvalidRequestRedirectURI().WithDescription("unable to retrieve client by id").WithParent(err)
|
|
||||||
}
|
|
||||||
return ValidateAuthRequestClient(ctx, authReq, client, verifier)
|
|
||||||
}
|
|
||||||
if validator, ok := authorizer.(AuthorizeValidator); ok {
|
|
||||||
validation = validator.ValidateAuthRequest
|
|
||||||
}
|
}
|
||||||
userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx))
|
userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -133,6 +112,11 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
|
||||||
AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer)
|
AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
client, err := authorizer.Storage().GetClientByClientID(ctx, req.GetClientID())
|
||||||
|
if err != nil {
|
||||||
|
AuthRequestError(w, r, req, oidc.DefaultToServerError(err, "unable to retrieve client by id"), authorizer)
|
||||||
|
return
|
||||||
|
}
|
||||||
RedirectToLogin(req.GetID(), client, w, r)
|
RedirectToLogin(req.GetID(), client, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +152,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage
|
||||||
if requestObject.Issuer != requestObject.ClientID {
|
if requestObject.Issuer != requestObject.ClientID {
|
||||||
return oidc.ErrInvalidRequest().WithDescription("missing or wrong issuer in request")
|
return oidc.ErrInvalidRequest().WithDescription("missing or wrong issuer in request")
|
||||||
}
|
}
|
||||||
if !slices.Contains(requestObject.Audience, issuer) {
|
if !str.Contains(requestObject.Audience, issuer) {
|
||||||
return oidc.ErrInvalidRequest().WithDescription("issuer missing in audience")
|
return oidc.ErrInvalidRequest().WithDescription("issuer missing in audience")
|
||||||
}
|
}
|
||||||
keySet := &jwtProfileKeySet{storage: storage, clientID: requestObject.Issuer}
|
keySet := &jwtProfileKeySet{storage: storage, clientID: requestObject.Issuer}
|
||||||
|
@ -182,7 +166,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage
|
||||||
// CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request
|
// CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request
|
||||||
// and clears the `RequestParam` of the auth request
|
// and clears the `RequestParam` of the auth request
|
||||||
func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oidc.RequestObject) {
|
func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oidc.RequestObject) {
|
||||||
if slices.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 {
|
if str.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 {
|
||||||
authReq.Scopes = requestObject.Scopes
|
authReq.Scopes = requestObject.Scopes
|
||||||
}
|
}
|
||||||
if requestObject.RedirectURI != "" {
|
if requestObject.RedirectURI != "" {
|
||||||
|
@ -227,37 +211,26 @@ func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oi
|
||||||
authReq.RequestParam = ""
|
authReq.RequestParam = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed.
|
// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed
|
||||||
//
|
|
||||||
// Deprecated: Use [ValidateAuthRequestClient] to prevent querying for the Client twice.
|
|
||||||
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
|
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
|
||||||
ctx, span := tracer.Start(ctx, "ValidateAuthRequest")
|
ctx, span := tracer.Start(ctx, "ValidateAuthRequest")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
client, err := storage.GetClientByClientID(ctx, authReq.ClientID)
|
|
||||||
if err != nil {
|
|
||||||
return "", oidc.ErrInvalidRequestRedirectURI().WithDescription("unable to retrieve client by id").WithParent(err)
|
|
||||||
}
|
|
||||||
return ValidateAuthRequestClient(ctx, authReq, client, verifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateAuthRequestClient validates the Auth request against the passed client.
|
|
||||||
// If id_token_hint is part of the request, the subject of the token is returned.
|
|
||||||
func ValidateAuthRequestClient(ctx context.Context, authReq *oidc.AuthRequest, client Client, verifier *IDTokenHintVerifier) (sub string, err error) {
|
|
||||||
ctx, span := tracer.Start(ctx, "ValidateAuthRequestClient")
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)
|
authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
client, err := storage.GetClientByClientID(ctx, authReq.ClientID)
|
||||||
|
if err != nil {
|
||||||
|
return "", oidc.DefaultToServerError(err, "unable to retrieve client by id")
|
||||||
|
}
|
||||||
authReq.Scopes, err = ValidateAuthReqScopes(client, authReq.Scopes)
|
authReq.Scopes, err = ValidateAuthReqScopes(client, authReq.Scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil {
|
if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -277,30 +250,44 @@ func ValidateAuthReqPrompt(prompts []string, maxAge *uint) (_ *uint, err error)
|
||||||
return maxAge, nil
|
return maxAge, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAuthReqScopes validates the passed scopes and deletes any unsupported scopes.
|
// ValidateAuthReqScopes validates the passed scopes
|
||||||
// An error is returned if scopes is empty.
|
|
||||||
func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) {
|
func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) {
|
||||||
if len(scopes) == 0 {
|
if len(scopes) == 0 {
|
||||||
return nil, oidc.ErrInvalidRequest().
|
return nil, oidc.ErrInvalidRequest().
|
||||||
WithDescription("The scope of your request is missing. Please ensure some scopes are requested. " +
|
WithDescription("The scope of your request is missing. Please ensure some scopes are requested. " +
|
||||||
"If you have any questions, you may contact the administrator of the application.")
|
"If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
}
|
||||||
scopes = slices.DeleteFunc(scopes, func(scope string) bool {
|
openID := false
|
||||||
return !(scope == oidc.ScopeOpenID ||
|
for i := len(scopes) - 1; i >= 0; i-- {
|
||||||
scope == oidc.ScopeProfile ||
|
scope := scopes[i]
|
||||||
|
if scope == oidc.ScopeOpenID {
|
||||||
|
openID = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !(scope == oidc.ScopeProfile ||
|
||||||
scope == oidc.ScopeEmail ||
|
scope == oidc.ScopeEmail ||
|
||||||
scope == oidc.ScopePhone ||
|
scope == oidc.ScopePhone ||
|
||||||
scope == oidc.ScopeAddress ||
|
scope == oidc.ScopeAddress ||
|
||||||
scope == oidc.ScopeOfflineAccess) &&
|
scope == oidc.ScopeOfflineAccess) &&
|
||||||
!client.IsScopeAllowed(scope)
|
!client.IsScopeAllowed(scope) {
|
||||||
})
|
scopes[i] = scopes[len(scopes)-1]
|
||||||
|
scopes[len(scopes)-1] = ""
|
||||||
|
scopes = scopes[:len(scopes)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !openID {
|
||||||
|
return nil, oidc.ErrInvalidScope().WithDescription("The scope openid is missing in your request. " +
|
||||||
|
"Please ensure the scope openid is added to the request. " +
|
||||||
|
"If you have any questions, you may contact the administrator of the application.")
|
||||||
|
}
|
||||||
|
|
||||||
return scopes, nil
|
return scopes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkURIAgainstRedirects just checks aginst the valid redirect URIs and ignores
|
// checkURIAgainstRedirects just checks aginst the valid redirect URIs and ignores
|
||||||
// other factors.
|
// other factors.
|
||||||
func checkURIAgainstRedirects(client Client, uri string) error {
|
func checkURIAgainstRedirects(client Client, uri string) error {
|
||||||
if slices.Contains(client.RedirectURIs(), uri) {
|
if str.Contains(client.RedirectURIs(), uri) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if globClient, ok := client.(HasRedirectGlobs); ok {
|
if globClient, ok := client.(HasRedirectGlobs); ok {
|
||||||
|
@ -325,12 +312,12 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res
|
||||||
return oidc.ErrInvalidRequestRedirectURI().WithDescription("The redirect_uri is missing in the request. " +
|
return oidc.ErrInvalidRequestRedirectURI().WithDescription("The redirect_uri is missing in the request. " +
|
||||||
"Please ensure it is added to the request. If you have any questions, you may contact the administrator of the application.")
|
"Please ensure it is added to the request. If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
}
|
||||||
if client.ApplicationType() == ApplicationTypeNative {
|
|
||||||
return validateAuthReqRedirectURINative(client, uri)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(uri, "https://") {
|
if strings.HasPrefix(uri, "https://") {
|
||||||
return checkURIAgainstRedirects(client, uri)
|
return checkURIAgainstRedirects(client, uri)
|
||||||
}
|
}
|
||||||
|
if client.ApplicationType() == ApplicationTypeNative {
|
||||||
|
return validateAuthReqRedirectURINative(client, uri)
|
||||||
|
}
|
||||||
if err := checkURIAgainstRedirects(client, uri); err != nil {
|
if err := checkURIAgainstRedirects(client, uri); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -351,15 +338,12 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res
|
||||||
// ValidateAuthReqRedirectURINative validates the passed redirect_uri and response_type to the registered uris and client type
|
// ValidateAuthReqRedirectURINative validates the passed redirect_uri and response_type to the registered uris and client type
|
||||||
func validateAuthReqRedirectURINative(client Client, uri string) error {
|
func validateAuthReqRedirectURINative(client Client, uri string) error {
|
||||||
parsedURL, isLoopback := HTTPLoopbackOrLocalhost(uri)
|
parsedURL, isLoopback := HTTPLoopbackOrLocalhost(uri)
|
||||||
isCustomSchema := !(strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://"))
|
isCustomSchema := !strings.HasPrefix(uri, "http://")
|
||||||
if err := checkURIAgainstRedirects(client, uri); err == nil {
|
if err := checkURIAgainstRedirects(client, uri); err == nil {
|
||||||
if client.DevMode() {
|
if client.DevMode() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !isLoopback && strings.HasPrefix(uri, "https://") {
|
// The RedirectURIs are only valid for native clients when localhost or non-"http://"
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// The RedirectURIs are only valid for native clients when localhost or non-"http://" and "https://"
|
|
||||||
if isLoopback || isCustomSchema {
|
if isLoopback || isCustomSchema {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -389,11 +373,11 @@ func HTTPLoopbackOrLocalhost(rawURL string) (*url.URL, bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" {
|
if parsedURL.Scheme != "http" {
|
||||||
hostName := parsedURL.Hostname()
|
return nil, false
|
||||||
return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback()
|
|
||||||
}
|
}
|
||||||
return nil, false
|
hostName := parsedURL.Hostname()
|
||||||
|
return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAuthReqResponseType validates the passed response_type to the registered response types
|
// ValidateAuthReqResponseType validates the passed response_type to the registered response types
|
||||||
|
@ -483,70 +467,41 @@ func AuthResponse(authReq AuthRequest, authorizer Authorizer, w http.ResponseWri
|
||||||
AuthResponseToken(w, r, authReq, authorizer, client)
|
AuthResponseToken(w, r, authReq, authorizer, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthResponseCode handles the creation of a successful authentication response using an authorization code
|
// AuthResponseCode creates the successful code authentication response
|
||||||
func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) {
|
func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) {
|
||||||
ctx, span := tracer.Start(r.Context(), "AuthResponseCode")
|
ctx, span := tracer.Start(r.Context(), "AuthResponseCode")
|
||||||
defer span.End()
|
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
var err error
|
code, err := CreateAuthRequestCode(r.Context(), authReq, authorizer.Storage(), authorizer.Crypto())
|
||||||
if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
|
|
||||||
err = handleFormPostResponse(w, r, authReq, authorizer)
|
|
||||||
} else {
|
|
||||||
err = handleRedirectResponse(w, r, authReq, authorizer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AuthRequestError(w, r, authReq, err, authorizer)
|
AuthRequestError(w, r, authReq, err, authorizer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
codeResponse := struct {
|
||||||
|
Code string `schema:"code"`
|
||||||
|
State string `schema:"state,omitempty"`
|
||||||
|
}{
|
||||||
|
Code: code,
|
||||||
|
State: authReq.GetState(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// handleFormPostResponse processes the authentication response using form post method
|
if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
|
||||||
func handleFormPostResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error {
|
err := AuthResponseFormPost(w, authReq.GetRedirectURI(), &codeResponse, authorizer.Encoder())
|
||||||
codeResponse, err := BuildAuthResponseCodeResponsePayload(r.Context(), authReq, authorizer)
|
if err != nil {
|
||||||
|
AuthRequestError(w, r, authReq, err, authorizer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), &codeResponse, authorizer.Encoder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
AuthRequestError(w, r, authReq, err, authorizer)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return AuthResponseFormPost(w, authReq.GetRedirectURI(), codeResponse, authorizer.Encoder())
|
http.Redirect(w, r, callback, http.StatusFound)
|
||||||
}
|
|
||||||
|
|
||||||
// handleRedirectResponse processes the authentication response using the redirect method
|
|
||||||
func handleRedirectResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error {
|
|
||||||
callbackURL, err := BuildAuthResponseCallbackURL(r.Context(), authReq, authorizer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, callbackURL, http.StatusFound)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildAuthResponseCodeResponsePayload generates the authorization code response payload for the authentication request
|
|
||||||
func BuildAuthResponseCodeResponsePayload(ctx context.Context, authReq AuthRequest, authorizer Authorizer) (*CodeResponseType, error) {
|
|
||||||
code, err := CreateAuthRequestCode(ctx, authReq, authorizer.Storage(), authorizer.Crypto())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionState := ""
|
|
||||||
if authRequestSessionState, ok := authReq.(AuthRequestSessionState); ok {
|
|
||||||
sessionState = authRequestSessionState.GetSessionState()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CodeResponseType{
|
|
||||||
Code: code,
|
|
||||||
State: authReq.GetState(),
|
|
||||||
SessionState: sessionState,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildAuthResponseCallbackURL generates the callback URL for a successful authorization code response
|
|
||||||
func BuildAuthResponseCallbackURL(ctx context.Context, authReq AuthRequest, authorizer Authorizer) (string, error) {
|
|
||||||
codeResponse, err := BuildAuthResponseCodeResponsePayload(ctx, authReq, authorizer)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), codeResponse, authorizer.Encoder())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthResponseToken creates the successful token(s) authentication response
|
// AuthResponseToken creates the successful token(s) authentication response
|
||||||
|
|
|
@ -11,15 +11,15 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
|
|
||||||
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/oidc/v4/example/server/storage"
|
||||||
|
tu "github.com/zitadel/oidc/v4/internal/testutil"
|
||||||
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op/mock"
|
||||||
"github.com/zitadel/schema"
|
"github.com/zitadel/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -137,6 +137,11 @@ func TestValidateAuthRequest(t *testing.T) {
|
||||||
args{&oidc.AuthRequest{}, mock.NewMockStorageExpectValidClientID(t), nil},
|
args{&oidc.AuthRequest{}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
oidc.ErrInvalidRequest(),
|
oidc.ErrInvalidRequest(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"scope openid missing fails",
|
||||||
|
args{&oidc.AuthRequest{Scopes: []string{"profile"}}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
|
oidc.ErrInvalidScope(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"response_type missing fails",
|
"response_type missing fails",
|
||||||
args{&oidc.AuthRequest{Scopes: []string{"openid"}}, mock.NewMockStorageExpectValidClientID(t), nil},
|
args{&oidc.AuthRequest{Scopes: []string{"openid"}}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
|
@ -282,6 +287,16 @@ func TestValidateAuthReqScopes(t *testing.T) {
|
||||||
err: true,
|
err: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"scope openid missing fails",
|
||||||
|
args{
|
||||||
|
mock.NewClientExpectAny(t, op.ApplicationTypeWeb),
|
||||||
|
[]string{"email"},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"scope ok",
|
"scope ok",
|
||||||
args{
|
args{
|
||||||
|
@ -433,24 +448,6 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"code flow registered https loopback v4 native ok",
|
|
||||||
args{
|
|
||||||
"https://127.0.0.1:4200/callback",
|
|
||||||
mock.NewClientWithConfig(t, []string{"https://127.0.0.1/callback"}, op.ApplicationTypeNative, nil, false),
|
|
||||||
oidc.ResponseTypeCode,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code flow registered https loopback v6 native ok",
|
|
||||||
args{
|
|
||||||
"https://[::1]:4200/callback",
|
|
||||||
mock.NewClientWithConfig(t, []string{"https://[::1]/callback"}, op.ApplicationTypeNative, nil, false),
|
|
||||||
oidc.ResponseTypeCode,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"code flow unregistered http native fails",
|
"code flow unregistered http native fails",
|
||||||
args{
|
args{
|
||||||
|
@ -1090,34 +1087,6 @@ func TestAuthResponseCode(t *testing.T) {
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "success with state and session_state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequestWithSessionState{
|
|
||||||
AuthRequest: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
TransferState: "state1",
|
|
||||||
},
|
|
||||||
SessionState: "session_state1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantCode: http.StatusFound,
|
|
||||||
wantLocationHeader: "/auth/callback/?code=id1&session_state=session_state1&state=state1",
|
|
||||||
wantBody: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "success without state", // reproduce issue #415
|
name: "success without state", // reproduce issue #415
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -1225,133 +1194,6 @@ func Test_parseAuthorizeCallbackRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildAuthResponseCodeResponsePayload(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
authReq op.AuthRequest
|
|
||||||
authorizer func(*testing.T) op.Authorizer
|
|
||||||
}
|
|
||||||
type res struct {
|
|
||||||
wantCode string
|
|
||||||
wantState string
|
|
||||||
wantSessionState string
|
|
||||||
wantErr bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
res res
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "create code error",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{
|
|
||||||
returnErr: io.ErrClosedPipe,
|
|
||||||
})
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success with state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
TransferState: "state1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantCode: "id1",
|
|
||||||
wantState: "state1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success without state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
TransferState: "",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantCode: "id1",
|
|
||||||
wantState: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success with session_state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequestWithSessionState{
|
|
||||||
AuthRequest: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
TransferState: "state1",
|
|
||||||
},
|
|
||||||
SessionState: "session_state1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantCode: "id1",
|
|
||||||
wantState: "state1",
|
|
||||||
wantSessionState: "session_state1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := op.BuildAuthResponseCodeResponsePayload(context.Background(), tt.args.authReq, tt.args.authorizer(t))
|
|
||||||
if tt.res.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.res.wantCode, got.Code)
|
|
||||||
assert.Equal(t, tt.res.wantState, got.State)
|
|
||||||
assert.Equal(t, tt.res.wantSessionState, got.SessionState)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateAuthReqIDTokenHint(t *testing.T) {
|
func TestValidateAuthReqIDTokenHint(t *testing.T) {
|
||||||
token, _ := tu.ValidIDToken()
|
token, _ := tu.ValidIDToken()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -1382,231 +1224,3 @@ func TestValidateAuthReqIDTokenHint(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildAuthResponseCallbackURL(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
authReq op.AuthRequest
|
|
||||||
authorizer func(*testing.T) op.Authorizer
|
|
||||||
}
|
|
||||||
type res struct {
|
|
||||||
wantURL string
|
|
||||||
wantErr bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
res res
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "error when generating code response",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{
|
|
||||||
returnErr: io.ErrClosedPipe,
|
|
||||||
})
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error when generating callback URL",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
CallbackURI: "://invalid-url",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success with state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
CallbackURI: "https://example.com/callback",
|
|
||||||
TransferState: "state1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantURL: "https://example.com/callback?code=id1&state=state1",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success without state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
CallbackURI: "https://example.com/callback",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantURL: "https://example.com/callback?code=id1",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success with session_state",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequestWithSessionState{
|
|
||||||
AuthRequest: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
CallbackURI: "https://example.com/callback",
|
|
||||||
TransferState: "state1",
|
|
||||||
},
|
|
||||||
SessionState: "session_state1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantURL: "https://example.com/callback?code=id1&session_state=session_state1&state=state1",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success with existing query parameters",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
CallbackURI: "https://example.com/callback?param=value",
|
|
||||||
TransferState: "state1",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantURL: "https://example.com/callback?param=value&code=id1&state=state1",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success with fragment response mode",
|
|
||||||
args: args{
|
|
||||||
authReq: &storage.AuthRequest{
|
|
||||||
ID: "id1",
|
|
||||||
CallbackURI: "https://example.com/callback",
|
|
||||||
TransferState: "state1",
|
|
||||||
ResponseMode: "fragment",
|
|
||||||
},
|
|
||||||
authorizer: func(t *testing.T) op.Authorizer {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
storage := mock.NewMockStorage(ctrl)
|
|
||||||
storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
|
|
||||||
|
|
||||||
authorizer := mock.NewMockAuthorizer(ctrl)
|
|
||||||
authorizer.EXPECT().Storage().Return(storage)
|
|
||||||
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
|
|
||||||
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
|
||||||
return authorizer
|
|
||||||
},
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
wantURL: "https://example.com/callback#code=id1&state=state1",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := op.BuildAuthResponseCallbackURL(context.Background(), tt.args.authReq, tt.args.authorizer(t))
|
|
||||||
if tt.res.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if tt.res.wantURL != "" {
|
|
||||||
// Parse the URLs to compare components instead of direct string comparison
|
|
||||||
expectedURL, err := url.Parse(tt.res.wantURL)
|
|
||||||
require.NoError(t, err)
|
|
||||||
actualURL, err := url.Parse(got)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Compare the base parts (scheme, host, path)
|
|
||||||
assert.Equal(t, expectedURL.Scheme, actualURL.Scheme)
|
|
||||||
assert.Equal(t, expectedURL.Host, actualURL.Host)
|
|
||||||
assert.Equal(t, expectedURL.Path, actualURL.Path)
|
|
||||||
|
|
||||||
// Compare the fragment if any
|
|
||||||
assert.Equal(t, expectedURL.Fragment, actualURL.Fragment)
|
|
||||||
|
|
||||||
// For query parameters, compare them independently of order
|
|
||||||
expectedQuery := expectedURL.Query()
|
|
||||||
actualQuery := actualURL.Query()
|
|
||||||
|
|
||||||
assert.Equal(t, len(expectedQuery), len(actualQuery), "Query parameter count does not match")
|
|
||||||
|
|
||||||
for key, expectedValues := range expectedQuery {
|
|
||||||
actualValues, exists := actualQuery[key]
|
|
||||||
assert.True(t, exists, "Expected query parameter %s not found", key)
|
|
||||||
assert.ElementsMatch(t, expectedValues, actualValues, "Values for parameter %s don't match", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go get github.com/dmarkham/enumer
|
//go:generate go get github.com/dmarkham/enumer
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op/mock"
|
||||||
"github.com/zitadel/schema"
|
"github.com/zitadel/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ type Configuration interface {
|
||||||
EndSessionEndpoint() *Endpoint
|
EndSessionEndpoint() *Endpoint
|
||||||
KeysEndpoint() *Endpoint
|
KeysEndpoint() *Endpoint
|
||||||
DeviceAuthorizationEndpoint() *Endpoint
|
DeviceAuthorizationEndpoint() *Endpoint
|
||||||
CheckSessionIframe() *Endpoint
|
|
||||||
|
|
||||||
AuthMethodPostSupported() bool
|
AuthMethodPostSupported() bool
|
||||||
CodeMethodS256Supported() bool
|
CodeMethodS256Supported() bool
|
||||||
|
@ -50,9 +49,6 @@ type Configuration interface {
|
||||||
|
|
||||||
SupportedUILocales() []language.Tag
|
SupportedUILocales() []language.Tag
|
||||||
DeviceAuthorization() DeviceAuthorizationConfig
|
DeviceAuthorization() DeviceAuthorizationConfig
|
||||||
|
|
||||||
BackChannelLogoutSupported() bool
|
|
||||||
BackChannelLogoutSessionSupported() bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IssuerFromRequest func(r *http.Request) string
|
type IssuerFromRequest func(r *http.Request) string
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package op
|
package op
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
|
"github.com/zitadel/oidc/v4/pkg/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Crypto interface {
|
type Crypto interface {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
|
strs "github.com/zitadel/oidc/v4/pkg/strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceAuthorizationConfig struct {
|
type DeviceAuthorizationConfig struct {
|
||||||
|
@ -91,7 +91,10 @@ func createDeviceAuthorization(ctx context.Context, req *oidc.DeviceAuthorizatio
|
||||||
}
|
}
|
||||||
config := o.DeviceAuthorization()
|
config := o.DeviceAuthorization()
|
||||||
|
|
||||||
deviceCode, _ := NewDeviceCode(RecommendedDeviceCodeBytes)
|
deviceCode, err := NewDeviceCode(RecommendedDeviceCodeBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewStatusError(err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
userCode, err := NewUserCode([]rune(config.UserCode.CharSet), config.UserCode.CharAmount, config.UserCode.DashInterval)
|
userCode, err := NewUserCode([]rune(config.UserCode.CharSet), config.UserCode.CharAmount, config.UserCode.DashInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewStatusError(err, http.StatusInternalServerError)
|
return nil, NewStatusError(err, http.StatusInternalServerError)
|
||||||
|
@ -160,14 +163,11 @@ func ParseDeviceCodeRequest(r *http.Request, o OpenIDProvider) (*oidc.DeviceAuth
|
||||||
// results in a 22 character base64 encoded string.
|
// results in a 22 character base64 encoded string.
|
||||||
const RecommendedDeviceCodeBytes = 16
|
const RecommendedDeviceCodeBytes = 16
|
||||||
|
|
||||||
// NewDeviceCode generates a new cryptographically secure device code as a base64 encoded string.
|
|
||||||
// The length of the string is nBytes * 4 / 3.
|
|
||||||
// An error is never returned.
|
|
||||||
//
|
|
||||||
// TODO(v4): change return type to string alone.
|
|
||||||
func NewDeviceCode(nBytes int) (string, error) {
|
func NewDeviceCode(nBytes int) (string, error) {
|
||||||
bytes := make([]byte, nBytes)
|
bytes := make([]byte, nBytes)
|
||||||
rand.Read(bytes)
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
return "", fmt.Errorf("%w getting entropy for device code", err)
|
||||||
|
}
|
||||||
return base64.RawURLEncoding.EncodeToString(bytes), nil
|
return base64.RawURLEncoding.EncodeToString(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ func (r *DeviceAuthorizationState) GetAMR() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DeviceAuthorizationState) GetAudience() []string {
|
func (r *DeviceAuthorizationState) GetAudience() []string {
|
||||||
if !slices.Contains(r.Audience, r.ClientID) {
|
if !strs.Contains(r.Audience, r.ClientID) {
|
||||||
r.Audience = append(r.Audience, r.ClientID)
|
r.Audience = append(r.Audience, r.ClientID)
|
||||||
}
|
}
|
||||||
return r.Audience
|
return r.Audience
|
||||||
|
@ -344,11 +344,10 @@ func CreateDeviceTokenResponse(ctx context.Context, tokenRequest TokenRequest, c
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
TokenType: oidc.BearerToken,
|
TokenType: oidc.BearerToken,
|
||||||
ExpiresIn: uint64(validity.Seconds()),
|
ExpiresIn: uint64(validity.Seconds()),
|
||||||
Scope: tokenRequest.GetScopes(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(v4): remove type assertion
|
// TODO(v4): remove type assertion
|
||||||
if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && slices.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) {
|
if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && strs.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) {
|
||||||
response.IDToken, err = CreateIDToken(ctx, IssuerFromContext(ctx), idTokenRequest, client.IDTokenLifetime(), accessToken, "", creator.Storage(), client)
|
response.IDToken, err = CreateIDToken(ctx, IssuerFromContext(ctx), idTokenRequest, client.IDTokenLifetime(), accessToken, "", creator.Storage(), client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -13,12 +13,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
"github.com/muhlemmer/gu"
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/oidc/v4/example/server/storage"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_deviceAuthorizationHandler(t *testing.T) {
|
func Test_deviceAuthorizationHandler(t *testing.T) {
|
||||||
|
@ -145,11 +145,21 @@ func runWithRandReader(r io.Reader, f func()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewDeviceCode(t *testing.T) {
|
func TestNewDeviceCode(t *testing.T) {
|
||||||
for i := 1; i <= 32; i++ {
|
t.Run("reader error", func(t *testing.T) {
|
||||||
got, err := op.NewDeviceCode(i)
|
runWithRandReader(errReader{}, func() {
|
||||||
require.NoError(t, err)
|
_, err := op.NewDeviceCode(16)
|
||||||
assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i))
|
require.Error(t, err)
|
||||||
}
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different lengths, rand reader", func(t *testing.T) {
|
||||||
|
for i := 1; i <= 32; i++ {
|
||||||
|
got, err := op.NewDeviceCode(i)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewUserCode(t *testing.T) {
|
func TestNewUserCode(t *testing.T) {
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiscoverStorage interface {
|
type DiscoverStorage interface {
|
||||||
|
@ -45,7 +45,6 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di
|
||||||
EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer),
|
EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer),
|
||||||
JwksURI: config.KeysEndpoint().Absolute(issuer),
|
JwksURI: config.KeysEndpoint().Absolute(issuer),
|
||||||
DeviceAuthorizationEndpoint: config.DeviceAuthorizationEndpoint().Absolute(issuer),
|
DeviceAuthorizationEndpoint: config.DeviceAuthorizationEndpoint().Absolute(issuer),
|
||||||
CheckSessionIframe: config.CheckSessionIframe().Absolute(issuer),
|
|
||||||
ScopesSupported: Scopes(config),
|
ScopesSupported: Scopes(config),
|
||||||
ResponseTypesSupported: ResponseTypes(config),
|
ResponseTypesSupported: ResponseTypes(config),
|
||||||
GrantTypesSupported: GrantTypes(config),
|
GrantTypesSupported: GrantTypes(config),
|
||||||
|
@ -62,8 +61,6 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di
|
||||||
CodeChallengeMethodsSupported: CodeChallengeMethods(config),
|
CodeChallengeMethodsSupported: CodeChallengeMethods(config),
|
||||||
UILocalesSupported: config.SupportedUILocales(),
|
UILocalesSupported: config.SupportedUILocales(),
|
||||||
RequestParameterSupported: config.RequestObjectSupported(),
|
RequestParameterSupported: config.RequestObjectSupported(),
|
||||||
BackChannelLogoutSupported: config.BackChannelLogoutSupported(),
|
|
||||||
BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,17 +92,11 @@ func createDiscoveryConfigV2(ctx context.Context, config Configuration, storage
|
||||||
CodeChallengeMethodsSupported: CodeChallengeMethods(config),
|
CodeChallengeMethodsSupported: CodeChallengeMethods(config),
|
||||||
UILocalesSupported: config.SupportedUILocales(),
|
UILocalesSupported: config.SupportedUILocales(),
|
||||||
RequestParameterSupported: config.RequestObjectSupported(),
|
RequestParameterSupported: config.RequestObjectSupported(),
|
||||||
BackChannelLogoutSupported: config.BackChannelLogoutSupported(),
|
|
||||||
BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Scopes(c Configuration) []string {
|
func Scopes(c Configuration) []string {
|
||||||
provider, ok := c.(*Provider)
|
return DefaultSupportedScopes // TODO: config
|
||||||
if ok && provider.config.SupportedScopes != nil {
|
|
||||||
return provider.config.SupportedScopes
|
|
||||||
}
|
|
||||||
return DefaultSupportedScopes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResponseTypes(c Configuration) []string {
|
func ResponseTypes(c Configuration) []string {
|
||||||
|
@ -140,7 +131,7 @@ func GrantTypes(c Configuration) []oidc.GrantType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SubjectTypes(c Configuration) []string {
|
func SubjectTypes(c Configuration) []string {
|
||||||
return []string{"public"} // TODO: config
|
return []string{"public"} //TODO: config
|
||||||
}
|
}
|
||||||
|
|
||||||
func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string {
|
func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string {
|
||||||
|
|
|
@ -6,14 +6,14 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
|
"github.com/zitadel/oidc/v4/pkg/op/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDiscover(t *testing.T) {
|
func TestDiscover(t *testing.T) {
|
||||||
|
@ -81,11 +81,6 @@ func Test_scopes(t *testing.T) {
|
||||||
args{},
|
args{},
|
||||||
op.DefaultSupportedScopes,
|
op.DefaultSupportedScopes,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"custom scopes",
|
|
||||||
args{newTestProvider(&op.Config{SupportedScopes: []string{"test1", "test2"}})},
|
|
||||||
[]string{"test1", "test2"},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package op_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEndpoint_Path(t *testing.T) {
|
func TestEndpoint_Path(t *testing.T) {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrAuthRequest interface {
|
type ErrAuthRequest interface {
|
||||||
|
@ -46,12 +46,6 @@ func AuthRequestError(w http.ResponseWriter, r *http.Request, authReq ErrAuthReq
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.State = authReq.GetState()
|
e.State = authReq.GetState()
|
||||||
var sessionState string
|
|
||||||
authRequestSessionState, ok := authReq.(AuthRequestSessionState)
|
|
||||||
if ok {
|
|
||||||
sessionState = authRequestSessionState.GetSessionState()
|
|
||||||
}
|
|
||||||
e.SessionState = sessionState
|
|
||||||
var responseMode oidc.ResponseMode
|
var responseMode oidc.ResponseMode
|
||||||
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
|
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
|
||||||
responseMode = rm.GetResponseMode()
|
responseMode = rm.GetResponseMode()
|
||||||
|
@ -98,12 +92,6 @@ func TryErrorRedirect(ctx context.Context, authReq ErrAuthRequest, parent error,
|
||||||
}
|
}
|
||||||
|
|
||||||
e.State = authReq.GetState()
|
e.State = authReq.GetState()
|
||||||
var sessionState string
|
|
||||||
authRequestSessionState, ok := authReq.(AuthRequestSessionState)
|
|
||||||
if ok {
|
|
||||||
sessionState = authRequestSessionState.GetSessionState()
|
|
||||||
}
|
|
||||||
e.SessionState = sessionState
|
|
||||||
var responseMode oidc.ResponseMode
|
var responseMode oidc.ResponseMode
|
||||||
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
|
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
|
||||||
responseMode = rm.GetResponseMode()
|
responseMode = rm.GetResponseMode()
|
||||||
|
|
|
@ -11,9 +11,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"github.com/zitadel/schema"
|
"github.com/zitadel/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -428,8 +428,7 @@ func TestTryErrorRedirect(t *testing.T) {
|
||||||
parent: oidc.ErrInteractionRequired().WithDescription("sign in"),
|
parent: oidc.ErrInteractionRequired().WithDescription("sign in"),
|
||||||
},
|
},
|
||||||
want: &Redirect{
|
want: &Redirect{
|
||||||
Header: make(http.Header),
|
URL: "http://example.com/callback?error=interaction_required&error_description=sign+in&state=state1",
|
||||||
URL: "http://example.com/callback?error=interaction_required&error_description=sign+in&state=state1",
|
|
||||||
},
|
},
|
||||||
wantLog: `{
|
wantLog: `{
|
||||||
"level":"WARN",
|
"level":"WARN",
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
httphelper "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KeyProvider interface {
|
type KeyProvider interface {
|
||||||
|
|
|
@ -7,13 +7,13 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
|
"github.com/zitadel/oidc/v4/pkg/op/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKeys(t *testing.T) {
|
func TestKeys(t *testing.T) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Authorizer)
|
// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Authorizer)
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package mock is a generated GoMock package.
|
||||||
package mock
|
package mock
|
||||||
|
@ -9,9 +9,9 @@ import (
|
||||||
slog "log/slog"
|
slog "log/slog"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
http "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
|
|
||||||
op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
http "github.com/zitadel/oidc/v4/pkg/http"
|
||||||
|
op "github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAuthorizer is a mock of Authorizer interface.
|
// MockAuthorizer is a mock of Authorizer interface.
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/zitadel/schema"
|
"github.com/zitadel/schema"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAuthorizer(t *testing.T) op.Authorizer {
|
func NewAuthorizer(t *testing.T) op.Authorizer {
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
"github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
"github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClient(t *testing.T) op.Client {
|
func NewClient(t *testing.T) op.Client {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Client)
|
// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Client)
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package mock is a generated GoMock package.
|
||||||
package mock
|
package mock
|
||||||
|
@ -8,9 +8,9 @@ import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
|
|
||||||
op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
oidc "github.com/zitadel/oidc/v4/pkg/oidc"
|
||||||
|
op "github.com/zitadel/oidc/v4/pkg/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockClient is a mock of Client interface.
|
// MockClient is a mock of Client interface.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Configuration)
|
// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Configuration)
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package mock is a generated GoMock package.
|
||||||
package mock
|
package mock
|
||||||
|
@ -8,8 +8,8 @@ import (
|
||||||
http "net/http"
|
http "net/http"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
op "github.com/zitadel/oidc/v4/pkg/op"
|
||||||
language "golang.org/x/text/language"
|
language "golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,48 +78,6 @@ func (mr *MockConfigurationMockRecorder) AuthorizationEndpoint() *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackChannelLogoutSessionSupported mocks base method.
|
|
||||||
func (m *MockConfiguration) BackChannelLogoutSessionSupported() bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "BackChannelLogoutSessionSupported")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackChannelLogoutSessionSupported indicates an expected call of BackChannelLogoutSessionSupported.
|
|
||||||
func (mr *MockConfigurationMockRecorder) BackChannelLogoutSessionSupported() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackChannelLogoutSessionSupported", reflect.TypeOf((*MockConfiguration)(nil).BackChannelLogoutSessionSupported))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackChannelLogoutSupported mocks base method.
|
|
||||||
func (m *MockConfiguration) BackChannelLogoutSupported() bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "BackChannelLogoutSupported")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackChannelLogoutSupported indicates an expected call of BackChannelLogoutSupported.
|
|
||||||
func (mr *MockConfigurationMockRecorder) BackChannelLogoutSupported() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackChannelLogoutSupported", reflect.TypeOf((*MockConfiguration)(nil).BackChannelLogoutSupported))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckSessionIframe mocks base method.
|
|
||||||
func (m *MockConfiguration) CheckSessionIframe() *op.Endpoint {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CheckSessionIframe")
|
|
||||||
ret0, _ := ret[0].(*op.Endpoint)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckSessionIframe indicates an expected call of CheckSessionIframe.
|
|
||||||
func (mr *MockConfigurationMockRecorder) CheckSessionIframe() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckSessionIframe", reflect.TypeOf((*MockConfiguration)(nil).CheckSessionIframe))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeMethodS256Supported mocks base method.
|
// CodeMethodS256Supported mocks base method.
|
||||||
func (m *MockConfiguration) CodeMethodS256Supported() bool {
|
func (m *MockConfiguration) CodeMethodS256Supported() bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: DiscoverStorage)
|
// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: DiscoverStorage)
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package mock is a generated GoMock package.
|
||||||
package mock
|
package mock
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
//go:generate go install github.com/golang/mock/mockgen@v1.6.0
|
//go:generate go install github.com/golang/mock/mockgen@v1.6.0
|
||||||
//go:generate mockgen -package mock -destination ./storage.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Storage
|
//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v4/pkg/op Storage
|
||||||
//go:generate mockgen -package mock -destination ./authorizer.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Authorizer
|
//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v4/pkg/op Authorizer
|
||||||
//go:generate mockgen -package mock -destination ./client.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Client
|
//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v4/pkg/op Client
|
||||||
//go:generate mockgen -package mock -destination ./glob.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op HasRedirectGlobs
|
//go:generate mockgen -package mock -destination ./glob.mock.go github.com/zitadel/oidc/v4/pkg/op HasRedirectGlobs
|
||||||
//go:generate mockgen -package mock -destination ./configuration.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Configuration
|
//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v4/pkg/op Configuration
|
||||||
//go:generate mockgen -package mock -destination ./discovery.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op DiscoverStorage
|
//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v4/pkg/op DiscoverStorage
|
||||||
//go:generate mockgen -package mock -destination ./signer.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op SigningKey,Key
|
//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v4/pkg/op SigningKey,Key
|
||||||
//go:generate mockgen -package mock -destination ./key.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op KeyProvider
|
//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/v4/pkg/op KeyProvider
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue