[
  {
    "path": ".codecov.yml",
    "content": "ignore:\n - \"tests/**\"\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [ramosbugs]\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\n\n# Controls when the workflow will run\non:\n  # Triggers the workflow on push or pull request events but only for the main branch\n  push: {}\n  pull_request: {}\n  schedule:\n    # Run daily to catch breakages in new Rust versions as well as new cargo audit findings.\n    - cron: '0 16 * * *'\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\nenv:\n  CARGO_TERM_COLOR: always\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job called \"build\"\n  test:\n    # The type of runner that the job will run on\n    runs-on: ${{ matrix.rust_os.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        rust_os:\n          - { rust: 1.65.0, os: ubuntu-22.04 }\n          - { rust: stable, os: ubuntu-22.04 }\n          - { rust: beta, os: ubuntu-22.04 }\n          - { rust: nightly, os: ubuntu-22.04 }\n\n    env:\n      CARGO_NET_GIT_FETCH_WITH_CLI: \"true\"\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - uses: actions/checkout@v2\n\n      - name: Print git branch name\n        run: git rev-parse --abbrev-ref HEAD\n\n      - run: git show-ref | grep $(git rev-parse HEAD)\n\n      - name: Install Rust toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: ${{ matrix.rust_os.rust }}\n          override: true\n          components: clippy, rustfmt\n          target: wasm32-unknown-unknown\n\n      # Newer dependency versions may not support rustc 1.65, so we use a Cargo.lock file for those\n      # builds.\n      - name: Use Rust 1.65 lockfile\n        if: ${{ matrix.rust_os.rust == '1.65.0' }}\n        run: |\n          cp Cargo-1.65.lock Cargo.lock\n          echo \"CARGO_LOCKED=--locked\" >> $GITHUB_ENV\n\n      - name: Run tests\n        run: cargo ${CARGO_LOCKED} test --tests --examples\n      - name: Doc tests\n        run: |\n          cargo ${CARGO_LOCKED} test --doc\n          cargo ${CARGO_LOCKED} test --doc --no-default-features\n          cargo ${CARGO_LOCKED} test --doc --all-features\n      - name: Test with all features enabled\n        run: cargo ${CARGO_LOCKED} test --all-features\n\n      - name: Check fmt\n        if: ${{ matrix.rust_os.rust == '1.65.0' }}\n        run: cargo ${CARGO_LOCKED} fmt --all -- --check\n\n      - name: Clippy\n        if: ${{ matrix.rust_os.rust == '1.65.0' }}\n        run: cargo ${CARGO_LOCKED} clippy --all --all-features -- --deny warnings\n\n      - name: Audit\n        if: ${{ matrix.rust_os.rust == 'stable' }}\n        run: |\n          cargo install --force cargo-audit\n          # The chrono thread safety issue doesn't affect this crate since the crate does not rely\n          # on the system's local time zone, only UTC. See:\n          # https://github.com/chronotope/chrono/issues/499#issuecomment-946388161\n          # FIXME(ramosbugs/openidconnect-rs#140): upgrade `rsa` once fix for RUSTSEC-2023-0071 is\n          # available.\n          cargo ${CARGO_LOCKED} audit \\\n            --ignore RUSTSEC-2020-0159 \\\n            --ignore RUSTSEC-2023-0071\n\n      - name: Check WASM build\n        run: cargo ${CARGO_LOCKED} check --target wasm32-unknown-unknown\n\n  coverage:\n    runs-on: ubuntu-latest\n    container:\n      image: xd009642/tarpaulin:0.32.0\n      options: --security-opt seccomp=unconfined\n    steps:\n      - uses: actions/checkout@v2\n      - name: Generate code coverage\n        run: |\n          cargo ${CARGO_LOCKED} tarpaulin --verbose --all-features --timeout 120 --out Xml\n      - name: Upload to codecov.io\n        uses: codecov/codecov-action@v3\n        with:\n          fail_ci_if_error: false\n"
  },
  {
    "path": ".gitignore",
    "content": "/target/\n**/*.rs.bk\nCargo.lock\n*~\n.DS_Store\n.idea/**\n*.iml\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"openidconnect\"\nversion = \"4.0.1\"\nauthors = [\"David A. Ramos <ramos@cs.stanford.edu>\"]\ndescription = \"OpenID Connect library\"\nkeywords = [\"openid\", \"oidc\", \"oauth2\", \"authentication\", \"auth\"]\nlicense = \"MIT\"\nrepository = \"https://github.com/ramosbugs/openidconnect-rs\"\nedition = \"2021\"\nreadme = \"README.md\"\nrust-version = \"1.65\"\n\n[package.metadata.docs.rs]\nall-features = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[features]\naccept-rfc3339-timestamps = []\naccept-string-booleans = []\ncurl = [\"oauth2/curl\"]\ndefault = [\"reqwest\", \"rustls-tls\"]\nnative-tls = [\"oauth2/native-tls\"]\nreqwest = [\"oauth2/reqwest\"]\nreqwest-blocking = [\"oauth2/reqwest-blocking\"]\nrustls-tls = [\"oauth2/rustls-tls\"]\ntiming-resistant-secret-traits = [\"oauth2/timing-resistant-secret-traits\"]\nureq = [\"oauth2/ureq\"]\n\n[[example]]\nname = \"gitlab\"\nrequired-features = [\"reqwest-blocking\"]\n\n[[example]]\nname = \"google\"\nrequired-features = [\"reqwest-blocking\"]\n\n[[example]]\nname = \"okta_device_grant\"\nrequired-features = [\"reqwest-blocking\"]\n\n[dependencies]\nbase64 = \"0.22\"\n# Disable 'time' dependency since it triggers RUSTSEC-2020-0071 and we don't need it.\nchrono = { version = \"0.4\", default-features = false, features = [\n    \"clock\",\n    \"std\",\n    \"wasmbind\"\n] }\nthiserror = \"1.0\"\nhttp = \"1.0\"\nitertools = \"0.14\"\nlog = \"0.4\"\noauth2 = { version = \"5.0.0\", default-features = false }\nrand = \"0.8.5\"\nhmac = \"0.12.1\"\nrsa = \"0.9.2\"\nsha2 = { version = \"0.10.6\", features = [\"oid\"] } # Object ID needed for pkcs1v15 padding\np256 = \"0.13.2\"\np384 = \"0.13.0\"\ndyn-clone = \"1.0.10\"\nserde = \"1.0\"\nserde_json = \"1.0\"\nserde_path_to_error = \"0.1\"\nserde_plain = \"1.0\"\nserde_with = \"3\"\nserde-value = \"0.7\"\nurl = { version = \"2.4\", features = [\"serde\"] }\nsubtle = \"2.4\"\ned25519-dalek = { version = \"2.0.0\", features = [\"pem\"] }\n\n[dev-dependencies]\ncolor-backtrace = { version = \"0.5\" }\nenv_logger = \"0.9\"\npretty_assertions = \"1.0\"\nreqwest = { version = \"0.12\", features = [\"blocking\", \"rustls-tls\"], default-features = false }\nretry = \"1.0\"\nanyhow = \"1.0\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 David Ramos\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) Library for Rust\n\n[![crates.io](https://img.shields.io/crates/v/openidconnect.svg)](https://crates.io/crates/openidconnect)\n[![docs.rs](https://docs.rs/openidconnect/badge.svg)](https://docs.rs/openidconnect)\n[![Build Status](https://github.com/ramosbugs/openidconnect-rs/actions/workflows/main.yml/badge.svg)](https://github.com/ramosbugs/openidconnect-rs/actions/workflows/main.yml)\n[![codecov](https://codecov.io/gh/ramosbugs/openidconnect-rs/branch/main/graph/badge.svg)](https://codecov.io/gh/ramosbugs/openidconnect-rs)\n\nThis library provides extensible, strongly-typed interfaces for the OpenID\nConnect protocol, which can be used to authenticate users via\n[Google](https://developers.google.com/identity/openid-connect/openid-connect),\n[GitLab](https://docs.gitlab.com/ee/integration/openid_connect_provider.html),\n[Microsoft](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc),\nand [many other providers](https://openid.net/certification/#OPENID-OP-P).\n\nAPI documentation and examples are available on [docs.rs](https://docs.rs/openidconnect).\n\n## Minimum Supported Rust Version (MSRV)\n\nThe MSRV for *3.3* and newer releases of this crate is Rust **1.65**.\n\nThe MSRV for *3.0* to *3.2* releases of this crate is Rust **1.57**.\n\nThe MSRV for *2.x* releases of this crate is Rust 1.45.\n\nSince the 3.0.0 release, this crate maintains a policy of supporting\nRust releases going back at least 6 months. Changes that break compatibility with Rust releases\nolder than 6 months will no longer be considered SemVer breaking changes and will not result in a\nnew major version number for this crate. MSRV changes will coincide with minor version updates\nand will not happen in patch releases.\n\n## Standards\n\n* [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)\n  * Supported features:\n    * Relying Party flows: code, implicit, hybrid\n    * Standard claims\n    * UserInfo endpoint\n    * RSA, HMAC, ECDSA (P-256/P-384 curves) and EdDSA (Ed25519 curve) ID token verification\n  * Unsupported features:\n    * Aggregated and distributed claims\n    * Passing request parameters as JWTs\n    * Verification of the `azp` claim (see [discussion](https://bitbucket.org/openid/connect/issues/973/))\n    * ECDSA-based ID token verification using the P-521 curve\n    * JSON Web Encryption (JWE)\n* [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)\n  * Supported features:\n    * Provider Metadata\n  * Unsupported features:\n    * WebFinger\n* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html)\n  * Supported features:\n    * Client Metadata\n    * Client Registration endpoint\n  * Unsupported features:\n    * Client Configuration endpoint\n* [OpenID Connect RP-Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)\n* [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662)\n* [OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009)\n* [OAuth 2.0 Device Authorization Grant](https://www.rfc-editor.org/rfc/rfc8628)\n"
  },
  {
    "path": "UPGRADE.md",
    "content": "# Upgrade Guide\n\n## Upgrading from 3.x to 4.x\n\nThe 4.0 release includes breaking changes to address several long-standing API issues, along with\na few minor improvements. Consider following the tips below to help ensure a smooth upgrade\nprocess. This document is not exhaustive but covers the breaking changes most likely to affect\ntypical uses of this crate.\n\n### Add typestate generic types to `Client`\n\nEach auth flow depends on one or more server endpoints. For example, the\nauthorization code flow depends on both an authorization endpoint and a token endpoint, while the\nclient credentials flow only depends on a token endpoint. Previously, it was possible to instantiate\na `Client` without a token endpoint and then attempt to use an auth flow that required a token\nendpoint, leading to errors at runtime. Also, the authorization endpoint was always required, even\nfor auth flows that do not use it.\n\nIn the 4.0 release, all endpoints are optional.\n[Typestates](https://cliffle.com/blog/rust-typestate/) are used to statically track, at compile\ntime, which endpoints' setters (e.g., `set_auth_uri()`) have been called. Auth flows that depend on\nan endpoint cannot be used without first calling the corresponding setter, which is enforced by the\ncompiler's type checker. This guarantees that certain errors will not arise at runtime.\n\nWhen using [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)\n(i.e., `Client::from_provider_metadata()`),\neach discoverable endpoint is set to a conditional typestate (`EndpointMaybeSet`). This is because\nit cannot be determined at compile time whether each of these endpoints will be returned by the\nOpenID Provider. When the conditional typestate is set, endpoints can  be used via fallible methods\nthat return `Err(ConfigurationError::MissingUrl(_))` if an endpoint has not been set.\n\nThere are three possible typestates, each implementing the `EndpointState` trait:\n* `EndpointNotSet`: the corresponding endpoint has **not** been set and cannot be used.\n* `EndpointSet`: the corresponding endpoint **has** been set and is ready to be used.\n* `EndpointMaybeSet`: the corresponding endpoint **may have** been set and can be used via fallible\n   methods that return `Result<_, ConfigurationError>`.\n\nThe following code changes are required to support the new interface:\n1. Update calls to\n   [`Client::new()`](https://docs.rs/openidconnect/latest/openidconnect/struct.Client.html#method.new)\n   to use the three-argument constructor (which accepts only a `ClientId`, `IssuerUrl`, and\n   `JsonWebKeySet`). Use the `set_auth_uri()`, `set_token_uri()`, `set_user_info_url()`, and\n   `set_client_secret()` methods to set the authorization endpoint, token endpoint, user info\n   endpoint, and client secret, respectively, if applicable to your application's auth flows.\n2. If using `Client::from_provider_metadata()`, update call sites that use each auth flow\n   (e.g., `Client::exchange_code()`) to handle the possibility of a `ConfigurationError` if the\n   corresponding endpoint was not specified in the provider metadata.\n3. If required by your usage of the `Client` or `CoreClient` types (i.e., if you see related\n   compiler errors), add the following generic parameters:\n   ```rust\n   HasAuthUrl: EndpointState,\n   HasDeviceAuthUrl: EndpointState,\n   HasIntrospectionUrl: EndpointState,\n   HasRevocationUrl: EndpointState,\n   HasTokenUrl: EndpointState,\n   HasUserInfoUrl: EndpointState,\n   ```\n   For example, if you store a `CoreClient` within another data type, you may need to annotate it as\n   `CoreClient<EndpointSet, EndpointNotSet, EndpointNotSet, EndpointNotSet, EndpointSet, EndpointNotSet>`\n   if it has both an authorization endpoint and a token endpoint set. Compiler error messages will\n   likely guide you to the appropriate combination of typestates.\n   \n   If, instead of using `CoreClient`, you are directly using `Client` with a different set of type\n   parameters, you will need to append the five generic typestate parameters. For example, replace:\n   ```rust\n   type SpecialClient = Client<\n       EmptyAdditionalClaims,\n       CoreAuthDisplay,\n       CoreGenderClaim,\n       CoreJweContentEncryptionAlgorithm,\n       CoreJwsSigningAlgorithm,\n       CoreJsonWebKeyType,\n       CoreJsonWebKeyUse,\n       CoreJsonWebKey,\n       CoreAuthPrompt,\n       StandardErrorResponse<CoreErrorResponseType>,\n       SpecialTokenResponse,\n       CoreTokenType,\n       CoreTokenIntrospectionResponse,\n       CoreRevocableToken,\n       CoreRevocationErrorResponse,\n   >;\n   ```\n   with:\n   ```rust\n   type SpecialClient<\n       HasAuthUrl = EndpointNotSet,\n       HasDeviceAuthUrl = EndpointNotSet,\n       HasIntrospectionUrl = EndpointNotSet,\n       HasRevocationUrl = EndpointNotSet,\n       HasTokenUrl = EndpointNotSet,\n       HasUserInfoUrl = EndpointNotSet,\n   > = Client<\n       EmptyAdditionalClaims,\n       CoreAuthDisplay,\n       CoreGenderClaim,\n       CoreJweContentEncryptionAlgorithm,\n       CoreJsonWebKey,\n       CoreAuthPrompt,\n       StandardErrorResponse<CoreErrorResponseType>,\n       SpecialTokenResponse,\n       CoreTokenIntrospectionResponse,\n       CoreRevocableToken,\n       CoreRevocationErrorResponse,\n       HasAuthUrl,\n       HasDeviceAuthUrl,\n       HasIntrospectionUrl,\n       HasRevocationUrl,\n       HasTokenUrl,\n       HasUserInfoUrl,\n   >;\n   ```\n   The default values (`= EndpointNotSet`) are optional but often helpful since they will allow you\n   to instantiate a client using `SpecialClient::new()` instead of having to specify\n   `SpecialClient::<EndpointNotSet, EndpointNotSet, EndpointNotSet, EndpointNotSet, EndpointNotSet, EndpointNotSet>::new()`.\n\n   Also note that the `CoreJwsSigningAlgorithm` (`JS`), `CoreJsonWebKeyType` (`JT`),\n   `CoreJsonWebKeyUse` (`JU`), and `CoreTokenType` (`TT`) type parameters have been removed (see\n   below) since they are now implied by the `JsonWebKey` (`K`) and `TokenResponse`\n   (`TR`)/`TokenIntrospectionResponse` (`TIR`) type parameters.\n\n### Replace JWT-related generic traits with associated types\n\nPreviously, the `JsonWebKey` trait had the following generic type parameters:\n```rust\nJS: JwsSigningAlgorithm<JT>,\nJT: JsonWebKeyType,\nJU: JsonWebKeyUse,\n```\n\nIn the 4.0 release, these generic type parameters have been removed and replaced with two associated\ntypes:\n```rust\n/// Allowed key usage.\ntype KeyUse: JsonWebKeyUse;\n\n/// JSON Web Signature (JWS) algorithm.\ntype SigningAlgorithm: JwsSigningAlgorithm;\n```\n\nThe `JT` type parameter was similarly removed from the `JwsSigningAlgorithm` trait and replaced\nwith an associated type:\n```rust\n/// Key type (e.g., RSA).\ntype KeyType: JsonWebKeyType;\n```\n\nSimilar changes were made to the lesser-used `PrivateSigningKey` and `JweContentEncryptionAlgorithm`\ntraits.\n\nWith the conversion to associated types, many generic type parameters throughout this crate became\nredundant and were removed in the 4.0 release. For example, the `Client` no longer needs the\n`JS`, `JT`, or `JU` parameters, which are implied by the `JsonWebKey` (`K`) type.\n\n### Rename endpoint getters and setters for consistency\n\nThe 2.0 release aimed to align the naming of each endpoint with the terminology used in the relevant\nRFC. For example, [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1) uses the\nterm \"endpoint URI\" to refer to the authorization and token endpoints, while\n[RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009#section-2) refers to the\n\"token revocation endpoint URL,\" and\n[RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662#section-2) uses neither \"URI\" nor \"URL\"\nto describe the introspection endpoint. However, the renaming in 2.0 was both internally\ninconsistent, and inconsistent with the specs.\n\nIn 4.0, the `Client`'s getters and setters for each endpoint are now named as follows:\n* Authorization endpoint: `auth_uri()`/`set_auth_uri()` (newly added)\n* Token endpoint: `token_uri()`/`set_token_uri()` (newly added)\n* Redirect: `redirect_uri()`/`set_redirect_uri()` (no change to setter)\n* Revocation endpoint: `revocation_url()`/`set_revocation_url()`\n* Introspection endpoint: `introspection_url()`/`set_introspection_url()`\n* Device authorization endpoint: `device_authorization_url()`/`set_device_authorization_url()`\n* User info: `user_info_url()`/`set_user_info_url()` (newly added)\n\n### Use stateful HTTP clients\n\nPreviously, the HTTP clients provided by this crate were stateless. For example, the\n`openidconnect::reqwest::async_http_client()` method would instantiate a new `reqwest::Client` for\neach request. This meant that TCP connections could not be reused across requests, and customizing\nHTTP clients (e.g., adding a custom request header to every request) was inconvenient.\n\nThe 4.0 release introduces two new traits: `AsyncHttpClient` and `SyncHttpClient`. Each\n`request_async()` and `request()` method now accepts a reference to a type that implements these\ntraits, respectively, rather than a function type.\n\n> [!WARNING]\n> To prevent\n[SSRF](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)\nvulnerabilities, be sure to configure the HTTP client **not to follow redirects**. For example, use\n> [`redirect::Policy::none`](https://docs.rs/reqwest/latest/reqwest/redirect/struct.Policy.html#method.none)\n> when using `reqwest`, or\n> [`redirects(0)`](https://docs.rs/ureq/latest/ureq/struct.AgentBuilder.html#method.redirects)\n> when using `ureq`.\n\nThe `AsyncHttpClient` trait is implemented for the following types:\n* `reqwest::Client` (when the default `reqwest` feature is enabled)\n* Any function type that implements:\n  ```rust\n  Fn(HttpRequest) -> F\n  where\n    E: std::error::Error + 'static,\n    F: Future<Output = Result<HttpResponse, E>>,\n  ```\n  To implement a custom asynchronous HTTP client, either directly implement the `AsyncHttpClient`\n  trait, or use a function that implements the signature above.\n\nThe `SyncHttpClient` trait is implemented for the following types:\n* `reqwest::blocking::Client` (when the `reqwest-blocking` feature is enabled; see below)\n* `ureq::Agent` (when the `ureq` feature is enabled)\n* `openidconnect::CurlHttpClient` (when the `curl` feature is enabled)\n* Any function type that implements:\n  ```rust\n  Fn(HttpRequest) -> Result<HttpResponse, E>\n  where\n    E: std::error::Error + 'static,\n  ```\n  To implement a custom synchronous HTTP client, either directly implement the `SyncHttpClient`\n  trait, or use a function that implements the signature above.\n\n### Upgrade `http` to 1.0 and `reqwest` to 0.12\n\nThe 4.0 release of this crate depends on the new stable [`http`](https://docs.rs/http/latest/http/)\n1.0 release, which affects various public interfaces. In particular, `reqwest` has been upgraded\nto 0.12, which uses `http` 1.0.\n\n### Enable the `reqwest-blocking` feature to use the synchronous `reqwest` HTTP client\n\nIn 4.0, enabling the (default) `reqwest` feature also enabled `reqwest`'s `blocking` feature.\nTo reduce dependencies and improve compilation speed, the `reqwest` feature now only enables\n`reqwest`'s asynchronous (non-blocking) client. To use the synchronous (blocking) client, enable the\n`reqwest-blocking` feature in `Cargo.toml`:\n```toml\nopenidconnect = { version = \"4\", features = [\"reqwest-blocking\" ] }\n```\n\n### Use `http::{Request, Response}` for custom HTTP clients\n\nThe `HttpRequest` and `HttpResponse` structs have been replaced with type aliases to\n[`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) and\n[`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html), respectively.\nCustom HTTP clients will need to be updated to use the `http` types. See the\n[`reqwest` client implementations](https://github.com/ramosbugs/oauth2-rs/blob/23b952b23e6069525bc7e4c4f2c4924b8d28ce3a/src/reqwest.rs)\nin the underlying `oauth2` crate for an example.\n\n### Replace `TT` generic type parameter in `OAuth2TokenResponse` with associated type\n\nPreviously, the `TokenResponse`, `OAuth2TokenResponse`, and `TokenIntrospectionResponse` traits had\na generic type parameter `TT: TokenType`. This has been replaced with an associated type called\n`TokenType` in `OAuth2TokenResponse` and `TokenIntrospectionResponse`.\nUses of `CoreTokenResponse` and `CoreTokenIntrospectionResponse` should continue to work without\nchanges, but custom implementations of either trait will need to be updated to replace the type\nparameter with an associated type.\n\n#### Remove `TT` generic type parameter from `Client` and each `*Request` type\n\nRemoving the `TT` generic type parameter from `TokenResponse` (see above) made the `TT` parameters\nto `Client` and each `*Request` (e.g., `CodeTokenRequest`) redundant. Consequently, the `TT`\nparameter has been removed from each of these types. `CoreClient` should continue to work\nwithout any changes, but code that provides generic types for `Client` or any of the `*Response`\ntypes will need to be updated to remove the `TT` type parameter.\n\n### Add `Display` to `ErrorResponse` trait\n\nTo improve error messages, the\n[`RequestTokenError::ServerResponse`](https://docs.rs/oauth2/latest/oauth2/enum.RequestTokenError.html#variant.ServerResponse)\nenum variant now prints a message describing the server response using the `Display` trait. For most\nusers (i.e., those using the default\n[`StandardErrorResponse`](https://docs.rs/oauth2/latest/oauth2/struct.StandardErrorResponse.html)),\nthis does not require any code changes. However, users providing their own implementations\nof the `ErrorResponse` trait must now implement the `Display` trait. See\n`oauth2::StandardErrorResponse`'s\n[`Display` implementation](https://github.com/ramosbugs/oauth2-rs/blob/9d8f11addf819134f15c6d7f03276adb3d32e80b/src/error.rs#L88-L108)\nfor an example.\n\n### Remove the `jwk-alg` feature flag\n\nThe 4.0 release removes the `jwk-alg` feature flag and unconditionally deserializes the optional\n`alg` field in `CoreJsonWebKey`. If a key specifies the `alg` field, the key may only be used for\nthe purposes of verifying signatures using that specific JWS signature algorithm. By comparison,\nthe 3.0 release ignored the `alg` field unless the `jwk-alg` feature flag was enabled.\n\n### Enable the `timing-resistant-secret-traits` feature flag to securely compare secrets\n\nOpenID Connect flows require comparing secrets (e.g., `CsrfToken` and `Nonce`) received from\nproviders. To do so securely\nwhile avoiding [timing side-channels](https://en.wikipedia.org/wiki/Timing_attack), the\ncomparison must be done in constant time, either using a constant-time crate such as\n[`constant_time_eq`](https://crates.io/crates/constant_time_eq) (which could break if a future\ncompiler version decides to be overly smart\nabout its optimizations), or by first computing a cryptographically-secure hash (e.g., SHA-256)\nof both values and then comparing the hashes using `==`.\n\nThe `timing-resistant-secret-traits` feature flag adds a safe (but comparatively expensive)\n`PartialEq` implementation to the secret types. Timing side-channels are why `PartialEq` is\nnot auto-derived for this crate's secret types, and the lack of `PartialEq` is intended to\nprompt users to think more carefully about these comparisons.\n\nIn the 3.0 release, the `Nonce` type implemented `PartialEq` by default, which also allowed the\n`IdToken`, `IdTokenClaims`, and `IdTokenFields` types to implement `PartialEq`. In 4.0, these\ntypes implement `PartialEq` only if the `timing-resistant-secret-traits` feature flag is enabled.\n\n### Move `hash_bytes()` method from `JwsSignatureAlgorithm` trait to `JsonWebKey`\n\nCertain JWS signature algorithms (e.g., `EdDSA`) require information from the corresponding public\nkey (e.g., the `crv` value) to determine which hash function to use for computing the `at_hash` and\n`c_hash` ID token claims. To accommodate this requirement, the 4.0 release moves the `hash_bytes()`\nmethod from the `JwsSignatureAlgorithm` trait to the `JsonWebKey` trait.\n\nThe `AccessTokenHash::from_token()` and `AuthorizationCodeHash::from_code()` methods now require\na `JsonWebKey` as an argument.\n"
  },
  {
    "path": "examples/gitlab.rs",
    "content": "//!\n//! This example showcases the process of integrating with the\n//! [GitLab OpenID Connect](https://docs.gitlab.com/ee/integration/openid_connect_provider.html)\n//! provider.\n//!\n//! Before running it, you'll need to generate your own\n//! [GitLab Application](https://docs.gitlab.com/ee/integration/oauth_provider.html).\n//! The application needs `openid`, `profile` and `email` permission.\n//!\n//! In order to run the example call:\n//!\n//! ```sh\n//! GITLAB_CLIENT_ID=xxx GITLAB_CLIENT_SECRET=yyy cargo run --example gitlab\n//! ```\n//!\n//! ...and follow the instructions.\n//!\n\nuse openidconnect::core::{\n    CoreClient, CoreGenderClaim, CoreIdTokenClaims, CoreIdTokenVerifier, CoreProviderMetadata,\n    CoreResponseType,\n};\nuse openidconnect::reqwest;\nuse openidconnect::{AdditionalClaims, UserInfoClaims};\nuse openidconnect::{\n    AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce,\n    OAuth2TokenResponse, RedirectUrl, Scope,\n};\nuse serde::{Deserialize, Serialize};\nuse url::Url;\n\nuse std::env;\nuse std::io::{BufRead, BufReader, Write};\nuse std::net::TcpListener;\nuse std::process::exit;\n\n#[derive(Debug, Deserialize, Serialize)]\nstruct GitLabClaims {\n    // Deprecated and thus optional as it might be removed in the futre\n    sub_legacy: Option<String>,\n    groups: Vec<String>,\n}\nimpl AdditionalClaims for GitLabClaims {}\n\nfn handle_error<T: std::error::Error>(fail: &T, msg: &'static str) {\n    let mut err_msg = format!(\"ERROR: {}\", msg);\n    let mut cur_fail: Option<&dyn std::error::Error> = Some(fail);\n    while let Some(cause) = cur_fail {\n        err_msg += &format!(\"\\n    caused by: {}\", cause);\n        cur_fail = cause.source();\n    }\n    println!(\"{}\", err_msg);\n    exit(1);\n}\n\nfn main() {\n    env_logger::init();\n\n    let gitlab_client_id = ClientId::new(\n        env::var(\"GITLAB_CLIENT_ID\").expect(\"Missing the GITLAB_CLIENT_ID environment variable.\"),\n    );\n    let gitlab_client_secret = ClientSecret::new(\n        env::var(\"GITLAB_CLIENT_SECRET\")\n            .expect(\"Missing the GITLAB_CLIENT_SECRET environment variable.\"),\n    );\n    let issuer_url = IssuerUrl::new(\"https://gitlab.com\".to_string()).unwrap_or_else(|err| {\n        handle_error(&err, \"Invalid issuer URL\");\n        unreachable!();\n    });\n\n    let http_client = reqwest::blocking::ClientBuilder::new()\n        // Following redirects opens the client up to SSRF vulnerabilities.\n        .redirect(reqwest::redirect::Policy::none())\n        .build()\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to build HTTP client\");\n            unreachable!();\n        });\n\n    // Fetch GitLab's OpenID Connect discovery document.\n    let provider_metadata = CoreProviderMetadata::discover(&issuer_url, &http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to discover OpenID Provider\");\n            unreachable!();\n        });\n\n    // Set up the config for the GitLab OAuth2 process.\n    let client = CoreClient::from_provider_metadata(\n        provider_metadata,\n        gitlab_client_id,\n        Some(gitlab_client_secret),\n    )\n    // This example will be running its own server at localhost:8080.\n    // See below for the server implementation.\n    .set_redirect_uri(\n        RedirectUrl::new(\"http://localhost:8080\".to_string()).unwrap_or_else(|err| {\n            handle_error(&err, \"Invalid redirect URL\");\n            unreachable!();\n        }),\n    );\n\n    // Generate the authorization URL to which we'll redirect the user.\n    let (authorize_url, csrf_state, nonce) = client\n        .authorize_url(\n            AuthenticationFlow::<CoreResponseType>::AuthorizationCode,\n            CsrfToken::new_random,\n            Nonce::new_random,\n        )\n        // This example is requesting access to the the user's profile including email.\n        .add_scope(Scope::new(\"email\".to_string()))\n        .add_scope(Scope::new(\"profile\".to_string()))\n        .url();\n\n    println!(\"Open this URL in your browser:\\n{authorize_url}\\n\");\n\n    let (code, state) = {\n        // A very naive implementation of the redirect server.\n        let listener = TcpListener::bind(\"127.0.0.1:8080\").unwrap();\n\n        // Accept one connection\n        let (mut stream, _) = listener.accept().unwrap();\n\n        let mut reader = BufReader::new(&stream);\n\n        let mut request_line = String::new();\n        reader.read_line(&mut request_line).unwrap();\n\n        let redirect_url = request_line.split_whitespace().nth(1).unwrap();\n        let url = Url::parse(&(\"http://localhost\".to_string() + redirect_url)).unwrap();\n\n        let code = url\n            .query_pairs()\n            .find(|(key, _)| key == \"code\")\n            .map(|(_, code)| AuthorizationCode::new(code.into_owned()))\n            .unwrap();\n\n        let state = url\n            .query_pairs()\n            .find(|(key, _)| key == \"state\")\n            .map(|(_, state)| CsrfToken::new(state.into_owned()))\n            .unwrap();\n\n        let message = \"Go back to your terminal :)\";\n        let response = format!(\n            \"HTTP/1.1 200 OK\\r\\ncontent-length: {}\\r\\n\\r\\n{}\",\n            message.len(),\n            message\n        );\n        stream.write_all(response.as_bytes()).unwrap();\n\n        (code, state)\n    };\n\n    println!(\"GitLab returned the following code:\\n{}\\n\", code.secret());\n    println!(\n        \"GitLab returned the following state:\\n{} (expected `{}`)\\n\",\n        state.secret(),\n        csrf_state.secret()\n    );\n\n    // Exchange the code with a token.\n    let token_response = client\n        .exchange_code(code)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"No user info endpoint\");\n            unreachable!();\n        })\n        .request(&http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to contact token endpoint\");\n            unreachable!();\n        });\n\n    println!(\n        \"GitLab returned access token:\\n{}\\n\",\n        token_response.access_token().secret()\n    );\n    println!(\"GitLab returned scopes: {:?}\", token_response.scopes());\n\n    let id_token_verifier: CoreIdTokenVerifier = client.id_token_verifier();\n    let id_token_claims: &CoreIdTokenClaims = token_response\n        .extra_fields()\n        .id_token()\n        .expect(\"Server did not return an ID token\")\n        .claims(&id_token_verifier, &nonce)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to verify ID token\");\n            unreachable!();\n        });\n    println!(\"GitLab returned ID token: {:?}\\n\", id_token_claims);\n\n    let userinfo_claims: UserInfoClaims<GitLabClaims, CoreGenderClaim> = client\n        .user_info(token_response.access_token().to_owned(), None)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"No user info endpoint\");\n            unreachable!();\n        })\n        .request(&http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed requesting user info\");\n            unreachable!();\n        });\n    println!(\"GitLab returned UserInfo: {:?}\", userinfo_claims);\n}\n"
  },
  {
    "path": "examples/google.rs",
    "content": "//!\n//! This example showcases the process of integrating with the\n//! [Google OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect)\n//! provider.\n//!\n//! Before running it, you'll need to generate your own Google OAuth2 credentials.\n//!\n//! In order to run the example call:\n//!\n//! ```sh\n//! GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy cargo run --example google\n//! ```\n//!\n//! ...and follow the instructions.\n//!\n\nuse openidconnect::core::{\n    CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod, CoreGrantType,\n    CoreIdTokenClaims, CoreIdTokenVerifier, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType, CoreRevocableToken,\n    CoreSubjectIdentifierType,\n};\nuse openidconnect::reqwest;\nuse openidconnect::{\n    AdditionalProviderMetadata, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret,\n    CsrfToken, IssuerUrl, Nonce, OAuth2TokenResponse, ProviderMetadata, RedirectUrl, RevocationUrl,\n    Scope,\n};\nuse serde::{Deserialize, Serialize};\nuse url::Url;\n\nuse std::env;\nuse std::io::{BufRead, BufReader, Write};\nuse std::net::TcpListener;\nuse std::process::exit;\n\nfn handle_error<T: std::error::Error>(fail: &T, msg: &'static str) {\n    let mut err_msg = format!(\"ERROR: {}\", msg);\n    let mut cur_fail: Option<&dyn std::error::Error> = Some(fail);\n    while let Some(cause) = cur_fail {\n        err_msg += &format!(\"\\n    caused by: {}\", cause);\n        cur_fail = cause.source();\n    }\n    println!(\"{}\", err_msg);\n    exit(1);\n}\n\n// Teach openidconnect-rs about a Google custom extension to the OpenID Discovery response that we can use as the RFC\n// 7009 OAuth 2.0 Token Revocation endpoint. For more information about the Google specific Discovery response see the\n// Google OpenID Connect service documentation at: https://developers.google.com/identity/protocols/oauth2/openid-connect#discovery\n#[derive(Clone, Debug, Deserialize, Serialize)]\nstruct RevocationEndpointProviderMetadata {\n    revocation_endpoint: String,\n}\nimpl AdditionalProviderMetadata for RevocationEndpointProviderMetadata {}\ntype GoogleProviderMetadata = ProviderMetadata<\n    RevocationEndpointProviderMetadata,\n    CoreAuthDisplay,\n    CoreClientAuthMethod,\n    CoreClaimName,\n    CoreClaimType,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseMode,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\nfn main() {\n    env_logger::init();\n\n    let google_client_id = ClientId::new(\n        env::var(\"GOOGLE_CLIENT_ID\").expect(\"Missing the GOOGLE_CLIENT_ID environment variable.\"),\n    );\n    let google_client_secret = ClientSecret::new(\n        env::var(\"GOOGLE_CLIENT_SECRET\")\n            .expect(\"Missing the GOOGLE_CLIENT_SECRET environment variable.\"),\n    );\n    let issuer_url =\n        IssuerUrl::new(\"https://accounts.google.com\".to_string()).unwrap_or_else(|err| {\n            handle_error(&err, \"Invalid issuer URL\");\n            unreachable!();\n        });\n\n    let http_client = reqwest::blocking::ClientBuilder::new()\n        // Following redirects opens the client up to SSRF vulnerabilities.\n        .redirect(reqwest::redirect::Policy::none())\n        .build()\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to build HTTP client\");\n            unreachable!();\n        });\n\n    // Fetch Google's OpenID Connect discovery document.\n    //\n    // Note: If we don't care about token revocation we can simply use CoreProviderMetadata here\n    // instead of GoogleProviderMetadata. If instead we wanted to optionally use the token\n    // revocation endpoint if it seems to be supported we could do something like this:\n    //   #[derive(Clone, Debug, Deserialize, Serialize)]\n    //   struct AllOtherProviderMetadata(HashMap<String, serde_json::Value>);\n    //   impl AdditionalClaims for AllOtherProviderMetadata {}\n    // And then test for the presence of \"revocation_endpoint\" in the map returned by a call to\n    // .additional_metadata().\n\n    let provider_metadata = GoogleProviderMetadata::discover(&issuer_url, &http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to discover OpenID Provider\");\n            unreachable!();\n        });\n\n    let revocation_endpoint = provider_metadata\n        .additional_metadata()\n        .revocation_endpoint\n        .clone();\n    println!(\n        \"Discovered Google revocation endpoint: {}\",\n        revocation_endpoint\n    );\n\n    // Set up the config for the Google OAuth2 process.\n    let client = CoreClient::from_provider_metadata(\n        provider_metadata,\n        google_client_id,\n        Some(google_client_secret),\n    )\n    // This example will be running its own server at localhost:8080.\n    // See below for the server implementation.\n    .set_redirect_uri(\n        RedirectUrl::new(\"http://localhost:8080\".to_string()).unwrap_or_else(|err| {\n            handle_error(&err, \"Invalid redirect URL\");\n            unreachable!();\n        }),\n    )\n    // Google supports OAuth 2.0 Token Revocation (RFC-7009)\n    .set_revocation_url(\n        RevocationUrl::new(revocation_endpoint).unwrap_or_else(|err| {\n            handle_error(&err, \"Invalid revocation endpoint URL\");\n            unreachable!();\n        }),\n    );\n\n    // Generate the authorization URL to which we'll redirect the user.\n    let (authorize_url, csrf_state, nonce) = client\n        .authorize_url(\n            AuthenticationFlow::<CoreResponseType>::AuthorizationCode,\n            CsrfToken::new_random,\n            Nonce::new_random,\n        )\n        // This example is requesting access to the \"calendar\" features and the user's profile.\n        .add_scope(Scope::new(\"email\".to_string()))\n        .add_scope(Scope::new(\"profile\".to_string()))\n        .url();\n\n    println!(\"Open this URL in your browser:\\n{}\\n\", authorize_url);\n\n    let (code, state) = {\n        // A very naive implementation of the redirect server.\n        let listener = TcpListener::bind(\"127.0.0.1:8080\").unwrap();\n\n        // Accept one connection\n        let (mut stream, _) = listener.accept().unwrap();\n\n        let mut reader = BufReader::new(&stream);\n\n        let mut request_line = String::new();\n        reader.read_line(&mut request_line).unwrap();\n\n        let redirect_url = request_line.split_whitespace().nth(1).unwrap();\n        let url = Url::parse(&(\"http://localhost\".to_string() + redirect_url)).unwrap();\n\n        let code = url\n            .query_pairs()\n            .find(|(key, _)| key == \"code\")\n            .map(|(_, code)| AuthorizationCode::new(code.into_owned()))\n            .unwrap();\n\n        let state = url\n            .query_pairs()\n            .find(|(key, _)| key == \"state\")\n            .map(|(_, state)| CsrfToken::new(state.into_owned()))\n            .unwrap();\n\n        let message = \"Go back to your terminal :)\";\n        let response = format!(\n            \"HTTP/1.1 200 OK\\r\\ncontent-length: {}\\r\\n\\r\\n{}\",\n            message.len(),\n            message\n        );\n        stream.write_all(response.as_bytes()).unwrap();\n\n        (code, state)\n    };\n\n    println!(\"Google returned the following code:\\n{}\\n\", code.secret());\n    println!(\n        \"Google returned the following state:\\n{} (expected `{}`)\\n\",\n        state.secret(),\n        csrf_state.secret()\n    );\n\n    // Exchange the code with a token.\n    let token_response = client\n        .exchange_code(code)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"No user info endpoint\");\n            unreachable!();\n        })\n        .request(&http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to contact token endpoint\");\n            unreachable!();\n        });\n\n    println!(\n        \"Google returned access token:\\n{}\\n\",\n        token_response.access_token().secret()\n    );\n    println!(\"Google returned scopes: {:?}\", token_response.scopes());\n\n    let id_token_verifier: CoreIdTokenVerifier = client.id_token_verifier();\n    let id_token_claims: &CoreIdTokenClaims = token_response\n        .extra_fields()\n        .id_token()\n        .expect(\"Server did not return an ID token\")\n        .claims(&id_token_verifier, &nonce)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to verify ID token\");\n            unreachable!();\n        });\n    println!(\"Google returned ID token: {:?}\", id_token_claims);\n\n    // Revoke the obtained token\n    let token_to_revoke: CoreRevocableToken = match token_response.refresh_token() {\n        Some(token) => token.into(),\n        None => token_response.access_token().into(),\n    };\n\n    client\n        .revoke_token(token_to_revoke)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to revoke token\");\n            unreachable!();\n        })\n        .request(&http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to revoke token\");\n            unreachable!();\n        });\n}\n"
  },
  {
    "path": "examples/okta_device_grant.rs",
    "content": "//!\n//! This example showcases the process of using the device grant flow to obtain an ID token from the\n//! [Okta](https://developer.okta.com/docs/guides/device-authorization-grant/main/#request-the-device-verification-code)\n//! provider.\n//!\n//! Before running it, you'll need to generate your own\n//! [Okta Server](https://developer.okta.com/signup/).\n//!\n//! In order to run the example call:\n//!\n//! ```sh\n//! CLIENT_ID=xxx CLIENT_SECRET=yyy ISSUER_URL=zzz cargo run --example okta_device_grant\n//! ```\n//!\n//! ...and follow the instructions.\n//!\n\nuse openidconnect::core::{\n    CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,\n    CoreDeviceAuthorizationResponse, CoreGrantType, CoreJsonWebKey,\n    CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreResponseMode,\n    CoreResponseType, CoreSubjectIdentifierType,\n};\nuse openidconnect::reqwest;\nuse openidconnect::{\n    AdditionalProviderMetadata, AuthType, ClientId, ClientSecret, DeviceAuthorizationUrl,\n    IssuerUrl, ProviderMetadata, Scope,\n};\nuse serde::{Deserialize, Serialize};\n\nuse std::env;\nuse std::process::exit;\n\n// Obtain the device_authorization_url from the OIDC metadata provider.\n#[derive(Clone, Debug, Deserialize, Serialize)]\nstruct DeviceEndpointProviderMetadata {\n    device_authorization_endpoint: DeviceAuthorizationUrl,\n}\nimpl AdditionalProviderMetadata for DeviceEndpointProviderMetadata {}\ntype DeviceProviderMetadata = ProviderMetadata<\n    DeviceEndpointProviderMetadata,\n    CoreAuthDisplay,\n    CoreClientAuthMethod,\n    CoreClaimName,\n    CoreClaimType,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseMode,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\nfn handle_error<T: std::error::Error>(fail: &T, msg: &'static str) {\n    let mut err_msg = format!(\"ERROR: {}\", msg);\n    let mut cur_fail: Option<&dyn std::error::Error> = Some(fail);\n    while let Some(cause) = cur_fail {\n        err_msg += &format!(\"\\n    caused by: {}\", cause);\n        cur_fail = cause.source();\n    }\n    println!(\"{}\", err_msg);\n    exit(1);\n}\n\nfn main() -> Result<(), anyhow::Error> {\n    env_logger::init();\n\n    let client_id =\n        ClientId::new(env::var(\"CLIENT_ID\").expect(\"Missing the CLIENT_ID environment variable.\"));\n    let client_secret = ClientSecret::new(\n        env::var(\"CLIENT_SECRET\").expect(\"Missing the CLIENT_SECRET environment variable.\"),\n    );\n    let issuer_url = IssuerUrl::new(\n        env::var(\"ISSUER_URL\").expect(\"Missing the ISSUER_URL environment variable.\"),\n    )\n    .unwrap_or_else(|err| {\n        handle_error(&err, \"Invalid issuer URL\");\n        unreachable!();\n    });\n\n    let http_client = reqwest::blocking::ClientBuilder::new()\n        // Following redirects opens the client up to SSRF vulnerabilities.\n        .redirect(reqwest::redirect::Policy::none())\n        .build()\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to build HTTP client\");\n            unreachable!();\n        });\n\n    // Fetch Okta's OpenID Connect discovery document.\n    let provider_metadata = DeviceProviderMetadata::discover(&issuer_url, &http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to discover OpenID Provider\");\n            unreachable!();\n        });\n\n    // Use the custom metadata to get the device_authorization_endpoint\n    let device_authorization_endpoint = provider_metadata\n        .additional_metadata()\n        .device_authorization_endpoint\n        .clone();\n\n    // Set up the config for the Okta device authorization process.\n    let client =\n        CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret))\n            .set_device_authorization_url(device_authorization_endpoint)\n            .set_auth_type(AuthType::RequestBody);\n\n    let details: CoreDeviceAuthorizationResponse = client\n        .exchange_device_code()\n        .add_scope(Scope::new(\"profile\".to_string()))\n        .request(&http_client)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to get device code\");\n            unreachable!();\n        });\n    println!(\"Fetching device code...\");\n    dbg!(&details);\n\n    // Display the URL and user-code.\n    println!(\n        \"Open this URL in your browser:\\n{}\\nand enter the code: {}\",\n        details.verification_uri_complete().unwrap().secret(),\n        details.user_code().secret()\n    );\n\n    // Now poll for the token\n    let token = client\n        .exchange_device_access_token(&details)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to get access token\");\n            unreachable!();\n        })\n        .request(&http_client, std::thread::sleep, None)\n        .unwrap_or_else(|err| {\n            handle_error(&err, \"Failed to get access token\");\n            unreachable!();\n        });\n\n    // Finally, display the ID Token to verify we are using OIDC\n    println!(\"ID Token response: {:?}\", token.extra_fields().id_token());\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/authorization.rs",
    "content": "use crate::core::CoreResponseType;\nuse crate::helpers::join_vec;\nuse crate::{\n    AdditionalClaims, AuthDisplay, AuthPrompt, AuthenticationContextClass, CsrfToken, GenderClaim,\n    IdToken, JweContentEncryptionAlgorithm, JwsSigningAlgorithm, LanguageTag, LoginHint, Nonce,\n    PkceCodeChallenge, RedirectUrl, ResponseType, Scope,\n};\n\nuse url::Url;\n\nuse std::borrow::Cow;\nuse std::time::Duration;\n\n/// Authentication flow, which determines how the Authorization Server returns the OpenID Connect\n/// ID token and OAuth2 access token to the Relying Party.\n#[derive(Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum AuthenticationFlow<RT: ResponseType> {\n    /// Authorization Code Flow.\n    ///\n    /// The authorization server will return an OAuth2 authorization code. Clients must subsequently\n    /// call `Client::exchange_code()` with the authorization code in order to retrieve an\n    /// OpenID Connect ID token and OAuth2 access token.\n    AuthorizationCode,\n    /// Implicit Flow.\n    ///\n    /// Boolean value indicates whether an OAuth2 access token should also be returned. If `true`,\n    /// the Authorization Server will return both an OAuth2 access token and OpenID Connect ID\n    /// token. If `false`, it will return only an OpenID Connect ID token.\n    Implicit(bool),\n    /// Hybrid Flow.\n    ///\n    /// A hybrid flow according to [OAuth 2.0 Multiple Response Type Encoding Practices](\n    ///     https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html). The enum value\n    /// contains the desired `response_type`s. See\n    /// [Section 3](https://openid.net/specs/openid-connect-core-1_0.html#Authentication) for\n    /// details.\n    Hybrid(Vec<RT>),\n}\n\n/// A request to the authorization endpoint.\npub struct AuthorizationRequest<'a, AD, P, RT>\nwhere\n    AD: AuthDisplay,\n    P: AuthPrompt,\n    RT: ResponseType,\n{\n    pub(crate) inner: oauth2::AuthorizationRequest<'a>,\n    pub(crate) acr_values: Vec<AuthenticationContextClass>,\n    pub(crate) authentication_flow: AuthenticationFlow<RT>,\n    pub(crate) claims_locales: Vec<LanguageTag>,\n    pub(crate) display: Option<AD>,\n    pub(crate) id_token_hint: Option<String>,\n    pub(crate) login_hint: Option<LoginHint>,\n    pub(crate) max_age: Option<Duration>,\n    pub(crate) nonce: Nonce,\n    pub(crate) prompts: Vec<P>,\n    pub(crate) ui_locales: Vec<LanguageTag>,\n}\nimpl<'a, AD, P, RT> AuthorizationRequest<'a, AD, P, RT>\nwhere\n    AD: AuthDisplay,\n    P: AuthPrompt,\n    RT: ResponseType,\n{\n    /// Appends a new scope to the authorization URL.\n    pub fn add_scope(mut self, scope: Scope) -> Self {\n        self.inner = self.inner.add_scope(scope);\n        self\n    }\n\n    /// Appends a collection of scopes to the authorization URL.\n    pub fn add_scopes<I>(mut self, scopes: I) -> Self\n    where\n        I: IntoIterator<Item = Scope>,\n    {\n        self.inner = self.inner.add_scopes(scopes);\n        self\n    }\n\n    /// Appends an extra param to the authorization URL.\n    ///\n    /// This method allows extensions to be used without direct support from\n    /// this crate. If `name` conflicts with a parameter managed by this crate, the\n    /// behavior is undefined. In particular, do not set parameters defined by\n    /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or\n    /// [RFC 7636](https://tools.ietf.org/html/rfc7636).\n    ///\n    /// # Security Warning\n    ///\n    /// Callers should follow the security recommendations for any OAuth2 extensions used with\n    /// this function, which are beyond the scope of\n    /// [RFC 6749](https://tools.ietf.org/html/rfc6749).\n    pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self\n    where\n        N: Into<Cow<'a, str>>,\n        V: Into<Cow<'a, str>>,\n    {\n        self.inner = self.inner.add_extra_param(name, value);\n        self\n    }\n\n    /// Enables the use of [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)\n    /// (PKCE).\n    ///\n    /// PKCE is *highly recommended* for all public clients (i.e., those for which there\n    /// is no client secret or for which the client secret is distributed with the client,\n    /// such as in a native, mobile app, or browser app).\n    pub fn set_pkce_challenge(mut self, pkce_code_challenge: PkceCodeChallenge) -> Self {\n        self.inner = self.inner.set_pkce_challenge(pkce_code_challenge);\n        self\n    }\n\n    /// Requests Authentication Context Class Reference values.\n    ///\n    /// ACR values should be added in order of preference. The Authentication Context Class\n    /// satisfied by the authentication performed is accessible from the ID token via the\n    /// [`IdTokenClaims::auth_context_ref()`](crate::IdTokenClaims::auth_context_ref) method.\n    pub fn add_auth_context_value(mut self, acr_value: AuthenticationContextClass) -> Self {\n        self.acr_values.push(acr_value);\n        self\n    }\n\n    /// Requests the preferred languages for claims returned by the OpenID Connect Provider.\n    ///\n    /// Languages should be added in order of preference.\n    pub fn add_claims_locale(mut self, claims_locale: LanguageTag) -> Self {\n        self.claims_locales.push(claims_locale);\n        self\n    }\n\n    // TODO: support 'claims' parameter\n    // https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter\n\n    /// Specifies how the OpenID Connect Provider displays the authentication and consent user\n    /// interfaces to the end user.\n    pub fn set_display(mut self, display: AD) -> Self {\n        self.display = Some(display);\n        self\n    }\n\n    /// Provides an ID token previously issued by this OpenID Connect Provider as a hint about\n    /// the user's identity.\n    ///\n    /// This field should be set whenever\n    /// [`CoreAuthPrompt::None`](crate::core::CoreAuthPrompt::None) is used (see\n    /// [`AuthorizationRequest::add_prompt`]), it but may be provided for any authorization\n    /// request.\n    pub fn set_id_token_hint<AC, GC, JE, JS>(\n        mut self,\n        id_token_hint: &'a IdToken<AC, GC, JE, JS>,\n    ) -> Self\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n        JS: JwsSigningAlgorithm,\n    {\n        self.id_token_hint = Some(id_token_hint.to_string());\n        self\n    }\n\n    /// Provides the OpenID Connect Provider with a hint about the user's identity.\n    ///\n    /// The nature of this hint is specific to each provider.\n    pub fn set_login_hint(mut self, login_hint: LoginHint) -> Self {\n        self.login_hint = Some(login_hint);\n        self\n    }\n\n    /// Sets a maximum amount of time since the user has last authenticated with the OpenID\n    /// Connect Provider.\n    ///\n    /// If more time has elapsed, the provider forces the user to re-authenticate.\n    pub fn set_max_age(mut self, max_age: Duration) -> Self {\n        self.max_age = Some(max_age);\n        self\n    }\n\n    /// Specifies what level of authentication and consent prompts the OpenID Connect Provider\n    /// should present to the user.\n    pub fn add_prompt(mut self, prompt: P) -> Self {\n        self.prompts.push(prompt);\n        self\n    }\n\n    /// Requests the preferred languages for the user interface presented by the OpenID Connect\n    /// Provider.\n    ///\n    /// Languages should be added in order of preference.\n    pub fn add_ui_locale(mut self, ui_locale: LanguageTag) -> Self {\n        self.ui_locales.push(ui_locale);\n        self\n    }\n\n    /// Overrides the `redirect_url` to the one specified.\n    pub fn set_redirect_uri(mut self, redirect_url: Cow<'a, RedirectUrl>) -> Self {\n        self.inner = self.inner.set_redirect_uri(redirect_url);\n        self\n    }\n\n    /// Returns the full authorization URL and CSRF state for this authorization\n    /// request.\n    pub fn url(self) -> (Url, CsrfToken, Nonce) {\n        let response_type = match self.authentication_flow {\n            AuthenticationFlow::AuthorizationCode => CoreResponseType::Code.to_oauth2(),\n            AuthenticationFlow::Implicit(include_token) => {\n                if include_token {\n                    oauth2::ResponseType::new(\n                        [CoreResponseType::IdToken, CoreResponseType::Token]\n                            .iter()\n                            .map(|response_type| response_type.as_ref())\n                            .collect::<Vec<_>>()\n                            .join(\" \"),\n                    )\n                } else {\n                    CoreResponseType::IdToken.to_oauth2()\n                }\n            }\n            AuthenticationFlow::Hybrid(ref response_types) => oauth2::ResponseType::new(\n                response_types\n                    .iter()\n                    .map(|response_type| response_type.as_ref())\n                    .collect::<Vec<_>>()\n                    .join(\" \"),\n            ),\n        };\n        let (mut inner, nonce) = (\n            self.inner\n                .set_response_type(&response_type)\n                .add_extra_param(\"nonce\", self.nonce.secret().clone()),\n            self.nonce,\n        );\n        if !self.acr_values.is_empty() {\n            inner = inner.add_extra_param(\"acr_values\", join_vec(&self.acr_values));\n        }\n        if !self.claims_locales.is_empty() {\n            inner = inner.add_extra_param(\"claims_locales\", join_vec(&self.claims_locales));\n        }\n        if let Some(ref display) = self.display {\n            inner = inner.add_extra_param(\"display\", display.as_ref());\n        }\n        if let Some(ref id_token_hint) = self.id_token_hint {\n            inner = inner.add_extra_param(\"id_token_hint\", id_token_hint);\n        }\n        if let Some(ref login_hint) = self.login_hint {\n            inner = inner.add_extra_param(\"login_hint\", login_hint.secret());\n        }\n        if let Some(max_age) = self.max_age {\n            inner = inner.add_extra_param(\"max_age\", max_age.as_secs().to_string());\n        }\n        if !self.prompts.is_empty() {\n            inner = inner.add_extra_param(\"prompt\", join_vec(&self.prompts));\n        }\n        if !self.ui_locales.is_empty() {\n            inner = inner.add_extra_param(\"ui_locales\", join_vec(&self.ui_locales));\n        }\n\n        let (url, state) = inner.url();\n        (url, state, nonce)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::core::CoreAuthenticationFlow;\n    use crate::core::{CoreAuthDisplay, CoreAuthPrompt, CoreClient, CoreIdToken, CoreResponseType};\n    use crate::IssuerUrl;\n    use crate::{\n        AuthUrl, AuthenticationContextClass, AuthenticationFlow, ClientId, ClientSecret, CsrfToken,\n        EndpointNotSet, EndpointSet, JsonWebKeySet, LanguageTag, LoginHint, Nonce, RedirectUrl,\n        Scope, TokenUrl,\n    };\n\n    use std::borrow::Cow;\n    use std::time::Duration;\n\n    fn new_client() -> CoreClient<\n        EndpointSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointSet,\n        EndpointNotSet,\n    > {\n        color_backtrace::install();\n        CoreClient::new(\n            ClientId::new(\"aaa\".to_string()),\n            IssuerUrl::new(\"https://example\".to_string()).unwrap(),\n            JsonWebKeySet::default(),\n        )\n        .set_client_secret(ClientSecret::new(\"bbb\".to_string()))\n        .set_auth_uri(AuthUrl::new(\"https://example/authorize\".to_string()).unwrap())\n        .set_token_uri(TokenUrl::new(\"https://example/token\".to_string()).unwrap())\n    }\n\n    #[test]\n    fn test_authorize_url_minimal() {\n        let client = new_client();\n\n        let (authorize_url, _, _) = client\n            .authorize_url(\n                AuthenticationFlow::AuthorizationCode::<CoreResponseType>,\n                || CsrfToken::new(\"CSRF123\".to_string()),\n                || Nonce::new(\"NONCE456\".to_string()),\n            )\n            .url();\n\n        assert_eq!(\n            \"https://example/authorize?response_type=code&client_id=aaa&\\\n             state=CSRF123&scope=openid&nonce=NONCE456\",\n            authorize_url.to_string()\n        );\n    }\n\n    #[test]\n    fn test_authorize_url_implicit_with_access_token() {\n        let client = new_client();\n\n        let (authorize_url, _, _) = client\n            .authorize_url(\n                AuthenticationFlow::<CoreResponseType>::Implicit(true),\n                || CsrfToken::new(\"CSRF123\".to_string()),\n                || Nonce::new(\"NONCE456\".to_string()),\n            )\n            .url();\n\n        assert_eq!(\n            \"https://example/authorize?response_type=id_token+token&client_id=aaa&\\\n             state=CSRF123&scope=openid&nonce=NONCE456\",\n            authorize_url.to_string()\n        );\n    }\n\n    #[test]\n    fn test_authorize_url_hybrid() {\n        let client = new_client();\n\n        let (authorize_url, _, _) = client\n            .authorize_url(\n                AuthenticationFlow::Hybrid(vec![\n                    CoreResponseType::Code,\n                    CoreResponseType::Extension(\"other\".to_string()),\n                ]),\n                || CsrfToken::new(\"CSRF123\".to_string()),\n                || Nonce::new(\"NONCE456\".to_string()),\n            )\n            .url();\n\n        assert_eq!(\n            \"https://example/authorize?response_type=code+other&client_id=aaa&\\\n             state=CSRF123&scope=openid&nonce=NONCE456\",\n            authorize_url.to_string()\n        );\n    }\n\n    #[test]\n    fn test_authorize_url_full() {\n        let client = new_client()\n            .set_redirect_uri(RedirectUrl::new(\"http://localhost:8888/\".to_string()).unwrap());\n\n        let flow = CoreAuthenticationFlow::AuthorizationCode;\n\n        fn new_csrf() -> CsrfToken {\n            CsrfToken::new(\"CSRF123\".to_string())\n        }\n        fn new_nonce() -> Nonce {\n            Nonce::new(\"NONCE456\".to_string())\n        }\n\n        let (authorize_url, _, _) = client\n            .authorize_url(flow.clone(), new_csrf, new_nonce)\n            .add_scope(Scope::new(\"email\".to_string()))\n            .set_display(CoreAuthDisplay::Touch)\n            .add_prompt(CoreAuthPrompt::Login)\n            .add_prompt(CoreAuthPrompt::Consent)\n            .set_max_age(Duration::from_secs(1800))\n            .add_ui_locale(LanguageTag::new(\"fr-CA\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"fr\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"en\".to_string()))\n            .add_auth_context_value(AuthenticationContextClass::new(\n                \"urn:mace:incommon:iap:silver\".to_string(),\n            ))\n            .url();\n        assert_eq!(\n            \"https://example/authorize?response_type=code&client_id=aaa&\\\n             state=CSRF123&redirect_uri=http%3A%2F%2Flocalhost%3A8888%2F&scope=openid+email&\\\n             nonce=NONCE456&acr_values=urn%3Amace%3Aincommon%3Aiap%3Asilver&display=touch&\\\n             max_age=1800&prompt=login+consent&ui_locales=fr-CA+fr+en\",\n            authorize_url.to_string()\n        );\n\n        let serialized_jwt =\n            \"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiYXVkIjpbIm15X2NsaWVudCJdL\\\n             CJleHAiOjE1NDQ5MzIxNDksImlhdCI6MTU0NDkyODU0OSwiYXV0aF90aW1lIjoxNTQ0OTI4NTQ4LCJub25jZSI\\\n             6InRoZV9ub25jZSIsImFjciI6InRoZV9hY3IiLCJzdWIiOiJzdWJqZWN0In0.gb5HuuyDMu-LvYvG-jJNIJPEZ\\\n             823qNwvgNjdAtW0HJpgwJWhJq0hOHUuZz6lvf8ud5xbg5GOo0Q37v3Ke08TvGu6E1USWjecZzp1aYVm9BiMvw5\\\n             EBRUrwAaOCG2XFjuOKUVfglSMJnRnoNqVVIWpCAr1ETjZzRIbkU3n5GQRguC5CwN5n45I3dtjoKuNGc2Ni-IMl\\\n             J2nRiCJOl2FtStdgs-doc-A9DHtO01x-5HCwytXvcE28Snur1JnqpUgmWrQ8gZMGuijKirgNnze2Dd5BsZRHZ2\\\n             CLGIwBsCnauBrJy_NNlQg4hUcSlGsuTa0dmZY7mCf4BN2WCpyOh0wgtkAgQ\";\n        let id_token = serde_json::from_value::<CoreIdToken>(serde_json::Value::String(\n            serialized_jwt.to_string(),\n        ))\n        .unwrap();\n\n        let (authorize_url, _, _) = client\n            .authorize_url(flow.clone(), new_csrf, new_nonce)\n            .add_scope(Scope::new(\"email\".to_string()))\n            .set_display(CoreAuthDisplay::Touch)\n            .set_id_token_hint(&id_token)\n            .set_login_hint(LoginHint::new(\"foo@bar.com\".to_string()))\n            .add_prompt(CoreAuthPrompt::Login)\n            .add_prompt(CoreAuthPrompt::Consent)\n            .set_max_age(Duration::from_secs(1800))\n            .add_ui_locale(LanguageTag::new(\"fr-CA\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"fr\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"en\".to_string()))\n            .add_auth_context_value(AuthenticationContextClass::new(\n                \"urn:mace:incommon:iap:silver\".to_string(),\n            ))\n            .add_extra_param(\"foo\", \"bar\")\n            .url();\n        assert_eq!(\n            format!(\n                \"https://example/authorize?response_type=code&client_id=aaa&state=CSRF123&\\\n                 redirect_uri=http%3A%2F%2Flocalhost%3A8888%2F&scope=openid+email&foo=bar&\\\n                 nonce=NONCE456&acr_values=urn%3Amace%3Aincommon%3Aiap%3Asilver&display=touch&\\\n                 id_token_hint={}&login_hint=foo%40bar.com&\\\n                 max_age=1800&prompt=login+consent&ui_locales=fr-CA+fr+en\",\n                serialized_jwt\n            ),\n            authorize_url.to_string()\n        );\n\n        let (authorize_url, _, _) = client\n            .authorize_url(flow, new_csrf, new_nonce)\n            .add_scopes(vec![\n                Scope::new(\"email\".to_string()),\n                Scope::new(\"profile\".to_string()),\n            ])\n            .set_display(CoreAuthDisplay::Touch)\n            .set_id_token_hint(&id_token)\n            .set_login_hint(LoginHint::new(\"foo@bar.com\".to_string()))\n            .add_prompt(CoreAuthPrompt::Login)\n            .add_prompt(CoreAuthPrompt::Consent)\n            .set_max_age(Duration::from_secs(1800))\n            .add_ui_locale(LanguageTag::new(\"fr-CA\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"fr\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"en\".to_string()))\n            .add_auth_context_value(AuthenticationContextClass::new(\n                \"urn:mace:incommon:iap:silver\".to_string(),\n            ))\n            .add_extra_param(\"foo\", \"bar\")\n            .url();\n        assert_eq!(\n            format!(\n                \"https://example/authorize?response_type=code&client_id=aaa&state=CSRF123&\\\n                 redirect_uri=http%3A%2F%2Flocalhost%3A8888%2F&scope=openid+email+profile&foo=bar&\\\n                 nonce=NONCE456&acr_values=urn%3Amace%3Aincommon%3Aiap%3Asilver&display=touch&\\\n                 id_token_hint={}&login_hint=foo%40bar.com&\\\n                 max_age=1800&prompt=login+consent&ui_locales=fr-CA+fr+en\",\n                serialized_jwt\n            ),\n            authorize_url.to_string()\n        );\n    }\n\n    #[test]\n    fn test_authorize_url_redirect_url_override() {\n        let client = new_client()\n            .set_redirect_uri(RedirectUrl::new(\"http://localhost:8888/\".to_string()).unwrap());\n\n        let flow = CoreAuthenticationFlow::AuthorizationCode;\n\n        fn new_csrf() -> CsrfToken {\n            CsrfToken::new(\"CSRF123\".to_string())\n        }\n        fn new_nonce() -> Nonce {\n            Nonce::new(\"NONCE456\".to_string())\n        }\n\n        let (authorize_url, _, _) = client\n            .authorize_url(flow, new_csrf, new_nonce)\n            .add_scope(Scope::new(\"email\".to_string()))\n            .set_display(CoreAuthDisplay::Touch)\n            .add_prompt(CoreAuthPrompt::Login)\n            .add_prompt(CoreAuthPrompt::Consent)\n            .set_max_age(Duration::from_secs(1800))\n            .add_ui_locale(LanguageTag::new(\"fr-CA\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"fr\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"en\".to_string()))\n            .add_auth_context_value(AuthenticationContextClass::new(\n                \"urn:mace:incommon:iap:silver\".to_string(),\n            ))\n            .set_redirect_uri(Cow::Owned(\n                RedirectUrl::new(\"http://localhost:8888/alternative\".to_string()).unwrap(),\n            ))\n            .url();\n        assert_eq!(\n            \"https://example/authorize?response_type=code&client_id=aaa&\\\n             state=CSRF123&redirect_uri=http%3A%2F%2Flocalhost%3A8888%2Falternative&scope=openid+email&\\\n             nonce=NONCE456&acr_values=urn%3Amace%3Aincommon%3Aiap%3Asilver&display=touch&\\\n             max_age=1800&prompt=login+consent&ui_locales=fr-CA+fr+en\",\n            authorize_url.to_string()\n        );\n    }\n}\n"
  },
  {
    "path": "src/claims.rs",
    "content": "use crate::helpers::{Boolean, DeserializeMapField, FlattenFilter, Timestamp};\nuse crate::types::localized::split_language_tag_key;\nuse crate::{\n    AddressCountry, AddressLocality, AddressPostalCode, AddressRegion, EndUserBirthday,\n    EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName,\n    EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone,\n    EndUserUsername, EndUserWebsiteUrl, FormattedAddress, LanguageTag, LocalizedClaim,\n    StreetAddress, SubjectIdentifier,\n};\n\nuse chrono::{DateTime, Utc};\nuse serde::de::{DeserializeOwned, Deserializer, MapAccess, Visitor};\nuse serde::ser::SerializeMap;\nuse serde::{Deserialize, Serialize, Serializer};\nuse serde_with::skip_serializing_none;\n\nuse std::fmt::{Debug, Formatter, Result as FormatterResult};\nuse std::marker::PhantomData;\nuse std::str;\n\n/// Additional claims beyond the set of Standard Claims defined by OpenID Connect Core.\npub trait AdditionalClaims: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// No additional claims.\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]\n// In order to support serde flatten, this must be an empty struct rather than an empty\n// tuple struct.\npub struct EmptyAdditionalClaims {}\nimpl AdditionalClaims for EmptyAdditionalClaims {}\n\n/// Address claims.\n#[skip_serializing_none]\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]\npub struct AddressClaim {\n    /// Full mailing address, formatted for display or use on a mailing label.\n    ///\n    /// This field MAY contain multiple lines, separated by newlines. Newlines can be represented\n    /// either as a carriage return/line feed pair (`\\r\\n`) or as a single line feed character\n    /// (`\\n`).\n    pub formatted: Option<FormattedAddress>,\n    /// Full street address component, which MAY include house number, street name, Post Office Box,\n    /// and multi-line extended street address information.\n    ///\n    /// This field MAY contain multiple lines, separated by newlines. Newlines can be represented\n    /// either as a carriage return/line feed pair (`\\r\\n`) or as a single line feed character\n    /// (`\\n`).\n    pub street_address: Option<StreetAddress>,\n    /// City or locality component.\n    pub locality: Option<AddressLocality>,\n    /// State, province, prefecture, or region component.\n    pub region: Option<AddressRegion>,\n    /// Zip code or postal code component.\n    pub postal_code: Option<AddressPostalCode>,\n    /// Country name component.\n    pub country: Option<AddressCountry>,\n}\n\n/// Gender claim.\npub trait GenderClaim: Clone + Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Standard Claims defined by OpenID Connect Core.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct StandardClaims<GC>\nwhere\n    GC: GenderClaim,\n{\n    pub(crate) sub: SubjectIdentifier,\n    pub(crate) name: Option<LocalizedClaim<EndUserName>>,\n    pub(crate) given_name: Option<LocalizedClaim<EndUserGivenName>>,\n    pub(crate) family_name: Option<LocalizedClaim<EndUserFamilyName>>,\n    pub(crate) middle_name: Option<LocalizedClaim<EndUserMiddleName>>,\n    pub(crate) nickname: Option<LocalizedClaim<EndUserNickname>>,\n    pub(crate) preferred_username: Option<EndUserUsername>,\n    pub(crate) profile: Option<LocalizedClaim<EndUserProfileUrl>>,\n    pub(crate) picture: Option<LocalizedClaim<EndUserPictureUrl>>,\n    pub(crate) website: Option<LocalizedClaim<EndUserWebsiteUrl>>,\n    pub(crate) email: Option<EndUserEmail>,\n    pub(crate) email_verified: Option<bool>,\n    pub(crate) gender: Option<GC>,\n    pub(crate) birthday: Option<EndUserBirthday>,\n    pub(crate) birthdate: Option<EndUserBirthday>,\n    pub(crate) zoneinfo: Option<EndUserTimezone>,\n    pub(crate) locale: Option<LanguageTag>,\n    pub(crate) phone_number: Option<EndUserPhoneNumber>,\n    pub(crate) phone_number_verified: Option<bool>,\n    pub(crate) address: Option<AddressClaim>,\n    pub(crate) updated_at: Option<DateTime<Utc>>,\n}\nimpl<GC> StandardClaims<GC>\nwhere\n    GC: GenderClaim,\n{\n    /// Initializes a set of Standard Claims.\n    ///\n    /// The Subject (`sub`) claim is the only required Standard Claim.\n    pub fn new(subject: SubjectIdentifier) -> Self {\n        Self {\n            sub: subject,\n            name: None,\n            given_name: None,\n            family_name: None,\n            middle_name: None,\n            nickname: None,\n            preferred_username: None,\n            profile: None,\n            picture: None,\n            website: None,\n            email: None,\n            email_verified: None,\n            gender: None,\n            birthday: None,\n            birthdate: None,\n            zoneinfo: None,\n            locale: None,\n            phone_number: None,\n            phone_number_verified: None,\n            address: None,\n            updated_at: None,\n        }\n    }\n\n    /// Returns the Subject (`sub`) claim.\n    pub fn subject(&self) -> &SubjectIdentifier {\n        &self.sub\n    }\n\n    /// Sets the Subject (`sub`) claim.\n    pub fn set_subject(mut self, subject: SubjectIdentifier) -> Self {\n        self.sub = subject;\n        self\n    }\n\n    field_getters_setters![\n        pub self [self] [\"claim\"] {\n            set_name -> name[Option<LocalizedClaim<EndUserName>>],\n            set_given_name -> given_name[Option<LocalizedClaim<EndUserGivenName>>],\n            set_family_name ->\n                family_name[Option<LocalizedClaim<EndUserFamilyName>>],\n            set_middle_name ->\n                middle_name[Option<LocalizedClaim<EndUserMiddleName>>],\n            set_nickname -> nickname[Option<LocalizedClaim<EndUserNickname>>],\n            set_preferred_username -> preferred_username[Option<EndUserUsername>],\n            set_profile -> profile[Option<LocalizedClaim<EndUserProfileUrl>>],\n            set_picture -> picture[Option<LocalizedClaim<EndUserPictureUrl>>],\n            set_website -> website[Option<LocalizedClaim<EndUserWebsiteUrl>>],\n            set_email -> email[Option<EndUserEmail>],\n            set_email_verified -> email_verified[Option<bool>],\n            set_gender -> gender[Option<GC>],\n            set_birthday -> birthday[Option<EndUserBirthday>],\n            set_birthdate -> birthdate[Option<EndUserBirthday>],\n            set_zoneinfo -> zoneinfo[Option<EndUserTimezone>],\n            set_locale -> locale[Option<LanguageTag>],\n            set_phone_number -> phone_number[Option<EndUserPhoneNumber>],\n            set_phone_number_verified -> phone_number_verified[Option<bool>],\n            set_address -> address[Option<AddressClaim>],\n            set_updated_at -> updated_at[Option<DateTime<Utc>>],\n        }\n    ];\n}\nimpl<GC> FlattenFilter for StandardClaims<GC>\nwhere\n    GC: GenderClaim,\n{\n    // When another struct (i.e., additional claims) is co-flattened with this one, only include\n    // fields in that other struct which are not part of this struct.\n    fn should_include(field_name: &str) -> bool {\n        !matches!(\n            split_language_tag_key(field_name),\n            (\"sub\", None)\n                | (\"name\", _)\n                | (\"given_name\", _)\n                | (\"family_name\", _)\n                | (\"middle_name\", _)\n                | (\"nickname\", _)\n                | (\"preferred_username\", None)\n                | (\"profile\", _)\n                | (\"picture\", _)\n                | (\"website\", _)\n                | (\"email\", None)\n                | (\"email_verified\", None)\n                | (\"gender\", None)\n                | (\"birthday\", None)\n                | (\"birthdate\", None)\n                | (\"zoneinfo\", None)\n                | (\"locale\", None)\n                | (\"phone_number\", None)\n                | (\"phone_number_verified\", None)\n                | (\"address\", None)\n                | (\"updated_at\", None)\n        )\n    }\n}\nimpl<'de, GC> Deserialize<'de> for StandardClaims<GC>\nwhere\n    GC: GenderClaim,\n{\n    /// Special deserializer that supports [RFC 5646](https://tools.ietf.org/html/rfc5646) language\n    /// tags associated with human-readable client metadata fields.\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct ClaimsVisitor<GC: GenderClaim>(PhantomData<GC>);\n        impl<'de, GC> Visitor<'de> for ClaimsVisitor<GC>\n        where\n            GC: GenderClaim,\n        {\n            type Value = StandardClaims<GC>;\n\n            fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {\n                formatter.write_str(\"struct StandardClaims\")\n            }\n            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>\n            where\n                V: MapAccess<'de>,\n            {\n                // NB: The non-localized fields are actually Option<Option<_>> here so that we can\n                // distinguish between omitted fields and fields explicitly set to `null`. The\n                // latter is necessary so that we can detect duplicate fields (e.g., if a key is\n                // present both with a null value and a non-null value, that's an error).\n                let mut sub = None;\n                let mut name = None;\n                let mut given_name = None;\n                let mut family_name = None;\n                let mut middle_name = None;\n                let mut nickname = None;\n                let mut preferred_username = None;\n                let mut profile = None;\n                let mut picture = None;\n                let mut website = None;\n                let mut email = None;\n                let mut email_verified = None;\n                let mut gender = None;\n                let mut birthday = None;\n                let mut birthdate = None;\n                let mut zoneinfo = None;\n                let mut locale = None;\n                let mut phone_number = None;\n                let mut phone_number_verified = None;\n                let mut address = None;\n                let mut updated_at = None;\n\n                macro_rules! field_case {\n                    ($field:ident, $typ:ty, $language_tag:ident) => {{\n                        $field = Some(<$typ>::deserialize_map_field(\n                            &mut map,\n                            stringify!($field),\n                            $language_tag,\n                            $field,\n                        )?);\n                    }};\n                }\n\n                while let Some(key) = map.next_key::<String>()? {\n                    let (field_name, language_tag) = split_language_tag_key(&key);\n                    match field_name {\n                        \"sub\" => field_case!(sub, SubjectIdentifier, language_tag),\n                        \"name\" => field_case!(name, LocalizedClaim<Option<_>>, language_tag),\n                        \"given_name\" => {\n                            field_case!(given_name, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"family_name\" => {\n                            field_case!(family_name, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"middle_name\" => {\n                            field_case!(middle_name, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"nickname\" => {\n                            field_case!(nickname, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"preferred_username\" => {\n                            field_case!(preferred_username, Option<_>, language_tag)\n                        }\n                        \"profile\" => {\n                            field_case!(profile, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"picture\" => {\n                            field_case!(picture, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"website\" => {\n                            field_case!(website, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"email\" => field_case!(email, Option<_>, language_tag),\n                        \"email_verified\" => {\n                            field_case!(email_verified, Option<Boolean>, language_tag)\n                        }\n                        \"gender\" => field_case!(gender, Option<_>, language_tag),\n                        \"birthday\" => field_case!(birthday, Option<_>, language_tag),\n                        \"birthdate\" => field_case!(birthdate, Option<_>, language_tag),\n                        \"zoneinfo\" => field_case!(zoneinfo, Option<_>, language_tag),\n                        \"locale\" => field_case!(locale, Option<_>, language_tag),\n                        \"phone_number\" => field_case!(phone_number, Option<_>, language_tag),\n                        \"phone_number_verified\" => {\n                            field_case!(phone_number_verified, Option<Boolean>, language_tag)\n                        }\n                        \"address\" => field_case!(address, Option<_>, language_tag),\n                        \"updated_at\" => field_case!(updated_at, Option<Timestamp>, language_tag),\n                        // Ignore unknown fields.\n                        _ => {\n                            map.next_value::<serde::de::IgnoredAny>()?;\n                            continue;\n                        }\n                    };\n                }\n\n                Ok(StandardClaims {\n                    sub: sub.ok_or_else(|| serde::de::Error::missing_field(\"sub\"))?,\n                    name: name.and_then(LocalizedClaim::flatten_or_none),\n                    given_name: given_name.and_then(LocalizedClaim::flatten_or_none),\n                    family_name: family_name.and_then(LocalizedClaim::flatten_or_none),\n                    middle_name: middle_name.and_then(LocalizedClaim::flatten_or_none),\n                    nickname: nickname.and_then(LocalizedClaim::flatten_or_none),\n                    preferred_username: preferred_username.flatten(),\n                    profile: profile.and_then(LocalizedClaim::flatten_or_none),\n                    picture: picture.and_then(LocalizedClaim::flatten_or_none),\n                    website: website.and_then(LocalizedClaim::flatten_or_none),\n                    email: email.flatten(),\n                    email_verified: email_verified.flatten().map(Boolean::into_inner),\n                    gender: gender.flatten(),\n                    birthday: birthday.flatten(),\n                    birthdate: birthdate.flatten(),\n                    zoneinfo: zoneinfo.flatten(),\n                    locale: locale.flatten(),\n                    phone_number: phone_number.flatten(),\n                    phone_number_verified: phone_number_verified.flatten().map(Boolean::into_inner),\n                    address: address.flatten(),\n                    updated_at: updated_at\n                        .flatten()\n                        .map(|sec| {\n                            sec.to_utc().map_err(|_| {\n                                serde::de::Error::custom(format!(\n                                    \"failed to parse `{sec}` as UTC datetime (in seconds) for key \\\n                                     `updated_at`\"\n                                ))\n                            })\n                        })\n                        .transpose()?,\n                })\n            }\n        }\n        deserializer.deserialize_map(ClaimsVisitor(PhantomData))\n    }\n}\nimpl<GC> Serialize for StandardClaims<GC>\nwhere\n    GC: GenderClaim,\n{\n    #[allow(clippy::cognitive_complexity)]\n    fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>\n    where\n        SE: Serializer,\n    {\n        serialize_fields! {\n            self -> serializer {\n                [sub]\n                [LanguageTag(name)]\n                [LanguageTag(given_name)]\n                [LanguageTag(family_name)]\n                [LanguageTag(middle_name)]\n                [LanguageTag(nickname)]\n                [Option(preferred_username)]\n                [LanguageTag(profile)]\n                [LanguageTag(picture)]\n                [LanguageTag(website)]\n                [Option(email)]\n                [Option(email_verified)]\n                [Option(gender)]\n                [Option(birthday)]\n                [Option(birthdate)]\n                [Option(zoneinfo)]\n                [Option(locale)]\n                [Option(phone_number)]\n                [Option(phone_number_verified)]\n                [Option(address)]\n                [Option(DateTime(Seconds(updated_at)))]\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::core::CoreGenderClaim;\n    use crate::StandardClaims;\n\n    // The spec states (https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse):\n    //   \"If a Claim is not returned, that Claim Name SHOULD be omitted from the JSON object\n    //   representing the Claims; it SHOULD NOT be present with a null or empty string value.\"\n    // However, we still aim to support identity providers that disregard this suggestion.\n    #[test]\n    fn test_null_optional_claims() {\n        let claims = serde_json::from_str::<StandardClaims<CoreGenderClaim>>(\n            r#\"{\n                \"sub\": \"24400320\",\n                \"name\": null,\n                \"given_name\": null,\n                \"family_name\": null,\n                \"middle_name\": null,\n                \"nickname\": null,\n                \"preferred_username\": null,\n                \"profile\": null,\n                \"picture\": null,\n                \"website\": null,\n                \"email\": null,\n                \"email_verified\": null,\n                \"gender\": null,\n                \"birthday\": null,\n                \"birthdate\": null,\n                \"zoneinfo\": null,\n                \"locale\": null,\n                \"phone_number\": null,\n                \"phone_number_verified\": null,\n                \"address\": null,\n                \"updated_at\": null\n            }\"#,\n        )\n        .expect(\"should deserialize successfully\");\n\n        assert_eq!(claims.subject().as_str(), \"24400320\");\n        assert_eq!(claims.name(), None);\n    }\n\n    fn expect_err_prefix(\n        result: Result<StandardClaims<CoreGenderClaim>, serde_json::Error>,\n        expected_prefix: &str,\n    ) {\n        let err_str = result.expect_err(\"deserialization should fail\").to_string();\n        assert!(\n            err_str.starts_with(expected_prefix),\n            \"error message should begin with `{}`: {}\",\n            expected_prefix,\n            err_str,\n        )\n    }\n\n    #[test]\n    fn test_duplicate_claims() {\n        expect_err_prefix(\n            serde_json::from_str(\n                r#\"{\n                    \"sub\": \"24400320\",\n                    \"sub\": \"24400321\"\n                }\"#,\n            ),\n            \"duplicate field `sub` at line\",\n        );\n\n        expect_err_prefix(\n            serde_json::from_str(\n                r#\"{\n                    \"name\": null,\n                    \"sub\": \"24400320\",\n                    \"name\": \"foo\",\n                }\"#,\n            ),\n            \"duplicate field `name` at line\",\n        );\n\n        expect_err_prefix(\n            serde_json::from_str(\n                r#\"{\n                    \"name#en\": null,\n                    \"sub\": \"24400320\",\n                    \"name#en\": \"foo\",\n                }\"#,\n            ),\n            \"duplicate field `name#en` at line\",\n        );\n    }\n\n    #[test]\n    fn test_err_field_name() {\n        expect_err_prefix(\n            serde_json::from_str(\n                r#\"{\n                    \"sub\": 24400320\n                }\"#,\n            ),\n            \"sub: invalid type: integer `24400320`, expected a string at line\",\n        );\n    }\n}\n"
  },
  {
    "path": "src/client.rs",
    "content": "use crate::{\n    AccessToken, AdditionalClaims, AdditionalProviderMetadata, AuthDisplay, AuthPrompt, AuthType,\n    AuthUrl, AuthenticationFlow, AuthorizationCode, AuthorizationRequest, ClaimName, ClaimType,\n    ClientAuthMethod, ClientCredentialsTokenRequest, ClientId, ClientSecret, CodeTokenRequest,\n    ConfigurationError, CsrfToken, DeviceAccessTokenRequest, DeviceAuthorizationRequest,\n    DeviceAuthorizationResponse, DeviceAuthorizationUrl, EndpointMaybeSet, EndpointNotSet,\n    EndpointSet, EndpointState, ErrorResponse, ExtraDeviceAuthorizationFields, GenderClaim,\n    GrantType, IdTokenVerifier, IntrospectionRequest, IntrospectionUrl, IssuerUrl, JsonWebKey,\n    JsonWebKeySet, JweContentEncryptionAlgorithm, JweKeyManagementAlgorithm, JwsSigningAlgorithm,\n    Nonce, PasswordTokenRequest, ProviderMetadata, RedirectUrl, RefreshToken, RefreshTokenRequest,\n    ResourceOwnerPassword, ResourceOwnerUsername, ResponseMode, ResponseType, RevocableToken,\n    RevocationRequest, RevocationUrl, Scope, SubjectIdentifier, SubjectIdentifierType,\n    TokenIntrospectionResponse, TokenResponse, TokenUrl, UserInfoRequest, UserInfoUrl,\n};\n\nuse std::marker::PhantomData;\n\nconst OPENID_SCOPE: &str = \"openid\";\n\n/// OpenID Connect client.\n///\n/// # Error Types\n///\n/// To enable compile time verification that only the correct and complete set of errors for the `Client` function being\n/// invoked are exposed to the caller, the `Client` type is specialized on multiple implementations of the\n/// [`ErrorResponse`] trait. The exact [`ErrorResponse`] implementation returned varies by the RFC that the invoked\n/// `Client` function implements:\n///\n///   - Generic type `TE` (aka Token Error) for errors defined by [RFC 6749 OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749).\n///   - Generic type `TRE` (aka Token Revocation Error) for errors defined by [RFC 7009 OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009).\n///\n/// For example when revoking a token, error code `unsupported_token_type` (from RFC 7009) may be returned:\n/// ```rust\n/// # use http::status::StatusCode;\n/// # use http::header::{HeaderValue, CONTENT_TYPE};\n/// # use openidconnect::core::CoreClient;\n/// # use openidconnect::{\n/// #     AccessToken,\n/// #     AuthUrl,\n/// #     ClientId,\n/// #     ClientSecret,\n/// #     HttpResponse,\n/// #     IssuerUrl,\n/// #     JsonWebKeySet,\n/// #     RequestTokenError,\n/// #     RevocationErrorResponseType,\n/// #     RevocationUrl,\n/// #     TokenUrl,\n/// # };\n/// # use thiserror::Error;\n/// #\n/// # let client =\n/// #     CoreClient::new(\n/// #         ClientId::new(\"aaa\".to_string()),\n/// #         IssuerUrl::new(\"https://example\".to_string()).unwrap(),\n/// #         JsonWebKeySet::default(),\n/// #     )\n/// #     .set_client_secret(ClientSecret::new(\"bbb\".to_string()))\n/// #     .set_auth_uri(AuthUrl::new(\"https://example/authorize\".to_string()).unwrap())\n/// #     .set_token_uri(TokenUrl::new(\"https://example/token\".to_string()).unwrap())\n/// #     .set_revocation_url(RevocationUrl::new(\"https://revocation/url\".to_string()).unwrap());\n/// #\n/// # #[derive(Debug, Error)]\n/// # enum FakeError {\n/// #     #[error(\"error\")]\n/// #     Err,\n/// # }\n/// #\n/// # let http_client = |_| -> Result<HttpResponse, FakeError> {\n/// #     Ok(http::Response::builder()\n/// #         .status(StatusCode::BAD_REQUEST)\n/// #         .header(CONTENT_TYPE, HeaderValue::from_str(\"application/json\").unwrap())\n/// #         .body(\n/// #             r#\"{\"error\": \"unsupported_token_type\",\n/// #                 \"error_description\": \"stuff happened\",\n/// #                 \"error_uri\": \"https://errors\"}\"#\n/// #             .to_string()\n/// #             .into_bytes(),\n/// #         )\n/// #         .unwrap())\n/// # };\n/// #\n/// let res = client\n///     .revoke_token(AccessToken::new(\"some token\".to_string()).into())\n///     .unwrap()\n///     .request(&http_client);\n///\n/// assert!(matches!(res, Err(\n///     RequestTokenError::ServerResponse(err)) if matches!(err.error(),\n///         RevocationErrorResponseType::UnsupportedTokenType)));\n/// ```\n#[derive(Clone, Debug)]\npub struct Client<\n    AC,\n    AD,\n    GC,\n    JE,\n    K,\n    P,\n    TE,\n    TR,\n    TIR,\n    RT,\n    TRE,\n    HasAuthUrl,\n    HasDeviceAuthUrl,\n    HasIntrospectionUrl,\n    HasRevocationUrl,\n    HasTokenUrl,\n    HasUserInfoUrl,\n> where\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    oauth2_client: oauth2::Client<\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n    >,\n    pub(crate) client_id: ClientId,\n    client_secret: Option<ClientSecret>,\n    pub(crate) issuer: IssuerUrl,\n    userinfo_endpoint: Option<UserInfoUrl>,\n    pub(crate) jwks: JsonWebKeySet<K>,\n    id_token_signing_algs: Option<Vec<K::SigningAlgorithm>>,\n    use_openid_scope: bool,\n    _phantom: PhantomData<(AC, AD, GC, JE, P, HasUserInfoUrl)>,\n}\nimpl<AC, AD, GC, JE, K, P, TE, TR, TIR, RT, TRE>\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n{\n    /// Initialize an OpenID Connect client.\n    pub fn new(client_id: ClientId, issuer: IssuerUrl, jwks: JsonWebKeySet<K>) -> Self {\n        Client {\n            oauth2_client: oauth2::Client::new(client_id.clone()),\n            client_id,\n            client_secret: None,\n            issuer,\n            userinfo_endpoint: None,\n            jwks,\n            id_token_signing_algs: None,\n            use_openid_scope: true,\n            _phantom: PhantomData,\n        }\n    }\n}\nimpl<AC, AD, GC, JE, K, P, TE, TR, TIR, RT, TRE>\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        EndpointSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointMaybeSet,\n        EndpointMaybeSet,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n{\n    /// Initialize an OpenID Connect client from OpenID Connect Discovery provider metadata.\n    ///\n    /// Use [`ProviderMetadata::discover`] or\n    /// [`ProviderMetadata::discover_async`] to fetch the provider metadata.\n    pub fn from_provider_metadata<A, CA, CN, CT, G, JK, RM, RS, S>(\n        provider_metadata: ProviderMetadata<A, AD, CA, CN, CT, G, JE, JK, K, RM, RS, S>,\n        client_id: ClientId,\n        client_secret: Option<ClientSecret>,\n    ) -> Self\n    where\n        A: AdditionalProviderMetadata,\n        CA: ClientAuthMethod,\n        CN: ClaimName,\n        CT: ClaimType,\n        G: GrantType,\n        JK: JweKeyManagementAlgorithm,\n        RM: ResponseMode,\n        RS: ResponseType,\n        S: SubjectIdentifierType,\n    {\n        let mut oauth2_client = oauth2::Client::new(client_id.clone())\n            .set_auth_uri(provider_metadata.authorization_endpoint().clone())\n            .set_token_uri_option(provider_metadata.token_endpoint().cloned());\n        if let Some(ref client_secret) = client_secret {\n            oauth2_client = oauth2_client.set_client_secret(client_secret.to_owned());\n        }\n\n        Client {\n            oauth2_client,\n            client_id,\n            client_secret,\n            issuer: provider_metadata.issuer().clone(),\n            userinfo_endpoint: provider_metadata.userinfo_endpoint().cloned(),\n            jwks: provider_metadata.jwks().to_owned(),\n            id_token_signing_algs: Some(\n                provider_metadata\n                    .id_token_signing_alg_values_supported()\n                    .to_owned(),\n            ),\n            use_openid_scope: true,\n            _phantom: PhantomData,\n        }\n    }\n}\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Set the type of client authentication used for communicating with the authorization\n    /// server.\n    ///\n    /// The default is to use HTTP Basic authentication, as recommended in\n    /// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1). Note that\n    /// if a client secret is omitted (i.e., [`set_client_secret()`](Self::set_client_secret) is not\n    /// called), [`AuthType::RequestBody`] is used regardless of the `auth_type` passed to\n    /// this function.\n    pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {\n        self.oauth2_client = self.oauth2_client.set_auth_type(auth_type);\n        self\n    }\n\n    /// Return the type of client authentication used for communicating with the authorization\n    /// server.\n    pub fn auth_type(&self) -> &AuthType {\n        self.oauth2_client.auth_type()\n    }\n\n    /// Set the authorization endpoint.\n    ///\n    /// The client uses the authorization endpoint to obtain authorization from the resource owner\n    /// via user-agent redirection. This URL is used in all standard OAuth2 flows except the\n    /// [Resource Owner Password Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.3)\n    /// and the [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).\n    pub fn set_auth_uri(\n        self,\n        auth_uri: AuthUrl,\n    ) -> Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        EndpointSet,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    > {\n        Client {\n            oauth2_client: self.oauth2_client.set_auth_uri(auth_uri),\n            client_id: self.client_id,\n            client_secret: self.client_secret,\n            issuer: self.issuer,\n            userinfo_endpoint: self.userinfo_endpoint,\n            jwks: self.jwks,\n            id_token_signing_algs: self.id_token_signing_algs,\n            use_openid_scope: self.use_openid_scope,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Return the Client ID.\n    pub fn client_id(&self) -> &ClientId {\n        &self.client_id\n    }\n\n    /// Set the client secret.\n    ///\n    /// A client secret is generally used for confidential (i.e., server-side) OAuth2 clients and\n    /// omitted from public (browser or native app) OAuth2 clients (see\n    /// [RFC 8252](https://tools.ietf.org/html/rfc8252)).\n    pub fn set_client_secret(mut self, client_secret: ClientSecret) -> Self {\n        self.oauth2_client = self.oauth2_client.set_client_secret(client_secret.clone());\n        self.client_secret = Some(client_secret);\n\n        self\n    }\n\n    /// Set the [RFC 8628](https://tools.ietf.org/html/rfc8628) device authorization endpoint used\n    /// for the Device Authorization Flow.\n    ///\n    /// See [`exchange_device_code()`](Self::exchange_device_code).\n    pub fn set_device_authorization_url(\n        self,\n        device_authorization_url: DeviceAuthorizationUrl,\n    ) -> Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        EndpointSet,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    > {\n        Client {\n            oauth2_client: self\n                .oauth2_client\n                .set_device_authorization_url(device_authorization_url),\n            client_id: self.client_id,\n            client_secret: self.client_secret,\n            issuer: self.issuer,\n            userinfo_endpoint: self.userinfo_endpoint,\n            jwks: self.jwks,\n            id_token_signing_algs: self.id_token_signing_algs,\n            use_openid_scope: self.use_openid_scope,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Set the [RFC 7662](https://tools.ietf.org/html/rfc7662) introspection endpoint.\n    ///\n    /// See [`introspect()`](Self::introspect).\n    pub fn set_introspection_url(\n        self,\n        introspection_url: IntrospectionUrl,\n    ) -> Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        EndpointSet,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    > {\n        Client {\n            oauth2_client: self.oauth2_client.set_introspection_url(introspection_url),\n            client_id: self.client_id,\n            client_secret: self.client_secret,\n            issuer: self.issuer,\n            userinfo_endpoint: self.userinfo_endpoint,\n            jwks: self.jwks,\n            id_token_signing_algs: self.id_token_signing_algs,\n            use_openid_scope: self.use_openid_scope,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Set the redirect URL used by the authorization endpoint.\n    pub fn set_redirect_uri(mut self, redirect_url: RedirectUrl) -> Self {\n        self.oauth2_client = self.oauth2_client.set_redirect_uri(redirect_url);\n        self\n    }\n\n    /// Return the redirect URL used by the authorization endpoint.\n    pub fn redirect_uri(&self) -> Option<&RedirectUrl> {\n        self.oauth2_client.redirect_uri()\n    }\n\n    /// Set the [RFC 7009](https://tools.ietf.org/html/rfc7009) revocation endpoint.\n    ///\n    /// See [`revoke_token()`](Self::revoke_token).\n    pub fn set_revocation_url(\n        self,\n        revocation_uri: RevocationUrl,\n    ) -> Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        EndpointSet,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    > {\n        Client {\n            oauth2_client: self.oauth2_client.set_revocation_url(revocation_uri),\n            client_id: self.client_id,\n            client_secret: self.client_secret,\n            issuer: self.issuer,\n            userinfo_endpoint: self.userinfo_endpoint,\n            jwks: self.jwks,\n            id_token_signing_algs: self.id_token_signing_algs,\n            use_openid_scope: self.use_openid_scope,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Set the token endpoint.\n    ///\n    /// The client uses the token endpoint to exchange an authorization code for an access token,\n    /// typically with client authentication. This URL is used in\n    /// all standard OAuth2 flows except the\n    /// [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2).\n    pub fn set_token_uri(\n        self,\n        token_uri: TokenUrl,\n    ) -> Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        EndpointSet,\n        HasUserInfoUrl,\n    > {\n        Client {\n            oauth2_client: self.oauth2_client.set_token_uri(token_uri),\n            client_id: self.client_id,\n            client_secret: self.client_secret,\n            issuer: self.issuer,\n            userinfo_endpoint: self.userinfo_endpoint,\n            jwks: self.jwks,\n            id_token_signing_algs: self.id_token_signing_algs,\n            use_openid_scope: self.use_openid_scope,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Set the user info endpoint.\n    ///\n    /// See [`user_info()`](Self::user_info).\n    pub fn set_user_info_url(\n        self,\n        userinfo_endpoint: UserInfoUrl,\n    ) -> Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        EndpointSet,\n    > {\n        Client {\n            oauth2_client: self.oauth2_client,\n            client_id: self.client_id,\n            client_secret: self.client_secret,\n            issuer: self.issuer,\n            userinfo_endpoint: Some(userinfo_endpoint),\n            jwks: self.jwks,\n            id_token_signing_algs: self.id_token_signing_algs,\n            use_openid_scope: self.use_openid_scope,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Enable the `openid` scope to be requested automatically.\n    ///\n    /// This scope is requested by default, so this function is only useful after previous calls to\n    /// [`disable_openid_scope`][Client::disable_openid_scope].\n    pub fn enable_openid_scope(mut self) -> Self {\n        self.use_openid_scope = true;\n        self\n    }\n\n    /// Disable the `openid` scope from being requested automatically.\n    pub fn disable_openid_scope(mut self) -> Self {\n        self.use_openid_scope = false;\n        self\n    }\n\n    /// Return an ID token verifier for use with the [`IdToken::claims`](crate::IdToken::claims)\n    /// method.\n    pub fn id_token_verifier(&self) -> IdTokenVerifier<K> {\n        let verifier = if let Some(ref client_secret) = self.client_secret {\n            IdTokenVerifier::new_confidential_client(\n                self.client_id.clone(),\n                client_secret.clone(),\n                self.issuer.clone(),\n                self.jwks.clone(),\n            )\n        } else {\n            IdTokenVerifier::new_public_client(\n                self.client_id.clone(),\n                self.issuer.clone(),\n                self.jwks.clone(),\n            )\n        };\n\n        if let Some(id_token_signing_algs) = self.id_token_signing_algs.clone() {\n            verifier.set_allowed_algs(id_token_signing_algs)\n        } else {\n            verifier\n        }\n    }\n}\n\n/// Methods requiring an authorization endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        EndpointSet,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Return the authorization endpoint.\n    pub fn auth_uri(&self) -> &AuthUrl {\n        self.oauth2_client.auth_uri()\n    }\n\n    /// Generate an authorization URL for a new authorization request.\n    ///\n    /// Requires [`set_auth_uri()`](Self::set_auth_uri) to have been previously\n    /// called to set the authorization endpoint.\n    ///\n    /// NOTE: [Passing authorization request parameters as a JSON Web Token\n    /// ](https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests)\n    /// instead of URL query parameters is not currently supported. The\n    /// [`claims` parameter](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter)\n    /// is also not directly supported, although the [`AuthorizationRequest::add_extra_param`]\n    /// method can be used to add custom parameters, including `claims`.\n    ///\n    /// # Arguments\n    ///\n    /// * `authentication_flow` - The authentication flow to use (code, implicit, or hybrid).\n    /// * `state_fn` - A function that returns an opaque value used by the client to maintain state\n    ///   between the request and callback. The authorization server includes this value when\n    ///   redirecting the user-agent back to the client.\n    /// * `nonce_fn` - Similar to `state_fn`, but used to generate an opaque nonce to be used\n    ///   when verifying the ID token returned by the OpenID Connect Provider.\n    ///\n    /// # Security Warning\n    ///\n    /// Callers should use a fresh, unpredictable `state` for each authorization request and verify\n    /// that this value matches the `state` parameter passed by the authorization server to the\n    /// redirect URI. Doing so mitigates\n    /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12)\n    ///  attacks.\n    ///\n    /// Similarly, callers should use a fresh, unpredictable `nonce` to help protect against ID\n    /// token reuse and forgery.\n    pub fn authorize_url<NF, RS, SF>(\n        &self,\n        authentication_flow: AuthenticationFlow<RS>,\n        state_fn: SF,\n        nonce_fn: NF,\n    ) -> AuthorizationRequest<AD, P, RS>\n    where\n        NF: FnOnce() -> Nonce + 'static,\n        RS: ResponseType,\n        SF: FnOnce() -> CsrfToken + 'static,\n    {\n        let request = AuthorizationRequest {\n            inner: self.oauth2_client.authorize_url(state_fn),\n            acr_values: Vec::new(),\n            authentication_flow,\n            claims_locales: Vec::new(),\n            display: None,\n            id_token_hint: None,\n            login_hint: None,\n            max_age: None,\n            nonce: nonce_fn(),\n            prompts: Vec::new(),\n            ui_locales: Vec::new(),\n        };\n        if self.use_openid_scope {\n            request.add_scope(Scope::new(OPENID_SCOPE.to_string()))\n        } else {\n            request\n        }\n    }\n}\n\n/// Methods requiring a token endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        EndpointSet,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Request an access token using the\n    /// [Client Credentials Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4).\n    ///\n    /// Requires [`set_token_uri()`](Self::set_token_uri) to have been previously\n    /// called to set the token endpoint.\n    pub fn exchange_client_credentials(&self) -> ClientCredentialsTokenRequest<TE, TR> {\n        self.oauth2_client.exchange_client_credentials()\n    }\n\n    /// Exchange a code returned during the\n    /// [Authorization Code Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1)\n    /// for an access token.\n    ///\n    /// Acquires ownership of the `code` because authorization codes may only be used once to\n    /// retrieve an access token from the authorization server.\n    ///\n    /// Requires [`set_token_uri()`](Self::set_token_uri) to have been previously\n    /// called to set the token endpoint.\n    pub fn exchange_code(&self, code: AuthorizationCode) -> CodeTokenRequest<TE, TR> {\n        self.oauth2_client.exchange_code(code)\n    }\n\n    /// Exchange an [RFC 8628](https://tools.ietf.org/html/rfc8628#section-3.2) Device Authorization\n    /// Response returned by [`exchange_device_code()`](Self::exchange_device_code) for an access\n    /// token.\n    ///\n    /// Requires [`set_token_uri()`](Self::set_token_uri) to have been previously\n    /// called to set the token endpoint.\n    pub fn exchange_device_access_token<'a, EF>(\n        &'a self,\n        auth_response: &'a DeviceAuthorizationResponse<EF>,\n    ) -> DeviceAccessTokenRequest<'a, 'static, TR, EF>\n    where\n        EF: ExtraDeviceAuthorizationFields,\n    {\n        self.oauth2_client\n            .exchange_device_access_token(auth_response)\n    }\n\n    /// Request an access token using the\n    /// [Resource Owner Password Credentials Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3).\n    ///\n    /// Requires\n    /// [`set_token_uri()`](Self::set_token_uri) to have\n    /// been previously called to set the token endpoint.\n    pub fn exchange_password<'a>(\n        &'a self,\n        username: &'a ResourceOwnerUsername,\n        password: &'a ResourceOwnerPassword,\n    ) -> PasswordTokenRequest<'a, TE, TR> {\n        self.oauth2_client.exchange_password(username, password)\n    }\n\n    /// Exchange a refresh token for an access token.\n    ///\n    /// See <https://tools.ietf.org/html/rfc6749#section-6>.\n    ///\n    /// Requires\n    /// [`set_token_uri()`](Self::set_token_uri) to have\n    /// been previously called to set the token endpoint.\n    pub fn exchange_refresh_token<'a>(\n        &'a self,\n        refresh_token: &'a RefreshToken,\n    ) -> RefreshTokenRequest<'a, TE, TR> {\n        self.oauth2_client.exchange_refresh_token(refresh_token)\n    }\n\n    /// Return the token endpoint.\n    pub fn token_uri(&self) -> &TokenUrl {\n        self.oauth2_client.token_uri()\n    }\n}\n\n/// Methods with a possibly-set token endpoint after calling\n/// [`from_provider_metadata()`](Self::from_provider_metadata).\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        EndpointMaybeSet,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Request an access token using the\n    /// [Client Credentials Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4).\n    ///\n    /// Requires [`from_provider_metadata()`](Self::from_provider_metadata) to have been previously\n    /// called to construct the client.\n    pub fn exchange_client_credentials(\n        &self,\n    ) -> Result<ClientCredentialsTokenRequest<TE, TR>, ConfigurationError> {\n        self.oauth2_client.exchange_client_credentials()\n    }\n\n    /// Exchange a code returned during the\n    /// [Authorization Code Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1)\n    /// for an access token.\n    ///\n    /// Acquires ownership of the `code` because authorization codes may only be used once to\n    /// retrieve an access token from the authorization server.\n    ///\n    /// Requires [`from_provider_metadata()`](Self::from_provider_metadata) to have been previously\n    /// called to construct the client.\n    pub fn exchange_code(\n        &self,\n        code: AuthorizationCode,\n    ) -> Result<CodeTokenRequest<TE, TR>, ConfigurationError> {\n        self.oauth2_client.exchange_code(code)\n    }\n\n    /// Exchange an [RFC 8628](https://tools.ietf.org/html/rfc8628#section-3.2) Device Authorization\n    /// Response returned by [`exchange_device_code()`](Self::exchange_device_code) for an access\n    /// token.\n    ///\n    /// Requires [`from_provider_metadata()`](Self::from_provider_metadata) to have been previously\n    /// called to construct the client.\n    pub fn exchange_device_access_token<'a, EF>(\n        &'a self,\n        auth_response: &'a DeviceAuthorizationResponse<EF>,\n    ) -> Result<DeviceAccessTokenRequest<'a, 'static, TR, EF>, ConfigurationError>\n    where\n        EF: ExtraDeviceAuthorizationFields,\n    {\n        self.oauth2_client\n            .exchange_device_access_token(auth_response)\n    }\n\n    /// Request an access token using the\n    /// [Resource Owner Password Credentials Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3).\n    ///\n    /// Requires [`from_provider_metadata()`](Self::from_provider_metadata) to have been previously\n    /// called to construct the client.\n    pub fn exchange_password<'a>(\n        &'a self,\n        username: &'a ResourceOwnerUsername,\n        password: &'a ResourceOwnerPassword,\n    ) -> Result<PasswordTokenRequest<'a, TE, TR>, ConfigurationError> {\n        self.oauth2_client.exchange_password(username, password)\n    }\n\n    /// Exchange a refresh token for an access token.\n    ///\n    /// See <https://tools.ietf.org/html/rfc6749#section-6>.\n    ///\n    /// Requires [`from_provider_metadata()`](Self::from_provider_metadata) to have been previously\n    /// called to construct the client.\n    pub fn exchange_refresh_token<'a>(\n        &'a self,\n        refresh_token: &'a RefreshToken,\n    ) -> Result<RefreshTokenRequest<'a, TE, TR>, ConfigurationError> {\n        self.oauth2_client.exchange_refresh_token(refresh_token)\n    }\n\n    /// Return the token endpoint.\n    pub fn token_uri(&self) -> Option<&TokenUrl> {\n        self.oauth2_client.token_uri()\n    }\n}\n\n/// Methods requiring a device authorization endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        EndpointSet,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Begin the [RFC 8628](https://tools.ietf.org/html/rfc8628) Device Authorization Flow and\n    /// retrieve a Device Authorization Response.\n    ///\n    /// Requires\n    /// [`set_device_authorization_url()`](Self::set_device_authorization_url) to have\n    /// been previously called to set the device authorization endpoint.\n    ///\n    /// See [`exchange_device_access_token()`](Self::exchange_device_access_token).\n    pub fn exchange_device_code(&self) -> DeviceAuthorizationRequest<TE> {\n        let request = self.oauth2_client.exchange_device_code();\n        if self.use_openid_scope {\n            request.add_scope(Scope::new(OPENID_SCOPE.to_string()))\n        } else {\n            request\n        }\n    }\n\n    /// Return the [RFC 8628](https://tools.ietf.org/html/rfc8628) device authorization endpoint\n    /// used for the Device Authorization Flow.\n    ///\n    /// See [`exchange_device_code()`](Self::exchange_device_code).\n    pub fn device_authorization_url(&self) -> &DeviceAuthorizationUrl {\n        self.oauth2_client.device_authorization_url()\n    }\n}\n\n/// Methods requiring an introspection endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        EndpointSet,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Retrieve metadata for an access token using the\n    /// [`RFC 7662`](https://tools.ietf.org/html/rfc7662) introspection endpoint.\n    ///\n    /// Requires [`set_introspection_url()`](Self::set_introspection_url) to have been previously\n    /// called to set the introspection endpoint.\n    pub fn introspect<'a>(&'a self, token: &'a AccessToken) -> IntrospectionRequest<'a, TE, TIR> {\n        self.oauth2_client.introspect(token)\n    }\n\n    /// Return the [RFC 7662](https://tools.ietf.org/html/rfc7662) introspection endpoint.\n    pub fn introspection_url(&self) -> &IntrospectionUrl {\n        self.oauth2_client.introspection_url()\n    }\n}\n\n/// Methods requiring a revocation endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        EndpointSet,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    /// Revoke an access or refresh token using the [RFC 7009](https://tools.ietf.org/html/rfc7009)\n    /// revocation endpoint.\n    ///\n    /// Requires [`set_revocation_url()`](Self::set_revocation_url) to have been previously\n    /// called to set the revocation endpoint.\n    pub fn revoke_token(\n        &self,\n        token: RT,\n    ) -> Result<RevocationRequest<RT, TRE>, ConfigurationError> {\n        self.oauth2_client.revoke_token(token)\n    }\n\n    /// Return the [RFC 7009](https://tools.ietf.org/html/rfc7009) revocation endpoint.\n    ///\n    /// See [`revoke_token()`](Self::revoke_token()).\n    pub fn revocation_url(&self) -> &RevocationUrl {\n        self.oauth2_client.revocation_url()\n    }\n}\n\n/// Methods requiring a user info endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        EndpointSet,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n{\n    /// Request info about the user associated with the given access token.\n    ///\n    /// Requires [`set_user_info_url()`](Self::set_user_info_url) to have been previously\n    /// called to set the user info endpoint.\n    ///\n    /// To help protect against token substitution attacks, this function optionally allows clients\n    /// to provide the subject identifier whose user info they expect to receive. If provided and\n    /// the subject returned by the OpenID Connect Provider does not match, the\n    /// [`UserInfoRequest::request`] or [`UserInfoRequest::request_async`] functions will return\n    /// [`UserInfoError::ClaimsVerification`](crate::UserInfoError::ClaimsVerification). If set to\n    /// `None`, any subject is accepted.\n    pub fn user_info(\n        &self,\n        access_token: AccessToken,\n        expected_subject: Option<SubjectIdentifier>,\n    ) -> UserInfoRequest<JE, K> {\n        self.user_info_impl(self.user_info_url(), access_token, expected_subject)\n    }\n\n    /// Return the user info endpoint.\n    ///\n    /// See ['user_info()'](Self::user_info).\n    pub fn user_info_url(&self) -> &UserInfoUrl {\n        // This is enforced statically via the HasUserInfo generic type.\n        self.userinfo_endpoint\n            .as_ref()\n            .expect(\"should have user info endpoint\")\n    }\n}\n\n/// Methods with a possibly-set user info endpoint.\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        EndpointMaybeSet,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n{\n    /// Request info about the user associated with the given access token.\n    ///\n    /// Requires [`from_provider_metadata()`](Self::from_provider_metadata) to have been previously\n    /// called to construct the client.\n    ///\n    /// To help protect against token substitution attacks, this function optionally allows clients\n    /// to provide the subject identifier whose user info they expect to receive. If provided and\n    /// the subject returned by the OpenID Connect Provider does not match, the\n    /// [`UserInfoRequest::request`] or [`UserInfoRequest::request_async`] functions will return\n    /// [`UserInfoError::ClaimsVerification`](crate::UserInfoError::ClaimsVerification). If set to\n    /// `None`, any subject is accepted.\n    pub fn user_info(\n        &self,\n        access_token: AccessToken,\n        expected_subject: Option<SubjectIdentifier>,\n    ) -> Result<UserInfoRequest<JE, K>, ConfigurationError> {\n        Ok(self.user_info_impl(\n            self.userinfo_endpoint\n                .as_ref()\n                .ok_or(ConfigurationError::MissingUrl(\"user info\"))?,\n            access_token,\n            expected_subject,\n        ))\n    }\n\n    /// Return the user info endpoint.\n    ///\n    /// See ['user_info()'](Self::user_info).\n    pub fn user_info_url(&self) -> Option<&UserInfoUrl> {\n        self.userinfo_endpoint.as_ref()\n    }\n}\n"
  },
  {
    "path": "src/core/crypto.rs",
    "content": "use crate::core::jwk::CoreJsonCurveType;\nuse crate::core::{CoreJsonWebKey, CoreJsonWebKeyType};\nuse crate::helpers::Base64UrlEncodedBytes;\nuse crate::{JsonWebKey, SignatureVerificationError};\n\nuse std::ops::Deref;\n\nfn rsa_public_key(\n    key: &CoreJsonWebKey,\n) -> Result<(&Base64UrlEncodedBytes, &Base64UrlEncodedBytes), String> {\n    if *key.key_type() != CoreJsonWebKeyType::RSA {\n        Err(\"RSA key required\".to_string())\n    } else {\n        let n = key\n            .n\n            .as_ref()\n            .ok_or_else(|| \"RSA modulus `n` is missing\".to_string())?;\n        let e = key\n            .e\n            .as_ref()\n            .ok_or_else(|| \"RSA exponent `e` is missing\".to_string())?;\n        Ok((n, e))\n    }\n}\n\nfn ec_public_key(\n    key: &CoreJsonWebKey,\n) -> Result<\n    (\n        &Base64UrlEncodedBytes,\n        &Base64UrlEncodedBytes,\n        &CoreJsonCurveType,\n    ),\n    String,\n> {\n    if *key.key_type() != CoreJsonWebKeyType::EllipticCurve {\n        Err(\"EC key required\".to_string())\n    } else {\n        let x = key\n            .x\n            .as_ref()\n            .ok_or_else(|| \"EC `x` part is missing\".to_string())?;\n        let y = key\n            .y\n            .as_ref()\n            .ok_or_else(|| \"EC `y` part is missing\".to_string())?;\n        let crv = key\n            .crv\n            .as_ref()\n            .ok_or_else(|| \"EC `crv` part is missing\".to_string())?;\n        Ok((x, y, crv))\n    }\n}\n\nfn ed_public_key(\n    key: &CoreJsonWebKey,\n) -> Result<(&Base64UrlEncodedBytes, &CoreJsonCurveType), String> {\n    if *key.key_type() != CoreJsonWebKeyType::OctetKeyPair {\n        Err(\"OKP key required\".to_string())\n    } else {\n        let x = key\n            .x\n            .as_ref()\n            .ok_or_else(|| \"OKP `x` part is missing\".to_string())?;\n        let crv = key\n            .crv\n            .as_ref()\n            .ok_or_else(|| \"OKP `crv` part is missing\".to_string())?;\n        Ok((x, crv))\n    }\n}\n\npub fn verify_rsa_signature(\n    key: &CoreJsonWebKey,\n    padding: impl rsa::traits::SignatureScheme,\n    msg: &[u8],\n    signature: &[u8],\n) -> Result<(), SignatureVerificationError> {\n    let (n, e) = rsa_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;\n    // let's n and e as a big integers to prevent issues with leading zeros\n    // according to https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.1\n    // `n` is always unsigned (hence has sign plus)\n\n    let n_bigint = rsa::BigUint::from_bytes_be(n.deref());\n    let e_bigint = rsa::BigUint::from_bytes_be(e.deref());\n    let public_key = rsa::RsaPublicKey::new(n_bigint, e_bigint)\n        .map_err(|e| SignatureVerificationError::InvalidKey(e.to_string()))?;\n\n    public_key\n        .verify(padding, msg, signature)\n        .map_err(|_| SignatureVerificationError::CryptoError(\"bad signature\".to_string()))\n}\n/// According to RFC5480, Section-2.2 implementations of Elliptic Curve Cryptography MUST support the uncompressed form.\n/// The first octet of the octet string indicates whether the uncompressed or compressed form is used. For the uncompressed\n/// form, the first octet has to be 0x04.\n/// According to https://briansmith.org/rustdoc/ring/signature/index.html#ecdsa__fixed-details-fixed-length-pkcs11-style-ecdsa-signatures,\n/// to recover the X and Y coordinates from an octet string, the Octet-String-To-Elliptic-Curve-Point Conversion\n/// is used (Section 2.3.4 of https://www.secg.org/sec1-v2.pdf).\n\npub fn verify_ec_signature(\n    key: &CoreJsonWebKey,\n    msg: &[u8],\n    signature: &[u8],\n) -> Result<(), SignatureVerificationError> {\n    use p256::ecdsa::signature::Verifier;\n\n    let (x, y, crv) = ec_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;\n    let mut pk = vec![0x04];\n    pk.extend(x.deref());\n    pk.extend(y.deref());\n    match *crv {\n        CoreJsonCurveType::P256 => {\n            let public_key = p256::ecdsa::VerifyingKey::from_sec1_bytes(&pk)\n                .map_err(|e| SignatureVerificationError::InvalidKey(e.to_string()))?;\n            public_key\n                .verify(\n                    msg,\n                    &p256::ecdsa::Signature::from_slice(signature).map_err(|_| {\n                        SignatureVerificationError::CryptoError(\"Invalid signature\".to_string())\n                    })?,\n                )\n                .map_err(|_| {\n                    SignatureVerificationError::CryptoError(\"EC Signature was wrong\".to_string())\n                })\n        }\n        CoreJsonCurveType::P384 => {\n            let public_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(&pk)\n                .map_err(|e| SignatureVerificationError::InvalidKey(e.to_string()))?;\n            public_key\n                .verify(\n                    msg,\n                    &p384::ecdsa::Signature::from_slice(signature).map_err(|_| {\n                        SignatureVerificationError::CryptoError(\"Invalid signature\".to_string())\n                    })?,\n                )\n                .map_err(|_| {\n                    SignatureVerificationError::CryptoError(\"EC Signature was wrong\".to_string())\n                })\n        }\n        CoreJsonCurveType::P521 => Err(SignatureVerificationError::UnsupportedAlg(\n            \"P521\".to_string(),\n        )),\n        _ => Err(SignatureVerificationError::InvalidKey(format!(\n            \"unrecognized curve `{crv:?}`\"\n        ))),\n    }\n}\n\npub fn verify_ed_signature(\n    key: &CoreJsonWebKey,\n    msg: &[u8],\n    signature: &[u8],\n) -> Result<(), SignatureVerificationError> {\n    use ed25519_dalek::Verifier;\n\n    let (x, crv) = ed_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;\n\n    match *crv {\n        CoreJsonCurveType::Ed25519 => {\n            let public_key = ed25519_dalek::VerifyingKey::try_from(x.deref().as_slice())\n                .map_err(|e| SignatureVerificationError::InvalidKey(e.to_string()))?;\n\n            public_key\n                .verify(\n                    msg,\n                    &ed25519_dalek::Signature::from_slice(signature).map_err(|_| {\n                        SignatureVerificationError::CryptoError(\"invalid signature\".to_string())\n                    })?,\n                )\n                .map_err(|_| {\n                    SignatureVerificationError::CryptoError(\"incorrect EdDSA signature\".to_string())\n                })\n        }\n        _ => Err(SignatureVerificationError::InvalidKey(format!(\n            \"unrecognized curve `{crv:?}`\"\n        ))),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::core::crypto::verify_rsa_signature;\n    use crate::core::CoreJsonWebKey;\n\n    use base64::prelude::BASE64_URL_SAFE_NO_PAD;\n    use base64::Engine;\n    use sha2::Digest;\n\n    #[test]\n    fn test_leading_zeros_are_parsed_correctly() {\n        // The message we signed\n        let msg = \"THIS IS A SIGNATURE TEST\";\n        let signature = BASE64_URL_SAFE_NO_PAD.decode(\"bg0ohqKwYHAiODeG6qkJ-6IhodN7LGPxAh4hbWeIoBdSXrXMt8Ft8U0BV7vANPvF56h20XB9C0021x2kt7iAbMgPNcZ7LCuXMPPq04DrBpMHafH5BXBwnyDKJKrzDm5sfr6OgEkcxSLHaSJ6gTWQ3waPt6_SeH2-Fi74rg13MHyX-0iqz7bZveoBbGIs5yQCwvXgrDS9zW5LUwUHozHfE6FuSi_Z92ioXeu7FHHDg1KFfg3hs8ZLx4wAX15Vw2GCQOzvyNdbItxXRLnrN1NPqxFquVNo5RGlx6ihR1Jfe7y_n0NSR2q2TuU4cIwR0LRwEaANy5SDqtleQPrTEn8nGQ\").unwrap();\n        // RSA pub key with leading 0\n        let key : CoreJsonWebKey = serde_json::from_value(serde_json::json!(\n            {\n            \"kty\": \"RSA\",\n            \"e\": \"AQAB\",\n            \"use\": \"sig\",\n            \"kid\": \"TEST_KEY_ID\",\n            \"alg\": \"RS256\",\n            \"n\": \"AN0M6Y760b9Ok2PxDOps1TgSmiOaR9mLIfUHtZ_o-6JypOckGcl1CxrteyokOb3WyDsfIAN9fFNrycv5YoLKO7sh0IcfzNEXFgzK84HTBcGuqhN8NV98Z6N9EryUrgJYsJeVoPYm0MzkDe4NyWHhnq-9OyNCQzVELH0NhhViQqRyM92OPrJcQlk8s3ZvcgRmkd-rEtRua8SbS3GEvfvgweVy5-qcJCGoziKfx-IteMOm6yKoHvqisKb91N-qw_kSS4YQUx-DZVDo2g24F7VIbcYzJGUOU674HUF1j-wJyXzG3VV8lAXD8hABs5Lh87gr8_hIZD5gbYBJRObJk9XZbfk\"\n            }\n        )).unwrap();\n\n        let mut hasher = sha2::Sha256::new();\n        hasher.update(msg);\n        let hash = hasher.finalize().to_vec();\n        assert! {\n            verify_rsa_signature(\n                &key,\n                rsa::Pkcs1v15Sign::new::<sha2::Sha256>(),\n                &hash,\n                &signature,\n            ).is_ok()\n        }\n    }\n}\n"
  },
  {
    "path": "src/core/jwk/mod.rs",
    "content": "use crate::core::{crypto, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm};\nuse crate::helpers::{deserialize_option_or_none, Base64UrlEncodedBytes};\nuse crate::types::jwks::check_key_compatibility;\nuse crate::{\n    JsonWebKey, JsonWebKeyAlgorithm, JsonWebKeyId, JsonWebKeyType, JsonWebKeyUse,\n    JsonWebTokenAlgorithm, PrivateSigningKey, SignatureVerificationError, SigningError,\n};\n\nuse ed25519_dalek::pkcs8::DecodePrivateKey;\nuse ed25519_dalek::Signer;\nuse rsa::pkcs1::DecodeRsaPrivateKey;\nuse serde::{Deserialize, Serialize};\nuse serde_with::skip_serializing_none;\nuse sha2::{Digest, Sha256, Sha384, Sha512};\n\n#[cfg(test)]\nmod tests;\n\n// Other than the 'kty' (key type) parameter, which must be present in all JWKs, Section 4 of RFC\n// 7517 states that \"member names used for representing key parameters for different keys types\n// need not be distinct.\" Therefore, it's possible that future or non-standard key types will supply\n// some of the following parameters but with different types, causing deserialization to fail. To\n// support such key types, we'll need to define a new impl for JsonWebKey. Deserializing the new\n// impl would probably need to involve first deserializing the raw values to access the 'kty'\n// parameter, and then deserializing the fields and types appropriate for that key type.\n/// Public or symmetric key expressed as a JSON Web Key.\n#[skip_serializing_none]\n#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]\npub struct CoreJsonWebKey {\n    pub(crate) kty: CoreJsonWebKeyType,\n    #[serde(rename = \"use\")]\n    pub(crate) use_: Option<CoreJsonWebKeyUse>,\n    pub(crate) kid: Option<JsonWebKeyId>,\n\n    /// The algorithm intended to be used with this key (see\n    /// [RFC 7517](https://www.rfc-editor.org/rfc/rfc7517#section-4.4)).\n    ///\n    /// It can either be an algorithm intended for use with JWS or JWE, or something different.\n    pub(crate) alg:\n        Option<JsonWebTokenAlgorithm<CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>>,\n\n    // From RFC 7517, Section 4: \"Additional members can be present in the JWK; if not understood\n    // by implementations encountering them, they MUST be ignored.  Member names used for\n    // representing key parameters for different keys types need not be distinct.\"\n    // Hence, we set fields we fail to deserialize (understand) as None.\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) n: Option<Base64UrlEncodedBytes>,\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) e: Option<Base64UrlEncodedBytes>,\n\n    //Elliptic Curve\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) crv: Option<CoreJsonCurveType>,\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) x: Option<Base64UrlEncodedBytes>,\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) y: Option<Base64UrlEncodedBytes>,\n\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) d: Option<Base64UrlEncodedBytes>,\n\n    // Used for symmetric keys, which we only generate internally from the client secret; these\n    // are never part of the JWK set.\n    #[serde(default, deserialize_with = \"deserialize_option_or_none\")]\n    pub(crate) k: Option<Base64UrlEncodedBytes>,\n}\nimpl CoreJsonWebKey {\n    /// Instantiate a new RSA public key from the raw modulus (`n`) and public exponent (`e`),\n    /// along with an optional (but recommended) key ID.\n    ///\n    /// The key ID is used for matching signed JSON Web Tokens with the keys used for verifying\n    /// their signatures.\n    pub fn new_rsa(n: Vec<u8>, e: Vec<u8>, kid: Option<JsonWebKeyId>) -> Self {\n        Self {\n            kty: CoreJsonWebKeyType::RSA,\n            use_: Some(CoreJsonWebKeyUse::Signature),\n            kid,\n            n: Some(Base64UrlEncodedBytes::new(n)),\n            e: Some(Base64UrlEncodedBytes::new(e)),\n            k: None,\n            crv: None,\n            x: None,\n            y: None,\n            d: None,\n            alg: None,\n        }\n    }\n    /// Instantiate a new EC public key from the raw x (`x`) and y(`y`) part of the curve,\n    /// along with an optional (but recommended) key ID.\n    ///\n    /// The key ID is used for matching signed JSON Web Tokens with the keys used for verifying\n    /// their signatures.\n    pub fn new_ec(\n        x: Vec<u8>,\n        y: Vec<u8>,\n        crv: CoreJsonCurveType,\n        kid: Option<JsonWebKeyId>,\n    ) -> Self {\n        Self {\n            kty: CoreJsonWebKeyType::EllipticCurve,\n            use_: Some(CoreJsonWebKeyUse::Signature),\n            kid,\n            n: None,\n            e: None,\n            k: None,\n            crv: Some(crv),\n            x: Some(Base64UrlEncodedBytes::new(x)),\n            y: Some(Base64UrlEncodedBytes::new(y)),\n            d: None,\n            alg: None,\n        }\n    }\n\n    /// Instantiate a new Octet Key-Pair public key from the raw x (`x`) part of the curve,\n    /// along with an optional (but recommended) key ID.\n    ///\n    /// The key ID is used for matching signed JSON Web Tokens with the keys used for verifying\n    /// their signatures.\n    pub fn new_okp(x: Vec<u8>, crv: CoreJsonCurveType, kid: Option<JsonWebKeyId>) -> Self {\n        Self {\n            kty: CoreJsonWebKeyType::OctetKeyPair,\n            use_: Some(CoreJsonWebKeyUse::Signature),\n            kid,\n            n: None,\n            e: None,\n            k: None,\n            crv: Some(crv),\n            x: Some(Base64UrlEncodedBytes::new(x)),\n            y: None,\n            d: None,\n            alg: None,\n        }\n    }\n}\nimpl JsonWebKey for CoreJsonWebKey {\n    type KeyUse = CoreJsonWebKeyUse;\n    type SigningAlgorithm = CoreJwsSigningAlgorithm;\n\n    fn key_id(&self) -> Option<&JsonWebKeyId> {\n        self.kid.as_ref()\n    }\n    fn key_type(&self) -> &CoreJsonWebKeyType {\n        &self.kty\n    }\n    fn key_use(&self) -> Option<&CoreJsonWebKeyUse> {\n        self.use_.as_ref()\n    }\n\n    fn signing_alg(&self) -> JsonWebKeyAlgorithm<&CoreJwsSigningAlgorithm> {\n        match self.alg {\n            None => JsonWebKeyAlgorithm::Unspecified,\n            Some(JsonWebTokenAlgorithm::Signature(ref alg)) => JsonWebKeyAlgorithm::Algorithm(alg),\n            Some(_) => JsonWebKeyAlgorithm::Unsupported,\n        }\n    }\n\n    fn new_symmetric(key: Vec<u8>) -> Self {\n        Self {\n            kty: CoreJsonWebKeyType::Symmetric,\n            use_: None,\n            kid: None,\n            n: None,\n            e: None,\n            k: Some(Base64UrlEncodedBytes::new(key)),\n            crv: None,\n            x: None,\n            y: None,\n            d: None,\n            alg: None,\n        }\n    }\n\n    fn verify_signature(\n        &self,\n        signature_alg: &CoreJwsSigningAlgorithm,\n        message: &[u8],\n        signature: &[u8],\n    ) -> Result<(), SignatureVerificationError> {\n        use hmac::Mac;\n\n        check_key_compatibility(self, signature_alg)\n            .map_err(|e| SignatureVerificationError::InvalidKey(e.to_owned()))?;\n\n        match *signature_alg {\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256 => {\n                let message = {\n                    let mut hasher = sha2::Sha256::new();\n                    hasher.update(message);\n                    &hasher.finalize()\n                };\n                crypto::verify_rsa_signature(\n                    self,\n                    rsa::Pkcs1v15Sign::new::<sha2::Sha256>(),\n                    message,\n                    signature,\n                )\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384 => {\n                let message = {\n                    let mut hasher = sha2::Sha384::new();\n                    hasher.update(message);\n                    &hasher.finalize()\n                };\n                crypto::verify_rsa_signature(\n                    self,\n                    rsa::Pkcs1v15Sign::new::<sha2::Sha384>(),\n                    message,\n                    signature,\n                )\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512 => {\n                let message = {\n                    let mut hasher = sha2::Sha512::new();\n                    hasher.update(message);\n                    &hasher.finalize()\n                };\n                crypto::verify_rsa_signature(\n                    self,\n                    rsa::Pkcs1v15Sign::new::<sha2::Sha512>(),\n                    message,\n                    signature,\n                )\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPssSha256 => {\n                let message = {\n                    let mut hasher = sha2::Sha256::new();\n                    hasher.update(message);\n                    &hasher.finalize()\n                };\n                crypto::verify_rsa_signature(\n                    self,\n                    rsa::Pss::new::<sha2::Sha256>(),\n                    message,\n                    signature,\n                )\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPssSha384 => {\n                let message = {\n                    let mut hasher = sha2::Sha384::new();\n                    hasher.update(message);\n                    &hasher.finalize()\n                };\n                crypto::verify_rsa_signature(\n                    self,\n                    rsa::Pss::new::<sha2::Sha384>(),\n                    message,\n                    signature,\n                )\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPssSha512 => {\n                let message = {\n                    let mut hasher = sha2::Sha512::new();\n                    hasher.update(message);\n                    &hasher.finalize()\n                };\n                crypto::verify_rsa_signature(\n                    self,\n                    rsa::Pss::new::<sha2::Sha512>(),\n                    message,\n                    signature,\n                )\n            }\n            CoreJwsSigningAlgorithm::HmacSha256 => {\n                let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(\n                    self.k.as_ref().ok_or_else(|| {\n                        SignatureVerificationError::InvalidKey(\n                            \"Symmetric key `k` is missing\".to_string(),\n                        )\n                    })?,\n                )\n                .map_err(|e| {\n                    SignatureVerificationError::Other(format!(\"Could not create key: {}\", e))\n                })?;\n                mac.update(message);\n                mac.verify(signature.into())\n                    .map_err(|_| SignatureVerificationError::CryptoError(\"bad HMAC\".to_string()))\n            }\n            CoreJwsSigningAlgorithm::HmacSha384 => {\n                let mut mac = hmac::Hmac::<sha2::Sha384>::new_from_slice(\n                    self.k.as_ref().ok_or_else(|| {\n                        SignatureVerificationError::InvalidKey(\n                            \"Symmetric key `k` is missing\".to_string(),\n                        )\n                    })?,\n                )\n                .map_err(|e| {\n                    SignatureVerificationError::Other(format!(\"Could not create key: {}\", e))\n                })?;\n                mac.update(message);\n                mac.verify(signature.into())\n                    .map_err(|_| SignatureVerificationError::CryptoError(\"bad HMAC\".to_string()))\n            }\n            CoreJwsSigningAlgorithm::HmacSha512 => {\n                let mut mac = hmac::Hmac::<sha2::Sha512>::new_from_slice(\n                    self.k.as_ref().ok_or_else(|| {\n                        SignatureVerificationError::InvalidKey(\n                            \"Symmetric key `k` is missing\".to_string(),\n                        )\n                    })?,\n                )\n                .map_err(|e| {\n                    SignatureVerificationError::Other(format!(\"Could not create key: {}\", e))\n                })?;\n                mac.update(message);\n                mac.verify(signature.into())\n                    .map_err(|_| SignatureVerificationError::CryptoError(\"bad HMAC\".to_string()))\n            }\n            CoreJwsSigningAlgorithm::EcdsaP256Sha256 => {\n                if matches!(self.crv, Some(CoreJsonCurveType::P256)) {\n                    crypto::verify_ec_signature(self, message, signature)\n                } else {\n                    Err(SignatureVerificationError::InvalidKey(\n                        \"Key uses different CRV than JWT\".to_string(),\n                    ))\n                }\n            }\n            CoreJwsSigningAlgorithm::EcdsaP384Sha384 => {\n                if matches!(self.crv, Some(CoreJsonCurveType::P384)) {\n                    crypto::verify_ec_signature(self, message, signature)\n                } else {\n                    Err(SignatureVerificationError::InvalidKey(\n                        \"Key uses different CRV than JWT\".to_string(),\n                    ))\n                }\n            }\n            CoreJwsSigningAlgorithm::EdDsa => match self.crv {\n                None => Err(SignatureVerificationError::InvalidKey(\n                    \"EdDSA key must specify `crv`\".to_string(),\n                )),\n                Some(CoreJsonCurveType::Ed25519) => {\n                    crypto::verify_ed_signature(self, message, signature)\n                }\n                Some(ref crv) => Err(SignatureVerificationError::InvalidKey(format!(\n                    \"Unsupported EdDSA curve {crv:?}\"\n                ))),\n            },\n            ref other => Err(SignatureVerificationError::UnsupportedAlg(\n                serde_plain::to_string(other).unwrap_or_else(|err| {\n                    panic!(\n                        \"signature alg {:?} failed to serialize to a string: {}\",\n                        other, err\n                    )\n                }),\n            )),\n        }\n    }\n\n    fn hash_bytes(&self, bytes: &[u8], alg: &Self::SigningAlgorithm) -> Result<Vec<u8>, String> {\n        check_key_compatibility(self, alg).map_err(String::from)?;\n\n        match *alg {\n            CoreJwsSigningAlgorithm::HmacSha256\n            | CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256\n            | CoreJwsSigningAlgorithm::RsaSsaPssSha256\n            | CoreJwsSigningAlgorithm::EcdsaP256Sha256 => {\n                let mut hasher = Sha256::new();\n                hasher.update(bytes);\n                Ok(hasher.finalize().to_vec())\n            }\n            CoreJwsSigningAlgorithm::HmacSha384\n            | CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384\n            | CoreJwsSigningAlgorithm::RsaSsaPssSha384\n            | CoreJwsSigningAlgorithm::EcdsaP384Sha384 => {\n                let mut hasher = Sha384::new();\n                hasher.update(bytes);\n                Ok(hasher.finalize().to_vec())\n            }\n            CoreJwsSigningAlgorithm::HmacSha512\n            | CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512\n            | CoreJwsSigningAlgorithm::RsaSsaPssSha512\n            | CoreJwsSigningAlgorithm::EcdsaP521Sha512 => {\n                let mut hasher = Sha512::new();\n                hasher.update(bytes);\n                Ok(hasher.finalize().to_vec())\n            }\n            CoreJwsSigningAlgorithm::EdDsa => match self.crv {\n                None => Err(\"EdDSA key must specify `crv`\".to_string()),\n                Some(CoreJsonCurveType::Ed25519) => {\n                    let mut hasher = Sha512::new();\n                    hasher.update(bytes);\n                    Ok(hasher.finalize().to_vec())\n                }\n                Some(ref crv) => Err(format!(\"Unsupported EdDSA curve {crv:?}\")),\n            },\n            CoreJwsSigningAlgorithm::None => {\n                Err(\"Signature algorithm `none` has no corresponding hash algorithm\".to_string())\n            }\n        }\n    }\n}\n\n/// HMAC secret key.\n///\n/// This key can be used for signing messages, or converted to a `CoreJsonWebKey` for verifying\n/// them.\n#[derive(Clone)]\npub struct CoreHmacKey {\n    secret: Vec<u8>,\n}\nimpl CoreHmacKey {\n    /// Instantiate a new key from the specified secret bytes.\n    pub fn new<T>(secret: T) -> Self\n    where\n        T: Into<Vec<u8>>,\n    {\n        Self {\n            secret: secret.into(),\n        }\n    }\n}\nimpl PrivateSigningKey for CoreHmacKey {\n    type VerificationKey = CoreJsonWebKey;\n\n    fn sign(\n        &self,\n        signature_alg: &CoreJwsSigningAlgorithm,\n        message: &[u8],\n    ) -> Result<Vec<u8>, SigningError> {\n        use hmac::Mac;\n        match *signature_alg {\n            CoreJwsSigningAlgorithm::HmacSha256 => {\n                let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&self.secret)\n                    .map_err(|e| SigningError::Other(format!(\"Could not create key: {}\", e)))?;\n                mac.update(message);\n                let result = mac.finalize();\n                Ok(result.into_bytes().as_slice().to_vec())\n            }\n            CoreJwsSigningAlgorithm::HmacSha384 => {\n                let mut mac = hmac::Hmac::<sha2::Sha384>::new_from_slice(&self.secret)\n                    .map_err(|e| SigningError::Other(format!(\"Could not create key: {}\", e)))?;\n                mac.update(message);\n                let result = mac.finalize();\n                Ok(result.into_bytes().as_slice().to_vec())\n            }\n            CoreJwsSigningAlgorithm::HmacSha512 => {\n                let mut mac = hmac::Hmac::<sha2::Sha512>::new_from_slice(&self.secret)\n                    .map_err(|e| SigningError::Other(format!(\"Could not create key: {}\", e)))?;\n                mac.update(message);\n                let result = mac.finalize();\n                Ok(result.into_bytes().as_slice().to_vec())\n            }\n            ref other => Err(SigningError::UnsupportedAlg(\n                serde_plain::to_string(other).unwrap_or_else(|err| {\n                    panic!(\n                        \"signature alg {:?} failed to serialize to a string: {}\",\n                        other, err\n                    )\n                }),\n            )),\n        }\n    }\n\n    fn as_verification_key(&self) -> CoreJsonWebKey {\n        CoreJsonWebKey::new_symmetric(self.secret.clone())\n    }\n}\n\nenum EdDsaSigningKey {\n    Ed25519(ed25519_dalek::SigningKey),\n}\n\nimpl EdDsaSigningKey {\n    fn from_ed25519_pem(pem: &str) -> Result<Self, String> {\n        Ok(Self::Ed25519(\n            ed25519_dalek::SigningKey::from_pkcs8_pem(pem).map_err(|err| err.to_string())?,\n        ))\n    }\n\n    fn sign(&self, message: &[u8]) -> Vec<u8> {\n        match self {\n            Self::Ed25519(key) => {\n                let signature = key.sign(message);\n\n                signature.to_vec()\n            }\n        }\n    }\n}\n\n/// EdDSA Private Key.\n///\n/// This key can be used for signing messages, or converted to a `CoreJsonWebKey` for verifying\n/// them.\npub struct CoreEdDsaPrivateSigningKey {\n    kid: Option<JsonWebKeyId>,\n    key_pair: EdDsaSigningKey,\n}\nimpl CoreEdDsaPrivateSigningKey {\n    /// Converts an EdDSA private key (in PEM format) to a JWK representing its public key.\n    pub fn from_ed25519_pem(pem: &str, kid: Option<JsonWebKeyId>) -> Result<Self, String> {\n        Ok(Self {\n            kid,\n            key_pair: EdDsaSigningKey::from_ed25519_pem(pem)?,\n        })\n    }\n}\nimpl PrivateSigningKey for CoreEdDsaPrivateSigningKey {\n    type VerificationKey = CoreJsonWebKey;\n\n    fn sign(\n        &self,\n        signature_alg: &CoreJwsSigningAlgorithm,\n        message: &[u8],\n    ) -> Result<Vec<u8>, SigningError> {\n        match *signature_alg {\n            CoreJwsSigningAlgorithm::EdDsa => Ok(self.key_pair.sign(message)),\n            ref other => Err(SigningError::UnsupportedAlg(\n                serde_plain::to_string(other).unwrap_or_else(|err| {\n                    panic!(\n                        \"signature alg {:?} failed to serialize to a string: {}\",\n                        other, err\n                    )\n                }),\n            )),\n        }\n    }\n\n    fn as_verification_key(&self) -> CoreJsonWebKey {\n        match &self.key_pair {\n            EdDsaSigningKey::Ed25519(key) => CoreJsonWebKey {\n                kty: CoreJsonWebKeyType::OctetKeyPair,\n                use_: Some(CoreJsonWebKeyUse::Signature),\n                kid: self.kid.clone(),\n                n: None,\n                e: None,\n                crv: Some(CoreJsonCurveType::Ed25519),\n                x: Some(Base64UrlEncodedBytes::new(\n                    key.verifying_key().as_bytes().to_vec(),\n                )),\n                y: None,\n                d: None,\n                k: None,\n                alg: None,\n            },\n        }\n    }\n}\n\n/// Trait used to allow testing with an alternative RNG.\n/// Clone is necessary to get a mutable version of the RNG.\npub(crate) trait RngClone: dyn_clone::DynClone + rand::RngCore + rand::CryptoRng {}\ndyn_clone::clone_trait_object!(RngClone);\nimpl<T> RngClone for T where T: rand::RngCore + rand::CryptoRng + Clone {}\n\n/// RSA private key.\n///\n/// This key can be used for signing messages, or converted to a `CoreJsonWebKey` for verifying\n/// them.\npub struct CoreRsaPrivateSigningKey {\n    key_pair: rsa::RsaPrivateKey,\n    rng: Box<dyn RngClone + Send + Sync>,\n    kid: Option<JsonWebKeyId>,\n}\nimpl CoreRsaPrivateSigningKey {\n    /// Converts an RSA private key (in PEM format) to a JWK representing its public key.\n    pub fn from_pem(pem: &str, kid: Option<JsonWebKeyId>) -> Result<Self, String> {\n        Self::from_pem_internal(pem, Box::new(rand::rngs::OsRng), kid)\n    }\n\n    pub(crate) fn from_pem_internal(\n        pem: &str,\n        rng: Box<dyn RngClone + Send + Sync>,\n        kid: Option<JsonWebKeyId>,\n    ) -> Result<Self, String> {\n        let key_pair = rsa::RsaPrivateKey::from_pkcs1_pem(pem).map_err(|err| err.to_string())?;\n        Ok(Self { key_pair, rng, kid })\n    }\n}\nimpl PrivateSigningKey for CoreRsaPrivateSigningKey {\n    type VerificationKey = CoreJsonWebKey;\n\n    fn sign(\n        &self,\n        signature_alg: &CoreJwsSigningAlgorithm,\n        msg: &[u8],\n    ) -> Result<Vec<u8>, SigningError> {\n        match *signature_alg {\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256 => {\n                let mut hasher = sha2::Sha256::new();\n                hasher.update(msg);\n                let hash = hasher.finalize().to_vec();\n\n                self.key_pair\n                    .sign_with_rng(\n                        &mut dyn_clone::clone_box(&self.rng),\n                        rsa::Pkcs1v15Sign::new::<sha2::Sha256>(),\n                        &hash,\n                    )\n                    .map_err(|_| SigningError::CryptoError)\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384 => {\n                let mut hasher = sha2::Sha384::new();\n                hasher.update(msg);\n                let hash = hasher.finalize().to_vec();\n\n                self.key_pair\n                    .sign_with_rng(\n                        &mut dyn_clone::clone_box(&self.rng),\n                        rsa::Pkcs1v15Sign::new::<sha2::Sha384>(),\n                        &hash,\n                    )\n                    .map_err(|_| SigningError::CryptoError)\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512 => {\n                let mut hasher = sha2::Sha512::new();\n                hasher.update(msg);\n                let hash = hasher.finalize().to_vec();\n\n                self.key_pair\n                    .sign_with_rng(\n                        &mut dyn_clone::clone_box(&self.rng),\n                        rsa::Pkcs1v15Sign::new::<sha2::Sha512>(),\n                        &hash,\n                    )\n                    .map_err(|_| SigningError::CryptoError)\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPssSha256 => {\n                let mut hasher = sha2::Sha256::new();\n                hasher.update(msg);\n                let hash = hasher.finalize().to_vec();\n\n                self.key_pair\n                    .sign_with_rng(\n                        &mut dyn_clone::clone_box(&self.rng),\n                        rsa::Pss::new_with_salt::<sha2::Sha256>(hash.len()),\n                        &hash,\n                    )\n                    .map_err(|_| SigningError::CryptoError)\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPssSha384 => {\n                let mut hasher = sha2::Sha384::new();\n                hasher.update(msg);\n                let hash = hasher.finalize().to_vec();\n\n                self.key_pair\n                    .sign_with_rng(\n                        &mut dyn_clone::clone_box(&self.rng),\n                        rsa::Pss::new_with_salt::<sha2::Sha384>(hash.len()),\n                        &hash,\n                    )\n                    .map_err(|_| SigningError::CryptoError)\n            }\n            CoreJwsSigningAlgorithm::RsaSsaPssSha512 => {\n                let mut hasher = sha2::Sha512::new();\n                hasher.update(msg);\n                let hash = hasher.finalize().to_vec();\n\n                self.key_pair\n                    .sign_with_rng(\n                        &mut dyn_clone::clone_box(&self.rng),\n                        rsa::Pss::new_with_salt::<sha2::Sha512>(hash.len()),\n                        &hash,\n                    )\n                    .map_err(|_| SigningError::CryptoError)\n            }\n            ref other => Err(SigningError::UnsupportedAlg(\n                serde_plain::to_string(other).unwrap_or_else(|err| {\n                    panic!(\n                        \"signature alg {:?} failed to serialize to a string: {}\",\n                        other, err\n                    )\n                }),\n            )),\n        }\n    }\n\n    fn as_verification_key(&self) -> CoreJsonWebKey {\n        use rsa::traits::PublicKeyParts;\n\n        let public_key = self.key_pair.to_public_key();\n        CoreJsonWebKey {\n            kty: CoreJsonWebKeyType::RSA,\n            use_: Some(CoreJsonWebKeyUse::Signature),\n            kid: self.kid.clone(),\n            n: Some(Base64UrlEncodedBytes::new(public_key.n().to_bytes_be())),\n            e: Some(Base64UrlEncodedBytes::new(public_key.e().to_bytes_be())),\n            k: None,\n            crv: None,\n            x: None,\n            y: None,\n            d: None,\n            alg: None,\n        }\n    }\n}\n\n/// Type of JSON Web Key.\n#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]\n#[non_exhaustive]\npub enum CoreJsonWebKeyType {\n    /// Elliptic Curve Cryptography (ECC) key.\n    ///\n    /// ECC algorithms such as ECDSA are currently unsupported.\n    #[serde(rename = \"EC\")]\n    EllipticCurve,\n    /// RSA key.\n    #[serde(rename = \"RSA\")]\n    RSA,\n    /// EdDSA key.\n    #[serde(rename = \"OKP\")]\n    OctetKeyPair,\n    /// Symmetric key.\n    #[serde(rename = \"oct\")]\n    Symmetric,\n}\nimpl JsonWebKeyType for CoreJsonWebKeyType {}\n\n/// Type of EC-Curve\n#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]\n#[non_exhaustive]\npub enum CoreJsonCurveType {\n    /// P-256 Curve\n    #[serde(rename = \"P-256\")]\n    P256,\n    /// P-384 Curve\n    #[serde(rename = \"P-384\")]\n    P384,\n    /// P-521 Curve (currently not supported)\n    #[serde(rename = \"P-521\")]\n    P521,\n    /// Ed25519 Curve\n    #[serde(rename = \"Ed25519\")]\n    Ed25519,\n}\n\n/// Usage restriction for a JSON Web key.\n#[derive(Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum CoreJsonWebKeyUse {\n    /// Key may be used for digital signatures.\n    Signature,\n\n    /// Key may be used for encryption.\n    Encryption,\n\n    /// Fallback case for other key uses not understood by this library.\n    Other(String),\n}\nimpl CoreJsonWebKeyUse {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"sig\" => Self::Signature,\n            \"enc\" => Self::Encryption,\n            other => Self::Other(other.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreJsonWebKeyUse {\n    fn as_ref(&self) -> &str {\n        match self {\n            CoreJsonWebKeyUse::Signature => \"sig\",\n            CoreJsonWebKeyUse::Encryption => \"enc\",\n            CoreJsonWebKeyUse::Other(other) => other.as_str(),\n        }\n    }\n}\nimpl JsonWebKeyUse for CoreJsonWebKeyUse {\n    fn allows_signature(&self) -> bool {\n        matches!(*self, CoreJsonWebKeyUse::Signature)\n    }\n    fn allows_encryption(&self) -> bool {\n        matches!(*self, CoreJsonWebKeyUse::Encryption)\n    }\n}\n// FIXME: Once https://github.com/serde-rs/serde/issues/912 is resolved, use #[serde(other)] instead\n// of custom serializer/deserializers. Right now this isn't possible because serde(other) only\n// supports unit variants.\ndeserialize_from_str!(CoreJsonWebKeyUse);\nserialize_as_str!(CoreJsonWebKeyUse);\n"
  },
  {
    "path": "src/core/jwk/tests.rs",
    "content": "use crate::core::jwk::CoreJsonCurveType;\nuse crate::core::{\n    CoreEdDsaPrivateSigningKey, CoreHmacKey, CoreJsonWebKey, CoreJsonWebKeySet, CoreJsonWebKeyType,\n    CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm,\n    CoreRsaPrivateSigningKey,\n};\nuse crate::helpers::Base64UrlEncodedBytes;\nuse crate::jwt::tests::{\n    TEST_EC_PUB_KEY_P256, TEST_EC_PUB_KEY_P384, TEST_ED_PUB_KEY_ED25519, TEST_RSA_PUB_KEY,\n};\nuse crate::verification::SignatureVerificationError;\nuse crate::{JsonWebKey, JsonWebKeyId, JsonWebTokenAlgorithm, PrivateSigningKey, SigningError};\n\nuse base64::prelude::BASE64_STANDARD;\nuse base64::Engine;\nuse rand::rngs::mock::StepRng;\nuse rand::{CryptoRng, RngCore};\nuse rsa::rand_core;\n\n#[test]\nfn test_core_jwk_deserialization_rsa() {\n    let json = \"{\n            \\\"kty\\\": \\\"RSA\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"kid\\\": \\\"2011-04-29\\\",\n            \\\"n\\\": \\\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhD\\\n                     R1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6C\\\n                     f0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1\\\n                     n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1\\\n                     jF44-csFCur-kEgU8awapJzKnqDKgw\\\",\n            \\\"e\\\": \\\"AQAB\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(json).expect(\"deserialization failed\");\n    assert_eq!(key.kty, CoreJsonWebKeyType::RSA);\n    assert_eq!(key.use_, Some(CoreJsonWebKeyUse::Signature));\n    assert_eq!(key.kid, Some(JsonWebKeyId::new(\"2011-04-29\".to_string())));\n    assert_eq!(\n        key.n,\n        Some(Base64UrlEncodedBytes::new(vec![\n            210, 252, 123, 106, 10, 30, 108, 103, 16, 74, 235, 143, 136, 178, 87, 102, 155, 77,\n            246, 121, 221, 173, 9, 155, 92, 74, 108, 217, 168, 128, 21, 181, 161, 51, 191, 11, 133,\n            108, 120, 113, 182, 223, 0, 11, 85, 79, 206, 179, 194, 237, 81, 43, 182, 143, 20, 92,\n            110, 132, 52, 117, 47, 171, 82, 161, 207, 193, 36, 64, 143, 121, 181, 138, 69, 120,\n            193, 100, 40, 133, 87, 137, 247, 162, 73, 227, 132, 203, 45, 159, 174, 45, 103, 253,\n            150, 251, 146, 108, 25, 142, 7, 115, 153, 253, 200, 21, 192, 175, 9, 125, 222, 90, 173,\n            239, 244, 77, 231, 14, 130, 127, 72, 120, 67, 36, 57, 191, 238, 185, 96, 104, 208, 71,\n            79, 197, 13, 109, 144, 191, 58, 152, 223, 175, 16, 64, 200, 156, 2, 214, 146, 171, 59,\n            60, 40, 150, 96, 157, 134, 253, 115, 183, 116, 206, 7, 64, 100, 124, 238, 234, 163, 16,\n            189, 18, 249, 133, 168, 235, 159, 89, 253, 212, 38, 206, 165, 178, 18, 15, 79, 42, 52,\n            188, 171, 118, 75, 126, 108, 84, 214, 132, 2, 56, 188, 196, 5, 135, 165, 158, 102, 237,\n            31, 51, 137, 69, 119, 99, 92, 71, 10, 247, 92, 249, 44, 32, 209, 218, 67, 225, 191,\n            196, 25, 226, 34, 166, 240, 208, 187, 53, 140, 94, 56, 249, 203, 5, 10, 234, 254, 144,\n            72, 20, 241, 172, 26, 164, 156, 202, 158, 160, 202, 131,\n        ]))\n    );\n    assert_eq!(key.e, Some(Base64UrlEncodedBytes::new(vec![1, 0, 1])));\n    assert_eq!(key.k, None);\n}\n#[test]\nfn test_core_jwk_deserialization_ec() {\n    let json = \"{\n            \\\"kty\\\": \\\"EC\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"kid\\\": \\\"2011-04-29\\\",\n            \\\"crv\\\": \\\"P-256\\\",\n            \\\"x\\\": \\\"kXCGZIr3oI6sKbnT6rRsIdxFXw3_VbLk_cveajgqXk8\\\",\n            \\\"y\\\": \\\"StDvKIgXqAxJ6DuebREh-1vgvZRW3dfrOxSIKzBtRI0\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(json).expect(\"deserialization failed\");\n    assert_eq!(key.kty, CoreJsonWebKeyType::EllipticCurve);\n    assert_eq!(key.use_, Some(CoreJsonWebKeyUse::Signature));\n    assert_eq!(key.kid, Some(JsonWebKeyId::new(\"2011-04-29\".to_string())));\n    assert_eq!(key.crv, Some(CoreJsonCurveType::P256));\n    assert_eq!(\n        key.y,\n        Some(Base64UrlEncodedBytes::new(vec![\n            0x4a, 0xd0, 0xef, 0x28, 0x88, 0x17, 0xa8, 0x0c, 0x49, 0xe8, 0x3b, 0x9e, 0x6d, 0x11,\n            0x21, 0xfb, 0x5b, 0xe0, 0xbd, 0x94, 0x56, 0xdd, 0xd7, 0xeb, 0x3b, 0x14, 0x88, 0x2b,\n            0x30, 0x6d, 0x44, 0x8d\n        ]))\n    );\n    assert_eq!(\n        key.x,\n        Some(Base64UrlEncodedBytes::new(vec![\n            0x91, 0x70, 0x86, 0x64, 0x8a, 0xf7, 0xa0, 0x8e, 0xac, 0x29, 0xb9, 0xd3, 0xea, 0xb4,\n            0x6c, 0x21, 0xdc, 0x45, 0x5f, 0x0d, 0xff, 0x55, 0xb2, 0xe4, 0xfd, 0xcb, 0xde, 0x6a,\n            0x38, 0x2a, 0x5e, 0x4f\n        ]))\n    );\n}\n\n#[test]\nfn test_core_jwk_deserialization_ed() {\n    let json = \"{\n            \\\"alg\\\": \\\"EdDSA\\\",\n            \\\"crv\\\": \\\"Ed25519\\\",\n            \\\"kty\\\": \\\"OKP\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"x\\\": \\\"vZ3CX884r0qNJ18pgXUTvFufK3ZmDzQfvMROJz6CLBc\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(json).expect(\"deserialization failed\");\n    assert_eq!(key.kty, CoreJsonWebKeyType::OctetKeyPair);\n    assert_eq!(key.use_, Some(CoreJsonWebKeyUse::Signature));\n    assert_eq!(key.crv, Some(CoreJsonCurveType::Ed25519));\n    assert_eq!(\n        key.x,\n        Some(Base64UrlEncodedBytes::new(vec![\n            0xBD, 0x9D, 0xC2, 0x5F, 0xCF, 0x38, 0xAF, 0x4A, 0x8D, 0x27, 0x5F, 0x29, 0x81, 0x75,\n            0x13, 0xBC, 0x5B, 0x9F, 0x2B, 0x76, 0x66, 0x0F, 0x34, 0x1F, 0xBC, 0xC4, 0x4E, 0x27,\n            0x3E, 0x82, 0x2C, 0x17\n        ]))\n    );\n}\n\n#[test]\nfn test_core_jwk_deserialization_symmetric() {\n    let json = \"{\\\n            \\\"kty\\\":\\\"oct\\\",\n            \\\"alg\\\":\\\"A128GCM\\\",\n            \\\"k\\\":\\\"GawgguFyGrWKav7AX4VKUg\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(json).expect(\"deserialization failed\");\n    assert_eq!(key.kty, CoreJsonWebKeyType::Symmetric);\n    assert_eq!(key.use_, None);\n    assert_eq!(key.kid, None);\n    assert_eq!(key.n, None);\n    assert_eq!(key.e, None);\n    assert_eq!(\n        key.alg,\n        Some(JsonWebTokenAlgorithm::Encryption(\n            CoreJweContentEncryptionAlgorithm::Aes128Gcm\n        ))\n    );\n    assert_eq!(\n        key.k,\n        Some(Base64UrlEncodedBytes::new(vec![\n            25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82,\n        ]))\n    );\n}\n\n#[test]\nfn test_core_jwk_deserialization_no_optional() {\n    let json = \"{\\\"kty\\\":\\\"oct\\\"}\";\n    let key: CoreJsonWebKey = serde_json::from_str(json).expect(\"deserialization failed\");\n    assert_eq!(key.kty, CoreJsonWebKeyType::Symmetric);\n    assert_eq!(key.use_, None);\n    assert_eq!(key.kid, None);\n    assert_eq!(key.n, None);\n    assert_eq!(key.e, None);\n    assert_eq!(key.k, None);\n}\n\n#[test]\nfn test_core_jwk_deserialization_unrecognized() {\n    // Unrecognized fields should be ignored during deserialization\n    let json = \"{\\\n            \\\"kty\\\": \\\"oct\\\",\n            \\\"unrecognized\\\": 1234\n        }\";\n    let key: CoreJsonWebKey = serde_json::from_str(json).expect(\"deserialization failed\");\n    assert_eq!(key.kty, CoreJsonWebKeyType::Symmetric);\n}\n\n#[test]\nfn test_core_jwk_deserialization_dupe_fields() {\n    // From RFC 7517, Section 4:\n    //   \"The member names within a JWK MUST be unique; JWK parsers MUST either\n    //   reject JWKs with duplicate member names or use a JSON parser that\n    //   returns only the lexically last duplicate member name, as specified\n    //   in Section 15.12 (The JSON Object) of ECMAScript 5.1 [ECMAScript].\"\n    let json = \"{\\\n            \\\"kty\\\":\\\"oct\\\",\n            \\\"k\\\":\\\"GawgguFyGrWKav7AX4VKUg\\\",\n            \\\"k\\\":\\\"GawgguFyGrWKav7AX4VKVg\\\"\n        }\";\n\n    assert!(serde_json::from_str::<CoreJsonWebKey>(json)\n        .expect_err(\"deserialization must fail when duplicate fields are present\")\n        .to_string()\n        // This is probably not ideal since the serde/serde_json contracts don't guarantee this\n        // error message. However, we want to be sure that this fails for the expected reason\n        // and not by happenstance, so this is fine for now.\n        .contains(\"duplicate field\"));\n}\n\nfn verify_signature(\n    key: &CoreJsonWebKey,\n    alg: &CoreJwsSigningAlgorithm,\n    signing_input: &str,\n    signature_base64: &str,\n) {\n    let signature = crate::core::base64_url_safe_no_pad()\n        .decode(signature_base64)\n        .expect(\"failed to base64url decode\");\n    key.verify_signature(alg, signing_input.as_bytes(), &signature)\n        .expect(\"signature verification failed\");\n    match key\n        .verify_signature(\n            alg,\n            (signing_input.to_string() + \"foobar\").as_bytes(),\n            &signature,\n        )\n        .expect_err(\"signature verification should fail\")\n    {\n        SignatureVerificationError::CryptoError(_) => {}\n        other => panic!(\"unexpected error: {:?}\", other),\n    }\n}\n\nfn verify_invalid_signature(\n    key: &CoreJsonWebKey,\n    alg: &CoreJwsSigningAlgorithm,\n    signing_input: &str,\n    signature_base64: &str,\n) {\n    let signature = crate::core::base64_url_safe_no_pad()\n        .decode(signature_base64)\n        .expect(\"failed to base64url decode\");\n    match key\n        .verify_signature(alg, signing_input.as_bytes(), &signature)\n        .expect_err(\"signature verification should fail\")\n    {\n        SignatureVerificationError::CryptoError(_) => {}\n        other => panic!(\"unexpected error: {:?}\", other),\n    }\n}\n\n#[test]\nfn test_eddsa_verification() {\n    let key_ed25519: CoreJsonWebKey =\n        serde_json::from_str(TEST_ED_PUB_KEY_ED25519).expect(\"deserialization failed\");\n    let pkcs1_signing_input = \"eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiSldUIn0.eyJpc3MiOiJqb2UifQ\";\n    let signature_ed25519 =\n        \"Augr7UH6hUbWVN0PHqSD5U0bb8y9UOw_eef09ZS5d5haUar_qAto8gyLJxUhNF5wHPoXhdvSGowkPvjiKsEsCQ\";\n\n    let signature_ed25519_other =\n        \"xb4NH-q33sCaRXf1ZhnzQxd4o5ZkBWKd9vGibacqPMAblW_mIJLm9kGerqHX08SPoeDY-dYUmZQz9ls6csfvAw\";\n    let signature_ed448 = \"xxXVMyaYYePdGfMOdU0nENuc70pKwP3vJuc_jBA0rCW-RtbvBLSsc0D9iCPzhrPmQ2X1nTjPkGiAXJ0_NslDBvy3sHu88N64YhnnYBWwwHttBU0jijn_ikbBUHzUwzGuasRFb1ESG_PwedhEcMi-YAwA\";\n\n    // test ed25519\n    verify_signature(\n        &key_ed25519,\n        &CoreJwsSigningAlgorithm::EdDsa,\n        pkcs1_signing_input,\n        signature_ed25519,\n    );\n\n    // signature from ed448 variant\n    verify_invalid_signature(\n        &key_ed25519,\n        &CoreJwsSigningAlgorithm::EdDsa,\n        pkcs1_signing_input,\n        signature_ed448,\n    );\n\n    // different signature\n    verify_invalid_signature(\n        &key_ed25519,\n        &CoreJwsSigningAlgorithm::EdDsa,\n        pkcs1_signing_input,\n        signature_ed25519_other,\n    );\n\n    // non-EdDsa key\n    if let Some(err) = key_ed25519\n        .verify_signature(\n            &CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            pkcs1_signing_input.as_bytes(),\n            signature_ed25519.as_bytes(),\n        )\n        .err()\n    {\n        let error_msg = \"key type does not match signature algorithm\".to_string();\n        match err {\n            SignatureVerificationError::InvalidKey(msg) => {\n                if msg != error_msg {\n                    panic!(\"The error should be about key type\")\n                }\n            }\n            _ => panic!(\"We should fail before actual validation\"),\n        }\n    }\n}\n\n#[test]\nfn test_ecdsa_verification() {\n    let key_p256: CoreJsonWebKey =\n        serde_json::from_str(TEST_EC_PUB_KEY_P256).expect(\"deserialization failed\");\n    let key_p384: CoreJsonWebKey =\n        serde_json::from_str(TEST_EC_PUB_KEY_P384).expect(\"deserialization failed\");\n    let pkcs1_signing_input = \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX\\\n                                   hhbXBsZSJ9.\\\n                                   SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH\\\n                                   lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk\\\n                                   b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm\\\n                                   UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4\";\n    let signature_p256 =\n        \"EnKCtAHhzhqxV2GTr1VEurse2kQ7oHpFoVqM66sYGlmahDRGSlfrVAsGCzdLv66OS2Qf1zt6OPHX-5ZAkMgzlA\";\n    let signature_p384 = \"B_9oDAabMasZ2Yt_cnAS21owaN0uWSInQBPxTqqiM3N3XjkksBRMGqguJLV5WoSMcvqgXwHTTQtbHGuh0Uf4g6LEr7XtO1T2KCttQR27d5YbvVZdORrzCm0Nsm1zkV-i\";\n\n    //test p256\n    verify_signature(\n        &key_p256,\n        &CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n        pkcs1_signing_input,\n        signature_p256,\n    );\n\n    //wrong algo should fail before ring validation\n    if let Some(err) = key_p256\n        .verify_signature(\n            &CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n            pkcs1_signing_input.as_bytes(),\n            signature_p256.as_bytes(),\n        )\n        .err()\n    {\n        let error_msg = \"Key uses different CRV than JWT\".to_string();\n        match err {\n            SignatureVerificationError::InvalidKey(msg) => {\n                if msg != error_msg {\n                    panic!(\"The error should be about different CRVs\")\n                }\n            }\n            _ => panic!(\"We should fail before actual validation\"),\n        }\n    }\n    // suppose we have alg specified correctly, but the signature given is actually a p384\n    verify_invalid_signature(\n        &key_p256,\n        &CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n        pkcs1_signing_input,\n        signature_p384,\n    );\n\n    //test p384\n    verify_signature(\n        &key_p384,\n        &CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n        pkcs1_signing_input,\n        signature_p384,\n    );\n\n    // suppose we have alg specified correctly, but the signature given is actually a p256\n    verify_invalid_signature(\n        &key_p384,\n        &CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n        pkcs1_signing_input,\n        signature_p256,\n    );\n\n    //wrong algo should fail before ring validation\n    if let Some(err) = key_p384\n        .verify_signature(\n            &CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            pkcs1_signing_input.as_bytes(),\n            signature_p384.as_bytes(),\n        )\n        .err()\n    {\n        let error_msg = \"Key uses different CRV than JWT\".to_string();\n        match err {\n            SignatureVerificationError::InvalidKey(msg) => {\n                if msg != error_msg {\n                    panic!(\"The error should be about different CRVs\")\n                }\n            }\n            _ => panic!(\"We should fail before actual validation\"),\n        }\n    }\n}\n\n#[test]\nfn test_rsa_pkcs1_verification() {\n    let key: CoreJsonWebKey =\n        serde_json::from_str(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    // Source: https://tools.ietf.org/html/rfc7520#section-4.1\n    let pkcs1_signing_input = \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX\\\n                                   hhbXBsZSJ9.\\\n                                   SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH\\\n                                   lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk\\\n                                   b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm\\\n                                   UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4\";\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        pkcs1_signing_input,\n        \"MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK\\\n             ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J\\\n             IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w\\\n             W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP\\\n             xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f\\\n             cIe8u9ipH84ogoree7vjbU5y18kDquDg\",\n    );\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n        pkcs1_signing_input,\n        \"dgTHNAePceEDFodrPybExGb2aF4fHb4bRpb_4bgYHq78fUdHFCScg0bZP51zjB\\\n             joH-4fr0P7Y8-Sns0GuXRy_itY2Yh0mEdXVn6HwZVOGIVRAuBkY0cAgSXGKU40\\\n             1G-GhamiNyNDfN2bwHftPPvCdsChtsLeAUvhWUKSLgIfT-jvMr9iZ5d0SQrUvv\\\n             G1ReEoBDyKUzqGQehO3CNGJ-QkI8p-fBTa2KHQxct6cU5_anSXCd-kC2rtEQS9\\\n             E8AcMFLA2Bv9IXsURBRU_bwMgxTG8c6ATDJM8k-zJSSP5a44EFKHUtH1xspYFo\\\n             KV6Za-frCV8kcFCILMf-4ATlj5Z62o1A\",\n    );\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n        pkcs1_signing_input,\n        \"hIRFVu3hlbIM9Xt2V9xldCoF_94BEDg-6kVetoceakgD-9hicX0BnOI3YxR-JQ\\\n             0to4saNEdGP1ulvanfa5uK3PnltQr1sJ1l1x_TPNh8vdvZ5WmAtkQcZvRiK580\\\n             hliHV1l65yLyGH4ckDicOg5VF4BASkBw6sUO_LCB8pMJotK5jQxDbNkPmSGbFV\\\n             nzVXXy6QI_r6nqmguo5DMFlPeploS-aQ7ArfYqR3gKEp3l5gWWKn86lwVKRGjv\\\n             zeRMf3ubhKxvHUyU8cE5p1VPpOzTJ3cPwUe68s24Ehf2jpgZIIXb9XQv4L0Unf\\\n             GAXTBY7Rszx9LvGByoFx3eOpbMvtLQxA\",\n    );\n\n    // Wrong key type\n    match key\n        .verify_signature(\n            &CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            pkcs1_signing_input.as_bytes(),\n            &Vec::new(),\n        )\n        .expect_err(\"signature verification should fail\")\n    {\n        SignatureVerificationError::InvalidKey(_) => {}\n        other => panic!(\"unexpected error: {:?}\", other),\n    }\n\n    // Wrong key usage\n    let enc_key_json = \"{\n            \\\"kty\\\": \\\"RSA\\\",\n            \\\"kid\\\": \\\"bilbo.baggins@hobbiton.example\\\",\n            \\\"use\\\": \\\"enc\\\",\n            \\\"n\\\": \\\"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT\\\n                     -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV\\\n                     wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-\\\n                     oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde\\\n                     3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC\\\n                     LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g\\\n                     HdrNP5zw\\\",\n            \\\"e\\\": \\\"AQAB\\\"\n        }\";\n    let enc_key: CoreJsonWebKey =\n        serde_json::from_str(enc_key_json).expect(\"deserialization failed\");\n    match enc_key\n        .verify_signature(\n            &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            pkcs1_signing_input.as_bytes(),\n            &Vec::new(),\n        )\n        .expect_err(\"signature verification should fail\")\n    {\n        SignatureVerificationError::InvalidKey(_) => {}\n        other => panic!(\"unexpected error: {:?}\", other),\n    }\n\n    // Key without usage specified should work\n    let nousage_key_json = \"{\n            \\\"kty\\\": \\\"RSA\\\",\n            \\\"kid\\\": \\\"bilbo.baggins@hobbiton.example\\\",\n            \\\"n\\\": \\\"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT\\\n                     -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV\\\n                     wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-\\\n                     oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde\\\n                     3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC\\\n                     LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g\\\n                     HdrNP5zw\\\",\n            \\\"e\\\": \\\"AQAB\\\"\n        }\";\n    let nousage_key: CoreJsonWebKey =\n        serde_json::from_str(nousage_key_json).expect(\"deserialization failed\");\n    verify_signature(\n        &nousage_key,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        pkcs1_signing_input,\n        \"MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK\\\n             ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J\\\n             IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w\\\n             W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP\\\n             xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f\\\n             cIe8u9ipH84ogoree7vjbU5y18kDquDg\",\n    );\n}\n\n#[test]\nfn test_rsa_pss_verification() {\n    let key: CoreJsonWebKey =\n        serde_json::from_str(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n    // Source: https://tools.ietf.org/html/rfc7520#section-4.2\n    let pss_signing_input =\n        \"eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.\\\n             SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH\\\n             lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk\\\n             b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm\\\n             UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4\";\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n        pss_signing_input,\n        \"Y62we_hs07d0qJ2cT_QpbrodwDhPK9rEpNX2b3GqLHFM18YtDlPCr40Xf_yLIosIrt\\\n             mMP4NgDSCkn2qOcRJBD8zrHumER4JIkGZbRIwU8gYms8xKX2HaveK9vrOjbHoWLjOU\\\n             nyNpprYUFGdRZ6oebT61bqU2CZrJG_GcqR87W8FOn7kqrCPI7B8oNHgliMke49hOpz\\\n             mluL20BKN5Mb3O42nwgmiONZK0Pjm2GTIAYRUvNQ741aCWVJ3rnWvo99qWhe86ap_H\\\n             v40SUSaMwJig5AqC-wHIzYaYU0PlQbi83Dgw7Zft9kL2dGB0vMWY_h2HDgZU0teAcK\\\n             SkhyH8ZDRyYQ\",\n    );\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n        pss_signing_input,\n        \"cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2I\\\n             pN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXU\\\n             vdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRX\\\n             e8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT\\\n             0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a\\\n             6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw\",\n    );\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n        pss_signing_input,\n        \"G8vtysTFbSXht_PU6NdXeYDOSIQhxcp6zFWuvtx2NCtgsm-J22CKqlapp1zjPkXTo4\\\n             xrYlIgFjQVQZ9Cr7KWJXK7qYUkdfJNkB1E96EQR32ocx_9RQDS_eQNlGWjoDRduD9z\\\n             2hKs-S0EhOy39wUeUYbcKA1MpkW71hUPI56Ou5kzclNbe22slB4mYd6Mx0dLOeFDF2\\\n             C7ZUDxso-cHMh4hU2E8vlp-TZUf9eqAri9T1F_pjRF8WNBj-vrqwy3bCROgIslYA8u\\\n             c_FEXn6fZ21up5mU9vg5_LdeBoSh4Idmz8HLn5rpVd57AsQ2PbLMsKXcpVUhwP_ID1\\\n             7zsAFuCEFJqA\",\n    );\n}\n\n#[test]\nfn test_hmac_sha256_verification() {\n    // the original spec example also has alg=HS256, which was removed to test other signing algorithms\n    let key_json = \"{\n            \\\"kty\\\": \\\"oct\\\",\n            \\\"kid\\\": \\\"018c0ae5-4d9b-471b-bfd6-eef314bc7037\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"k\\\": \\\"hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(key_json).expect(\"deserialization failed\");\n    // Source: https://tools.ietf.org/html/rfc7520#section-4.4\n    let signing_input = \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW\\\n                             VlZjMxNGJjNzAzNyJ9.\\\n                             SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH\\\n                             lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk\\\n                             b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm\\\n                             UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4\";\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::HmacSha256,\n        signing_input,\n        \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0\",\n    );\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::HmacSha384,\n        signing_input,\n        \"O1jhTTHkuaiubwDZoIBLv6zjEarXHc22NNu05IdYh_yzIKGYXJQcaI2WnF4BCq7j\",\n    );\n\n    verify_signature(\n        &key,\n        &CoreJwsSigningAlgorithm::HmacSha512,\n        signing_input,\n        \"rdWYqzXuAJp4OW-exqIwrO8HJJQDYu0_fkTIUBHmyHMFJ0pVe7fjP7QtE7BaX-7FN5\\\n             YiyiM11MwIEAxzxBj6qw\",\n    );\n}\n\nfn expect_hmac(\n    secret_key: &CoreHmacKey,\n    message: &[u8],\n    alg: &CoreJwsSigningAlgorithm,\n    expected_sig_base64: &str,\n) {\n    let sig = secret_key.sign(alg, message).unwrap();\n    assert_eq!(expected_sig_base64, BASE64_STANDARD.encode(&sig));\n\n    secret_key\n        .as_verification_key()\n        .verify_signature(alg, message, &sig)\n        .unwrap();\n}\n\n#[test]\nfn test_hmac_signing() {\n    let secret_key = CoreHmacKey::new(\"my_secret_key\");\n    let message = \"hello HMAC\".as_ref();\n    expect_hmac(\n        &secret_key,\n        message,\n        &CoreJwsSigningAlgorithm::HmacSha256,\n        \"Pm6UhOcfx6D8LeCG4taMQNQXDTHwnVOSEcB7tidkM2M=\",\n    );\n\n    expect_hmac(\n        &secret_key,\n        message,\n        &CoreJwsSigningAlgorithm::HmacSha384,\n        \"BiYrxF0XjImSnfqT2n+Tu3EspstKZmVtUHbK77LHerfKNwCikuClNJDAVwr2xMLp\",\n    );\n\n    expect_hmac(\n        &secret_key,\n        message,\n        &CoreJwsSigningAlgorithm::HmacSha512,\n        \"glKjDMXBhB6sSKGCdLW4QeBOJ3vOgOlbMJjbeus8/KQ3dk7dtsqtrpfoDoW8lrU+rncd2jBWaKnp1zKdpEfSn\\\n             A==\",\n    );\n\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, message),\n        Err(SigningError::UnsupportedAlg(\"RS256\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384, message),\n        Err(SigningError::UnsupportedAlg(\"RS384\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512, message),\n        Err(SigningError::UnsupportedAlg(\"RS512\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::RsaSsaPssSha256, message),\n        Err(SigningError::UnsupportedAlg(\"PS256\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::RsaSsaPssSha384, message),\n        Err(SigningError::UnsupportedAlg(\"PS384\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::RsaSsaPssSha512, message),\n        Err(SigningError::UnsupportedAlg(\"PS512\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::EcdsaP256Sha256, message),\n        Err(SigningError::UnsupportedAlg(\"ES256\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::EcdsaP384Sha384, message),\n        Err(SigningError::UnsupportedAlg(\"ES384\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::EcdsaP521Sha512, message),\n        Err(SigningError::UnsupportedAlg(\"ES512\".to_string())),\n    );\n    assert_eq!(\n        secret_key.sign(&CoreJwsSigningAlgorithm::None, message),\n        Err(SigningError::UnsupportedAlg(\"none\".to_string())),\n    );\n}\n\nconst TEST_ED25519_KEY: &str = \"\\\n        -----BEGIN PRIVATE KEY-----\\n\\\n        MC4CAQAwBQYDK2VwBCIEICWeYPLxoZKHZlQ6rkBi11E9JwchynXtljATLqym/XS9\\n\\\n        -----END PRIVATE KEY-----\\\n        \";\n\n// This is just a test key that isn't used for anything else.\nconst TEST_RSA_KEY: &str = \"\\\n                               -----BEGIN RSA PRIVATE KEY-----\\n\\\n                                MIIEowIBAAKCAQEAsRMj0YYjy7du6v1gWyKSTJx3YjBzZTG0XotRP0IaObw0k+68\\n\\\n                                30dXadjL5jVhSWNdcg9OyMyTGWfdNqfdrS6ppBqlQNgjZJdloIqL9zOLBZrDm7G4\\n\\\n                                +qN4KeZ4/5TyEilq2zOHHGFEzXpOq/UxqVnm3J4fhjqCNaS2nKd7HVVXGBQQ+4+F\\n\\\n                                dVT+MyJXemw5maz2F/h324TQi6XoUPEwUddxBwLQFSOlzWnHYMc4/lcyZJ8MpTXC\\n\\\n                                MPe/YJFNtb9CaikKUdf8x4mzwH7usSf8s2d6R4dQITzKrjrEJ0u3w3eGkBBapoMV\\n\\\n                                FBGPjP3Haz5FsVtHc5VEN3FZVIDF6HrbJH1C4QIDAQABAoIBAHSS3izM+3nc7Bel\\n\\\n                                8S5uRxRKmcm5je6b11u6qiVUFkHWJmMRc6QmqmSThkCq+b4/vUAe1cYZ7+l02Exo\\n\\\n                                HOcrZiEULaDP6hUKGqyjKVv3wdlRtt8kFFxlC/HBufzAiNDuFVvzw0oquwnvMCXC\\n\\\n                                yQvtlK+/JY/PqvM32cSt+b4o9apySsHqAtdsoHHohK82jsQqIfCi1v8XYV/xRBJB\\n\\\n                                cQMCaA0Ls3tFpmJv3JdikyyQxio4kZ5tswghC63znCp1iL+qDq1wjjKzjick9MDb\\n\\\n                                Qzb95X09QQP201l1FPWN7Kbhj4ybg6PJGz/VHQcvILcBCoYIc0UY/OMSBt9VN9yD\\n\\\n                                wr1WlbECgYEA37difsTMcLmUEN57sicFe1q4lxH6eqnUBjmoKBflx4oMIIyRnfjF\\n\\\n                                Jwsu9yIiBkJfBCP85nl2tZdcV0wfZLf6amxB/KMtdfW6r8eoTDzE472OYxSIg1F5\\n\\\n                                dI4qn2nBI0Dou0g58xj+Kv0iLaym0pxtyJkSg/rxZGwKb9a+x5WAs50CgYEAyqC0\\n\\\n                                NcZs2BRIiT5kEOF6+MeUvarbKh1mangKHKcTdXRrvoJ+Z5izm7FifBixo/79MYpt\\n\\\n                                0VofW0IzYKtAI9KZDq2JcozEbZ+lt/ZPH5QEXO4T39QbDoAG8BbOmEP7l+6m+7QO\\n\\\n                                PiQ0WSNjDnwk3W7Zihgg31DH7hyxsxQCapKLcxUCgYAwERXPiPcoDSd8DGFlYK7z\\n\\\n                                1wUsKEe6DT0p7T9tBd1v5wA+ChXLbETn46Y+oQ3QbHg/yn+vAU/5KkFD3G4uVL0w\\n\\\n                                Gnx/DIxa+OYYmHxXjQL8r6ClNycxl9LRsS4FPFKsAWk/u///dFI/6E1spNjfDY8k\\n\\\n                                94ab5tHwsqn3Z5tsBHo3nQKBgFUmxbSXh2Qi2fy6+GhTqU7k6G/wXhvLsR9rBKzX\\n\\\n                                1YiVfTXZNu+oL0ptd/q4keZeIN7x0oaY/fZm0pp8PP8Q4HtXmBxIZb+/yG+Pld6q\\n\\\n                                YE8BSd7VDu3ABapdm0JHx3Iou4mpOBcLNeiDw3vx1bgsfkTXMPFHzE0XR+H+tak9\\n\\\n                                nlalAoGBALAmAF7WBGdOt43Rj8hPaKOM/ahj+6z3CNwVreToNsVBHoyNmiO8q7MC\\n\\\n                                +tRo4jgdrzk1pzs66OIHfbx5P1mXKPtgPZhvI5omAY8WqXEgeNqSL1Ksp6LZ2ql/\\n\\\n                                ouZns5xwKc9+aRL+GWoAGNzwzcjE8cP52sBy/r0rYXTs/sZo5kgV\\n\\\n                                -----END RSA PRIVATE KEY-----\\\n                                \";\n\nfn expect_ed_sig(\n    private_key: &CoreEdDsaPrivateSigningKey,\n    message: &[u8],\n    alg: &CoreJwsSigningAlgorithm,\n    expected_sig_base64: &str,\n) {\n    let sig = private_key.sign(alg, message).unwrap();\n    assert_eq!(expected_sig_base64, BASE64_STANDARD.encode(&sig));\n\n    let public_key = private_key.as_verification_key();\n    public_key.verify_signature(alg, message, &sig).unwrap();\n}\n\nfn expect_rsa_sig(\n    private_key: &CoreRsaPrivateSigningKey,\n    message: &[u8],\n    alg: &CoreJwsSigningAlgorithm,\n    expected_sig_base64: &str,\n) {\n    let sig = private_key.sign(alg, message).unwrap();\n    assert_eq!(expected_sig_base64, BASE64_STANDARD.encode(&sig));\n\n    let public_key = private_key.as_verification_key();\n    public_key.verify_signature(alg, message, &sig).unwrap();\n}\n\n#[derive(Clone)]\nstruct TestRng(StepRng);\n\nimpl CryptoRng for TestRng {}\nimpl RngCore for TestRng {\n    fn next_u32(&mut self) -> u32 {\n        self.0.next_u32()\n    }\n    fn next_u64(&mut self) -> u64 {\n        self.0.next_u64()\n    }\n    fn fill_bytes(&mut self, dest: &mut [u8]) {\n        self.0.fill_bytes(dest)\n    }\n    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {\n        self.0.try_fill_bytes(dest)\n    }\n}\n\n#[test]\nfn test_ed_signing() {\n    let private_key = CoreEdDsaPrivateSigningKey::from_ed25519_pem(\n        TEST_ED25519_KEY,\n        Some(JsonWebKeyId::new(\"test_key\".to_string())),\n    )\n    .unwrap();\n\n    let public_key_jwk = private_key.as_verification_key();\n    let public_key_jwk_str = serde_json::to_string(&public_key_jwk).unwrap();\n\n    assert_eq!(\n        \"{\\\n            \\\"kty\\\":\\\"OKP\\\",\\\n            \\\"use\\\":\\\"sig\\\",\\\n            \\\"kid\\\":\\\"test_key\\\",\\\n            \\\"crv\\\":\\\"Ed25519\\\",\\\n            \\\"x\\\":\\\"E6lXdyel1n9C1lcr3FK8OsfsfO2ZgcWhPflJ6yIf7e8\\\"\\\n            }\",\n        public_key_jwk_str\n    );\n\n    let message = \"hello EdDsa\".as_ref();\n    expect_ed_sig(\n        &private_key,\n        message,\n        &CoreJwsSigningAlgorithm::EdDsa,\n        \"XqP8sXaPrQa37+2lw+aiXv+6pegjioYUgo1/ShcX6kRhD2Vxh8DrQUbQlaGbljLJTNNc453E2Axp+Mxm+4OVAQ==\",\n    );\n\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::HmacSha256, message),\n        Err(SigningError::UnsupportedAlg(\"HS256\".to_string())),\n    );\n\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::None, message),\n        Err(SigningError::UnsupportedAlg(\"none\".to_string())),\n    );\n}\n\n#[test]\nfn test_rsa_signing() {\n    let private_key = CoreRsaPrivateSigningKey::from_pem_internal(\n        TEST_RSA_KEY,\n        // Constant salt used for PSS test vectors below.\n        Box::new(TestRng(StepRng::new(127, 0))),\n        Some(JsonWebKeyId::new(\"test_key\".to_string())),\n    )\n    .unwrap();\n\n    let public_key_jwk = private_key.as_verification_key();\n    let public_key_jwk_str = serde_json::to_string(&public_key_jwk).unwrap();\n    assert_eq!(\n        \"{\\\n             \\\"kty\\\":\\\"RSA\\\",\\\n             \\\"use\\\":\\\"sig\\\",\\\n             \\\"kid\\\":\\\"test_key\\\",\\\n             \\\"n\\\":\\\"sRMj0YYjy7du6v1gWyKSTJx3YjBzZTG0XotRP0IaObw0k-6830dXadjL5jVhSWNdcg9OyMyTGWfdNq\\\n             fdrS6ppBqlQNgjZJdloIqL9zOLBZrDm7G4-qN4KeZ4_5TyEilq2zOHHGFEzXpOq_UxqVnm3J4fhjqCNaS2nKd7\\\n             HVVXGBQQ-4-FdVT-MyJXemw5maz2F_h324TQi6XoUPEwUddxBwLQFSOlzWnHYMc4_lcyZJ8MpTXCMPe_YJFNtb\\\n             9CaikKUdf8x4mzwH7usSf8s2d6R4dQITzKrjrEJ0u3w3eGkBBapoMVFBGPjP3Haz5FsVtHc5VEN3FZVIDF6Hrb\\\n             JH1C4Q\\\",\\\n             \\\"e\\\":\\\"AQAB\\\"\\\n             }\",\n        public_key_jwk_str\n    );\n\n    let message = \"hello RSA\".as_ref();\n    expect_rsa_sig(\n        &private_key,\n        message,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        \"KBvV+F7Xofg4i4qUA0JEqfhQQdjZ7ralUYTPKRIitaKL4a6ni+abagsHs5V63+bmQF5t6DM4aRH2ZC943Tonkr\\\n            AUY1mpaqic2vqtrtWk3cyrcHtkPCLNKzFf/6xvHPjeKH1Bu/qTQ0mn+hN6taOgw3ORbm6P9MkelX1RVEia98uwB\\\n            Zn2BxKeqNYm11vqKDyS5ZFzHwpPrC4rri/uTIcXsQEXB+Lbb+naDpQn8qJqP+S+uM2LGWIXp5ExAJ55A111nIqE\\\n            Ap0aKwf2U8Q81DWI8lbHbL1dd7FRDtZKm+ainO5ck4L/axtH7C4GIZd+TiXL3iYpiWmNkqlwv9WsNPe8Rg==\",\n    );\n\n    expect_rsa_sig(\n        &private_key,\n        message,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n        \"YsyhW9DkIoNJPqTNY7pidJi5wWtQGr4xety+2Zt1DKNMG0ENFkxCGPLCYcL9vGSS9kfkrPtQ3Eve7g9DKfg1fg\\\n            071SXJHxAlK0iC8mVYfQrxxyFlQDIPEhvCJx6VkWVm2jJhN+vByGRJLTo2n3gtYtMREfz+c5xnXWeIy+JQ0LXOy\\\n            SyOZl0qHxn1VteczH79uCK0Vv8ZH3IfbQMU+2HjbVeUYRzCoAhlT4V2GY4U1pCrZBlfEyhr0ncHz90FRvvhLT3y\\\n            SlHa7yY7CRJ+z1CLBOzBiH1Eko4tIJKy/qO9M6EGeFtXhqd4td5g2oY/mUZYjHYjgcDO+wAXrZ9lP/ZVUg==\",\n    );\n\n    expect_rsa_sig(\n        &private_key,\n        message,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n        \"GXN3rmZhlJw46FHoqiuELcIi6iUr3cVC0HZpjBJhrTVfta/8a4PpzmLbQxjdb1cdU/56XDXkqDSNvzRn4PwAOL\\\n            460n0Eg8d8mxwPRHQuyypze9240FEw3lyjp5uPJHn5PrmeelczJ1Xseinmp+JwpKHiHhmxp7FjgJc/o3J/hlz0n\\\n            G1cgTndtrlp5JPJOJNt4XfgIgqoobH5Wk3ML35C50mD93Ld6V6nn6rK72wgecK1SDXeF4ztQUAjg4VTEojLm5VF\\\n            kfR7kXV0dIbAvZXDa1uuIOlVDIRfF93rxme1Ze46Dywan+zfsGCcpFfFAsnGLsgNDmATB8IS1lTf1SGMoA==\",\n    );\n\n    expect_rsa_sig(\n            &private_key,\n            message,\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            \"sPwTRDAOGOZr0ofAL/lMSXaR5L5aoFANLei4bqAQFHiFxDDrWZZ28MZiunwCGakOM8Itwas+sIX6vo3VTy9d5Bh\\\n            Cotb/gY5DhMX/iZJubfS8U1fB8rFWXDpREeAegGvXPjBd0A4M6z9it9Tss43dYqO12mYEpz6rFvbHJKSR9Hnmak4\\\n            F8TcuZswYtjhHhiib05PGjeJTo/5F15WtR7RYK4slYGOU4mf5wzZSpfgifJ2XjyQQe6oCFvVXftVtiQhEljHiEGv\\\n            GZH5y4FA7h06PqkHMwQEnRsBhVm4pUqRi641tglZ3HZcoxYUE8AdMX7ltJzy/vpOew2bVGoF3mUnlZw==\",\n        );\n\n    expect_rsa_sig(\n            &private_key,\n            message,\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n            \"m0EsYFpNa5YjvEYPcfUpXPMqAWmWmkTaQiyK2HZ9Ejt+cBO/S5jcVqd0y2rCDMV1DpSb/JI8uhwp+qYm/2YKpIa\\\n            zp+u9PpjlL3jvYn19WbwJTCztJ9XSjcEbtkf1fS/d/BU7FgQzYIE0k++QqHjgzkTI5+2XLYX2WP5dc0r67Or5xaF\\\n            0ixL1edpEDKfgF3jiKuLmR2dv4MWHPLYRb1I0zm5C/E7g57DfJT4uNzmLX9gTGr4xe6CxVEYy4eFdE+q1O5J6RXd\\\n            FZnl4qFK9+x1pk0dhWkpIEaKhweI7YP79iFPnAiUnRM6BsdY+puwjGlaaGtYVFcuPO4uXEXtB1AnsEQ==\",\n        );\n\n    expect_rsa_sig(\n            &private_key,\n            message,\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n            \"N8BMNKm1dMOm0/BLzAjtnzvRlVtzgO8fUeKnfvUtK8XWeII5nk74hE3AoAJNPLuTninYtfaF68Supu5CsCJAqO9\\\n            1JnVvG8P1DX19iCTzJ83o69+kluBIz7x0l796RysDhqcjybGC+fj0M5MpgkNNcKlNwRixus3sfgCgh3mEB+E1Q11\\\n            hQKjCTdyOcqzGoima+Na17VBWzU3XXLvB328UfkV2nswBlLUsZMT3I4n/aIziENQCLVPlLdX8z+1NjHSAgd9rZMf\\\n            gfy0eMsjNuQpqPzVW3mbxlCMMVWpd8LKBprfa291xEk1wwvJCuU9EK7QmQPmYa1HAh+E+R2Dw3ibHdA==\",\n        );\n\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::HmacSha256, message),\n        Err(SigningError::UnsupportedAlg(\"HS256\".to_string())),\n    );\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::HmacSha384, message),\n        Err(SigningError::UnsupportedAlg(\"HS384\".to_string())),\n    );\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::HmacSha512, message),\n        Err(SigningError::UnsupportedAlg(\"HS512\".to_string())),\n    );\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::EcdsaP256Sha256, message),\n        Err(SigningError::UnsupportedAlg(\"ES256\".to_string())),\n    );\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::EcdsaP384Sha384, message),\n        Err(SigningError::UnsupportedAlg(\"ES384\".to_string())),\n    );\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::EcdsaP521Sha512, message),\n        Err(SigningError::UnsupportedAlg(\"ES512\".to_string())),\n    );\n    assert_eq!(\n        private_key.sign(&CoreJwsSigningAlgorithm::None, message),\n        Err(SigningError::UnsupportedAlg(\"none\".to_string())),\n    );\n}\n\n#[test]\nfn test_rsa_pss_signing() {\n    let private_key = CoreRsaPrivateSigningKey::from_pem(TEST_RSA_KEY, None).unwrap();\n\n    const MESSAGE: &str = \"This is a probabilistic signature scheme\";\n    let sig1 = private_key\n        .sign(\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            MESSAGE.as_bytes(),\n        )\n        .unwrap();\n    let sig2 = private_key\n        .sign(\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            MESSAGE.as_bytes(),\n        )\n        .unwrap();\n\n    assert_ne!(sig1, sig2);\n}\n\n// Tests that JsonWebKeySet ignores unsupported keys during deserialization so that clients can\n// use providers that include unsupported keys as long as they only use supported ones to sign\n// payloads.\n#[test]\nfn test_jwks_unsupported_key() {\n    let jwks_json = \"{\n            \\\"keys\\\": [\n                {\n                    \\\"kty\\\": \\\"RSA\\\",\n                    \\\"use\\\": \\\"sig\\\",\n                    \\\"kid\\\": \\\"2011-04-29\\\",\n                    \\\"n\\\": \\\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhD\\\n                             R1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6C\\\n                             f0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1\\\n                             n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1\\\n                             jF44-csFCur-kEgU8awapJzKnqDKgw\\\",\n                    \\\"e\\\": \\\"AQAB\\\"\n                },\n                {\n                    \\\"kty\\\": \\\"MAGIC\\\",\n                    \\\"use\\\": \\\"sig\\\",\n                    \\\"kid\\\": \\\"2040-01-01\\\",\n                    \\\"magic\\\": \\\"magic\\\"\n                },\n                {\n                    \\\"kty\\\": \\\"EC\\\",\n                    \\\"use\\\": \\\"sig\\\",\n                    \\\"kid\\\": \\\"2011-05-01\\\",\n                    \\\"crv\\\": \\\"P-256\\\",\n                    \\\"x\\\": \\\"kXCGZIr3oI6sKbnT6rRsIdxFXw3_VbLk_cveajgqXk8\\\",\n                    \\\"y\\\": \\\"StDvKIgXqAxJ6DuebREh-1vgvZRW3dfrOxSIKzBtRI0\\\"\n                }\n            ]\n        }\";\n    let jwks = serde_json::from_str::<CoreJsonWebKeySet>(jwks_json)\n        .expect(\"deserialization should succeed\");\n\n    assert_eq!(jwks.keys().len(), 2);\n\n    assert_eq!(jwks.keys()[0].kty, CoreJsonWebKeyType::RSA);\n    assert_eq!(jwks.keys()[0].use_, Some(CoreJsonWebKeyUse::Signature));\n    assert_eq!(\n        jwks.keys()[0].kid,\n        Some(JsonWebKeyId::new(\"2011-04-29\".to_string()))\n    );\n\n    assert_eq!(jwks.keys()[1].kty, CoreJsonWebKeyType::EllipticCurve);\n    assert_eq!(jwks.keys()[1].use_, Some(CoreJsonWebKeyUse::Signature));\n    assert_eq!(\n        jwks.keys()[1].kid,\n        Some(JsonWebKeyId::new(\"2011-05-01\".to_string()))\n    );\n    assert_eq!(jwks.keys()[1].crv, Some(CoreJsonCurveType::P256));\n}\n\n// Tests that JsonWebKeySet ignores keys with unsupported algorithms\n#[test]\nfn test_jwks_unsupported_alg() {\n    let jwks_json = \"{\n            \\\"keys\\\": [\n                {\n                    \\\"kty\\\": \\\"EC\\\",\n                    \\\"alg\\\": \\\"MAGIC\\\",\n                    \\\"crv\\\": \\\"P-256\\\",\n                    \\\"x\\\": \\\"kXCGZIr3oI6sKbnT6rRsIdxFXw3_VbLk_cveajgqXk8\\\",\n                    \\\"y\\\": \\\"StDvKIgXqAxJ6DuebREh-1vgvZRW3dfrOxSIKzBtRI0\\\"\n                },\n                {\n                    \\\"kty\\\": \\\"EC\\\",\n                    \\\"alg\\\": \\\"ES256\\\",\n                    \\\"kid\\\": \\\"2011-05-01\\\",\n                    \\\"crv\\\": \\\"P-256\\\",\n                    \\\"x\\\": \\\"kXCGZIr3oI6sKbnT6rRsIdxFXw3_VbLk_cveajgqXk8\\\",\n                    \\\"y\\\": \\\"StDvKIgXqAxJ6DuebREh-1vgvZRW3dfrOxSIKzBtRI0\\\"\n                }\n            ]\n        }\";\n    let jwks = serde_json::from_str::<CoreJsonWebKeySet>(jwks_json)\n        .expect(\"deserialization should succeed\");\n    assert_eq!(jwks.keys().len(), 1);\n    let key = &jwks.keys()[0];\n    assert_eq!(&key.kid, &Some(JsonWebKeyId::new(\"2011-05-01\".to_string())));\n}\n\n// Test filtering keys by algorithm\n#[test]\nfn test_jwks_same_kid_different_alg() {\n    let jwks_json = \"{\n            \\\"keys\\\": [\n                {\n                    \\\"kty\\\": \\\"RSA\\\",\n                    \\\"use\\\": \\\"sig\\\",\n                    \\\"kid\\\": \\\"2011-04-29\\\",\n                    \\\"alg\\\": \\\"PS256\\\",\n                    \\\"n\\\": \\\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhD\\\n                             R1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6C\\\n                             f0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1\\\n                             n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1\\\n                             jF44-csFCur-kEgU8awapJzKnqDKgw\\\",\n                    \\\"e\\\": \\\"AQAB\\\"\n                },\n                {\n                    \\\"kty\\\": \\\"RSA\\\",\n                    \\\"use\\\": \\\"sig\\\",\n                    \\\"kid\\\": \\\"2011-04-29\\\",\n                    \\\"alg\\\": \\\"PS384\\\",\n                    \\\"n\\\": \\\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhD\\\n                             R1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6C\\\n                             f0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1\\\n                             n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1\\\n                             jF44-csFCur-kEgU8awapJzKnqDKgw\\\",\n                    \\\"e\\\": \\\"AQAB\\\"\n                }\n            ]\n        }\";\n    let jwks = serde_json::from_str::<CoreJsonWebKeySet>(jwks_json)\n        .expect(\"deserialization should succeed\");\n    assert_eq!(jwks.keys().len(), 2);\n\n    {\n        let keys = jwks.filter_keys(\n            Some(&JsonWebKeyId::new(\"2011-04-29\".to_string())),\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n        );\n        assert_eq!(keys.len(), 1);\n        assert_eq!(\n            keys[0].alg,\n            Some(JsonWebTokenAlgorithm::Signature(\n                CoreJwsSigningAlgorithm::RsaSsaPssSha384\n            ))\n        );\n    }\n\n    {\n        let keys = jwks.filter_keys(\n            Some(&JsonWebKeyId::new(\"2011-04-29\".to_string())),\n            &CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n        );\n        assert_eq!(keys.len(), 0);\n    }\n}\n\n#[test]\nfn test_hash_bytes_eddsa() {\n    let ed_key_json = \"{\n            \\\"alg\\\": \\\"EdDSA\\\",\n            \\\"crv\\\": \\\"Ed25519\\\",\n            \\\"kty\\\": \\\"OKP\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"x\\\": \\\"vZ3CX884r0qNJ18pgXUTvFufK3ZmDzQfvMROJz6CLBc\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(ed_key_json).expect(\"deserialization failed\");\n\n    let hash = key\n        .hash_bytes(\"justatest\".as_bytes(), &CoreJwsSigningAlgorithm::EdDsa)\n        .expect(\"Hashing should succeed\");\n\n    assert_eq!(\n        \"2Kyc+bZQPseH8P3KAdKLu6D7stgxaNFXa/ckQX19RqZu9L65J0RmDlkULYCRExxRO77JW052i6r/+PK4rboICw==\",\n        BASE64_STANDARD.encode(hash)\n    );\n}\n\n#[test]\nfn test_hash_bytes_rsa() {\n    let rsa_key_json = \"{\n            \\\"kty\\\": \\\"RSA\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"kid\\\": \\\"2011-04-29\\\",\n            \\\"n\\\": \\\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhD\\\n                     R1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6C\\\n                     f0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1\\\n                     n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1\\\n                     jF44-csFCur-kEgU8awapJzKnqDKgw\\\",\n            \\\"e\\\": \\\"AQAB\\\"\n        }\";\n\n    let key: CoreJsonWebKey = serde_json::from_str(rsa_key_json).expect(\"deserialization failed\");\n\n    let hash = key\n        .hash_bytes(\n            \"justatest\".as_bytes(),\n            &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        )\n        .expect(\"Hashing should succeed\");\n    assert_eq!(\n        \"erGZHWfaQ5DcuMr6dSkOJDb/4VcxpPazY9vMsVB8dLo=\",\n        BASE64_STANDARD.encode(hash)\n    );\n\n    let hash = key\n        .hash_bytes(\n            \"justatest\".as_bytes(),\n            &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n        )\n        .expect(\"Hashing should succeed\");\n    assert_eq!(\n        \"JeQIV8/xLfKxSCCXuq7Hb/pIxnsjSZJM9+Dx23ah1oiEvra2q0Pm7eSS07gkl+Y2\",\n        BASE64_STANDARD.encode(hash)\n    );\n\n    let hash = key\n        .hash_bytes(\n            \"justatest\".as_bytes(),\n            &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n        )\n        .expect(\"Hashing should succeed\");\n    assert_eq!(\n        \"2Kyc+bZQPseH8P3KAdKLu6D7stgxaNFXa/ckQX19RqZu9L65J0RmDlkULYCRExxRO77JW052i6r/+PK4rboICw==\",\n        BASE64_STANDARD.encode(hash)\n    );\n}\n"
  },
  {
    "path": "src/core/mod.rs",
    "content": "use crate::registration::{\n    ClientMetadata, ClientRegistrationRequest, ClientRegistrationResponse,\n    EmptyAdditionalClientMetadata, EmptyAdditionalClientRegistrationResponse,\n    RegisterErrorResponseType,\n};\nuse crate::{\n    ApplicationType, AuthDisplay, AuthPrompt, AuthenticationFlow, ClaimName, ClaimType, Client,\n    ClientAuthMethod, DeviceAuthorizationResponse, EmptyAdditionalClaims,\n    EmptyAdditionalProviderMetadata, EmptyExtraDeviceAuthorizationFields, EmptyExtraTokenFields,\n    ErrorResponseType, GenderClaim, GrantType, IdToken, IdTokenClaims, IdTokenFields,\n    IdTokenVerifier, JsonWebKeySet, JweContentEncryptionAlgorithm, JweKeyManagementAlgorithm,\n    JwsSigningAlgorithm, ProviderMetadata, ResponseMode, ResponseType, StandardErrorResponse,\n    StandardTokenIntrospectionResponse, StandardTokenResponse, SubjectIdentifierType,\n    UserInfoClaims, UserInfoJsonWebToken, UserInfoVerifier,\n};\n\nuse base64::alphabet::URL_SAFE;\nuse base64::engine::general_purpose::NO_PAD;\nuse base64::engine::GeneralPurpose;\nuse oauth2::{EndpointNotSet, ResponseType as OAuth2ResponseType};\nuse serde::{Deserialize, Serialize};\n\nuse std::fmt::Display;\n\npub use crate::core::jwk::{\n    CoreEdDsaPrivateSigningKey, CoreHmacKey, CoreJsonCurveType, CoreJsonWebKey, CoreJsonWebKeyType,\n    CoreJsonWebKeyUse, CoreRsaPrivateSigningKey,\n};\n\npub use oauth2::basic::{\n    BasicErrorResponseType as CoreErrorResponseType,\n    BasicRequestTokenError as CoreRequestTokenError,\n    BasicRevocationErrorResponse as CoreRevocationErrorResponse, BasicTokenType as CoreTokenType,\n};\npub use oauth2::StandardRevocableToken as CoreRevocableToken;\n\nmod crypto;\n\n// Private purely for organizational reasons; exported publicly above.\nmod jwk;\n\n/// Standard implementation of DeviceAuthorizationResponse which throws away extra received response fields.\npub type CoreDeviceAuthorizationResponse =\n    DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>;\n\n/// OpenID Connect Core token introspection response.\npub type CoreTokenIntrospectionResponse =\n    StandardTokenIntrospectionResponse<EmptyExtraTokenFields, CoreTokenType>;\n\n/// OpenID Connect Core authentication flows.\npub type CoreAuthenticationFlow = AuthenticationFlow<CoreResponseType>;\n\n/// OpenID Connect Core client.\npub type CoreClient<\n    HasAuthUrl = EndpointNotSet,\n    HasDeviceAuthUrl = EndpointNotSet,\n    HasIntrospectionUrl = EndpointNotSet,\n    HasRevocationUrl = EndpointNotSet,\n    HasTokenUrl = EndpointNotSet,\n    HasUserInfoUrl = EndpointNotSet,\n> = Client<\n    EmptyAdditionalClaims,\n    CoreAuthDisplay,\n    CoreGenderClaim,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJsonWebKey,\n    CoreAuthPrompt,\n    StandardErrorResponse<CoreErrorResponseType>,\n    CoreTokenResponse,\n    CoreTokenIntrospectionResponse,\n    CoreRevocableToken,\n    CoreRevocationErrorResponse,\n    HasAuthUrl,\n    HasDeviceAuthUrl,\n    HasIntrospectionUrl,\n    HasRevocationUrl,\n    HasTokenUrl,\n    HasUserInfoUrl,\n>;\n\n/// OpenID Connect Core client metadata.\npub type CoreClientMetadata = ClientMetadata<\n    EmptyAdditionalClientMetadata,\n    CoreApplicationType,\n    CoreClientAuthMethod,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\n/// OpenID Connect Core client registration request.\npub type CoreClientRegistrationRequest = ClientRegistrationRequest<\n    EmptyAdditionalClientMetadata,\n    EmptyAdditionalClientRegistrationResponse,\n    CoreApplicationType,\n    CoreClientAuthMethod,\n    CoreRegisterErrorResponseType,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\n/// OpenID Connect Core client registration response.\npub type CoreClientRegistrationResponse = ClientRegistrationResponse<\n    EmptyAdditionalClientMetadata,\n    EmptyAdditionalClientRegistrationResponse,\n    CoreApplicationType,\n    CoreClientAuthMethod,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\n/// OpenID Connect Core ID token.\npub type CoreIdToken = IdToken<\n    EmptyAdditionalClaims,\n    CoreGenderClaim,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJwsSigningAlgorithm,\n>;\n\n/// OpenID Connect Core ID token claims.\npub type CoreIdTokenClaims = IdTokenClaims<EmptyAdditionalClaims, CoreGenderClaim>;\n\n/// OpenID Connect Core ID token fields.\npub type CoreIdTokenFields = IdTokenFields<\n    EmptyAdditionalClaims,\n    EmptyExtraTokenFields,\n    CoreGenderClaim,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJwsSigningAlgorithm,\n>;\n\n/// OpenID Connect Core ID token verifier.\npub type CoreIdTokenVerifier<'a> = IdTokenVerifier<'a, CoreJsonWebKey>;\n\n/// OpenID Connect Core token response.\npub type CoreTokenResponse = StandardTokenResponse<CoreIdTokenFields, CoreTokenType>;\n\n/// OpenID Connect Core JSON Web Key Set.\npub type CoreJsonWebKeySet = JsonWebKeySet<CoreJsonWebKey>;\n\n/// OpenID Connect Core provider metadata.\npub type CoreProviderMetadata = ProviderMetadata<\n    EmptyAdditionalProviderMetadata,\n    CoreAuthDisplay,\n    CoreClientAuthMethod,\n    CoreClaimName,\n    CoreClaimType,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseMode,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\n/// OpenID Connect Core user info claims.\npub type CoreUserInfoClaims = UserInfoClaims<EmptyAdditionalClaims, CoreGenderClaim>;\n\n/// OpenID Connect Core user info JSON Web Token.\npub type CoreUserInfoJsonWebToken = UserInfoJsonWebToken<\n    EmptyAdditionalClaims,\n    CoreGenderClaim,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJwsSigningAlgorithm,\n>;\n\n/// OpenID Connect Core user info verifier.\npub type CoreUserInfoVerifier<'a> =\n    UserInfoVerifier<'a, CoreJweContentEncryptionAlgorithm, CoreJsonWebKey>;\n\n/// OpenID Connect Core client application type.\n///\n/// These values are defined in\n/// [Section 2 of OpenID Connect Dynamic Client Registration 1.0](\n///     http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata).\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreApplicationType {\n    /// Native Clients MUST only register `redirect_uri`s using custom URI schemes or URLs using\n    /// the `http` scheme with `localhost` as the hostname. Authorization Servers MAY place\n    /// additional constraints on Native Clients.\n    Native,\n    /// Web Clients using the OAuth Implicit Grant Type MUST only register URLs using the `https`\n    /// scheme as `redirect_uri`s; they MUST NOT use `localhost` as the hostname.\n    Web,\n    /// An extension not defined by the OpenID Connect Dynamic Client Registration spec.\n    Extension(String),\n}\n// FIXME: Once https://github.com/serde-rs/serde/issues/912 is resolved, use #[serde(other)] instead\n// of custom serializer/deserializers. Right now this isn't possible because serde(other) only\n// supports unit variants.\ndeserialize_from_str!(CoreApplicationType);\nserialize_as_str!(CoreApplicationType);\nimpl CoreApplicationType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"native\" => CoreApplicationType::Native,\n            \"web\" => CoreApplicationType::Web,\n            ext => CoreApplicationType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreApplicationType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreApplicationType::Native => \"native\",\n            CoreApplicationType::Web => \"web\",\n            CoreApplicationType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl ApplicationType for CoreApplicationType {}\n\n/// How the Authorization Server displays the authentication and consent user interface pages\n/// to the End-User.\n///\n/// These values are defined in\n/// [Section 3.1.2.1](http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreAuthDisplay {\n    /// The Authorization Server SHOULD display the authentication and consent UI consistent\n    /// with a full User Agent page view. If the display parameter is not specified, this is\n    /// the default display mode.\n    Page,\n    /// The Authorization Server SHOULD display the authentication and consent UI consistent\n    /// with a popup User Agent window. The popup User Agent window should be of an appropriate\n    /// size for a login-focused dialog and should not obscure the entire window that it is\n    /// popping up over.\n    Popup,\n    /// The Authorization Server SHOULD display the authentication and consent UI consistent\n    /// with a device that leverages a touch interface.\n    Touch,\n    /// The Authorization Server SHOULD display the authentication and consent UI consistent\n    /// with a \"feature phone\" type display.\n    Wap,\n    /// An extension not defined by the OpenID Connect Core spec.\n    Extension(String),\n}\ndeserialize_from_str!(CoreAuthDisplay);\nserialize_as_str!(CoreAuthDisplay);\nimpl CoreAuthDisplay {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"page\" => CoreAuthDisplay::Page,\n            \"popup\" => CoreAuthDisplay::Popup,\n            \"touch\" => CoreAuthDisplay::Touch,\n            \"wap\" => CoreAuthDisplay::Wap,\n            ext => CoreAuthDisplay::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreAuthDisplay {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreAuthDisplay::Page => \"page\",\n            CoreAuthDisplay::Popup => \"popup\",\n            CoreAuthDisplay::Touch => \"touch\",\n            CoreAuthDisplay::Wap => \"wap\",\n            CoreAuthDisplay::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl AuthDisplay for CoreAuthDisplay {}\nimpl Display for CoreAuthDisplay {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n\n/// Whether the Authorization Server should prompt the End-User for re-authentication and\n/// consent.\n///\n/// These values are defined in\n/// [Section 3.1.2.1](http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreAuthPrompt {\n    /// The Authorization Server MUST NOT display any authentication or consent user interface\n    /// pages. An error is returned if an End-User is not already authenticated or the Client\n    /// does not have pre-configured consent for the requested Claims or does not fulfill other\n    /// conditions for processing the request. The error code will typically be\n    /// `login_required,` `interaction_required`, or another code defined in\n    /// [Section 3.1.2.6](http://openid.net/specs/openid-connect-core-1_0.html#AuthError).\n    /// This can be used as a method to check for existing authentication and/or consent.\n    None,\n    /// The Authorization Server SHOULD prompt the End-User for reauthentication. If it cannot\n    /// reauthenticate the End-User, it MUST return an error, typically `login_required`.\n    Login,\n    /// The Authorization Server SHOULD prompt the End-User for consent before returning\n    /// information to the Client. If it cannot obtain consent, it MUST return an error,\n    /// typically `consent_required`.\n    Consent,\n    /// The Authorization Server SHOULD prompt the End-User to select a user account. This\n    /// enables an End-User who has multiple accounts at the Authorization Server to select\n    /// amongst the multiple accounts that they might have current sessions for. If it cannot\n    /// obtain an account selection choice made by the End-User, it MUST return an error,\n    /// typically `account_selection_required`.\n    SelectAccount,\n    /// An extension not defined by the OpenID Connect Core spec.\n    Extension(String),\n}\ndeserialize_from_str!(CoreAuthPrompt);\nserialize_as_str!(CoreAuthPrompt);\nimpl CoreAuthPrompt {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"none\" => CoreAuthPrompt::None,\n            \"login\" => CoreAuthPrompt::Login,\n            \"consent\" => CoreAuthPrompt::Consent,\n            \"select_account\" => CoreAuthPrompt::SelectAccount,\n            ext => CoreAuthPrompt::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreAuthPrompt {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreAuthPrompt::None => \"none\",\n            CoreAuthPrompt::Login => \"login\",\n            CoreAuthPrompt::Consent => \"consent\",\n            CoreAuthPrompt::SelectAccount => \"select_account\",\n            CoreAuthPrompt::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl AuthPrompt for CoreAuthPrompt {}\n\nimpl Display for CoreAuthPrompt {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n\nnew_type![\n    /// OpenID Connect Core claim name.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    CoreClaimName(String)\n];\nimpl ClaimName for CoreClaimName {}\n\n/// Representation of a Claim Value.\n///\n/// See [Section 5.6](http://openid.net/specs/openid-connect-core-1_0.html#ClaimTypes) for\n/// further information.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreClaimType {\n    /// Aggregated Claim Type.\n    ///\n    /// See [Section 5.6.2](\n    ///     http://openid.net/specs/openid-connect-core-1_0.html#AggregatedDistributedClaims)\n    /// for details.\n    Aggregated,\n    /// Distributed Claim Type.\n    ///\n    /// See [Section 5.6.2](\n    ///     http://openid.net/specs/openid-connect-core-1_0.html#AggregatedDistributedClaims)\n    /// for details.\n    Distributed,\n    /// Normal Claims are represented as members in a JSON object. The Claim Name is the member\n    /// name and the Claim Value is the member value.\n    Normal,\n    /// An extension not defined by the OpenID Connect Core spec.\n    Extension(String),\n}\ndeserialize_from_str!(CoreClaimType);\nserialize_as_str!(CoreClaimType);\nimpl CoreClaimType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"normal\" => CoreClaimType::Normal,\n            \"aggregated\" => CoreClaimType::Aggregated,\n            \"distributed\" => CoreClaimType::Distributed,\n            ext => CoreClaimType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreClaimType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreClaimType::Normal => \"normal\",\n            CoreClaimType::Aggregated => \"aggregated\",\n            CoreClaimType::Distributed => \"distributed\",\n            CoreClaimType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl ClaimType for CoreClaimType {}\n\n/// OpenID Connect Core client authentication method.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreClientAuthMethod {\n    /// Client secret passed via the HTTP Basic authentication scheme.\n    ClientSecretBasic,\n    /// Client authentication using a JSON Web Token signed with the client secret used as an HMAC\n    /// key.\n    ClientSecretJwt,\n    /// Client secret passed via the POST request body.\n    ClientSecretPost,\n    /// JSON Web Token signed with a private key whose public key has been previously registered\n    /// with the OpenID Connect provider.\n    PrivateKeyJwt,\n    /// The Client does not authenticate itself at the Token Endpoint, either because it uses only\n    /// the Implicit Flow (and so does not use the Token Endpoint) or because it is a Public Client\n    /// with no Client Secret or other authentication mechanism.\n    None,\n    /// An extension not defined by the OpenID Connect Core spec.\n    Extension(String),\n}\ndeserialize_from_str!(CoreClientAuthMethod);\nserialize_as_str!(CoreClientAuthMethod);\nimpl CoreClientAuthMethod {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"client_secret_basic\" => CoreClientAuthMethod::ClientSecretBasic,\n            \"client_secret_jwt\" => CoreClientAuthMethod::ClientSecretJwt,\n            \"client_secret_post\" => CoreClientAuthMethod::ClientSecretPost,\n            \"private_key_jwt\" => CoreClientAuthMethod::PrivateKeyJwt,\n            \"none\" => CoreClientAuthMethod::None,\n            ext => CoreClientAuthMethod::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreClientAuthMethod {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreClientAuthMethod::ClientSecretBasic => \"client_secret_basic\",\n            CoreClientAuthMethod::ClientSecretJwt => \"client_secret_jwt\",\n            CoreClientAuthMethod::ClientSecretPost => \"client_secret_post\",\n            CoreClientAuthMethod::PrivateKeyJwt => \"private_key_jwt\",\n            CoreClientAuthMethod::None => \"none\",\n            CoreClientAuthMethod::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl ClientAuthMethod for CoreClientAuthMethod {}\n\nnew_type![\n    /// OpenID Connect Core gender claim.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    CoreGenderClaim(String)\n];\nimpl GenderClaim for CoreGenderClaim {}\n\n/// OpenID Connect Core grant type.\n///\n// These are defined in various specs, including the Client Registration spec:\n//   http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata\n#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\npub enum CoreGrantType {\n    /// Authorization code grant.\n    AuthorizationCode,\n    /// Client credentials grant.\n    ClientCredentials,\n    /// Device Authorization Grant as described in [RFC 8628](https://tools.ietf.org/html/rfc8628).\n    DeviceCode,\n    /// Implicit grant.\n    Implicit,\n    /// JWT-based authentication as described in [RFC 7523](https://tools.ietf.org/html/rfc7523).\n    JwtBearer,\n    /// End user password grant.\n    Password,\n    /// Refresh token grant.\n    RefreshToken,\n    /// An extension not defined by any of the supported specifications.\n    Extension(String),\n}\ndeserialize_from_str!(CoreGrantType);\nserialize_as_str!(CoreGrantType);\nimpl CoreGrantType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"authorization_code\" => CoreGrantType::AuthorizationCode,\n            \"client_credentials\" => CoreGrantType::ClientCredentials,\n            \"urn:ietf:params:oauth:grant-type:device_code\" => CoreGrantType::DeviceCode,\n            \"implicit\" => CoreGrantType::Implicit,\n            \"urn:ietf:params:oauth:grant-type:jwt-bearer\" => CoreGrantType::JwtBearer,\n            \"password\" => CoreGrantType::Password,\n            \"refresh_token\" => CoreGrantType::RefreshToken,\n            ext => CoreGrantType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreGrantType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreGrantType::AuthorizationCode => \"authorization_code\",\n            CoreGrantType::ClientCredentials => \"client_credentials\",\n            CoreGrantType::DeviceCode => \"urn:ietf:params:oauth:grant-type:device_code\",\n            CoreGrantType::Implicit => \"implicit\",\n            CoreGrantType::JwtBearer => \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n            CoreGrantType::Password => \"password\",\n            CoreGrantType::RefreshToken => \"refresh_token\",\n            CoreGrantType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl GrantType for CoreGrantType {}\n\n/// OpenID Connect Core JWE encryption algorithms.\n///\n/// These algorithms represent the `enc` header parameter values for JSON Web Encryption.\n/// The values are described in\n/// [Section 5.1 of RFC 7518](https://tools.ietf.org/html/rfc7518#section-5.1).\n#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]\n#[non_exhaustive]\npub enum CoreJweContentEncryptionAlgorithm {\n    /// AES-128 CBC HMAC SHA-256 authenticated encryption.\n    #[serde(rename = \"A128CBC-HS256\")]\n    Aes128CbcHmacSha256,\n    /// AES-192 CBC HMAC SHA-384 authenticated encryption.\n    #[serde(rename = \"A192CBC-HS384\")]\n    Aes192CbcHmacSha384,\n    /// AES-256 CBC HMAC SHA-512 authenticated encryption.\n    #[serde(rename = \"A256CBC-HS512\")]\n    Aes256CbcHmacSha512,\n    /// AES-128 GCM.\n    #[serde(rename = \"A128GCM\")]\n    Aes128Gcm,\n    /// AES-192 GCM.\n    #[serde(rename = \"A192GCM\")]\n    Aes192Gcm,\n    /// AES-256 GCM.\n    #[serde(rename = \"A256GCM\")]\n    Aes256Gcm,\n}\nimpl JweContentEncryptionAlgorithm for CoreJweContentEncryptionAlgorithm {\n    type KeyType = CoreJsonWebKeyType;\n\n    fn key_type(&self) -> Result<CoreJsonWebKeyType, String> {\n        Ok(CoreJsonWebKeyType::Symmetric)\n    }\n}\n\n/// OpenID Connect Core JWE key management algorithms.\n///\n/// These algorithms represent the `alg` header parameter values for JSON Web Encryption.\n/// They are used to encrypt the Content Encryption Key (CEK) to produce the JWE Encrypted Key, or\n/// to use key agreement to agree upon the CEK. The values are described in\n/// [Section 4.1 of RFC 7518](https://tools.ietf.org/html/rfc7518#section-4.1).\n#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]\n#[non_exhaustive]\npub enum CoreJweKeyManagementAlgorithm {\n    /// RSAES-PKCS1-V1_5.\n    #[serde(rename = \"RSA1_5\")]\n    RsaPkcs1V15,\n    /// RSAES OAEP using default parameters.\n    #[serde(rename = \"RSA-OAEP\")]\n    RsaOaep,\n    /// RSAES OAEP using SHA-256 and MGF1 with SHA-256.\n    #[serde(rename = \"RSA-OAEP-256\")]\n    RsaOaepSha256,\n    /// AES-128 Key Wrap.\n    #[serde(rename = \"A128KW\")]\n    AesKeyWrap128,\n    /// AES-192 Key Wrap.\n    #[serde(rename = \"A192KW\")]\n    AesKeyWrap192,\n    /// AES-256 Key Wrap.\n    #[serde(rename = \"A256KW\")]\n    AesKeyWrap256,\n    /// Direct use of a shared symmetric key as the Content Encryption Key (CEK).\n    #[serde(rename = \"dir\")]\n    Direct,\n    /// Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF.\n    #[serde(rename = \"ECDH-ES\")]\n    EcdhEs,\n    /// ECDH-ES using Concat KDF and CEK wrapped with AES-128 Key Wrap.\n    #[serde(rename = \"ECDH-ES+A128KW\")]\n    EcdhEsAesKeyWrap128,\n    /// ECDH-ES using Concat KDF and CEK wrapped with AES-192 Key Wrap.\n    #[serde(rename = \"ECDH-ES+A192KW\")]\n    EcdhEsAesKeyWrap192,\n    /// ECDH-ES using Concat KDF and CEK wrapped with AES-256 Key Wrap.\n    #[serde(rename = \"ECDH-ES+A256KW\")]\n    EcdhEsAesKeyWrap256,\n    /// Key wrapping with AES GCM using 128 bit key.\n    #[serde(rename = \"A128GCMKW\")]\n    Aes128Gcm,\n    /// Key wrapping with AES GCM using 192 bit key.\n    #[serde(rename = \"A192GCMKW\")]\n    Aes192Gcm,\n    /// Key wrapping with AES GCM using 256 bit key.\n    #[serde(rename = \"A256GCMKW\")]\n    Aes256Gcm,\n    /// PBES2 with HMAC SHA-256 wrapped with AES-128 Key Wrap.\n    #[serde(rename = \"PBES2-HS256+A128KW\")]\n    PbEs2HmacSha256AesKeyWrap128,\n    /// PBES2 with HMAC SHA-384 wrapped with AES-192 Key Wrap.\n    #[serde(rename = \"PBES2-HS384+A192KW\")]\n    PbEs2HmacSha384AesKeyWrap192,\n    /// PBES2 with HMAC SHA-512 wrapped with AES-256 Key Wrap.\n    #[serde(rename = \"PBES2-HS512+A256KW\")]\n    PbEs2HmacSha512AesKeyWrap256,\n}\nimpl JweKeyManagementAlgorithm for CoreJweKeyManagementAlgorithm {}\n\n/// OpenID Connect Core JWS signing algorithms.\n///\n/// These algorithms represent the `alg` header parameter values for JSON Web Signature.\n/// They are used to digitally sign or create a MAC of the contents of the JWS Protected Header and\n/// the JWS Payload. The values are described in\n/// [Section 3.1 of RFC 7518](https://tools.ietf.org/html/rfc7518#section-3.1).\n#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]\n#[non_exhaustive]\npub enum CoreJwsSigningAlgorithm {\n    /// HMAC using SHA-256 (currently unsupported).\n    #[serde(rename = \"HS256\")]\n    HmacSha256,\n    /// HMAC using SHA-384 (currently unsupported).\n    #[serde(rename = \"HS384\")]\n    HmacSha384,\n    /// HMAC using SHA-512 (currently unsupported).\n    #[serde(rename = \"HS512\")]\n    HmacSha512,\n    /// RSA SSA PKCS#1 v1.5 using SHA-256.\n    #[serde(rename = \"RS256\")]\n    RsaSsaPkcs1V15Sha256,\n    /// RSA SSA PKCS#1 v1.5 using SHA-384.\n    #[serde(rename = \"RS384\")]\n    RsaSsaPkcs1V15Sha384,\n    /// RSA SSA PKCS#1 v1.5 using SHA-512.\n    #[serde(rename = \"RS512\")]\n    RsaSsaPkcs1V15Sha512,\n    /// ECDSA using P-256 and SHA-256 (currently unsupported).\n    #[serde(rename = \"ES256\")]\n    EcdsaP256Sha256,\n    /// ECDSA using P-384 and SHA-384 (currently unsupported).\n    #[serde(rename = \"ES384\")]\n    EcdsaP384Sha384,\n    /// ECDSA using P-521 and SHA-512 (currently unsupported).\n    #[serde(rename = \"ES512\")]\n    EcdsaP521Sha512,\n    /// RSA SSA-PSS using SHA-256 and MGF1 with SHA-256.\n    #[serde(rename = \"PS256\")]\n    RsaSsaPssSha256,\n    /// RSA SSA-PSS using SHA-384 and MGF1 with SHA-384.\n    #[serde(rename = \"PS384\")]\n    RsaSsaPssSha384,\n    /// RSA SSA-PSS using SHA-512 and MGF1 with SHA-512.\n    #[serde(rename = \"PS512\")]\n    RsaSsaPssSha512,\n    /// EdDSA signature (algorithm depends on `crv` header).\n    #[serde(rename = \"EdDSA\")]\n    EdDsa,\n    /// No digital signature or MAC performed.\n    ///\n    /// # Security Warning\n    ///\n    /// This algorithm provides no security over the integrity of the JSON Web Token. Clients\n    /// should be careful not to rely on unsigned JWT's for security purposes. See\n    /// [Critical vulnerabilities in JSON Web Token libraries](\n    ///     https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) for\n    /// further discussion.\n    #[serde(rename = \"none\")]\n    None,\n}\nimpl JwsSigningAlgorithm for CoreJwsSigningAlgorithm {\n    type KeyType = CoreJsonWebKeyType;\n\n    fn key_type(&self) -> Option<CoreJsonWebKeyType> {\n        match *self {\n            CoreJwsSigningAlgorithm::HmacSha256\n            | CoreJwsSigningAlgorithm::HmacSha384\n            | CoreJwsSigningAlgorithm::HmacSha512 => Some(CoreJsonWebKeyType::Symmetric),\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256\n            | CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384\n            | CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512\n            | CoreJwsSigningAlgorithm::RsaSsaPssSha256\n            | CoreJwsSigningAlgorithm::RsaSsaPssSha384\n            | CoreJwsSigningAlgorithm::RsaSsaPssSha512 => Some(CoreJsonWebKeyType::RSA),\n            CoreJwsSigningAlgorithm::EcdsaP256Sha256\n            | CoreJwsSigningAlgorithm::EcdsaP384Sha384\n            | CoreJwsSigningAlgorithm::EcdsaP521Sha512 => Some(CoreJsonWebKeyType::EllipticCurve),\n            CoreJwsSigningAlgorithm::EdDsa => Some(CoreJsonWebKeyType::OctetKeyPair),\n            CoreJwsSigningAlgorithm::None => None,\n        }\n    }\n\n    fn uses_shared_secret(&self) -> bool {\n        self.key_type()\n            .map(|kty| kty == CoreJsonWebKeyType::Symmetric)\n            .unwrap_or(false)\n    }\n\n    fn rsa_sha_256() -> Self {\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256\n    }\n}\n\n/// OpenID Connect Core authentication error response types.\n///\n/// This type represents errors returned in a redirect from the Authorization Endpoint to the\n/// client's redirect URI.\n///\n/// These values are defined across both\n/// [Section 4.1.2.1](https://tools.ietf.org/html/rfc6749#section-4.1.2.1) of RFC 6749 and\n/// [Section 3.1.2.6](https://openid.net/specs/openid-connect-core-1_0.html#AuthError) of the\n/// OpenID Connect Core spec.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreAuthErrorResponseType {\n    /// The resource owner or authorization server denied the request.\n    AccessDenied,\n    /// The End-User is REQUIRED to select a session at the Authorization Server. The End-User MAY\n    /// be authenticated at the Authorization Server with different associated accounts, but the\n    /// End-User did not select a session. This error MAY be returned when the `prompt` parameter\n    /// value in the Authentication Request is `none`, but the Authentication Request cannot be\n    /// completed without displaying a user interface to prompt for a session to use.\n    AccountSelectionRequired,\n    /// The Authorization Server requires End-User consent. This error MAY be returned when the\n    /// `prompt` parameter value in the Authentication Request is `none`, but the Authentication\n    /// Request cannot be completed without displaying a user interface for End-User consent.\n    ConsentRequired,\n    /// The Authorization Server requires End-User interaction of some form to proceed. This error\n    /// MAY be returned when the `prompt` parameter value in the Authentication Request is `none`,\n    /// but the Authentication Request cannot be completed without displaying a user interface for\n    /// End-User interaction.\n    InteractionRequired,\n    /// The request is missing a required parameter, includes an invalid parameter value, includes\n    /// a parameter more than once, or is otherwise malformed.\n    InvalidRequest,\n    /// The `request` parameter contains an invalid Request Object.\n    InvalidRequestObject,\n    /// The `request_uri` in the Authorization Request returns an error or contains invalid data.\n    InvalidRequestUri,\n    /// The requested scope is invalid, unknown, or malformed.\n    InvalidScope,\n    /// The Authorization Server requires End-User authentication. This error MAY be returned when\n    /// the `prompt` parameter value in the Authentication Request is `none`, but the Authentication\n    /// Request cannot be completed without displaying a user interface for End-User authentication.\n    LoginRequired,\n    /// The OpenID Connect Provider does not support use of the `registration` parameter.\n    RegistrationNotSupported,\n    /// The OpenID Connect Provider does not support use of the `request` parameter.\n    RequestNotSupported,\n    /// The OpenID Connect Provider does not support use of the `request_uri` parameter.\n    RequestUriNotSupported,\n    /// The authorization server encountered an unexpected condition that prevented it from\n    /// fulfilling the request. (This error code is needed because a 500 Internal Server Error HTTP\n    /// status code cannot be returned to the client via an HTTP redirect.)\n    ServerError,\n    /// The authorization server is currently unable to handle the request due to a temporary\n    /// overloading or maintenance of the server.  (This error code is needed because a 503 Service\n    /// Unavailable HTTP status code cannot be returned to the client via an HTTP redirect.)\n    TemporarilyUnavailable,\n    /// The client is not authorized to request an authorization code using this method.\n    UnauthorizedClient,\n    /// The authorization server does not support obtaining an authorization code using this method.\n    UnsupportedResponseType,\n    /// An extension not defined by any of the supported specifications.\n    Extension(String),\n}\ndeserialize_from_str!(CoreAuthErrorResponseType);\nserialize_as_str!(CoreAuthErrorResponseType);\nimpl CoreAuthErrorResponseType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"access_denied\" => CoreAuthErrorResponseType::AccessDenied,\n            \"account_selection_required\" => CoreAuthErrorResponseType::AccountSelectionRequired,\n            \"consent_required\" => CoreAuthErrorResponseType::ConsentRequired,\n            \"interaction_required\" => CoreAuthErrorResponseType::InteractionRequired,\n            \"invalid_request\" => CoreAuthErrorResponseType::InvalidRequest,\n            \"invalid_request_object\" => CoreAuthErrorResponseType::InvalidRequestObject,\n            \"invalid_request_uri\" => CoreAuthErrorResponseType::InvalidRequestUri,\n            \"invalid_scope\" => CoreAuthErrorResponseType::InvalidScope,\n            \"login_required\" => CoreAuthErrorResponseType::LoginRequired,\n            \"registration_not_supported\" => CoreAuthErrorResponseType::RegistrationNotSupported,\n            \"request_not_supported\" => CoreAuthErrorResponseType::RequestNotSupported,\n            \"request_uri_not_supported\" => CoreAuthErrorResponseType::RequestUriNotSupported,\n            \"server_error\" => CoreAuthErrorResponseType::ServerError,\n            \"temporarily_unavailable\" => CoreAuthErrorResponseType::TemporarilyUnavailable,\n            \"unauthorized_client\" => CoreAuthErrorResponseType::UnauthorizedClient,\n            \"unsupported_response_type\" => CoreAuthErrorResponseType::UnsupportedResponseType,\n            ext => CoreAuthErrorResponseType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreAuthErrorResponseType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreAuthErrorResponseType::AccessDenied => \"access_denied\",\n            CoreAuthErrorResponseType::AccountSelectionRequired => \"account_selection_required\",\n            CoreAuthErrorResponseType::ConsentRequired => \"consent_required\",\n            CoreAuthErrorResponseType::InteractionRequired => \"interaction_required\",\n            CoreAuthErrorResponseType::InvalidRequest => \"invalid_request\",\n            CoreAuthErrorResponseType::InvalidRequestObject => \"invalid_request_obbject\",\n            CoreAuthErrorResponseType::InvalidRequestUri => \"invalid_request_uri\",\n            CoreAuthErrorResponseType::InvalidScope => \"invalid_scope\",\n            CoreAuthErrorResponseType::LoginRequired => \"login_required\",\n            CoreAuthErrorResponseType::RegistrationNotSupported => \"registration_not_supported\",\n            CoreAuthErrorResponseType::RequestNotSupported => \"request_not_supported\",\n            CoreAuthErrorResponseType::RequestUriNotSupported => \"request_uri_not_supported\",\n            CoreAuthErrorResponseType::ServerError => \"server_error\",\n            CoreAuthErrorResponseType::TemporarilyUnavailable => \"temporarily_unavailable\",\n            CoreAuthErrorResponseType::UnauthorizedClient => \"unauthorized_client\",\n            CoreAuthErrorResponseType::UnsupportedResponseType => \"unsupported_response_type\",\n            CoreAuthErrorResponseType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\n\n/// OpenID Connect Core registration error response type.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreRegisterErrorResponseType {\n    /// The value of one of the Client Metadata fields is invalid and the server has rejected this\n    /// request. Note that an Authorization Server MAY choose to substitute a valid value for any\n    /// requested parameter of a Client's Metadata.\n    InvalidClientMetadata,\n    /// The value of one or more `redirect_uri`s is invalid.\n    InvalidRedirectUri,\n    /// An extension not defined by any of the supported specifications.\n    Extension(String),\n}\ndeserialize_from_str!(CoreRegisterErrorResponseType);\nserialize_as_str!(CoreRegisterErrorResponseType);\nimpl CoreRegisterErrorResponseType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"invalid_client_metadata\" => CoreRegisterErrorResponseType::InvalidClientMetadata,\n            \"invalid_redirect_uri\" => CoreRegisterErrorResponseType::InvalidRedirectUri,\n            ext => CoreRegisterErrorResponseType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreRegisterErrorResponseType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreRegisterErrorResponseType::InvalidClientMetadata => \"invalid_client_metadata\",\n            CoreRegisterErrorResponseType::InvalidRedirectUri => \"invalid_redirect_uri\",\n            CoreRegisterErrorResponseType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl ErrorResponseType for CoreRegisterErrorResponseType {}\nimpl RegisterErrorResponseType for CoreRegisterErrorResponseType {}\nimpl Display for CoreRegisterErrorResponseType {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n\n/// OpenID Connect Core response mode.\n///\n/// Informs the Authorization Server of the mechanism to be used for returning Authorization\n/// Response parameters from the Authorization Endpoint.\n///\n/// The default Response Mode for the OAuth 2.0 `code` Response Type is the `query` encoding.\n/// The default Response Mode for the OAuth 2.0 `token` Response Type is the `fragment` encoding.\n/// These values are defined in\n/// [OAuth 2.0 Multiple Response Type Encoding Practices](\n///     http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseTypesAndModes)\n/// and [OAuth 2.0 Form Post Response Mode](\n///     http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode).\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreResponseMode {\n    /// In this mode, Authorization Response parameters are encoded in the query string added to\n    /// the `redirect_uri` when redirecting back to the Client.\n    Query,\n    /// In this mode, Authorization Response parameters are encoded in the fragment added to the\n    /// `redirect_uri` when redirecting back to the Client.\n    Fragment,\n    /// In this mode, Authorization Response parameters are encoded as HTML form values that are\n    /// auto-submitted in the User Agent, and thus are transmitted via the HTTP `POST` method to the\n    /// Client, with the result parameters being encoded in the body using the\n    /// `application/x-www-form-urlencoded` format. The `action` attribute of the form MUST be the\n    /// Client's Redirection URI. The method of the form attribute MUST be `POST`. Because the\n    /// Authorization Response is intended to be used only once, the Authorization Server MUST\n    /// instruct the User Agent (and any intermediaries) not to store or reuse the content of the\n    /// response.\n    ///\n    /// Any technique supported by the User Agent MAY be used to cause the submission of the form,\n    /// and any form content necessary to support this MAY be included, such as submit controls and\n    /// client-side scripting commands. However, the Client MUST be able to process the message\n    /// without regard for the mechanism by which the form submission was initiated.\n    ///\n    /// See [OAuth 2.0 Form Post Response Mode](\n    ///     http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode)\n    /// for further information.\n    FormPost,\n    /// An extension not defined by any of the supported specifications.\n    Extension(String),\n}\ndeserialize_from_str!(CoreResponseMode);\nserialize_as_str!(CoreResponseMode);\nimpl CoreResponseMode {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"query\" => CoreResponseMode::Query,\n            \"fragment\" => CoreResponseMode::Fragment,\n            \"form_post\" => CoreResponseMode::FormPost,\n            ext => CoreResponseMode::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreResponseMode {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreResponseMode::Query => \"query\",\n            CoreResponseMode::Fragment => \"fragment\",\n            CoreResponseMode::FormPost => \"form_post\",\n            CoreResponseMode::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl ResponseMode for CoreResponseMode {}\n\n/// OpenID Connect Core response type.\n///\n/// Informs the Authorization Server of the desired authorization processing flow, including what\n/// parameters are returned from the endpoints used.\n///\n/// This type represents a single Response Type. Multiple Response Types are represented via the\n/// `ResponseTypes` type, which wraps a `Vec<ResponseType>`.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreResponseType {\n    /// Used by the OAuth 2.0 Authorization Code Flow.\n    Code,\n    /// When supplied as the `response_type` parameter in an OAuth 2.0 Authorization Request, a\n    /// successful response MUST include the parameter `id_token`.\n    IdToken,\n    /// When supplied as the `response_type` parameter in an OAuth 2.0 Authorization Request, the\n    /// Authorization Server SHOULD NOT return an OAuth 2.0 Authorization Code, Access Token, Access\n    /// Token Type, or ID Token in a successful response to the grant request. If a `redirect_uri`\n    /// is supplied, the User Agent SHOULD be redirected there after granting or denying access.\n    /// The request MAY include a `state` parameter, and if so, the Authorization Server MUST echo\n    /// its value as a response parameter when issuing either a successful response or an error\n    /// response. The default Response Mode for this Response Type is the query encoding. Both\n    /// successful and error responses SHOULD be returned using the supplied Response Mode, or if\n    /// none is supplied, using the default Response Mode.\n    ///\n    /// This Response Type is not generally used with OpenID Connect but may be supported by the\n    /// Authorization Server.\n    None,\n    /// Used by the OAuth 2.0 Implicit Flow.\n    Token,\n    /// An extension not defined by the OpenID Connect Core spec.\n    Extension(String),\n}\ndeserialize_from_str!(CoreResponseType);\nserialize_as_str!(CoreResponseType);\nimpl CoreResponseType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"code\" => CoreResponseType::Code,\n            \"id_token\" => CoreResponseType::IdToken,\n            \"none\" => CoreResponseType::None,\n            \"token\" => CoreResponseType::Token,\n            ext => CoreResponseType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreResponseType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreResponseType::Code => \"code\",\n            CoreResponseType::IdToken => \"id_token\",\n            CoreResponseType::None => \"none\",\n            CoreResponseType::Token => \"token\",\n            CoreResponseType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl ResponseType for CoreResponseType {\n    fn to_oauth2(&self) -> OAuth2ResponseType {\n        OAuth2ResponseType::new(self.as_ref().to_string())\n    }\n}\n\n/// OpenID Connect Core Subject Identifier type.\n///\n/// A Subject Identifier is a locally unique and never reassigned identifier within the Issuer for\n/// the End-User, which is intended to be consumed by the Client.\n///\n/// See [Section 8](http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes) for\n/// further information.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum CoreSubjectIdentifierType {\n    /// This provides a different `sub` value to each Client, so as not to enable Clients to\n    /// correlate the End-User's activities without permission.\n    Pairwise,\n    /// This provides the same `sub` (subject) value to all Clients. It is the default if the\n    /// provider has no `subject_types_supported` element in its discovery document.\n    Public,\n    /// An extension not defined by the OpenID Connect Core spec.\n    Extension(String),\n}\ndeserialize_from_str!(CoreSubjectIdentifierType);\nserialize_as_str!(CoreSubjectIdentifierType);\nimpl CoreSubjectIdentifierType {\n    fn from_str(s: &str) -> Self {\n        match s {\n            \"pairwise\" => CoreSubjectIdentifierType::Pairwise,\n            \"public\" => CoreSubjectIdentifierType::Public,\n            ext => CoreSubjectIdentifierType::Extension(ext.to_string()),\n        }\n    }\n}\nimpl AsRef<str> for CoreSubjectIdentifierType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            CoreSubjectIdentifierType::Pairwise => \"pairwise\",\n            CoreSubjectIdentifierType::Public => \"public\",\n            CoreSubjectIdentifierType::Extension(ref ext) => ext.as_str(),\n        }\n    }\n}\nimpl SubjectIdentifierType for CoreSubjectIdentifierType {}\n\npub(crate) fn base64_url_safe_no_pad() -> GeneralPurpose {\n    GeneralPurpose::new(&URL_SAFE, NO_PAD.with_decode_allow_trailing_bits(true))\n}\n\n#[cfg(test)]\nmod tests;\n"
  },
  {
    "path": "src/core/tests.rs",
    "content": "use crate::core::{CoreGrantType, CoreJwsSigningAlgorithm};\n\n#[test]\nfn test_grant_type_serialize() {\n    let serialized_implicit = serde_json::to_string(&CoreGrantType::Implicit).unwrap();\n    assert_eq!(\"\\\"implicit\\\"\", serialized_implicit);\n    assert_eq!(\n        CoreGrantType::Implicit,\n        serde_json::from_str::<CoreGrantType>(&serialized_implicit).unwrap()\n    );\n}\n\n#[test]\nfn test_signature_alg_serde_plain() {\n    assert_eq!(\n        serde_plain::to_string(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256).unwrap(),\n        \"RS256\"\n    );\n    assert_eq!(\n        serde_plain::from_str::<CoreJwsSigningAlgorithm>(\"RS256\").unwrap(),\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256\n    );\n}\n"
  },
  {
    "path": "src/discovery/mod.rs",
    "content": "use crate::http_utils::{check_content_type, MIME_TYPE_JSON};\nuse crate::{\n    AsyncHttpClient, AuthDisplay, AuthUrl, AuthenticationContextClass, ClaimName, ClaimType,\n    ClientAuthMethod, GrantType, HttpRequest, HttpResponse, IssuerUrl, JsonWebKey, JsonWebKeySet,\n    JsonWebKeySetUrl, JweContentEncryptionAlgorithm, JweKeyManagementAlgorithm,\n    JwsSigningAlgorithm, LanguageTag, OpPolicyUrl, OpTosUrl, RegistrationUrl, ResponseMode,\n    ResponseType, ResponseTypes, Scope, ServiceDocUrl, SubjectIdentifierType, SyncHttpClient,\n    TokenUrl, UserInfoUrl,\n};\n\nuse http::header::{HeaderValue, ACCEPT};\nuse http::method::Method;\nuse http::status::StatusCode;\nuse serde::de::DeserializeOwned;\nuse serde::{Deserialize, Serialize};\nuse serde_with::{serde_as, skip_serializing_none, VecSkipError};\nuse thiserror::Error;\n\nuse std::fmt::Debug;\nuse std::future::Future;\n\n#[cfg(test)]\nmod tests;\n\nconst CONFIG_URL_SUFFIX: &str = \".well-known/openid-configuration\";\n\n/// Trait for adding extra fields to [`ProviderMetadata`].\npub trait AdditionalProviderMetadata: Clone + Debug + DeserializeOwned + Serialize {}\n\n// In order to support serde flatten, this must be an empty struct rather than an empty\n// tuple struct.\n/// Empty (default) extra [`ProviderMetadata`] fields.\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]\npub struct EmptyAdditionalProviderMetadata {}\nimpl AdditionalProviderMetadata for EmptyAdditionalProviderMetadata {}\n\n/// Provider metadata returned by [OpenID Connect Discovery](\n/// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata).\n#[serde_as]\n#[skip_serializing_none]\n#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]\n#[allow(clippy::type_complexity)]\npub struct ProviderMetadata<A, AD, CA, CN, CT, G, JE, JK, K, RM, RT, S>\nwhere\n    A: AdditionalProviderMetadata,\n    AD: AuthDisplay,\n    CA: ClientAuthMethod,\n    CN: ClaimName,\n    CT: ClaimType,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RM: ResponseMode,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    issuer: IssuerUrl,\n    authorization_endpoint: AuthUrl,\n    token_endpoint: Option<TokenUrl>,\n    userinfo_endpoint: Option<UserInfoUrl>,\n    jwks_uri: JsonWebKeySetUrl,\n    #[serde(default = \"JsonWebKeySet::default\", skip)]\n    jwks: JsonWebKeySet<K>,\n    registration_endpoint: Option<RegistrationUrl>,\n    scopes_supported: Option<Vec<Scope>>,\n    #[serde(bound(deserialize = \"RT: ResponseType\"))]\n    response_types_supported: Vec<ResponseTypes<RT>>,\n    #[serde(bound(deserialize = \"RM: ResponseMode\"))]\n    response_modes_supported: Option<Vec<RM>>,\n    #[serde(bound(deserialize = \"G: GrantType\"))]\n    grant_types_supported: Option<Vec<G>>,\n    acr_values_supported: Option<Vec<AuthenticationContextClass>>,\n    #[serde(bound(deserialize = \"S: SubjectIdentifierType\"))]\n    subject_types_supported: Vec<S>,\n    #[serde(bound(deserialize = \"K: JsonWebKey\"))]\n    #[serde_as(as = \"VecSkipError<_>\")]\n    id_token_signing_alg_values_supported: Vec<K::SigningAlgorithm>,\n    #[serde(\n        bound(deserialize = \"JK: JweKeyManagementAlgorithm\"),\n        default = \"Option::default\"\n    )]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    id_token_encryption_alg_values_supported: Option<Vec<JK>>,\n    #[serde(\n        bound(\n            deserialize = \"JE: JweContentEncryptionAlgorithm<KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType>\"\n        ),\n        default = \"Option::default\"\n    )]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    id_token_encryption_enc_values_supported: Option<Vec<JE>>,\n    #[serde(bound(deserialize = \"K: JsonWebKey\"), default = \"Option::default\")]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    userinfo_signing_alg_values_supported: Option<Vec<K::SigningAlgorithm>>,\n    #[serde(\n        bound(deserialize = \"JK: JweKeyManagementAlgorithm\"),\n        default = \"Option::default\"\n    )]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    userinfo_encryption_alg_values_supported: Option<Vec<JK>>,\n    #[serde(\n        bound(\n            deserialize = \"JE: JweContentEncryptionAlgorithm<KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType>\"\n        ),\n        default = \"Option::default\"\n    )]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    userinfo_encryption_enc_values_supported: Option<Vec<JE>>,\n    #[serde(bound(deserialize = \"K: JsonWebKey\"), default = \"Option::default\")]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    request_object_signing_alg_values_supported: Option<Vec<K::SigningAlgorithm>>,\n    #[serde(\n        bound(deserialize = \"JK: JweKeyManagementAlgorithm\"),\n        default = \"Option::default\"\n    )]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    request_object_encryption_alg_values_supported: Option<Vec<JK>>,\n    #[serde(\n        bound(\n            deserialize = \"JE: JweContentEncryptionAlgorithm<KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType>\"\n        ),\n        default = \"Option::default\"\n    )]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    request_object_encryption_enc_values_supported: Option<Vec<JE>>,\n    #[serde(bound(deserialize = \"CA: ClientAuthMethod\"))]\n    token_endpoint_auth_methods_supported: Option<Vec<CA>>,\n    #[serde(bound(deserialize = \"K: JsonWebKey\"), default = \"Option::default\")]\n    #[serde_as(as = \"Option<VecSkipError<_>>\")]\n    token_endpoint_auth_signing_alg_values_supported: Option<Vec<K::SigningAlgorithm>>,\n    #[serde(bound(deserialize = \"AD: AuthDisplay\"))]\n    display_values_supported: Option<Vec<AD>>,\n    #[serde(bound(deserialize = \"CT: ClaimType\"))]\n    claim_types_supported: Option<Vec<CT>>,\n    #[serde(bound(deserialize = \"CN: ClaimName\"))]\n    claims_supported: Option<Vec<CN>>,\n    service_documentation: Option<ServiceDocUrl>,\n    claims_locales_supported: Option<Vec<LanguageTag>>,\n    ui_locales_supported: Option<Vec<LanguageTag>>,\n    claims_parameter_supported: Option<bool>,\n    request_parameter_supported: Option<bool>,\n    request_uri_parameter_supported: Option<bool>,\n    require_request_uri_registration: Option<bool>,\n    op_policy_uri: Option<OpPolicyUrl>,\n    op_tos_uri: Option<OpTosUrl>,\n\n    #[serde(bound(deserialize = \"A: AdditionalProviderMetadata\"), flatten)]\n    additional_metadata: A,\n}\nimpl<A, AD, CA, CN, CT, G, JE, JK, K, RM, RT, S>\n    ProviderMetadata<A, AD, CA, CN, CT, G, JE, JK, K, RM, RT, S>\nwhere\n    A: AdditionalProviderMetadata,\n    AD: AuthDisplay,\n    CA: ClientAuthMethod,\n    CN: ClaimName,\n    CT: ClaimType,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RM: ResponseMode,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    /// Instantiates new provider metadata.\n    pub fn new(\n        issuer: IssuerUrl,\n        authorization_endpoint: AuthUrl,\n        jwks_uri: JsonWebKeySetUrl,\n        response_types_supported: Vec<ResponseTypes<RT>>,\n        subject_types_supported: Vec<S>,\n        id_token_signing_alg_values_supported: Vec<K::SigningAlgorithm>,\n        additional_metadata: A,\n    ) -> Self {\n        Self {\n            issuer,\n            authorization_endpoint,\n            token_endpoint: None,\n            userinfo_endpoint: None,\n            jwks_uri,\n            jwks: JsonWebKeySet::new(Vec::new()),\n            registration_endpoint: None,\n            scopes_supported: None,\n            response_types_supported,\n            response_modes_supported: None,\n            grant_types_supported: None,\n            acr_values_supported: None,\n            subject_types_supported,\n            id_token_signing_alg_values_supported,\n            id_token_encryption_alg_values_supported: None,\n            id_token_encryption_enc_values_supported: None,\n            userinfo_signing_alg_values_supported: None,\n            userinfo_encryption_alg_values_supported: None,\n            userinfo_encryption_enc_values_supported: None,\n            request_object_signing_alg_values_supported: None,\n            request_object_encryption_alg_values_supported: None,\n            request_object_encryption_enc_values_supported: None,\n            token_endpoint_auth_methods_supported: None,\n            token_endpoint_auth_signing_alg_values_supported: None,\n            display_values_supported: None,\n            claim_types_supported: None,\n            claims_supported: None,\n            service_documentation: None,\n            claims_locales_supported: None,\n            ui_locales_supported: None,\n            claims_parameter_supported: None,\n            request_parameter_supported: None,\n            request_uri_parameter_supported: None,\n            require_request_uri_registration: None,\n            op_policy_uri: None,\n            op_tos_uri: None,\n            additional_metadata,\n        }\n    }\n\n    field_getters_setters![\n        pub self [self] [\"provider metadata value\"] {\n            set_issuer -> issuer[IssuerUrl],\n            set_authorization_endpoint -> authorization_endpoint[AuthUrl],\n            set_token_endpoint -> token_endpoint[Option<TokenUrl>],\n            set_userinfo_endpoint -> userinfo_endpoint[Option<UserInfoUrl>],\n            set_jwks_uri -> jwks_uri[JsonWebKeySetUrl],\n            set_jwks -> jwks[JsonWebKeySet<K>],\n            set_registration_endpoint -> registration_endpoint[Option<RegistrationUrl>],\n            set_scopes_supported -> scopes_supported[Option<Vec<Scope>>],\n            set_response_types_supported -> response_types_supported[Vec<ResponseTypes<RT>>],\n            set_response_modes_supported -> response_modes_supported[Option<Vec<RM>>],\n            set_grant_types_supported -> grant_types_supported[Option<Vec<G>>],\n            set_acr_values_supported\n                -> acr_values_supported[Option<Vec<AuthenticationContextClass>>],\n            set_subject_types_supported -> subject_types_supported[Vec<S>],\n            set_id_token_signing_alg_values_supported\n                -> id_token_signing_alg_values_supported[Vec<K::SigningAlgorithm>],\n            set_id_token_encryption_alg_values_supported\n                -> id_token_encryption_alg_values_supported[Option<Vec<JK>>],\n            set_id_token_encryption_enc_values_supported\n                -> id_token_encryption_enc_values_supported[Option<Vec<JE>>],\n            set_userinfo_signing_alg_values_supported\n                -> userinfo_signing_alg_values_supported[Option<Vec<K::SigningAlgorithm>>],\n            set_userinfo_encryption_alg_values_supported\n                -> userinfo_encryption_alg_values_supported[Option<Vec<JK>>],\n            set_userinfo_encryption_enc_values_supported\n                -> userinfo_encryption_enc_values_supported[Option<Vec<JE>>],\n            set_request_object_signing_alg_values_supported\n                -> request_object_signing_alg_values_supported[Option<Vec<K::SigningAlgorithm>>],\n            set_request_object_encryption_alg_values_supported\n                -> request_object_encryption_alg_values_supported[Option<Vec<JK>>],\n            set_request_object_encryption_enc_values_supported\n                -> request_object_encryption_enc_values_supported[Option<Vec<JE>>],\n            set_token_endpoint_auth_methods_supported\n                -> token_endpoint_auth_methods_supported[Option<Vec<CA>>],\n            set_token_endpoint_auth_signing_alg_values_supported\n                -> token_endpoint_auth_signing_alg_values_supported[Option<Vec<K::SigningAlgorithm>>],\n            set_display_values_supported -> display_values_supported[Option<Vec<AD>>],\n            set_claim_types_supported -> claim_types_supported[Option<Vec<CT>>],\n            set_claims_supported -> claims_supported[Option<Vec<CN>>],\n            set_service_documentation -> service_documentation[Option<ServiceDocUrl>],\n            set_claims_locales_supported -> claims_locales_supported[Option<Vec<LanguageTag>>],\n            set_ui_locales_supported -> ui_locales_supported[Option<Vec<LanguageTag>>],\n            set_claims_parameter_supported -> claims_parameter_supported[Option<bool>],\n            set_request_parameter_supported -> request_parameter_supported[Option<bool>],\n            set_request_uri_parameter_supported -> request_uri_parameter_supported[Option<bool>],\n            set_require_request_uri_registration -> require_request_uri_registration[Option<bool>],\n            set_op_policy_uri -> op_policy_uri[Option<OpPolicyUrl>],\n            set_op_tos_uri -> op_tos_uri[Option<OpTosUrl>],\n        }\n    ];\n\n    /// Fetches the OpenID Connect Discovery document and associated JSON Web Key Set from the\n    /// OpenID Connect Provider.\n    pub fn discover<C>(\n        issuer_url: &IssuerUrl,\n        http_client: &C,\n    ) -> Result<Self, DiscoveryError<<C as SyncHttpClient>::Error>>\n    where\n        C: SyncHttpClient,\n    {\n        let discovery_url = issuer_url\n            .join(CONFIG_URL_SUFFIX)\n            .map_err(DiscoveryError::UrlParse)?;\n\n        http_client\n            .call(\n                Self::discovery_request(discovery_url.clone()).map_err(|err| {\n                    DiscoveryError::Other(format!(\"failed to prepare request: {err}\"))\n                })?,\n            )\n            .map_err(DiscoveryError::Request)\n            .and_then(|http_response| {\n                Self::discovery_response(issuer_url, &discovery_url, http_response)\n            })\n            .and_then(|provider_metadata| {\n                JsonWebKeySet::fetch(provider_metadata.jwks_uri(), http_client).map(|jwks| Self {\n                    jwks,\n                    ..provider_metadata\n                })\n            })\n    }\n\n    /// Asynchronously fetches the OpenID Connect Discovery document and associated JSON Web Key Set\n    /// from the OpenID Connect Provider.\n    pub fn discover_async<'c, C>(\n        issuer_url: IssuerUrl,\n        http_client: &'c C,\n    ) -> impl Future<Output = Result<Self, DiscoveryError<<C as AsyncHttpClient<'c>>::Error>>> + 'c\n    where\n        Self: 'c,\n        C: AsyncHttpClient<'c>,\n    {\n        Box::pin(async move {\n            let discovery_url = issuer_url\n                .join(CONFIG_URL_SUFFIX)\n                .map_err(DiscoveryError::UrlParse)?;\n\n            let provider_metadata = http_client\n                .call(\n                    Self::discovery_request(discovery_url.clone()).map_err(|err| {\n                        DiscoveryError::Other(format!(\"failed to prepare request: {err}\"))\n                    })?,\n                )\n                .await\n                .map_err(DiscoveryError::Request)\n                .and_then(|http_response| {\n                    Self::discovery_response(&issuer_url, &discovery_url, http_response)\n                })?;\n\n            JsonWebKeySet::fetch_async(provider_metadata.jwks_uri(), http_client)\n                .await\n                .map(|jwks| Self {\n                    jwks,\n                    ..provider_metadata\n                })\n        })\n    }\n\n    fn discovery_request(discovery_url: url::Url) -> Result<HttpRequest, http::Error> {\n        http::Request::builder()\n            .uri(discovery_url.to_string())\n            .method(Method::GET)\n            .header(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON))\n            .body(Vec::new())\n    }\n\n    fn discovery_response<RE>(\n        issuer_url: &IssuerUrl,\n        discovery_url: &url::Url,\n        discovery_response: HttpResponse,\n    ) -> Result<Self, DiscoveryError<RE>>\n    where\n        RE: std::error::Error + 'static,\n    {\n        if discovery_response.status() != StatusCode::OK {\n            return Err(DiscoveryError::Response(\n                discovery_response.status(),\n                discovery_response.body().to_owned(),\n                format!(\n                    \"HTTP status code {} at {}\",\n                    discovery_response.status(),\n                    discovery_url\n                ),\n            ));\n        }\n\n        check_content_type(discovery_response.headers(), MIME_TYPE_JSON).map_err(|err_msg| {\n            DiscoveryError::Response(\n                discovery_response.status(),\n                discovery_response.body().to_owned(),\n                err_msg,\n            )\n        })?;\n\n        let provider_metadata = serde_path_to_error::deserialize::<_, Self>(\n            &mut serde_json::Deserializer::from_slice(discovery_response.body()),\n        )\n        .map_err(DiscoveryError::Parse)?;\n\n        if provider_metadata.issuer() != issuer_url {\n            Err(DiscoveryError::Validation(format!(\n                \"unexpected issuer URI `{}` (expected `{}`)\",\n                provider_metadata.issuer().as_str(),\n                issuer_url.as_str()\n            )))\n        } else {\n            Ok(provider_metadata)\n        }\n    }\n\n    /// Returns additional provider metadata fields.\n    pub fn additional_metadata(&self) -> &A {\n        &self.additional_metadata\n    }\n    /// Returns mutable additional provider metadata fields.\n    pub fn additional_metadata_mut(&mut self) -> &mut A {\n        &mut self.additional_metadata\n    }\n}\n\n/// Error retrieving provider metadata.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum DiscoveryError<RE>\nwhere\n    RE: std::error::Error + 'static,\n{\n    /// An unexpected error occurred.\n    #[error(\"Other error: {0}\")]\n    Other(String),\n    /// Failed to parse server response.\n    #[error(\"Failed to parse server response\")]\n    Parse(#[source] serde_path_to_error::Error<serde_json::Error>),\n    /// An error occurred while sending the request or receiving the response (e.g., network\n    /// connectivity failed).\n    #[error(\"Request failed\")]\n    Request(#[source] RE),\n    /// Server returned an invalid response.\n    #[error(\"Server returned invalid response: {2}\")]\n    Response(StatusCode, Vec<u8>, String),\n    /// Failed to parse discovery URL from issuer URL.\n    #[error(\"Failed to parse URL\")]\n    UrlParse(#[source] url::ParseError),\n    /// Failed to validate provider metadata.\n    #[error(\"Validation error: {0}\")]\n    Validation(String),\n}\n"
  },
  {
    "path": "src/discovery/tests.rs",
    "content": "use crate::core::{\n    CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClientAuthMethod, CoreGrantType,\n    CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJwsSigningAlgorithm,\n    CoreProviderMetadata, CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType,\n};\nuse crate::{\n    AuthUrl, AuthenticationContextClass, IssuerUrl, JsonWebKeySetUrl, LanguageTag, OpPolicyUrl,\n    OpTosUrl, RegistrationUrl, ResponseTypes, Scope, ServiceDocUrl, TokenUrl, UserInfoUrl,\n};\n\n#[test]\nfn test_discovery_deserialization() {\n    // Fetched from: https://rp.certification.openid.net:8080/openidconnect-rs/\n    //     rp-response_type-code/.well-known/openid-configuration\n    let json_response_standard = \"\\\n            \\\"issuer\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\",\\\n            \\\"authorization_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/authorization\\\",\\\n            \\\"token_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/token\\\",\\\n            \\\"userinfo_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/userinfo\\\",\\\n            \\\"jwks_uri\\\":\\\"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\\\",\\\n            \\\"registration_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/registration\\\",\\\n            \\\"scopes_supported\\\":[\\\n               \\\"email\\\",\\\n               \\\"phone\\\",\\\n               \\\"profile\\\",\\\n               \\\"openid\\\",\\\n               \\\"address\\\",\\\n               \\\"offline_access\\\",\\\n               \\\"openid\\\"\\\n            ],\\\n            \\\"response_types_supported\\\":[\\\n               \\\"code\\\"\\\n            ],\\\n            \\\"response_modes_supported\\\":[\\\n               \\\"query\\\",\\\n               \\\"fragment\\\",\\\n               \\\"form_post\\\"\\\n            ],\\\n            \\\"grant_types_supported\\\":[\\\n               \\\"authorization_code\\\",\\\n               \\\"implicit\\\",\\\n               \\\"urn:ietf:params:oauth:grant-type:jwt-bearer\\\",\\\n               \\\"refresh_token\\\"\\\n            ],\\\n            \\\"acr_values_supported\\\":[\\\n               \\\"PASSWORD\\\"\\\n            ],\\\n            \\\"subject_types_supported\\\":[\\\n               \\\"public\\\",\\\n               \\\"pairwise\\\"\\\n            ],\\\n            \\\"id_token_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"RS384\\\",\\\n               \\\"RS512\\\",\\\n               \\\"ES256\\\",\\\n               \\\"ES384\\\",\\\n               \\\"ES512\\\",\\\n               \\\"HS256\\\",\\\n               \\\"HS384\\\",\\\n               \\\"HS512\\\",\\\n               \\\"PS256\\\",\\\n               \\\"PS384\\\",\\\n               \\\"PS512\\\",\\\n               \\\"none\\\"\\\n            ],\\\n            \\\"id_token_encryption_alg_values_supported\\\":[\\\n               \\\"RSA1_5\\\",\\\n               \\\"RSA-OAEP\\\",\\\n               \\\"RSA-OAEP-256\\\",\\\n               \\\"A128KW\\\",\\\n               \\\"A192KW\\\",\\\n               \\\"A256KW\\\",\\\n               \\\"ECDH-ES\\\",\\\n               \\\"ECDH-ES+A128KW\\\",\\\n               \\\"ECDH-ES+A192KW\\\",\\\n               \\\"ECDH-ES+A256KW\\\"\\\n            ],\\\n            \\\"id_token_encryption_enc_values_supported\\\":[\\\n               \\\"A128CBC-HS256\\\",\\\n               \\\"A192CBC-HS384\\\",\\\n               \\\"A256CBC-HS512\\\",\\\n               \\\"A128GCM\\\",\\\n               \\\"A192GCM\\\",\\\n               \\\"A256GCM\\\"\\\n            ],\\\n            \\\"userinfo_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"RS384\\\",\\\n               \\\"RS512\\\",\\\n               \\\"ES256\\\",\\\n               \\\"ES384\\\",\\\n               \\\"ES512\\\",\\\n               \\\"HS256\\\",\\\n               \\\"HS384\\\",\\\n               \\\"HS512\\\",\\\n               \\\"PS256\\\",\\\n               \\\"PS384\\\",\\\n               \\\"PS512\\\",\\\n               \\\"none\\\"\\\n            ],\\\n            \\\"userinfo_encryption_alg_values_supported\\\":[\\\n               \\\"RSA1_5\\\",\\\n               \\\"RSA-OAEP\\\",\\\n               \\\"RSA-OAEP-256\\\",\\\n               \\\"A128KW\\\",\\\n               \\\"A192KW\\\",\\\n               \\\"A256KW\\\",\\\n               \\\"ECDH-ES\\\",\\\n               \\\"ECDH-ES+A128KW\\\",\\\n               \\\"ECDH-ES+A192KW\\\",\\\n               \\\"ECDH-ES+A256KW\\\"\\\n            ],\\\n            \\\"userinfo_encryption_enc_values_supported\\\":[\\\n               \\\"A128CBC-HS256\\\",\\\n               \\\"A192CBC-HS384\\\",\\\n               \\\"A256CBC-HS512\\\",\\\n               \\\"A128GCM\\\",\\\n               \\\"A192GCM\\\",\\\n               \\\"A256GCM\\\"\\\n            ],\\\n            \\\"request_object_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"RS384\\\",\\\n               \\\"RS512\\\",\\\n               \\\"ES256\\\",\\\n               \\\"ES384\\\",\\\n               \\\"ES512\\\",\\\n               \\\"HS256\\\",\\\n               \\\"HS384\\\",\\\n               \\\"HS512\\\",\\\n               \\\"PS256\\\",\\\n               \\\"PS384\\\",\\\n               \\\"PS512\\\",\\\n               \\\"none\\\"\\\n            ],\\\n            \\\"request_object_encryption_alg_values_supported\\\":[\\\n               \\\"RSA1_5\\\",\\\n               \\\"RSA-OAEP\\\",\\\n               \\\"RSA-OAEP-256\\\",\\\n               \\\"A128KW\\\",\\\n               \\\"A192KW\\\",\\\n               \\\"A256KW\\\",\\\n               \\\"ECDH-ES\\\",\\\n               \\\"ECDH-ES+A128KW\\\",\\\n               \\\"ECDH-ES+A192KW\\\",\\\n               \\\"ECDH-ES+A256KW\\\"\\\n            ],\\\n            \\\"request_object_encryption_enc_values_supported\\\":[\\\n               \\\"A128CBC-HS256\\\",\\\n               \\\"A192CBC-HS384\\\",\\\n               \\\"A256CBC-HS512\\\",\\\n               \\\"A128GCM\\\",\\\n               \\\"A192GCM\\\",\\\n               \\\"A256GCM\\\"\\\n            ],\\\n            \\\"token_endpoint_auth_methods_supported\\\":[\\\n               \\\"client_secret_post\\\",\\\n               \\\"client_secret_basic\\\",\\\n               \\\"client_secret_jwt\\\",\\\n               \\\"private_key_jwt\\\"\\\n            ],\\\n            \\\"token_endpoint_auth_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"RS384\\\",\\\n               \\\"RS512\\\",\\\n               \\\"ES256\\\",\\\n               \\\"ES384\\\",\\\n               \\\"ES512\\\",\\\n               \\\"HS256\\\",\\\n               \\\"HS384\\\",\\\n               \\\"HS512\\\",\\\n               \\\"PS256\\\",\\\n               \\\"PS384\\\",\\\n               \\\"PS512\\\"\\\n            ],\\\n            \\\"claim_types_supported\\\":[\\\n               \\\"normal\\\",\\\n               \\\"aggregated\\\",\\\n               \\\"distributed\\\"\\\n            ],\\\n            \\\"claims_supported\\\":[\\\n               \\\"name\\\",\\\n               \\\"given_name\\\",\\\n               \\\"middle_name\\\",\\\n               \\\"picture\\\",\\\n               \\\"email_verified\\\",\\\n               \\\"birthdate\\\",\\\n               \\\"sub\\\",\\\n               \\\"address\\\",\\\n               \\\"zoneinfo\\\",\\\n               \\\"email\\\",\\\n               \\\"gender\\\",\\\n               \\\"preferred_username\\\",\\\n               \\\"family_name\\\",\\\n               \\\"website\\\",\\\n               \\\"profile\\\",\\\n               \\\"phone_number_verified\\\",\\\n               \\\"nickname\\\",\\\n               \\\"updated_at\\\",\\\n               \\\"phone_number\\\",\\\n               \\\"locale\\\"\\\n            ],\\\n            \\\"claims_parameter_supported\\\":true,\\\n            \\\"request_parameter_supported\\\":true,\\\n            \\\"request_uri_parameter_supported\\\":true,\\\n            \\\"require_request_uri_registration\\\":true\";\n\n    let json_response = format!(\n            \"{{{},{}}}\",\n            json_response_standard,\n            \"\\\"end_session_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\\\",\\\n            \\\"version\\\":\\\"3.0\\\"\"\n        );\n\n    let all_signing_algs = vec![\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n        CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n        CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n        CoreJwsSigningAlgorithm::EcdsaP521Sha512,\n        CoreJwsSigningAlgorithm::HmacSha256,\n        CoreJwsSigningAlgorithm::HmacSha384,\n        CoreJwsSigningAlgorithm::HmacSha512,\n        CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n        CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n        CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n        CoreJwsSigningAlgorithm::None,\n    ];\n    let all_encryption_algs = vec![\n        CoreJweKeyManagementAlgorithm::RsaPkcs1V15,\n        CoreJweKeyManagementAlgorithm::RsaOaep,\n        CoreJweKeyManagementAlgorithm::RsaOaepSha256,\n        CoreJweKeyManagementAlgorithm::AesKeyWrap128,\n        CoreJweKeyManagementAlgorithm::AesKeyWrap192,\n        CoreJweKeyManagementAlgorithm::AesKeyWrap256,\n        CoreJweKeyManagementAlgorithm::EcdhEs,\n        CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap128,\n        CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap192,\n        CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap256,\n    ];\n    let new_provider_metadata = CoreProviderMetadata::new(\n        IssuerUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\"\n                .to_string(),\n        )\n        .unwrap(),\n        AuthUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/\\\n                 rp-response_type-code/authorization\"\n                .to_string(),\n        )\n        .unwrap(),\n        JsonWebKeySetUrl::new(\n            \"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\"\n                .to_string(),\n        )\n        .unwrap(),\n        vec![ResponseTypes::new(vec![CoreResponseType::Code])],\n        vec![\n            CoreSubjectIdentifierType::Public,\n            CoreSubjectIdentifierType::Pairwise,\n        ],\n        all_signing_algs.clone(),\n        Default::default(),\n    )\n    .set_request_object_signing_alg_values_supported(Some(all_signing_algs.clone()))\n    .set_token_endpoint_auth_signing_alg_values_supported(Some(vec![\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n        CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n        CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n        CoreJwsSigningAlgorithm::EcdsaP521Sha512,\n        CoreJwsSigningAlgorithm::HmacSha256,\n        CoreJwsSigningAlgorithm::HmacSha384,\n        CoreJwsSigningAlgorithm::HmacSha512,\n        CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n        CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n        CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n    ]))\n    .set_scopes_supported(Some(vec![\n        Scope::new(\"email\".to_string()),\n        Scope::new(\"phone\".to_string()),\n        Scope::new(\"profile\".to_string()),\n        Scope::new(\"openid\".to_string()),\n        Scope::new(\"address\".to_string()),\n        Scope::new(\"offline_access\".to_string()),\n        Scope::new(\"openid\".to_string()),\n    ]))\n    .set_userinfo_signing_alg_values_supported(Some(all_signing_algs))\n    .set_id_token_encryption_enc_values_supported(Some(vec![\n        CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256,\n        CoreJweContentEncryptionAlgorithm::Aes192CbcHmacSha384,\n        CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512,\n        CoreJweContentEncryptionAlgorithm::Aes128Gcm,\n        CoreJweContentEncryptionAlgorithm::Aes192Gcm,\n        CoreJweContentEncryptionAlgorithm::Aes256Gcm,\n    ]))\n    .set_grant_types_supported(Some(vec![\n        CoreGrantType::AuthorizationCode,\n        CoreGrantType::Implicit,\n        CoreGrantType::JwtBearer,\n        CoreGrantType::RefreshToken,\n    ]))\n    .set_response_modes_supported(Some(vec![\n        CoreResponseMode::Query,\n        CoreResponseMode::Fragment,\n        CoreResponseMode::FormPost,\n    ]))\n    .set_require_request_uri_registration(Some(true))\n    .set_registration_endpoint(Some(\n        RegistrationUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/\\\n                 rp-response_type-code/registration\"\n                .to_string(),\n        )\n        .unwrap(),\n    ))\n    .set_claims_parameter_supported(Some(true))\n    .set_request_object_encryption_enc_values_supported(Some(vec![\n        CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256,\n        CoreJweContentEncryptionAlgorithm::Aes192CbcHmacSha384,\n        CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512,\n        CoreJweContentEncryptionAlgorithm::Aes128Gcm,\n        CoreJweContentEncryptionAlgorithm::Aes192Gcm,\n        CoreJweContentEncryptionAlgorithm::Aes256Gcm,\n    ]))\n    .set_userinfo_endpoint(Some(\n        UserInfoUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/\\\n                 rp-response_type-code/userinfo\"\n                .to_string(),\n        )\n        .unwrap(),\n    ))\n    .set_token_endpoint_auth_methods_supported(Some(vec![\n        CoreClientAuthMethod::ClientSecretPost,\n        CoreClientAuthMethod::ClientSecretBasic,\n        CoreClientAuthMethod::ClientSecretJwt,\n        CoreClientAuthMethod::PrivateKeyJwt,\n    ]))\n    .set_claims_supported(Some(\n        vec![\n            \"name\",\n            \"given_name\",\n            \"middle_name\",\n            \"picture\",\n            \"email_verified\",\n            \"birthdate\",\n            \"sub\",\n            \"address\",\n            \"zoneinfo\",\n            \"email\",\n            \"gender\",\n            \"preferred_username\",\n            \"family_name\",\n            \"website\",\n            \"profile\",\n            \"phone_number_verified\",\n            \"nickname\",\n            \"updated_at\",\n            \"phone_number\",\n            \"locale\",\n        ]\n        .iter()\n        .map(|claim| CoreClaimName::new((*claim).to_string()))\n        .collect(),\n    ))\n    .set_request_object_encryption_alg_values_supported(Some(all_encryption_algs.clone()))\n    .set_claim_types_supported(Some(vec![\n        CoreClaimType::Normal,\n        CoreClaimType::Aggregated,\n        CoreClaimType::Distributed,\n    ]))\n    .set_request_uri_parameter_supported(Some(true))\n    .set_request_parameter_supported(Some(true))\n    .set_token_endpoint(Some(\n        TokenUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/\\\n                 rp-response_type-code/token\"\n                .to_string(),\n        )\n        .unwrap(),\n    ))\n    .set_id_token_encryption_alg_values_supported(Some(all_encryption_algs.clone()))\n    .set_userinfo_encryption_alg_values_supported(Some(all_encryption_algs))\n    .set_userinfo_encryption_enc_values_supported(Some(vec![\n        CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256,\n        CoreJweContentEncryptionAlgorithm::Aes192CbcHmacSha384,\n        CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512,\n        CoreJweContentEncryptionAlgorithm::Aes128Gcm,\n        CoreJweContentEncryptionAlgorithm::Aes192Gcm,\n        CoreJweContentEncryptionAlgorithm::Aes256Gcm,\n    ]))\n    .set_acr_values_supported(Some(vec![AuthenticationContextClass::new(\n        \"PASSWORD\".to_string(),\n    )]));\n\n    let provider_metadata: CoreProviderMetadata = serde_json::from_str(&json_response).unwrap();\n    assert_eq!(provider_metadata, new_provider_metadata);\n\n    let serialized = serde_json::to_string(&provider_metadata).unwrap();\n    assert_eq!(serialized, format!(\"{{{}}}\", json_response_standard));\n\n    assert_eq!(\n        IssuerUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.issuer()\n    );\n    assert_eq!(\n        AuthUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\n                 /authorization\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.authorization_endpoint()\n    );\n    assert_eq!(\n        Some(\n            &TokenUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs\\\n                     /rp-response_type-code/token\"\n                    .to_string()\n            )\n            .unwrap()\n        ),\n        provider_metadata.token_endpoint()\n    );\n    assert_eq!(\n        Some(\n            &UserInfoUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs\\\n                     /rp-response_type-code/userinfo\"\n                    .to_string()\n            )\n            .unwrap()\n        ),\n        provider_metadata.userinfo_endpoint()\n    );\n    assert_eq!(\n        &JsonWebKeySetUrl::new(\n            \"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\"\n                .to_string()\n        )\n        .unwrap(),\n        provider_metadata.jwks_uri()\n    );\n    assert_eq!(\n        Some(\n            &RegistrationUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs\\\n                     /rp-response_type-code/registration\"\n                    .to_string()\n            )\n            .unwrap()\n        ),\n        provider_metadata.registration_endpoint()\n    );\n    assert_eq!(\n        Some(\n            &[\n                \"email\",\n                \"phone\",\n                \"profile\",\n                \"openid\",\n                \"address\",\n                \"offline_access\",\n                \"openid\",\n            ]\n            .iter()\n            .map(|s| (*s).to_string())\n            .map(Scope::new)\n            .collect::<Vec<_>>()\n        ),\n        provider_metadata.scopes_supported()\n    );\n    assert_eq!(\n        vec![ResponseTypes::new(vec![CoreResponseType::Code])],\n        *provider_metadata.response_types_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreResponseMode::Query,\n            CoreResponseMode::Fragment,\n            CoreResponseMode::FormPost,\n        ]),\n        provider_metadata.response_modes_supported()\n    );\n    assert_eq!(\n        Some(\n            &vec![\n                CoreGrantType::AuthorizationCode,\n                CoreGrantType::Implicit,\n                CoreGrantType::JwtBearer,\n                CoreGrantType::RefreshToken,\n            ]\n            .into_iter()\n            .collect::<Vec<_>>()\n        ),\n        provider_metadata.grant_types_supported()\n    );\n    assert_eq!(\n        Some(&vec![AuthenticationContextClass::new(\n            \"PASSWORD\".to_string(),\n        )]),\n        provider_metadata.acr_values_supported()\n    );\n    assert_eq!(\n        vec![\n            CoreSubjectIdentifierType::Public,\n            CoreSubjectIdentifierType::Pairwise,\n        ],\n        *provider_metadata.subject_types_supported()\n    );\n    assert_eq!(\n        vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n            CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n            CoreJwsSigningAlgorithm::EcdsaP521Sha512,\n            CoreJwsSigningAlgorithm::HmacSha256,\n            CoreJwsSigningAlgorithm::HmacSha384,\n            CoreJwsSigningAlgorithm::HmacSha512,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n            CoreJwsSigningAlgorithm::None,\n        ],\n        *provider_metadata.id_token_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweKeyManagementAlgorithm::RsaPkcs1V15,\n            CoreJweKeyManagementAlgorithm::RsaOaep,\n            CoreJweKeyManagementAlgorithm::RsaOaepSha256,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap128,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap192,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap256,\n            CoreJweKeyManagementAlgorithm::EcdhEs,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap128,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap192,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap256,\n        ]),\n        provider_metadata.id_token_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256,\n            CoreJweContentEncryptionAlgorithm::Aes192CbcHmacSha384,\n            CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512,\n            CoreJweContentEncryptionAlgorithm::Aes128Gcm,\n            CoreJweContentEncryptionAlgorithm::Aes192Gcm,\n            CoreJweContentEncryptionAlgorithm::Aes256Gcm,\n        ]),\n        provider_metadata.id_token_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n            CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n            CoreJwsSigningAlgorithm::EcdsaP521Sha512,\n            CoreJwsSigningAlgorithm::HmacSha256,\n            CoreJwsSigningAlgorithm::HmacSha384,\n            CoreJwsSigningAlgorithm::HmacSha512,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n            CoreJwsSigningAlgorithm::None,\n        ]),\n        provider_metadata.userinfo_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweKeyManagementAlgorithm::RsaPkcs1V15,\n            CoreJweKeyManagementAlgorithm::RsaOaep,\n            CoreJweKeyManagementAlgorithm::RsaOaepSha256,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap128,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap192,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap256,\n            CoreJweKeyManagementAlgorithm::EcdhEs,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap128,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap192,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap256,\n        ]),\n        provider_metadata.userinfo_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256,\n            CoreJweContentEncryptionAlgorithm::Aes192CbcHmacSha384,\n            CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512,\n            CoreJweContentEncryptionAlgorithm::Aes128Gcm,\n            CoreJweContentEncryptionAlgorithm::Aes192Gcm,\n            CoreJweContentEncryptionAlgorithm::Aes256Gcm,\n        ]),\n        provider_metadata.userinfo_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n            CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n            CoreJwsSigningAlgorithm::EcdsaP521Sha512,\n            CoreJwsSigningAlgorithm::HmacSha256,\n            CoreJwsSigningAlgorithm::HmacSha384,\n            CoreJwsSigningAlgorithm::HmacSha512,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n            CoreJwsSigningAlgorithm::None,\n        ]),\n        provider_metadata.request_object_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweKeyManagementAlgorithm::RsaPkcs1V15,\n            CoreJweKeyManagementAlgorithm::RsaOaep,\n            CoreJweKeyManagementAlgorithm::RsaOaepSha256,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap128,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap192,\n            CoreJweKeyManagementAlgorithm::AesKeyWrap256,\n            CoreJweKeyManagementAlgorithm::EcdhEs,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap128,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap192,\n            CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap256,\n        ]),\n        provider_metadata.request_object_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256,\n            CoreJweContentEncryptionAlgorithm::Aes192CbcHmacSha384,\n            CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512,\n            CoreJweContentEncryptionAlgorithm::Aes128Gcm,\n            CoreJweContentEncryptionAlgorithm::Aes192Gcm,\n            CoreJweContentEncryptionAlgorithm::Aes256Gcm,\n        ]),\n        provider_metadata.request_object_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreClientAuthMethod::ClientSecretPost,\n            CoreClientAuthMethod::ClientSecretBasic,\n            CoreClientAuthMethod::ClientSecretJwt,\n            CoreClientAuthMethod::PrivateKeyJwt,\n        ]),\n        provider_metadata.token_endpoint_auth_methods_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384,\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha512,\n            CoreJwsSigningAlgorithm::EcdsaP256Sha256,\n            CoreJwsSigningAlgorithm::EcdsaP384Sha384,\n            CoreJwsSigningAlgorithm::EcdsaP521Sha512,\n            CoreJwsSigningAlgorithm::HmacSha256,\n            CoreJwsSigningAlgorithm::HmacSha384,\n            CoreJwsSigningAlgorithm::HmacSha512,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha256,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha384,\n            CoreJwsSigningAlgorithm::RsaSsaPssSha512,\n        ]),\n        provider_metadata.token_endpoint_auth_signing_alg_values_supported()\n    );\n    assert_eq!(None, provider_metadata.display_values_supported());\n    assert_eq!(\n        Some(&vec![\n            CoreClaimType::Normal,\n            CoreClaimType::Aggregated,\n            CoreClaimType::Distributed,\n        ]),\n        provider_metadata.claim_types_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreClaimName::new(\"name\".to_string()),\n            CoreClaimName::new(\"given_name\".to_string()),\n            CoreClaimName::new(\"middle_name\".to_string()),\n            CoreClaimName::new(\"picture\".to_string()),\n            CoreClaimName::new(\"email_verified\".to_string()),\n            CoreClaimName::new(\"birthdate\".to_string()),\n            CoreClaimName::new(\"sub\".to_string()),\n            CoreClaimName::new(\"address\".to_string()),\n            CoreClaimName::new(\"zoneinfo\".to_string()),\n            CoreClaimName::new(\"email\".to_string()),\n            CoreClaimName::new(\"gender\".to_string()),\n            CoreClaimName::new(\"preferred_username\".to_string()),\n            CoreClaimName::new(\"family_name\".to_string()),\n            CoreClaimName::new(\"website\".to_string()),\n            CoreClaimName::new(\"profile\".to_string()),\n            CoreClaimName::new(\"phone_number_verified\".to_string()),\n            CoreClaimName::new(\"nickname\".to_string()),\n            CoreClaimName::new(\"updated_at\".to_string()),\n            CoreClaimName::new(\"phone_number\".to_string()),\n            CoreClaimName::new(\"locale\".to_string()),\n        ]),\n        provider_metadata.claims_supported()\n    );\n    assert_eq!(None, provider_metadata.service_documentation());\n    assert_eq!(None, provider_metadata.claims_locales_supported());\n    assert_eq!(None, provider_metadata.ui_locales_supported());\n    assert_eq!(Some(true), provider_metadata.claims_parameter_supported());\n    assert_eq!(Some(true), provider_metadata.request_parameter_supported());\n    assert_eq!(\n        Some(true),\n        provider_metadata.request_uri_parameter_supported()\n    );\n    assert_eq!(\n        Some(true),\n        provider_metadata.require_request_uri_registration()\n    );\n    assert_eq!(None, provider_metadata.op_policy_uri());\n    assert_eq!(None, provider_metadata.op_tos_uri());\n\n    // Note: the following fields provided by the response above are not part of the OpenID\n    // Connect Discovery 1.0 spec:\n    // - end_session_endpoint\n    // - version\n\n    let serialized_json = serde_json::to_string(&provider_metadata).unwrap();\n\n    let redeserialized_metadata: CoreProviderMetadata =\n        serde_json::from_str(&serialized_json).unwrap();\n    assert_eq!(provider_metadata, redeserialized_metadata);\n}\n\n// Tests the fields missing from the example response in test_discovery_deserialization().\n#[test]\nfn test_discovery_deserialization_other_fields() {\n    let json_response = \"{\n        \\\"issuer\\\" : \\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\",\n        \\\"authorization_endpoint\\\" : \\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/authorization\\\",\n        \\\"jwks_uri\\\" : \\\"https://rp.certification.openid.net:8080/static/jwks_oMXD5waO08Q1GEnv.json\\\",\n        \\\"response_types_supported\\\" : [\n           \\\"code\\\",\n           \\\"code token\\\",\n           \\\"code id_token\\\",\n           \\\"id_token token\\\",\n           \\\"code id_token token\\\",\n           \\\"token id_token\\\",\n           \\\"token id_token code\\\",\n           \\\"id_token\\\",\n           \\\"token\\\"\n        ],\n        \\\"subject_types_supported\\\" : [\n           \\\"public\\\",\n           \\\"pairwise\\\"\n        ],\n        \\\"id_token_signing_alg_values_supported\\\" : [\n           \\\"HS256\\\",\n           \\\"HS384\\\",\n           \\\"HS512\\\"\n        ],\n        \\\"display_values_supported\\\" : [\n           \\\"page\\\",\n           \\\"popup\\\",\n           \\\"touch\\\",\n           \\\"wap\\\"\n        ],\n        \\\"service_documentation\\\" : \\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/documentation\\\",\n        \\\"claims_locales_supported\\\" : [\n           \\\"de\\\",\n           \\\"fr\\\",\n           \\\"de-CH-1901\\\"\n        ],\n        \\\"ui_locales_supported\\\" : [\n           \\\"ja\\\",\n           \\\"sr-Latn\\\",\n           \\\"yue-HK\\\"\n        ],\n        \\\"op_policy_uri\\\" : \\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/op_policy\\\",\n        \\\"op_tos_uri\\\" : \\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/op_tos\\\"\n    }\";\n\n    let provider_metadata: CoreProviderMetadata = serde_json::from_str(json_response).unwrap();\n\n    assert_eq!(\n        IssuerUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.issuer()\n    );\n    assert_eq!(\n        AuthUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\n                 /authorization\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.authorization_endpoint()\n    );\n    assert_eq!(None, provider_metadata.token_endpoint());\n    assert_eq!(None, provider_metadata.userinfo_endpoint());\n    assert_eq!(\n        JsonWebKeySetUrl::new(\n            \"https://rp.certification.openid.net:8080/static/jwks_oMXD5waO08Q1GEnv.json\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.jwks_uri()\n    );\n    assert_eq!(None, provider_metadata.registration_endpoint());\n    assert_eq!(None, provider_metadata.scopes_supported());\n    assert_eq!(\n        vec![\n            ResponseTypes::new(vec![CoreResponseType::Code]),\n            ResponseTypes::new(vec![CoreResponseType::Code, CoreResponseType::Token]),\n            ResponseTypes::new(vec![CoreResponseType::Code, CoreResponseType::IdToken]),\n            ResponseTypes::new(vec![CoreResponseType::IdToken, CoreResponseType::Token]),\n            ResponseTypes::new(vec![\n                CoreResponseType::Code,\n                CoreResponseType::IdToken,\n                CoreResponseType::Token,\n            ]),\n            ResponseTypes::new(vec![CoreResponseType::Token, CoreResponseType::IdToken]),\n            ResponseTypes::new(vec![\n                CoreResponseType::Token,\n                CoreResponseType::IdToken,\n                CoreResponseType::Code,\n            ]),\n            ResponseTypes::new(vec![CoreResponseType::IdToken]),\n            ResponseTypes::new(vec![CoreResponseType::Token]),\n        ],\n        *provider_metadata.response_types_supported()\n    );\n    assert_eq!(None, provider_metadata.response_modes_supported());\n    assert_eq!(None, provider_metadata.grant_types_supported());\n    assert_eq!(None, provider_metadata.acr_values_supported());\n    assert_eq!(\n        vec![\n            CoreSubjectIdentifierType::Public,\n            CoreSubjectIdentifierType::Pairwise,\n        ],\n        *provider_metadata.subject_types_supported()\n    );\n    assert_eq!(\n        vec![\n            CoreJwsSigningAlgorithm::HmacSha256,\n            CoreJwsSigningAlgorithm::HmacSha384,\n            CoreJwsSigningAlgorithm::HmacSha512,\n        ],\n        *provider_metadata.id_token_signing_alg_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.id_token_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.id_token_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.userinfo_signing_alg_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.userinfo_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.userinfo_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.request_object_signing_alg_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.request_object_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.request_object_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.token_endpoint_auth_methods_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.token_endpoint_auth_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreAuthDisplay::Page,\n            CoreAuthDisplay::Popup,\n            CoreAuthDisplay::Touch,\n            CoreAuthDisplay::Wap,\n        ]),\n        provider_metadata.display_values_supported()\n    );\n    assert_eq!(None, provider_metadata.claim_types_supported());\n    assert_eq!(None, provider_metadata.claims_supported());\n\n    assert_eq!(\n        Some(\n            &ServiceDocUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\n                 /documentation\"\n                    .to_string()\n            )\n            .unwrap()\n        ),\n        provider_metadata.service_documentation()\n    );\n    assert_eq!(\n        Some(&vec![\n            LanguageTag::new(\"de\".to_string()),\n            LanguageTag::new(\"fr\".to_string()),\n            LanguageTag::new(\"de-CH-1901\".to_string()),\n        ]),\n        provider_metadata.claims_locales_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            LanguageTag::new(\"ja\".to_string()),\n            LanguageTag::new(\"sr-Latn\".to_string()),\n            LanguageTag::new(\"yue-HK\".to_string()),\n        ]),\n        provider_metadata.ui_locales_supported()\n    );\n    assert_eq!(None, provider_metadata.claims_parameter_supported());\n    assert_eq!(None, provider_metadata.request_parameter_supported());\n    assert_eq!(None, provider_metadata.request_uri_parameter_supported());\n    assert_eq!(None, provider_metadata.require_request_uri_registration());\n    assert_eq!(\n        Some(\n            &OpPolicyUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\n                 /op_policy\"\n                    .to_string()\n            )\n            .unwrap()\n        ),\n        provider_metadata.op_policy_uri()\n    );\n    assert_eq!(\n        Some(\n            &OpTosUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\n                 /op_tos\"\n                    .to_string()\n            )\n            .unwrap()\n        ),\n        provider_metadata.op_tos_uri()\n    );\n\n    let serialized_json = serde_json::to_string(&provider_metadata).unwrap();\n\n    let redeserialized_metadata: CoreProviderMetadata =\n        serde_json::from_str(&serialized_json).unwrap();\n    assert_eq!(provider_metadata, redeserialized_metadata);\n}\n\n// Tests that we ignore enum values that the OIDC provider supports but that the client does\n// not (which trigger serde deserialization errors while parsing the provider metadata).\n#[test]\nfn test_unsupported_enum_values() {\n    let json_response = \"{\\\n            \\\"issuer\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\",\\\n            \\\"authorization_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/authorization\\\",\\\n            \\\"jwks_uri\\\":\\\"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\\\",\\\n            \\\"response_types_supported\\\":[\\\n               \\\"code\\\"\\\n            ],\\\n            \\\"subject_types_supported\\\":[\\\n               \\\"public\\\",\\\n               \\\"pairwise\\\"\\\n            ],\\\n            \\\"id_token_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"MAGIC\\\",\\\n               \\\"none\\\"\\\n            ],\\\n            \\\"id_token_encryption_alg_values_supported\\\":[\\\n               \\\"RSA1_5\\\",\\\n               \\\"MAGIC\\\"\\\n            ],\\\n            \\\"id_token_encryption_enc_values_supported\\\":[\\\n               \\\"A128CBC-HS256\\\",\\\n               \\\"MAGIC\\\"\\\n            ],\\\n            \\\"userinfo_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"MAGIC\\\",\\\n               \\\"none\\\"\\\n            ],\\\n            \\\"userinfo_encryption_alg_values_supported\\\":[\\\n               \\\"RSA1_5\\\",\\\n               \\\"MAGIC\\\"\\\n            ],\\\n            \\\"userinfo_encryption_enc_values_supported\\\":[\\\n               \\\"A128CBC-HS256\\\",\\\n               \\\"MAGIC\\\"\\\n            ],\\\n            \\\"request_object_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"MAGIC\\\",\\\n               \\\"none\\\"\\\n            ],\\\n            \\\"request_object_encryption_alg_values_supported\\\":[\\\n               \\\"RSA1_5\\\",\\\n               \\\"MAGIC\\\"\\\n            ],\\\n            \\\"request_object_encryption_enc_values_supported\\\":[\\\n               \\\"A128CBC-HS256\\\",\\\n               \\\"MAGIC\\\"\\\n            ],\\\n            \\\"token_endpoint_auth_signing_alg_values_supported\\\":[\\\n               \\\"RS256\\\",\\\n               \\\"MAGIC\\\",\\\n               \\\"none\\\"\\\n            ]\\\n        }\";\n\n    let provider_metadata: CoreProviderMetadata = serde_json::from_str(json_response).unwrap();\n\n    assert_eq!(\n        IssuerUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.issuer()\n    );\n    assert_eq!(\n        AuthUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\n                 /authorization\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.authorization_endpoint()\n    );\n    assert_eq!(None, provider_metadata.token_endpoint());\n    assert_eq!(None, provider_metadata.userinfo_endpoint());\n    assert_eq!(\n        JsonWebKeySetUrl::new(\n            \"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\"\n                .to_string()\n        )\n        .unwrap(),\n        *provider_metadata.jwks_uri()\n    );\n    assert_eq!(None, provider_metadata.registration_endpoint());\n    assert_eq!(None, provider_metadata.scopes_supported());\n    assert_eq!(\n        vec![ResponseTypes::new(vec![CoreResponseType::Code])],\n        *provider_metadata.response_types_supported()\n    );\n    assert_eq!(None, provider_metadata.response_modes_supported());\n    assert_eq!(None, provider_metadata.grant_types_supported());\n    assert_eq!(None, provider_metadata.acr_values_supported());\n    assert_eq!(\n        vec![\n            CoreSubjectIdentifierType::Public,\n            CoreSubjectIdentifierType::Pairwise,\n        ],\n        *provider_metadata.subject_types_supported()\n    );\n    assert_eq!(\n        vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::None,\n        ],\n        *provider_metadata.id_token_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![CoreJweKeyManagementAlgorithm::RsaPkcs1V15]),\n        provider_metadata.id_token_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256\n        ]),\n        provider_metadata.id_token_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::None,\n        ]),\n        provider_metadata.userinfo_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![CoreJweKeyManagementAlgorithm::RsaPkcs1V15]),\n        provider_metadata.userinfo_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256\n        ]),\n        provider_metadata.userinfo_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::None,\n        ]),\n        provider_metadata.request_object_signing_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![CoreJweKeyManagementAlgorithm::RsaPkcs1V15]),\n        provider_metadata.request_object_encryption_alg_values_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256\n        ]),\n        provider_metadata.request_object_encryption_enc_values_supported()\n    );\n    assert_eq!(\n        None,\n        provider_metadata.token_endpoint_auth_methods_supported()\n    );\n    assert_eq!(\n        Some(&vec![\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n            CoreJwsSigningAlgorithm::None,\n        ]),\n        provider_metadata.token_endpoint_auth_signing_alg_values_supported()\n    );\n    assert_eq!(None, provider_metadata.display_values_supported());\n    assert_eq!(None, provider_metadata.claim_types_supported());\n    assert_eq!(None, provider_metadata.claims_supported());\n\n    assert_eq!(None, provider_metadata.service_documentation());\n    assert_eq!(None, provider_metadata.claims_locales_supported());\n    assert_eq!(None, provider_metadata.ui_locales_supported());\n    assert_eq!(None, provider_metadata.claims_parameter_supported());\n    assert_eq!(None, provider_metadata.request_parameter_supported());\n    assert_eq!(None, provider_metadata.request_uri_parameter_supported());\n    assert_eq!(None, provider_metadata.require_request_uri_registration());\n    assert_eq!(None, provider_metadata.op_policy_uri());\n    assert_eq!(None, provider_metadata.op_tos_uri());\n\n    let serialized_json = serde_json::to_string(&provider_metadata).unwrap();\n\n    let redeserialized_metadata: CoreProviderMetadata =\n        serde_json::from_str(&serialized_json).unwrap();\n    assert_eq!(provider_metadata, redeserialized_metadata);\n}\n"
  },
  {
    "path": "src/helpers.rs",
    "content": "use crate::types::localized::join_language_tag_key;\nuse crate::{LanguageTag, LocalizedClaim};\n\nuse chrono::{DateTime, TimeZone, Utc};\nuse serde::de::value::MapDeserializer;\nuse serde::de::{DeserializeOwned, Deserializer, Error, MapAccess, Visitor};\nuse serde::{Deserialize, Serialize, Serializer};\nuse serde_json::from_value;\nuse serde_value::ValueDeserializer;\nuse serde_with::{DeserializeAs, SerializeAs};\n\nuse std::cmp::PartialEq;\nuse std::fmt::{Debug, Display, Formatter, Result as FormatterResult};\nuse std::marker::PhantomData;\n\npub(crate) fn deserialize_string_or_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>\nwhere\n    T: DeserializeOwned,\n    D: Deserializer<'de>,\n{\n    let value: serde_json::Value = Deserialize::deserialize(deserializer)?;\n    match from_value::<Vec<T>>(value.clone()) {\n        Ok(val) => Ok(val),\n        Err(_) => {\n            let single_val: T = from_value(value).map_err(Error::custom)?;\n            Ok(vec![single_val])\n        }\n    }\n}\n\npub(crate) fn deserialize_string_or_vec_opt<'de, T, D>(\n    deserializer: D,\n) -> Result<Option<Vec<T>>, D::Error>\nwhere\n    T: DeserializeOwned,\n    D: Deserializer<'de>,\n{\n    let value: serde_json::Value = Deserialize::deserialize(deserializer)?;\n    match from_value::<Option<Vec<T>>>(value.clone()) {\n        Ok(val) => Ok(val),\n        Err(_) => {\n            let single_val: T = from_value(value).map_err(Error::custom)?;\n            Ok(Some(vec![single_val]))\n        }\n    }\n}\n\n// Attempt to deserialize the value; if the value is null or an error occurs, return None.\n// This is useful when deserializing fields that may mean different things in different\n// contexts, and where we would rather ignore the result than fail to deserialize. For example,\n// the fields in JWKs are not well defined; extensions could theoretically define their own\n// field names that overload field names used by other JWK types.\npub(crate) fn deserialize_option_or_none<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>\nwhere\n    T: DeserializeOwned,\n    D: Deserializer<'de>,\n{\n    let value: serde_json::Value = Deserialize::deserialize(deserializer)?;\n    match from_value::<Option<T>>(value) {\n        Ok(val) => Ok(val),\n        Err(_) => Ok(None),\n    }\n}\n\npub trait DeserializeMapField: Sized {\n    fn deserialize_map_field<'de, V>(\n        map: &mut V,\n        field_name: &'static str,\n        language_tag: Option<LanguageTag>,\n        field_value: Option<Self>,\n    ) -> Result<Self, V::Error>\n    where\n        V: MapAccess<'de>;\n}\n\nimpl<T> DeserializeMapField for T\nwhere\n    T: DeserializeOwned,\n{\n    fn deserialize_map_field<'de, V>(\n        map: &mut V,\n        field_name: &'static str,\n        language_tag: Option<LanguageTag>,\n        field_value: Option<Self>,\n    ) -> Result<Self, V::Error>\n    where\n        V: MapAccess<'de>,\n    {\n        if field_value.is_some() {\n            return Err(serde::de::Error::duplicate_field(field_name));\n        } else if let Some(language_tag) = language_tag {\n            return Err(serde::de::Error::custom(format!(\n                \"unexpected language tag `{language_tag}` for key `{field_name}`\"\n            )));\n        }\n        map.next_value().map_err(|err| {\n            V::Error::custom(format!(\n                \"{}: {err}\",\n                join_language_tag_key(field_name, language_tag.as_ref())\n            ))\n        })\n    }\n}\n\nimpl<T> DeserializeMapField for LocalizedClaim<T>\nwhere\n    T: DeserializeOwned,\n{\n    fn deserialize_map_field<'de, V>(\n        map: &mut V,\n        field_name: &'static str,\n        language_tag: Option<LanguageTag>,\n        field_value: Option<Self>,\n    ) -> Result<Self, V::Error>\n    where\n        V: MapAccess<'de>,\n    {\n        let mut localized_claim = field_value.unwrap_or_default();\n        if localized_claim.contains_key(language_tag.as_ref()) {\n            return Err(serde::de::Error::custom(format!(\n                \"duplicate field `{}`\",\n                join_language_tag_key(field_name, language_tag.as_ref())\n            )));\n        }\n\n        let localized_value = map.next_value().map_err(|err| {\n            V::Error::custom(format!(\n                \"{}: {err}\",\n                join_language_tag_key(field_name, language_tag.as_ref())\n            ))\n        })?;\n        localized_claim.insert(language_tag, localized_value);\n\n        Ok(localized_claim)\n    }\n}\n\n// Some providers return boolean values as strings. Provide support for\n// parsing using stdlib.\n#[cfg(feature = \"accept-string-booleans\")]\npub(crate) mod serde_string_bool {\n    use serde::{de, Deserializer};\n\n    use std::fmt;\n\n    pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct BooleanLikeVisitor;\n\n        impl<'de> de::Visitor<'de> for BooleanLikeVisitor {\n            type Value = bool;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n                formatter.write_str(\"A boolean-like value\")\n            }\n\n            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                Ok(v)\n            }\n\n            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                v.parse().map_err(E::custom)\n            }\n        }\n        deserializer.deserialize_any(BooleanLikeVisitor)\n    }\n}\n\n/// Serde space-delimited string serializer for an `Option<Vec<String>>`.\n///\n/// This function serializes a string vector into a single space-delimited string.\n/// If `string_vec_opt` is `None`, the function serializes it as `None` (e.g., `null`\n/// in the case of JSON serialization).\npub(crate) fn serialize_space_delimited_vec<T, S>(\n    vec: &[T],\n    serializer: S,\n) -> Result<S::Ok, S::Error>\nwhere\n    T: AsRef<str>,\n    S: Serializer,\n{\n    let space_delimited = vec\n        .iter()\n        .map(AsRef::<str>::as_ref)\n        .collect::<Vec<_>>()\n        .join(\" \");\n\n    serializer.serialize_str(&space_delimited)\n}\n\npub(crate) trait FlattenFilter {\n    fn should_include(field_name: &str) -> bool;\n}\n\n/// Helper container for filtering map keys out of serde(flatten). This is needed because\n/// [`crate::StandardClaims`] doesn't have a fixed set of field names due to its support for\n/// localized claims. Consequently, serde by default passes all of the claims to the deserializer\n/// for `AC` (additional claims), leading to duplicate claims. [`FilteredFlatten`] is used for\n/// eliminating the duplicate claims.\n#[derive(Serialize)]\npub(crate) struct FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: DeserializeOwned + Serialize,\n{\n    // We include another level of flattening here because the derived flatten\n    // ([`serde::private::de::FlatMapDeserializer`]) seems to support a wider set of types\n    // (e.g., various forms of enum tagging) than [`serde_value::ValueDeserializer`].\n    #[serde(flatten)]\n    inner: Flatten<T>,\n    #[serde(skip)]\n    _phantom: PhantomData<F>,\n}\nimpl<F, T> From<T> for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: DeserializeOwned + Serialize,\n{\n    fn from(value: T) -> Self {\n        Self {\n            inner: Flatten { inner: value },\n            _phantom: PhantomData,\n        }\n    }\n}\nimpl<F, T> AsRef<T> for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: DeserializeOwned + Serialize,\n{\n    fn as_ref(&self) -> &T {\n        self.inner.as_ref()\n    }\n}\nimpl<F, T> AsMut<T> for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: DeserializeOwned + Serialize,\n{\n    fn as_mut(&mut self) -> &mut T {\n        self.inner.as_mut()\n    }\n}\nimpl<F, T> PartialEq for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: DeserializeOwned + PartialEq + Serialize,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.inner == other.inner\n    }\n}\nimpl<F, T> Clone for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: Clone + DeserializeOwned + Serialize,\n{\n    fn clone(&self) -> Self {\n        Self {\n            inner: Flatten {\n                inner: self.inner.inner.clone(),\n            },\n            _phantom: PhantomData,\n        }\n    }\n}\nimpl<F, T> Debug for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: Debug + DeserializeOwned + Serialize,\n{\n    // Transparent Debug since we don't care about this struct.\n    fn fmt(&self, f: &mut Formatter) -> FormatterResult {\n        Debug::fmt(&self.inner, f)\n    }\n}\n\nimpl<'de, F, T> Deserialize<'de> for FilteredFlatten<F, T>\nwhere\n    F: FlattenFilter,\n    T: DeserializeOwned + Serialize,\n{\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct MapVisitor<F: FlattenFilter, T: DeserializeOwned + Serialize>(PhantomData<(F, T)>);\n\n        impl<'de, F, T> Visitor<'de> for MapVisitor<F, T>\n        where\n            F: FlattenFilter,\n            T: DeserializeOwned + Serialize,\n        {\n            type Value = Flatten<T>;\n\n            fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {\n                formatter.write_str(\"map type T\")\n            }\n\n            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>\n            where\n                V: MapAccess<'de>,\n            {\n                let mut entries = Vec::<(serde_value::Value, serde_value::Value)>::new();\n                // JSON only supports String keys, and we really only need to support JSON input.\n                while let Some(key) = map.next_key::<serde_value::Value>()? {\n                    let key_str = String::deserialize(ValueDeserializer::new(key.clone()))?;\n                    if F::should_include(&key_str) {\n                        entries.push((key, map.next_value()?));\n                    }\n                }\n\n                Deserialize::deserialize(MapDeserializer::new(entries.into_iter()))\n                    .map_err(serde_value::DeserializerError::into_error)\n            }\n        }\n\n        Ok(FilteredFlatten {\n            inner: deserializer.deserialize_map(MapVisitor(PhantomData::<(F, T)>))?,\n            _phantom: PhantomData,\n        })\n    }\n}\n\n#[derive(Deserialize, Serialize)]\nstruct Flatten<T>\nwhere\n    T: DeserializeOwned + Serialize,\n{\n    #[serde(flatten, bound = \"T: DeserializeOwned + Serialize\")]\n    inner: T,\n}\nimpl<T> AsRef<T> for Flatten<T>\nwhere\n    T: DeserializeOwned + Serialize,\n{\n    fn as_ref(&self) -> &T {\n        &self.inner\n    }\n}\nimpl<T> AsMut<T> for Flatten<T>\nwhere\n    T: DeserializeOwned + Serialize,\n{\n    fn as_mut(&mut self) -> &mut T {\n        &mut self.inner\n    }\n}\nimpl<T> PartialEq for Flatten<T>\nwhere\n    T: DeserializeOwned + PartialEq + Serialize,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.inner == other.inner\n    }\n}\nimpl<T> Debug for Flatten<T>\nwhere\n    T: Debug + DeserializeOwned + Serialize,\n{\n    // Transparent Debug since we don't care about this struct.\n    fn fmt(&self, f: &mut Formatter) -> FormatterResult {\n        Debug::fmt(&self.inner, f)\n    }\n}\n\npub(crate) fn join_vec<T>(entries: &[T]) -> String\nwhere\n    T: AsRef<str>,\n{\n    entries\n        .iter()\n        .map(AsRef::as_ref)\n        .collect::<Vec<_>>()\n        .join(\" \")\n}\n\n/// Newtype around a bool, optionally supporting string values.\n#[derive(Debug, Deserialize, Serialize)]\n#[serde(transparent)]\npub(crate) struct Boolean(\n    #[cfg_attr(\n        feature = \"accept-string-booleans\",\n        serde(deserialize_with = \"crate::helpers::serde_string_bool::deserialize\")\n    )]\n    pub bool,\n);\nimpl Boolean {\n    pub(crate) fn into_inner(self) -> bool {\n        self.0\n    }\n}\nimpl Display for Boolean {\n    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {\n        Display::fmt(&self.0, f)\n    }\n}\n\n/// Timestamp as seconds since the unix epoch, or optionally an ISO 8601 string.\n#[derive(Debug, Deserialize, Serialize)]\n#[serde(untagged)]\npub(crate) enum Timestamp {\n    Seconds(serde_json::Number),\n    #[cfg(feature = \"accept-rfc3339-timestamps\")]\n    Rfc3339(String),\n}\nimpl Timestamp {\n    // The spec is ambiguous about whether seconds should be expressed as integers, or\n    // whether floating-point values are allowed. For compatibility with a wide range of\n    // clients, we round down to the nearest second.\n    pub(crate) fn from_utc(utc: &DateTime<Utc>) -> Self {\n        Timestamp::Seconds(utc.timestamp().into())\n    }\n\n    pub(crate) fn to_utc(&self) -> Result<DateTime<Utc>, ()> {\n        match self {\n            Timestamp::Seconds(seconds) => {\n                let (secs, nsecs) = if seconds.is_i64() {\n                    (seconds.as_i64().ok_or(())?, 0u32)\n                } else {\n                    let secs_f64 = seconds.as_f64().ok_or(())?;\n                    let secs = secs_f64.floor();\n                    (\n                        secs as i64,\n                        ((secs_f64 - secs) * 1_000_000_000.).floor() as u32,\n                    )\n                };\n                Utc.timestamp_opt(secs, nsecs).single().ok_or(())\n            }\n            #[cfg(feature = \"accept-rfc3339-timestamps\")]\n            Timestamp::Rfc3339(iso) => {\n                let datetime = DateTime::parse_from_rfc3339(iso).map_err(|_| ())?;\n                Ok(datetime.into())\n            }\n        }\n    }\n}\n\nimpl Display for Timestamp {\n    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {\n        match self {\n            Timestamp::Seconds(seconds) => Display::fmt(seconds, f),\n            #[cfg(feature = \"accept-rfc3339-timestamps\")]\n            Timestamp::Rfc3339(iso) => Display::fmt(iso, f),\n        }\n    }\n}\n\nimpl<'de> DeserializeAs<'de, DateTime<Utc>> for Timestamp {\n    fn deserialize_as<D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let seconds: Timestamp = Deserialize::deserialize(deserializer)?;\n        seconds.to_utc().map_err(|_| {\n            serde::de::Error::custom(format!(\n                \"failed to parse `{}` as UTC datetime (in seconds)\",\n                seconds\n            ))\n        })\n    }\n}\n\nimpl SerializeAs<DateTime<Utc>> for Timestamp {\n    fn serialize_as<S>(source: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        Timestamp::from_utc(source).serialize(serializer)\n    }\n}\n\nnew_type![\n    #[derive(Deserialize, Hash, Serialize)]\n    pub(crate) Base64UrlEncodedBytes(\n        #[serde(with = \"serde_base64url_byte_array\")]\n        Vec<u8>\n    )\n];\n\nmod serde_base64url_byte_array {\n    use crate::core::base64_url_safe_no_pad;\n\n    use base64::prelude::BASE64_URL_SAFE_NO_PAD;\n    use base64::Engine;\n    use serde::de::Error;\n    use serde::{Deserialize, Deserializer, Serializer};\n    use serde_json::{from_value, Value};\n\n    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let value: Value = Deserialize::deserialize(deserializer)?;\n        let base64_encoded: String = from_value(value).map_err(D::Error::custom)?;\n\n        base64_url_safe_no_pad()\n            .decode(&base64_encoded)\n            .map_err(|err| {\n                D::Error::custom(format!(\n                    \"invalid base64url encoding `{}`: {:?}\",\n                    base64_encoded, err\n                ))\n            })\n    }\n\n    pub fn serialize<S>(v: &[u8], serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let base64_encoded = BASE64_URL_SAFE_NO_PAD.encode(v);\n        serializer.serialize_str(&base64_encoded)\n    }\n}\n"
  },
  {
    "path": "src/http_utils.rs",
    "content": "use crate::AccessToken;\n\nuse http::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};\n\npub const MIME_TYPE_JSON: &str = \"application/json\";\npub const MIME_TYPE_JWKS: &str = \"application/jwk-set+json\";\npub const MIME_TYPE_JWT: &str = \"application/jwt\";\n\npub const BEARER: &str = \"Bearer\";\n\n// The [essence](https://mimesniff.spec.whatwg.org/#mime-type-essence) is the <type>/<subtype>\n// representation.\npub fn content_type_has_essence(content_type: &HeaderValue, expected_essence: &str) -> bool {\n    #[allow(clippy::or_fun_call)]\n    content_type\n        .to_str()\n        .ok()\n        .filter(|ct| {\n            ct[..ct.find(';').unwrap_or(ct.len())].to_lowercase() == expected_essence.to_lowercase()\n        })\n        .is_some()\n}\n\npub fn check_content_type(headers: &HeaderMap, expected_content_type: &str) -> Result<(), String> {\n    headers\n        .get(CONTENT_TYPE)\n        .map_or(Ok(()), |content_type|\n            // Section 3.1.1.1 of RFC 7231 indicates that media types are case insensitive and\n            // may be followed by optional whitespace and/or a parameter (e.g., charset).\n            // See https://tools.ietf.org/html/rfc7231#section-3.1.1.1.\n            if !content_type_has_essence(content_type, expected_content_type) {\n                Err(\n                    format!(\n                        \"Unexpected response Content-Type: {:?}, should be `{}`\",\n                        content_type,\n                        expected_content_type\n                    )\n                )\n            } else {\n                Ok(())\n            }\n        )\n}\n\npub fn auth_bearer(access_token: &AccessToken) -> (HeaderName, HeaderValue) {\n    (\n        AUTHORIZATION,\n        HeaderValue::from_str(&format!(\"{} {}\", BEARER, access_token.secret()))\n            .expect(\"invalid access token\"),\n    )\n}\n"
  },
  {
    "path": "src/id_token/mod.rs",
    "content": "use crate::helpers::{deserialize_string_or_vec, FilteredFlatten, Timestamp};\nuse crate::jwt::JsonWebTokenAccess;\nuse crate::jwt::{JsonWebTokenError, JsonWebTokenJsonPayloadSerde};\nuse crate::types::jwk::JwsSigningAlgorithm;\nuse crate::{\n    AccessToken, AccessTokenHash, AdditionalClaims, AddressClaim, Audience, AudiencesClaim,\n    AuthenticationContextClass, AuthenticationMethodReference, AuthorizationCode,\n    AuthorizationCodeHash, ClaimsVerificationError, ClientId, EndUserBirthday, EndUserEmail,\n    EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName, EndUserNickname,\n    EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone, EndUserUsername,\n    EndUserWebsiteUrl, ExtraTokenFields, GenderClaim, IdTokenVerifier, IssuerClaim, IssuerUrl,\n    JsonWebKey, JsonWebToken, JsonWebTokenAlgorithm, JweContentEncryptionAlgorithm, LanguageTag,\n    LocalizedClaim, Nonce, NonceVerifier, PrivateSigningKey, SignatureVerificationError,\n    StandardClaims, SubjectIdentifier,\n};\n\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\nuse serde_json::Value;\nuse serde_with::{serde_as, skip_serializing_none};\n\nuse std::fmt::Debug;\nuse std::str::FromStr;\n\n#[cfg(test)]\nmod tests;\n\n// This wrapper layer exists instead of directly verifying the JWT and returning the claims so that\n// we can pass it around and easily access a serialized JWT representation of it (e.g., for passing\n// to the authorization endpoint as an id_token_hint).\n/// OpenID Connect ID token.\n#[cfg_attr(\n    any(test, feature = \"timing-resistant-secret-traits\"),\n    derive(PartialEq)\n)]\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct IdToken<\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n>(\n    #[serde(bound = \"AC: AdditionalClaims\")]\n    JsonWebToken<JE, JS, IdTokenClaims<AC, GC>, JsonWebTokenJsonPayloadSerde>,\n);\n\nimpl<AC, GC, JE, JS> FromStr for IdToken<AC, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    type Err = serde_json::Error;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        serde_json::from_value(Value::String(s.to_string()))\n    }\n}\n\nimpl<AC, GC, JE, JS> IdToken<AC, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    /// Initializes an ID token with the specified claims, signed using the given signing key and\n    /// algorithm.\n    ///\n    /// If an `access_token` and/or `code` are provided, this method sets the `at_hash` and/or\n    /// `c_hash` claims using the given signing algorithm, respectively. Otherwise, those claims are\n    /// unchanged from the values specified in `claims`.\n    pub fn new<S>(\n        claims: IdTokenClaims<AC, GC>,\n        signing_key: &S,\n        alg: JS,\n        access_token: Option<&AccessToken>,\n        code: Option<&AuthorizationCode>,\n    ) -> Result<Self, JsonWebTokenError>\n    where\n        S: PrivateSigningKey,\n        <S as PrivateSigningKey>::VerificationKey: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        let verification_key = signing_key.as_verification_key();\n        let at_hash = access_token\n            .map(|at| {\n                AccessTokenHash::from_token(at, &alg, &verification_key)\n                    .map_err(JsonWebTokenError::SigningError)\n            })\n            .transpose()?\n            .or_else(|| claims.access_token_hash.clone());\n        let c_hash = code\n            .map(|c| {\n                AuthorizationCodeHash::from_code(c, &alg, &verification_key)\n                    .map_err(JsonWebTokenError::SigningError)\n            })\n            .transpose()?\n            .or_else(|| claims.code_hash.clone());\n\n        JsonWebToken::new(\n            IdTokenClaims {\n                access_token_hash: at_hash,\n                code_hash: c_hash,\n                ..claims\n            },\n            signing_key,\n            &alg,\n        )\n        .map(Self)\n    }\n\n    /// Verifies and returns a reference to the ID token claims.\n    pub fn claims<'a, K, N>(\n        &'a self,\n        verifier: &IdTokenVerifier<K>,\n        nonce_verifier: N,\n    ) -> Result<&'a IdTokenClaims<AC, GC>, ClaimsVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>,\n        N: NonceVerifier,\n    {\n        verifier.verified_claims(&self.0, nonce_verifier)\n    }\n\n    /// Verifies and returns the ID token claims.\n    pub fn into_claims<K, N>(\n        self,\n        verifier: &IdTokenVerifier<K>,\n        nonce_verifier: N,\n    ) -> Result<IdTokenClaims<AC, GC>, ClaimsVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>,\n        N: NonceVerifier,\n    {\n        verifier.verified_claims_owned(self.0, nonce_verifier)\n    }\n\n    /// Returns the [`JwsSigningAlgorithm`] used to sign this ID token.\n    ///\n    /// This function returns an error if the token is unsigned or utilizes JSON Web Encryption\n    /// (JWE).\n    pub fn signing_alg(&self) -> Result<&JS, SignatureVerificationError> {\n        match self.0.unverified_header().alg {\n            JsonWebTokenAlgorithm::Signature(ref signing_alg) => Ok(signing_alg),\n            JsonWebTokenAlgorithm::Encryption(ref other) => {\n                Err(SignatureVerificationError::UnsupportedAlg(\n                    serde_plain::to_string(other).unwrap_or_else(|err| {\n                        panic!(\n                            \"encryption alg {:?} failed to serialize to a string: {}\",\n                            other, err\n                        )\n                    }),\n                ))\n            }\n            JsonWebTokenAlgorithm::None => Err(SignatureVerificationError::NoSignature),\n        }\n    }\n\n    /// Returns the [`JsonWebKey`] usable for verifying this ID token's JSON Web Signature.\n    ///\n    /// This function returns an error if the token has no signature or a corresponding key cannot\n    /// be found.\n    pub fn signing_key<'s, K>(\n        &self,\n        verifier: &'s IdTokenVerifier<'s, K>,\n    ) -> Result<&'s K, SignatureVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        verifier\n            .jwt_verifier\n            .signing_key(self.0.unverified_header().kid.as_ref(), self.signing_alg()?)\n    }\n}\nimpl<AC, GC, JE, JS> ToString for IdToken<AC, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    fn to_string(&self) -> String {\n        serde_json::to_value(self)\n            // This should never arise, since we're just asking serde_json to serialize the\n            // signing input concatenated with the signature, both of which are precomputed.\n            .expect(\"ID token serialization failed\")\n            .as_str()\n            // This should also never arise, since our IdToken serializer always calls serialize_str\n            .expect(\"ID token serializer did not produce a str\")\n            .to_owned()\n    }\n}\n\n/// OpenID Connect ID token claims.\n#[cfg_attr(\n    any(test, feature = \"timing-resistant-secret-traits\"),\n    derive(PartialEq)\n)]\n#[serde_as]\n#[skip_serializing_none]\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct IdTokenClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    #[serde(rename = \"iss\")]\n    issuer: IssuerUrl,\n    // We always serialize as an array, which is valid according to the spec. This sets the\n    // 'default' attribute to be compatible with non-spec compliant OIDC providers that omit this\n    // field.\n    #[serde(\n        default,\n        rename = \"aud\",\n        deserialize_with = \"deserialize_string_or_vec\"\n    )]\n    audiences: Vec<Audience>,\n    #[serde_as(as = \"Timestamp\")]\n    #[serde(rename = \"exp\")]\n    expiration: DateTime<Utc>,\n    #[serde_as(as = \"Timestamp\")]\n    #[serde(rename = \"iat\")]\n    issue_time: DateTime<Utc>,\n    #[serde_as(as = \"Option<Timestamp>\")]\n    auth_time: Option<DateTime<Utc>>,\n    nonce: Option<Nonce>,\n    #[serde(rename = \"acr\")]\n    auth_context_ref: Option<AuthenticationContextClass>,\n    #[serde(rename = \"amr\")]\n    auth_method_refs: Option<Vec<AuthenticationMethodReference>>,\n    #[serde(rename = \"azp\")]\n    authorized_party: Option<ClientId>,\n    #[serde(rename = \"at_hash\")]\n    access_token_hash: Option<AccessTokenHash>,\n    #[serde(rename = \"c_hash\")]\n    code_hash: Option<AuthorizationCodeHash>,\n\n    #[serde(bound = \"GC: GenderClaim\")]\n    #[serde(flatten)]\n    standard_claims: StandardClaims<GC>,\n\n    #[serde(bound = \"AC: AdditionalClaims\")]\n    #[serde(flatten)]\n    additional_claims: FilteredFlatten<StandardClaims<GC>, AC>,\n}\nimpl<AC, GC> IdTokenClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    /// Initializes new ID token claims.\n    pub fn new(\n        issuer: IssuerUrl,\n        audiences: Vec<Audience>,\n        expiration: DateTime<Utc>,\n        issue_time: DateTime<Utc>,\n        standard_claims: StandardClaims<GC>,\n        additional_claims: AC,\n    ) -> Self {\n        Self {\n            issuer,\n            audiences,\n            expiration,\n            issue_time,\n            auth_time: None,\n            nonce: None,\n            auth_context_ref: None,\n            auth_method_refs: None,\n            authorized_party: None,\n            access_token_hash: None,\n            code_hash: None,\n            standard_claims,\n            additional_claims: additional_claims.into(),\n        }\n    }\n\n    field_getters_setters![\n        pub self [self] [\"claim\"] {\n            set_issuer -> issuer[IssuerUrl] [\"iss\"],\n            set_audiences -> audiences[Vec<Audience>] [\"aud\"],\n            set_expiration -> expiration[DateTime<Utc>] [\"exp\"],\n            set_issue_time -> issue_time[DateTime<Utc>] [\"iat\"],\n            set_auth_time -> auth_time[Option<DateTime<Utc>>],\n            set_nonce -> nonce[Option<Nonce>],\n            set_auth_context_ref -> auth_context_ref[Option<AuthenticationContextClass>] [\"acr\"],\n            set_auth_method_refs -> auth_method_refs[Option<Vec<AuthenticationMethodReference>>] [\"amr\"],\n            set_authorized_party -> authorized_party[Option<ClientId>] [\"azp\"],\n            set_access_token_hash -> access_token_hash[Option<AccessTokenHash>] [\"at_hash\"],\n            set_code_hash -> code_hash[Option<AuthorizationCodeHash>] [\"c_hash\"],\n        }\n    ];\n\n    /// Returns the `sub` claim.\n    pub fn subject(&self) -> &SubjectIdentifier {\n        &self.standard_claims.sub\n    }\n    /// Sets the `sub` claim.\n    pub fn set_subject(mut self, subject: SubjectIdentifier) -> Self {\n        self.standard_claims.sub = subject;\n        self\n    }\n\n    field_getters_setters![\n        pub self [self.standard_claims] [\"claim\"] {\n            set_name -> name[Option<LocalizedClaim<EndUserName>>],\n            set_given_name -> given_name[Option<LocalizedClaim<EndUserGivenName>>],\n            set_family_name ->\n                family_name[Option<LocalizedClaim<EndUserFamilyName>>],\n            set_middle_name ->\n                middle_name[Option<LocalizedClaim<EndUserMiddleName>>],\n            set_nickname -> nickname[Option<LocalizedClaim<EndUserNickname>>],\n            set_preferred_username -> preferred_username[Option<EndUserUsername>],\n            set_profile -> profile[Option<LocalizedClaim<EndUserProfileUrl>>],\n            set_picture -> picture[Option<LocalizedClaim<EndUserPictureUrl>>],\n            set_website -> website[Option<LocalizedClaim<EndUserWebsiteUrl>>],\n            set_email -> email[Option<EndUserEmail>],\n            set_email_verified -> email_verified[Option<bool>],\n            set_gender -> gender[Option<GC>],\n            set_birthday -> birthday[Option<EndUserBirthday>],\n            set_birthdate -> birthdate[Option<EndUserBirthday>],\n            set_zoneinfo -> zoneinfo[Option<EndUserTimezone>],\n            set_locale -> locale[Option<LanguageTag>],\n            set_phone_number -> phone_number[Option<EndUserPhoneNumber>],\n            set_phone_number_verified -> phone_number_verified[Option<bool>],\n            set_address -> address[Option<AddressClaim>],\n            set_updated_at -> updated_at[Option<DateTime<Utc>>],\n        }\n    ];\n\n    /// Returns additional ID token claims.\n    pub fn additional_claims(&self) -> &AC {\n        self.additional_claims.as_ref()\n    }\n    /// Returns mutable additional ID token claims.\n    pub fn additional_claims_mut(&mut self) -> &mut AC {\n        self.additional_claims.as_mut()\n    }\n}\nimpl<AC, GC> AudiencesClaim for IdTokenClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn audiences(&self) -> Option<&Vec<Audience>> {\n        Some(IdTokenClaims::audiences(self))\n    }\n}\nimpl<'a, AC, GC> AudiencesClaim for &'a IdTokenClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn audiences(&self) -> Option<&Vec<Audience>> {\n        Some(IdTokenClaims::audiences(self))\n    }\n}\nimpl<AC, GC> IssuerClaim for IdTokenClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn issuer(&self) -> Option<&IssuerUrl> {\n        Some(IdTokenClaims::issuer(self))\n    }\n}\nimpl<'a, AC, GC> IssuerClaim for &'a IdTokenClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn issuer(&self) -> Option<&IssuerUrl> {\n        Some(IdTokenClaims::issuer(self))\n    }\n}\n\n/// Extends the base OAuth2 token response with an ID token.\n#[cfg_attr(\n    any(test, feature = \"timing-resistant-secret-traits\"),\n    derive(PartialEq)\n)]\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct IdTokenFields<AC, EF, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    EF: ExtraTokenFields,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    #[serde(bound = \"AC: AdditionalClaims\")]\n    id_token: Option<IdToken<AC, GC, JE, JS>>,\n    #[serde(bound = \"EF: ExtraTokenFields\", flatten)]\n    extra_fields: EF,\n}\nimpl<AC, EF, GC, JE, JS> IdTokenFields<AC, EF, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    EF: ExtraTokenFields,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    /// Initializes new ID token fields containing the specified [`IdToken`] and extra fields.\n    pub fn new(id_token: Option<IdToken<AC, GC, JE, JS>>, extra_fields: EF) -> Self {\n        Self {\n            id_token,\n            extra_fields,\n        }\n    }\n\n    /// Returns the [`IdToken`] contained in the OAuth2 token response.\n    pub fn id_token(&self) -> Option<&IdToken<AC, GC, JE, JS>> {\n        self.id_token.as_ref()\n    }\n    /// Returns the extra fields contained in the OAuth2 token response.\n    pub fn extra_fields(&self) -> &EF {\n        &self.extra_fields\n    }\n}\nimpl<AC, EF, GC, JE, JS> ExtraTokenFields for IdTokenFields<AC, EF, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    EF: ExtraTokenFields,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n}\n"
  },
  {
    "path": "src/id_token/tests.rs",
    "content": "use crate::claims::{AdditionalClaims, EmptyAdditionalClaims, StandardClaims};\nuse crate::core::{\n    CoreGenderClaim, CoreIdToken, CoreIdTokenClaims, CoreTokenResponse, CoreTokenType,\n};\nuse crate::jwt::JsonWebTokenAccess;\nuse crate::{\n    AccessTokenHash, AddressClaim, AddressCountry, AddressLocality, AddressPostalCode,\n    AddressRegion, Audience, AudiencesClaim, AuthenticationContextClass,\n    AuthenticationMethodReference, AuthorizationCodeHash, ClientId, EndUserBirthday, EndUserEmail,\n    EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName, EndUserNickname,\n    EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone, EndUserUsername,\n    EndUserWebsiteUrl, FormattedAddress, IdTokenClaims, IssuerClaim, IssuerUrl, LanguageTag, Nonce,\n    StreetAddress, SubjectIdentifier,\n};\n\nuse chrono::{TimeZone, Utc};\nuse oauth2::TokenResponse;\nuse pretty_assertions::assert_eq;\nuse serde::{Deserialize, Serialize};\nuse url::Url;\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\n\n#[test]\nfn test_id_token() {\n    static ID_TOKEN: &str = concat!(\n        \"eyJhbGciOiJSUzI1NiJ9.\",\n        \"eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsImF1ZCI6WyJzNkJoZ\",\n        \"FJrcXQzIl0sImV4cCI6MTMxMTI4MTk3MCwiaWF0IjoxMzExMjgwOTcwLCJzdWIiOi\",\n        \"IyNDQwMDMyMCIsInRmYV9tZXRob2QiOiJ1MmYifQ.\",\n        \"aW52YWxpZF9zaWduYXR1cmU\"\n    );\n\n    // `serde::Deserialize` implementation is tested within the `FromStr` implementation\n    let id_token = CoreIdToken::from_str(ID_TOKEN).expect(\"failed to parse id_token\");\n\n    let claims = id_token.0.unverified_payload_ref();\n\n    assert_eq!(\n        *claims.issuer().url(),\n        Url::parse(\"https://server.example.com\").unwrap()\n    );\n    assert_eq!(\n        *claims.audiences(),\n        vec![Audience::new(\"s6BhdRkqt3\".to_string())]\n    );\n    assert_eq!(\n        claims.expiration(),\n        Utc.timestamp_opt(1311281970, 0)\n            .single()\n            .expect(\"valid timestamp\")\n    );\n    assert_eq!(\n        claims.issue_time(),\n        Utc.timestamp_opt(1311280970, 0)\n            .single()\n            .expect(\"valid timestamp\")\n    );\n    assert_eq!(\n        *claims.subject(),\n        SubjectIdentifier::new(\"24400320\".to_string())\n    );\n\n    // test `ToString` implementation\n    assert_eq!(&id_token.to_string(), ID_TOKEN);\n\n    // test `serde::Serialize` implementation too\n    let de = serde_json::to_string(&id_token).expect(\"failed to deserializee id token\");\n    assert_eq!(de, format!(\"\\\"{}\\\"\", ID_TOKEN));\n}\n\n#[test]\nfn test_oauth2_response() {\n    let response_str = \"{\\\n            \\\"access_token\\\":\\\"foobar\\\",\\\n            \\\"token_type\\\":\\\"bearer\\\",\\\n            \\\"id_token\\\":\\\"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsImF\\\n            1ZCI6WyJzNkJoZFJrcXQzIl0sImV4cCI6MTMxMTI4MTk3MCwiaWF0IjoxMzExMjgwOTcwLCJzdWIiOiIyNDQwMD\\\n            MyMCIsInRmYV9tZXRob2QiOiJ1MmYifQ.aW52YWxpZF9zaWduYXR1cmU\\\"\\\n        }\";\n    let response =\n        serde_json::from_str::<CoreTokenResponse>(response_str).expect(\"failed to deserialize\");\n\n    assert_eq!(*response.access_token().secret(), \"foobar\");\n    assert_eq!(*response.token_type(), CoreTokenType::Bearer);\n\n    let id_token = response.extra_fields().id_token();\n    let claims = id_token.unwrap().0.unverified_payload_ref();\n\n    assert_eq!(\n        *claims.issuer().url(),\n        Url::parse(\"https://server.example.com\").unwrap()\n    );\n    assert_eq!(\n        *claims.audiences(),\n        vec![Audience::new(\"s6BhdRkqt3\".to_string())]\n    );\n    assert_eq!(\n        claims.expiration(),\n        Utc.timestamp_opt(1311281970, 0)\n            .single()\n            .expect(\"valid timestamp\")\n    );\n    assert_eq!(\n        claims.issue_time(),\n        Utc.timestamp_opt(1311280970, 0)\n            .single()\n            .expect(\"valid timestamp\")\n    );\n    assert_eq!(\n        *claims.subject(),\n        SubjectIdentifier::new(\"24400320\".to_string())\n    );\n\n    assert_eq!(\n        serde_json::to_string(&response).expect(\"failed to serialize\"),\n        response_str\n    );\n}\n\n#[test]\nfn test_minimal_claims_serde() {\n    let new_claims = CoreIdTokenClaims::new(\n        IssuerUrl::new(\"https://server.example.com\".to_string()).unwrap(),\n        vec![Audience::new(\"s6BhdRkqt3\".to_string())],\n        Utc.timestamp_opt(1311281970, 0)\n            .single()\n            .expect(\"valid timestamp\"),\n        Utc.timestamp_opt(1311280970, 0)\n            .single()\n            .expect(\"valid timestamp\"),\n        StandardClaims::new(SubjectIdentifier::new(\"24400320\".to_string())),\n        EmptyAdditionalClaims {},\n    );\n    let expected_serialized_claims = \"\\\n                                          {\\\n                                          \\\"iss\\\":\\\"https://server.example.com\\\",\\\n                                          \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n                                          \\\"exp\\\":1311281970,\\\n                                          \\\"iat\\\":1311280970,\\\n                                          \\\"sub\\\":\\\"24400320\\\"\\\n                                          }\";\n\n    let new_serialized_claims = serde_json::to_string(&new_claims).expect(\"failed to serialize\");\n    assert_eq!(new_serialized_claims, expected_serialized_claims);\n\n    let claims: CoreIdTokenClaims = serde_json::from_str(\n        \"{\n            \\\"iss\\\": \\\"https://server.example.com\\\",\n            \\\"sub\\\": \\\"24400320\\\",\n            \\\"aud\\\": \\\"s6BhdRkqt3\\\",\n            \\\"exp\\\": 1311281970,\n            \\\"iat\\\": 1311280970\n            }\",\n    )\n    .expect(\"failed to deserialize\");\n    assert_eq!(claims, new_claims);\n    assert_eq!(claims.issuer().url(), new_claims.issuer().url());\n    assert_eq!(claims.audiences(), new_claims.audiences());\n    assert_eq!(claims.expiration(), new_claims.expiration());\n    assert_eq!(claims.issue_time(), new_claims.issue_time());\n    assert_eq!(claims.auth_time(), None);\n    assert!(claims.nonce().is_none());\n    assert_eq!(claims.auth_context_ref(), None);\n    assert_eq!(claims.auth_method_refs(), None);\n    assert_eq!(claims.authorized_party(), None);\n    assert_eq!(claims.access_token_hash(), None);\n    assert_eq!(claims.code_hash(), None);\n    assert_eq!(*claims.additional_claims(), EmptyAdditionalClaims {});\n    assert_eq!(claims.subject(), new_claims.subject());\n    assert_eq!(claims.name(), None);\n    assert_eq!(claims.given_name(), None);\n    assert_eq!(claims.family_name(), None);\n    assert_eq!(claims.middle_name(), None);\n    assert_eq!(claims.nickname(), None);\n    assert_eq!(claims.preferred_username(), None);\n    assert_eq!(claims.profile(), None);\n    assert_eq!(claims.picture(), None);\n    assert_eq!(claims.website(), None);\n    assert_eq!(claims.email(), None);\n    assert_eq!(claims.email_verified(), None);\n    assert_eq!(claims.gender(), None);\n    assert_eq!(claims.birthday(), None);\n    assert_eq!(claims.birthdate(), None);\n    assert_eq!(claims.zoneinfo(), None);\n    assert_eq!(claims.locale(), None);\n    assert_eq!(claims.phone_number(), None);\n    assert_eq!(claims.phone_number_verified(), None);\n    assert_eq!(claims.address(), None);\n    assert_eq!(claims.updated_at(), None);\n\n    let serialized_claims = serde_json::to_string(&claims).expect(\"failed to serialize\");\n    assert_eq!(serialized_claims, expected_serialized_claims);\n\n    let claims_round_trip: CoreIdTokenClaims =\n        serde_json::from_str(&serialized_claims).expect(\"failed to deserialize\");\n    assert_eq!(claims, claims_round_trip);\n}\n\n#[test]\nfn test_complete_claims_serde() {\n    let claims_json = \"{\\\n                           \\\"iss\\\":\\\"https://server.example.com\\\",\\\n                           \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n                           \\\"exp\\\":1311281970,\\\n                           \\\"iat\\\":1311280970,\\\n                           \\\"auth_time\\\":1311282970,\\\n                           \\\"nonce\\\":\\\"Zm9vYmFy\\\",\\\n                           \\\"acr\\\":\\\"urn:mace:incommon:iap:silver\\\",\\\n                           \\\"amr\\\":[\\\"password\\\",\\\"totp\\\"],\\\n                           \\\"azp\\\":\\\"dGhpc19jbGllbnQ\\\",\\\n                           \\\"at_hash\\\":\\\"_JPLB-GtkomFJxAOWKHPHQ\\\",\\\n                           \\\"c_hash\\\":\\\"VpTQii5T_8rgwxA-Wtb2Bw\\\",\\\n                           \\\"sub\\\":\\\"24400320\\\",\\\n                           \\\"name\\\":\\\"Homer Simpson\\\",\\\n                           \\\"name#es\\\":\\\"Jomer Simpson\\\",\\\n                           \\\"given_name\\\":\\\"Homer\\\",\\\n                           \\\"given_name#es\\\":\\\"Jomer\\\",\\\n                           \\\"family_name\\\":\\\"Simpson\\\",\\\n                           \\\"family_name#es\\\":\\\"Simpson\\\",\\\n                           \\\"middle_name\\\":\\\"Jay\\\",\\\n                           \\\"middle_name#es\\\":\\\"Jay\\\",\\\n                           \\\"nickname\\\":\\\"Homer\\\",\\\n                           \\\"nickname#es\\\":\\\"Jomer\\\",\\\n                           \\\"preferred_username\\\":\\\"homersimpson\\\",\\\n                           \\\"profile\\\":\\\"https://example.com/profile?id=12345\\\",\\\n                           \\\"profile#es\\\":\\\"https://example.com/profile?id=12345&lang=es\\\",\\\n                           \\\"picture\\\":\\\"https://example.com/avatar?id=12345\\\",\\\n                           \\\"picture#es\\\":\\\"https://example.com/avatar?id=12345&lang=es\\\",\\\n                           \\\"website\\\":\\\"https://homersimpson.me\\\",\\\n                           \\\"website#es\\\":\\\"https://homersimpson.me/?lang=es\\\",\\\n                           \\\"email\\\":\\\"homer@homersimpson.me\\\",\\\n                           \\\"email_verified\\\":true,\\\n                           \\\"gender\\\":\\\"male\\\",\\\n                           \\\"birthday\\\":\\\"1956-05-12\\\",\\\n                           \\\"birthdate\\\":\\\"1956-07-12\\\",\\\n                           \\\"zoneinfo\\\":\\\"America/Los_Angeles\\\",\\\n                           \\\"locale\\\":\\\"en-US\\\",\\\n                           \\\"phone_number\\\":\\\"+1 (555) 555-5555\\\",\\\n                           \\\"phone_number_verified\\\":false,\\\n                           \\\"address\\\":{\\\n                           \\\"formatted\\\":\\\"1234 Hollywood Blvd., Los Angeles, CA 90210\\\",\\\n                           \\\"street_address\\\":\\\"1234 Hollywood Blvd.\\\",\\\n                           \\\"locality\\\":\\\"Los Angeles\\\",\\\n                           \\\"region\\\":\\\"CA\\\",\\\n                           \\\"postal_code\\\":\\\"90210\\\",\\\n                           \\\"country\\\":\\\"US\\\"\\\n                           },\\\n                           \\\"updated_at\\\":1311283970\\\n                           }\";\n\n    let new_claims = CoreIdTokenClaims::new(\n        IssuerUrl::new(\"https://server.example.com\".to_string()).unwrap(),\n        vec![Audience::new(\"s6BhdRkqt3\".to_string())],\n        Utc.timestamp_opt(1311281970, 0)\n            .single()\n            .expect(\"valid timestamp\"),\n        Utc.timestamp_opt(1311280970, 0)\n            .single()\n            .expect(\"valid timestamp\"),\n        StandardClaims {\n            sub: SubjectIdentifier::new(\"24400320\".to_string()),\n            name: Some(\n                vec![\n                    (None, EndUserName::new(\"Homer Simpson\".to_string())),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserName::new(\"Jomer Simpson\".to_string()),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            given_name: Some(\n                vec![\n                    (None, EndUserGivenName::new(\"Homer\".to_string())),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserGivenName::new(\"Jomer\".to_string()),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            family_name: Some(\n                vec![\n                    (None, EndUserFamilyName::new(\"Simpson\".to_string())),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserFamilyName::new(\"Simpson\".to_string()),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            middle_name: Some(\n                vec![\n                    (None, EndUserMiddleName::new(\"Jay\".to_string())),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserMiddleName::new(\"Jay\".to_string()),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            nickname: Some(\n                vec![\n                    (None, EndUserNickname::new(\"Homer\".to_string())),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserNickname::new(\"Jomer\".to_string()),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            preferred_username: Some(EndUserUsername::new(\"homersimpson\".to_string())),\n            profile: Some(\n                vec![\n                    (\n                        None,\n                        EndUserProfileUrl::new(\"https://example.com/profile?id=12345\".to_string()),\n                    ),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserProfileUrl::new(\n                            \"https://example.com/profile?id=12345&lang=es\".to_string(),\n                        ),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            picture: Some(\n                vec![\n                    (\n                        None,\n                        EndUserPictureUrl::new(\"https://example.com/avatar?id=12345\".to_string()),\n                    ),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserPictureUrl::new(\n                            \"https://example.com/avatar?id=12345&lang=es\".to_string(),\n                        ),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            website: Some(\n                vec![\n                    (\n                        None,\n                        EndUserWebsiteUrl::new(\"https://homersimpson.me\".to_string()),\n                    ),\n                    (\n                        Some(LanguageTag::new(\"es\".to_string())),\n                        EndUserWebsiteUrl::new(\"https://homersimpson.me/?lang=es\".to_string()),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            email: Some(EndUserEmail::new(\"homer@homersimpson.me\".to_string())),\n            email_verified: Some(true),\n            gender: Some(CoreGenderClaim::new(\"male\".to_string())),\n            birthday: Some(EndUserBirthday::new(\"1956-05-12\".to_string())),\n            birthdate: Some(EndUserBirthday::new(\"1956-07-12\".to_string())),\n            zoneinfo: Some(EndUserTimezone::new(\"America/Los_Angeles\".to_string())),\n            locale: Some(LanguageTag::new(\"en-US\".to_string())),\n            phone_number: Some(EndUserPhoneNumber::new(\"+1 (555) 555-5555\".to_string())),\n            phone_number_verified: Some(false),\n            address: Some(AddressClaim {\n                formatted: Some(FormattedAddress::new(\n                    \"1234 Hollywood Blvd., Los Angeles, CA 90210\".to_string(),\n                )),\n                street_address: Some(StreetAddress::new(\"1234 Hollywood Blvd.\".to_string())),\n                locality: Some(AddressLocality::new(\"Los Angeles\".to_string())),\n                region: Some(AddressRegion::new(\"CA\".to_string())),\n                postal_code: Some(AddressPostalCode::new(\"90210\".to_string())),\n                country: Some(AddressCountry::new(\"US\".to_string())),\n            }),\n            updated_at: Some(\n                Utc.timestamp_opt(1311283970, 0)\n                    .single()\n                    .expect(\"valid timestamp\"),\n            ),\n        },\n        EmptyAdditionalClaims {},\n    )\n    .set_auth_time(Some(\n        Utc.timestamp_opt(1311282970, 0)\n            .single()\n            .expect(\"valid timestamp\"),\n    ))\n    .set_nonce(Some(Nonce::new(\"Zm9vYmFy\".to_string())))\n    .set_auth_context_ref(Some(AuthenticationContextClass::new(\n        \"urn:mace:incommon:iap:silver\".to_string(),\n    )))\n    .set_auth_method_refs(Some(vec![\n        AuthenticationMethodReference::new(\"password\".to_string()),\n        AuthenticationMethodReference::new(\"totp\".to_string()),\n    ]))\n    .set_authorized_party(Some(ClientId::new(\"dGhpc19jbGllbnQ\".to_string())))\n    .set_access_token_hash(Some(AccessTokenHash::new(\n        \"_JPLB-GtkomFJxAOWKHPHQ\".to_string(),\n    )))\n    .set_code_hash(Some(AuthorizationCodeHash::new(\n        \"VpTQii5T_8rgwxA-Wtb2Bw\".to_string(),\n    )));\n\n    let claims: CoreIdTokenClaims =\n        serde_json::from_str(claims_json).expect(\"failed to deserialize\");\n    assert_eq!(claims, new_claims);\n    assert_eq!(claims.issuer(), new_claims.issuer());\n    assert_eq!(claims.issuer().url(), new_claims.issuer().url());\n    assert_eq!(claims.audiences(), new_claims.audiences());\n    assert_eq!(claims.expiration(), new_claims.expiration());\n    assert_eq!(claims.issue_time(), new_claims.issue_time());\n    assert_eq!(claims.auth_time(), new_claims.auth_time());\n    assert_eq!(\n        claims.nonce().unwrap().secret(),\n        new_claims.nonce().unwrap().secret()\n    );\n    assert_eq!(claims.auth_context_ref(), new_claims.auth_context_ref());\n    assert_eq!(claims.auth_method_refs(), new_claims.auth_method_refs());\n    assert_eq!(claims.authorized_party(), new_claims.authorized_party());\n    assert_eq!(claims.access_token_hash(), new_claims.access_token_hash());\n    assert_eq!(claims.code_hash(), new_claims.code_hash());\n    assert_eq!(*claims.additional_claims(), EmptyAdditionalClaims {});\n    assert_eq!(claims.subject(), new_claims.subject());\n    assert_eq!(claims.name(), new_claims.name());\n    assert_eq!(claims.given_name(), new_claims.given_name());\n    assert_eq!(claims.family_name(), new_claims.family_name());\n    assert_eq!(claims.middle_name(), new_claims.middle_name());\n    assert_eq!(claims.nickname(), new_claims.nickname());\n    assert_eq!(claims.preferred_username(), new_claims.preferred_username());\n    assert_eq!(claims.preferred_username(), new_claims.preferred_username());\n    assert_eq!(claims.profile(), new_claims.profile());\n    assert_eq!(claims.picture(), new_claims.picture());\n    assert_eq!(claims.website(), new_claims.website());\n    assert_eq!(claims.email(), new_claims.email());\n    assert_eq!(claims.email_verified(), new_claims.email_verified());\n    assert_eq!(claims.gender(), new_claims.gender());\n    assert_eq!(claims.birthday(), new_claims.birthday());\n    assert_eq!(claims.birthdate(), new_claims.birthdate());\n    assert_eq!(claims.zoneinfo(), new_claims.zoneinfo());\n    assert_eq!(claims.locale(), new_claims.locale());\n    assert_eq!(claims.phone_number(), new_claims.phone_number(),);\n    assert_eq!(\n        claims.phone_number_verified(),\n        new_claims.phone_number_verified()\n    );\n    assert_eq!(claims.address(), new_claims.address());\n    assert_eq!(claims.updated_at(), new_claims.updated_at());\n\n    let serialized_claims = serde_json::to_string(&claims).expect(\"failed to serialize\");\n    let claims_round_trip: CoreIdTokenClaims =\n        serde_json::from_str(&serialized_claims).expect(\"failed to deserialize\");\n    assert_eq!(claims, claims_round_trip);\n\n    let serialized_new_claims = serde_json::to_string(&new_claims).expect(\"failed to serialize\");\n    assert_eq!(serialized_new_claims, claims_json);\n}\n\n// See https://github.com/ramosbugs/openidconnect-rs/issues/23\n#[test]\n#[cfg(feature = \"accept-rfc3339-timestamps\")]\nfn test_accept_rfc3339_timestamp() {\n    let claims: CoreIdTokenClaims = serde_json::from_str(\n        \"{\n            \\\"iss\\\": \\\"https://server.example.com\\\",\n            \\\"sub\\\": \\\"24400320\\\",\n            \\\"aud\\\": \\\"s6BhdRkqt3\\\",\n            \\\"exp\\\": 1311281970,\n            \\\"iat\\\": 1311280970,\n            \\\"updated_at\\\": \\\"2021-12-22T02:10:37.000Z\\\"\n            }\",\n    )\n    .expect(\"failed to deserialize\");\n    assert_eq!(\n        claims.updated_at(),\n        Some(\n            Utc.timestamp_opt(1640139037, 0)\n                .single()\n                .expect(\"valid timestamp\")\n        )\n    );\n}\n\n#[test]\nfn test_unknown_claims_serde() {\n    let expected_serialized_claims = \"{\\\n                                          \\\"iss\\\":\\\"https://server.example.com\\\",\\\n                                          \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n                                          \\\"exp\\\":1311281970,\\\n                                          \\\"iat\\\":1311280970,\\\n                                          \\\"sub\\\":\\\"24400320\\\"\\\n                                          }\";\n\n    let claims: CoreIdTokenClaims = serde_json::from_str(\n        \"{\n            \\\"iss\\\": \\\"https://server.example.com\\\",\n            \\\"sub\\\": \\\"24400320\\\",\n            \\\"aud\\\": \\\"s6BhdRkqt3\\\",\n            \\\"exp\\\": 1311281970,\n            \\\"iat\\\": 1311280970,\n            \\\"some_other_field\\\":\\\"some_other_value\\\"\\\n            }\",\n    )\n    .expect(\"failed to deserialize\");\n\n    let serialized_claims = serde_json::to_string(&claims).expect(\"failed to serialize\");\n    assert_eq!(serialized_claims, expected_serialized_claims);\n\n    let claims_round_trip: CoreIdTokenClaims =\n        serde_json::from_str(&serialized_claims).expect(\"failed to deserialize\");\n    assert_eq!(claims, claims_round_trip);\n}\n\n#[test]\nfn test_audience() {\n    let single_aud_str_claims = serde_json::from_str::<CoreIdTokenClaims>(\n        \"{\n                    \\\"iss\\\": \\\"https://server.example.com\\\",\n                    \\\"sub\\\": \\\"24400320\\\",\n                    \\\"aud\\\": \\\"s6BhdRkqt3\\\",\n                    \\\"exp\\\": 1311281970,\n                    \\\"iat\\\": 1311280970\n                }\",\n    )\n    .expect(\"failed to deserialize\");\n    assert_eq!(\n        *single_aud_str_claims.audiences(),\n        vec![Audience::new(\"s6BhdRkqt3\".to_string())],\n    );\n\n    // We always serialize aud as an array, which is valid according to the spec.\n    assert_eq!(\n        serde_json::to_string(&single_aud_str_claims).expect(\"failed to serialize\"),\n        \"{\\\n             \\\"iss\\\":\\\"https://server.example.com\\\",\\\n             \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n             \\\"exp\\\":1311281970,\\\n             \\\"iat\\\":1311280970,\\\n             \\\"sub\\\":\\\"24400320\\\"\\\n             }\",\n    );\n\n    let single_aud_vec_claims = serde_json::from_str::<CoreIdTokenClaims>(\n        \"{\n                    \\\"iss\\\": \\\"https://server.example.com\\\",\n                    \\\"sub\\\": \\\"24400320\\\",\n                    \\\"aud\\\": [\\\"s6BhdRkqt3\\\"],\n                    \\\"exp\\\": 1311281970,\n                    \\\"iat\\\": 1311280970\n                }\",\n    )\n    .expect(\"failed to deserialize\");\n    assert_eq!(\n        *single_aud_vec_claims.audiences(),\n        vec![Audience::new(\"s6BhdRkqt3\".to_string())],\n    );\n    assert_eq!(\n        serde_json::to_string(&single_aud_vec_claims).expect(\"failed to serialize\"),\n        \"{\\\n             \\\"iss\\\":\\\"https://server.example.com\\\",\\\n             \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n             \\\"exp\\\":1311281970,\\\n             \\\"iat\\\":1311280970,\\\n             \\\"sub\\\":\\\"24400320\\\"\\\n             }\",\n    );\n\n    let multi_aud_claims = serde_json::from_str::<CoreIdTokenClaims>(\n        \"{\\\n                    \\\"iss\\\": \\\"https://server.example.com\\\",\n                    \\\"sub\\\": \\\"24400320\\\",\n                    \\\"aud\\\": [\\\"s6BhdRkqt3\\\", \\\"aud2\\\"],\n                    \\\"exp\\\": 1311281970,\n                    \\\"iat\\\": 1311280970\n                }\",\n    )\n    .expect(\"failed to deserialize\");\n    assert_eq!(\n        *multi_aud_claims.audiences(),\n        vec![\n            Audience::new(\"s6BhdRkqt3\".to_string()),\n            Audience::new(\"aud2\".to_string())\n        ],\n    );\n    assert_eq!(\n        serde_json::to_string(&multi_aud_claims).expect(\"failed to serialize\"),\n        \"{\\\n             \\\"iss\\\":\\\"https://server.example.com\\\",\\\n             \\\"aud\\\":[\\\"s6BhdRkqt3\\\",\\\"aud2\\\"],\\\n             \\\"exp\\\":1311281970,\\\n             \\\"iat\\\":1311280970,\\\n             \\\"sub\\\":\\\"24400320\\\"\\\n             }\",\n    );\n}\n\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\nstruct TestClaims {\n    pub tfa_method: String,\n}\nimpl AdditionalClaims for TestClaims {}\n\n#[test]\nfn test_additional_claims() {\n    let claims = serde_json::from_str::<IdTokenClaims<TestClaims, CoreGenderClaim>>(\n        \"{\n                \\\"iss\\\": \\\"https://server.example.com\\\",\n                \\\"sub\\\": \\\"24400320\\\",\n                \\\"aud\\\": [\\\"s6BhdRkqt3\\\"],\n                \\\"exp\\\": 1311281970,\n                \\\"iat\\\": 1311280970,\n                \\\"tfa_method\\\": \\\"u2f\\\"\n            }\",\n    )\n    .expect(\"failed to deserialize\");\n    assert_eq!(claims.additional_claims().tfa_method, \"u2f\");\n    assert_eq!(\n        serde_json::to_string(&claims).expect(\"failed to serialize\"),\n        \"{\\\n             \\\"iss\\\":\\\"https://server.example.com\\\",\\\n             \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n             \\\"exp\\\":1311281970,\\\n             \\\"iat\\\":1311280970,\\\n             \\\"sub\\\":\\\"24400320\\\",\\\n             \\\"tfa_method\\\":\\\"u2f\\\"\\\n             }\",\n    );\n\n    serde_json::from_str::<IdTokenClaims<TestClaims, CoreGenderClaim>>(\n        \"{\n                \\\"iss\\\": \\\"https://server.example.com\\\",\n                \\\"sub\\\": \\\"24400320\\\",\n                \\\"aud\\\": [\\\"s6BhdRkqt3\\\"],\n                \\\"exp\\\": 1311281970,\n                \\\"iat\\\": 1311280970\n            }\",\n    )\n    .expect_err(\"missing claim should fail to deserialize\");\n}\n\n#[derive(Debug, Deserialize, Serialize)]\nstruct AllOtherClaims(HashMap<String, serde_json::Value>);\nimpl AdditionalClaims for AllOtherClaims {}\n\n#[test]\nfn test_catch_all_additional_claims() {\n    let claims = serde_json::from_str::<IdTokenClaims<AllOtherClaims, CoreGenderClaim>>(\n        \"{\n                \\\"iss\\\": \\\"https://server.example.com\\\",\n                \\\"sub\\\": \\\"24400320\\\",\n                \\\"aud\\\": [\\\"s6BhdRkqt3\\\"],\n                \\\"exp\\\": 1311281970,\n                \\\"iat\\\": 1311280970,\n                \\\"tfa_method\\\": \\\"u2f\\\",\n                \\\"updated_at\\\": 1000\n            }\",\n    )\n    .expect(\"failed to deserialize\");\n\n    assert_eq!(claims.additional_claims().0.len(), 1);\n    assert_eq!(claims.additional_claims().0[\"tfa_method\"], \"u2f\");\n}\n\n#[test]\nfn test_audiences_claim() {\n    let claims = serde_json::from_str::<CoreIdTokenClaims>(\n        \"{\n                    \\\"iss\\\": \\\"https://server.example.com\\\",\n                    \\\"sub\\\": \\\"24400320\\\",\n                    \\\"aud\\\": \\\"s6BhdRkqt3\\\",\n                    \\\"exp\\\": 1311281970,\n                    \\\"iat\\\": 1311280970\n                }\",\n    )\n    .expect(\"failed to deserialize\");\n\n    fn verify_audiences<A: AudiencesClaim>(audiences_claim: &A) {\n        assert_eq!(\n            (*audiences_claim).audiences(),\n            Some(&vec![Audience::new(\"s6BhdRkqt3\".to_string())]),\n        )\n    }\n    verify_audiences(&claims);\n    verify_audiences(&&claims);\n}\n\n#[test]\nfn test_issuer_claim() {\n    let claims = serde_json::from_str::<CoreIdTokenClaims>(\n        \"{\n                    \\\"iss\\\": \\\"https://server.example.com\\\",\n                    \\\"sub\\\": \\\"24400320\\\",\n                    \\\"aud\\\": \\\"s6BhdRkqt3\\\",\n                    \\\"exp\\\": 1311281970,\n                    \\\"iat\\\": 1311280970\n                }\",\n    )\n    .expect(\"failed to deserialize\");\n\n    fn verify_issuer<I: IssuerClaim>(issuer_claim: &I) {\n        assert_eq!(\n            (*issuer_claim).issuer(),\n            Some(&IssuerUrl::new(\"https://server.example.com\".to_string()).unwrap()),\n        )\n    }\n    verify_issuer(&claims);\n    verify_issuer(&&claims);\n}\n"
  },
  {
    "path": "src/jwt/mod.rs",
    "content": "use crate::{\n    JsonWebKey, JsonWebKeyId, JweContentEncryptionAlgorithm, JwsSigningAlgorithm,\n    PrivateSigningKey, SignatureVerificationError, SigningError,\n};\n\nuse base64::prelude::BASE64_URL_SAFE_NO_PAD;\nuse base64::Engine;\nuse serde::de::{DeserializeOwned, Error as _, Visitor};\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\nuse serde_with::skip_serializing_none;\nuse thiserror::Error;\n\nuse std::fmt::Debug;\nuse std::marker::PhantomData;\n\n#[cfg(test)]\npub(crate) mod tests;\n\nnew_type![\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    JsonWebTokenContentType(String)\n];\n\n/// Error type used when normalizing [`JsonWebTokenType`] objects\n#[derive(Error, Debug)]\n#[error(\"Invalid JWT type: {typ}\")]\npub struct InvalidJsonWebTokenTypeError {\n    typ: String,\n}\n\nnew_type![\n    /// JSON Web Token type field (typ)\n    ///\n    /// This type stores the raw (deserialized) value.\n    ///\n    /// To compare two different JSON Web Token types, please use the normalized version via [`JsonWebTokenType::normalize`].\n    #[derive(Deserialize, Hash, Serialize)]\n    JsonWebTokenType(String)\n\n    impl {\n        /// Expands a [`JsonWebTokenType`] and produces a [`NormalizedJsonWebTokenType`] according to RFC2045 and RFC7515.\n        ///\n        /// See [RFC 2045 section 5.1](https://tools.ietf.org/html/rfc2045#section-5.1) for the full Content-Type Header Field spec.\n        /// See [RFC 7515 section 4.19](https://tools.ietf.org/html/rfc7515#section-4.1.9) for specific requirements of JSON Web Token Types.\n        pub fn normalize(&self) -> Result<NormalizedJsonWebTokenType, InvalidJsonWebTokenTypeError> {\n            self.try_into()\n        }\n    }\n];\n\n/// Normalized JSON Web Token type field (typ)\n///\n/// This type stores the normalized value of a [`JsonWebTokenType`].\n/// To retrieve a normalized value according to RFC2045 and RFC7515 see [`JsonWebTokenType::normalize`]\n///\n/// See [RFC 2045 section 5.1](https://tools.ietf.org/html/rfc2045#section-5.1) for the full Content-Type Header Field spec.\n/// See [RFC 7515 section 4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9) for specific requirements of JSON Web Token Types.\n///\n/// It is recommended to instantiate `NormalizedJsonWebTokenType` objects via [`JsonWebTokenType`] and then call [`JsonWebTokenType::normalize`].\n///\n/// ```rust\n/// # use openidconnect::{NormalizedJsonWebTokenType, JsonWebTokenType};\n/// let token_type = JsonWebTokenType::new(\"jwt+at\".to_string()).normalize();\n/// // normalized value looks like \"application/jwt+at\"\n/// # assert_eq!(*token_type.unwrap(), \"application/jwt+at\")\n/// ```\n#[derive(Clone, Debug, Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize)]\npub struct NormalizedJsonWebTokenType(String);\n\nimpl std::ops::Deref for NormalizedJsonWebTokenType {\n    type Target = String;\n    fn deref(&self) -> &String {\n        &self.0\n    }\n}\nimpl From<NormalizedJsonWebTokenType> for String {\n    fn from(t: NormalizedJsonWebTokenType) -> String {\n        t.0\n    }\n}\n\nimpl From<NormalizedJsonWebTokenType> for JsonWebTokenType {\n    fn from(t: NormalizedJsonWebTokenType) -> JsonWebTokenType {\n        JsonWebTokenType::new(t.0)\n    }\n}\n\nimpl TryFrom<&JsonWebTokenType> for NormalizedJsonWebTokenType {\n    type Error = InvalidJsonWebTokenTypeError;\n\n    /// Normalizes a [`JsonWebTokenType`] and produces a [`NormalizedJsonWebTokenType`] according to RFC2045.\n    ///\n    /// See [RFC 2045 section 5.1](https://tools.ietf.org/html/rfc2045#section-5.1) for the full Content-Type Header Field spec.\n    /// See [RFC 7515 section 4.19](https://tools.ietf.org/html/rfc7515#section-4.1.9) for specific requirements of JSON Web Token Types.\n    fn try_from(t: &JsonWebTokenType) -> Result<NormalizedJsonWebTokenType, Self::Error> {\n        let lowercase_jwt_type = t.0.to_lowercase();\n        if let Some(slash_location) = lowercase_jwt_type.find('/') {\n            if let Some(semicolon_location) = lowercase_jwt_type.find(';') {\n                // If '/' is not before ';' as then the MIME type is invalid\n                // e.g. some;arg=\"1/2\" is invalid, but application/some;arg=1 is valid\n                // OR\n                // If MIME type has not at least one character\n                // OR\n                // If MIME subtype has not at least one character\n                if slash_location > semicolon_location\n                    || slash_location == 0\n                    || slash_location.saturating_add(1) >= semicolon_location\n                {\n                    Err(InvalidJsonWebTokenTypeError {\n                        typ: lowercase_jwt_type,\n                    })\n                } else {\n                    Ok(NormalizedJsonWebTokenType(lowercase_jwt_type))\n                }\n            } else {\n                Ok(NormalizedJsonWebTokenType(lowercase_jwt_type))\n            }\n        } else {\n            Ok(NormalizedJsonWebTokenType(format!(\n                \"application/{lowercase_jwt_type}\"\n            )))\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum JsonWebTokenAlgorithm<JE, JS>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    Encryption(JE),\n    Signature(JS),\n    /// No digital signature or MAC performed.\n    ///\n    /// # Security Warning\n    ///\n    /// This algorithm provides no security over the integrity of the JSON Web Token. Clients\n    /// should be careful not to rely on unsigned JWT's for security purposes. See\n    /// [Critical vulnerabilities in JSON Web Token libraries](\n    ///     https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) for\n    /// further discussion.\n    None,\n}\nimpl<'de, JE, JS> Deserialize<'de> for JsonWebTokenAlgorithm<JE, JS>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let value: serde_json::Value = Deserialize::deserialize(deserializer)?;\n        // TODO: get rid of this clone() (see below)\n        let s: String = serde_json::from_value(value.clone()).map_err(D::Error::custom)?;\n\n        // NB: These comparisons are case-sensitive. Section 4.1.1 of RFC 7515 states: \"The \"alg\"\n        // value is a case-sensitive ASCII string containing a StringOrURI value.\"\n        if s == \"none\" {\n            Ok(JsonWebTokenAlgorithm::None)\n        // TODO: Figure out a way to deserialize the enums without giving up ownership\n        } else if let Ok(val) = serde_json::from_value::<JE>(value.clone()) {\n            Ok(JsonWebTokenAlgorithm::Encryption(val))\n        } else if let Ok(val) = serde_json::from_value::<JS>(value) {\n            Ok(JsonWebTokenAlgorithm::Signature(val))\n        } else {\n            Err(D::Error::custom(format!(\n                \"unrecognized JSON Web Algorithm `{}`\",\n                s\n            )))\n        }\n    }\n}\nimpl<JE, JS> Serialize for JsonWebTokenAlgorithm<JE, JS>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>\n    where\n        SE: Serializer,\n    {\n        match self {\n            JsonWebTokenAlgorithm::Encryption(ref enc) => enc.serialize(serializer),\n            JsonWebTokenAlgorithm::Signature(ref sig) => sig.serialize(serializer),\n            JsonWebTokenAlgorithm::None => serializer.serialize_str(\"none\"),\n        }\n    }\n}\n\n#[skip_serializing_none]\n#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]\npub struct JsonWebTokenHeader<JE, JS>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    #[serde(\n        bound = \"JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>, JS: JwsSigningAlgorithm\"\n    )]\n    pub alg: JsonWebTokenAlgorithm<JE, JS>,\n    // Additional critical header parameters that must be understood by this implementation. Since\n    // we don't understand any such extensions, we reject any JWT with this value present (the\n    // spec specifically prohibits including public (standard) headers in this field).\n    // See https://tools.ietf.org/html/rfc7515#section-4.1.11.\n    pub crit: Option<Vec<String>>,\n    pub cty: Option<JsonWebTokenContentType>,\n    pub kid: Option<JsonWebKeyId>,\n    pub typ: Option<JsonWebTokenType>,\n    // Other JOSE header fields are omitted since the OpenID Connect spec specifically says that\n    // the \"x5u\", \"x5c\", \"jku\", \"jwk\" header parameter fields SHOULD NOT be used.\n    // See http://openid.net/specs/openid-connect-core-1_0-final.html#IDToken.\n}\n\npub trait JsonWebTokenPayloadSerde<P>: Debug\nwhere\n    P: Debug + DeserializeOwned + Serialize,\n{\n    fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<P, DE>;\n    fn serialize(payload: &P) -> Result<String, serde_json::Error>;\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct JsonWebTokenJsonPayloadSerde;\nimpl<P> JsonWebTokenPayloadSerde<P> for JsonWebTokenJsonPayloadSerde\nwhere\n    P: Debug + DeserializeOwned + Serialize,\n{\n    fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<P, DE> {\n        serde_json::from_slice(payload)\n            .map_err(|err| DE::custom(format!(\"Failed to parse payload JSON: {:?}\", err)))\n    }\n\n    fn serialize(payload: &P) -> Result<String, serde_json::Error> {\n        serde_json::to_string(payload).map_err(Into::into)\n    }\n}\n\n// Helper trait so that we can get borrowed payload when we have a reference to the JWT and owned\n// payload when we own the JWT.\npub trait JsonWebTokenAccess<JE, JS, P>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n{\n    type ReturnType;\n\n    fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS>;\n    fn unverified_payload(self) -> Self::ReturnType;\n    fn unverified_payload_ref(&self) -> &P;\n\n    fn payload<K>(\n        self,\n        signature_alg: &JS,\n        key: &K,\n    ) -> Result<Self::ReturnType, SignatureVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>;\n\n    fn signing_alg(&self) -> Result<&JS, SignatureVerificationError> {\n        match self.unverified_header().alg {\n            JsonWebTokenAlgorithm::Signature(ref signing_alg) => Ok(signing_alg),\n            JsonWebTokenAlgorithm::Encryption(ref other) => {\n                Err(SignatureVerificationError::UnsupportedAlg(\n                    serde_plain::to_string(other).unwrap_or_else(|err| {\n                        panic!(\n                            \"encryption alg {:?} failed to serialize to a string: {}\",\n                            other, err\n                        )\n                    }),\n                ))\n            }\n            // Section 2 of OpenID Connect Core 1.0 specifies that \"ID Tokens MUST NOT use\n            // none as the alg value unless the Response Type used returns no ID Token from\n            // the Authorization Endpoint (such as when using the Authorization Code Flow)\n            // and the Client explicitly requested the use of none at Registration time.\"\n            //\n            // While there's technically a use case where this is ok, we choose not to\n            // support it for now to protect against accidental misuse. If demand arises,\n            // we can figure out a API that mitigates the risk.\n            JsonWebTokenAlgorithm::None => Err(SignatureVerificationError::NoSignature),\n        }\n    }\n}\n\n/// Error creating a JSON Web Token.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum JsonWebTokenError {\n    /// Failed to serialize JWT.\n    #[error(\"Failed to serialize JWT\")]\n    SerializationError(#[source] serde_json::Error),\n    /// Failed to sign JWT.\n    #[error(\"Failed to sign JWT\")]\n    SigningError(#[source] SigningError),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct JsonWebToken<JE, JS, P, S>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n    S: JsonWebTokenPayloadSerde<P>,\n{\n    header: JsonWebTokenHeader<JE, JS>,\n    payload: P,\n    signature: Vec<u8>,\n    signing_input: String,\n    _phantom: PhantomData<S>,\n}\nimpl<JE, JS, P, S> JsonWebToken<JE, JS, P, S>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n    S: JsonWebTokenPayloadSerde<P>,\n{\n    pub fn new<SK>(payload: P, signing_key: &SK, alg: &JS) -> Result<Self, JsonWebTokenError>\n    where\n        SK: PrivateSigningKey,\n        <SK as PrivateSigningKey>::VerificationKey: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        let header = JsonWebTokenHeader::<JE, _> {\n            alg: JsonWebTokenAlgorithm::Signature(alg.clone()),\n            crit: None,\n            cty: None,\n            kid: signing_key.as_verification_key().key_id().cloned(),\n            typ: None,\n        };\n\n        let header_json =\n            serde_json::to_string(&header).map_err(JsonWebTokenError::SerializationError)?;\n        let header_base64 = BASE64_URL_SAFE_NO_PAD.encode(header_json);\n\n        let serialized_payload =\n            S::serialize(&payload).map_err(JsonWebTokenError::SerializationError)?;\n        let payload_base64 = BASE64_URL_SAFE_NO_PAD.encode(serialized_payload);\n\n        let signing_input = format!(\"{}.{}\", header_base64, payload_base64);\n\n        let signature = signing_key\n            .sign(alg, signing_input.as_bytes())\n            .map_err(JsonWebTokenError::SigningError)?;\n\n        Ok(JsonWebToken {\n            header,\n            payload,\n            signature,\n            signing_input,\n            _phantom: PhantomData,\n        })\n    }\n}\n// Owned JWT.\nimpl<JE, JS, P, S> JsonWebTokenAccess<JE, JS, P> for JsonWebToken<JE, JS, P, S>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n    S: JsonWebTokenPayloadSerde<P>,\n{\n    type ReturnType = P;\n    fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS> {\n        &self.header\n    }\n    fn unverified_payload(self) -> Self::ReturnType {\n        self.payload\n    }\n    fn unverified_payload_ref(&self) -> &P {\n        &self.payload\n    }\n    fn payload<K>(\n        self,\n        signature_alg: &JS,\n        key: &K,\n    ) -> Result<Self::ReturnType, SignatureVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        key.verify_signature(\n            signature_alg,\n            self.signing_input.as_bytes(),\n            &self.signature,\n        )?;\n        Ok(self.payload)\n    }\n}\n// Borrowed JWT.\nimpl<'a, JE, JS, P, S> JsonWebTokenAccess<JE, JS, P> for &'a JsonWebToken<JE, JS, P, S>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n    S: JsonWebTokenPayloadSerde<P>,\n{\n    type ReturnType = &'a P;\n    fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS> {\n        &self.header\n    }\n    fn unverified_payload(self) -> Self::ReturnType {\n        &self.payload\n    }\n    fn unverified_payload_ref(&self) -> &P {\n        &self.payload\n    }\n    fn payload<K>(\n        self,\n        signature_alg: &JS,\n        key: &K,\n    ) -> Result<Self::ReturnType, SignatureVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        key.verify_signature(\n            signature_alg,\n            self.signing_input.as_bytes(),\n            &self.signature,\n        )?;\n        Ok(&self.payload)\n    }\n}\nimpl<'de, JE, JS, P, S> Deserialize<'de> for JsonWebToken<JE, JS, P, S>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n    S: JsonWebTokenPayloadSerde<P>,\n{\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct JsonWebTokenVisitor<\n            JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n            JS: JwsSigningAlgorithm,\n            P: Debug + DeserializeOwned + Serialize,\n            S: JsonWebTokenPayloadSerde<P>,\n        >(PhantomData<(JE, JS, P, S)>);\n        impl<'de, JE, JS, P, S> Visitor<'de> for JsonWebTokenVisitor<JE, JS, P, S>\n        where\n            JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n            JS: JwsSigningAlgorithm,\n            P: Debug + DeserializeOwned + Serialize,\n            S: JsonWebTokenPayloadSerde<P>,\n        {\n            type Value = JsonWebToken<JE, JS, P, S>;\n\n            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n                formatter.write_str(\"JsonWebToken\")\n            }\n\n            fn visit_str<DE>(self, v: &str) -> Result<Self::Value, DE>\n            where\n                DE: serde::de::Error,\n            {\n                let raw_token = v.to_string();\n                let header: JsonWebTokenHeader<JE, JS>;\n                let payload: P;\n                let signature;\n                let signing_input;\n\n                {\n                    let parts = raw_token.split('.').collect::<Vec<_>>();\n\n                    // NB: We avoid including the full payload encoding in the error output to avoid\n                    // clients potentially logging sensitive values.\n                    if parts.len() != 3 {\n                        return Err(DE::custom(format!(\n                            \"Invalid JSON web token: found {} parts (expected 3)\",\n                            parts.len()\n                        )));\n                    }\n\n                    let header_json = crate::core::base64_url_safe_no_pad()\n                        .decode(parts[0])\n                        .map_err(|err| {\n                            DE::custom(format!(\"Invalid base64url header encoding: {:?}\", err))\n                        })?;\n                    header = serde_json::from_slice(&header_json).map_err(|err| {\n                        DE::custom(format!(\"Failed to parse header JSON: {:?}\", err))\n                    })?;\n\n                    let raw_payload = crate::core::base64_url_safe_no_pad()\n                        .decode(parts[1])\n                        .map_err(|err| {\n                            DE::custom(format!(\"Invalid base64url payload encoding: {:?}\", err))\n                        })?;\n                    payload = S::deserialize::<DE>(&raw_payload)?;\n\n                    signature = crate::core::base64_url_safe_no_pad()\n                        .decode(parts[2])\n                        .map_err(|err| {\n                            DE::custom(format!(\"Invalid base64url signature encoding: {:?}\", err))\n                        })?;\n\n                    signing_input = format!(\"{}.{}\", parts[0], parts[1]);\n                }\n\n                Ok(JsonWebToken {\n                    header,\n                    payload,\n                    signature,\n                    signing_input,\n                    _phantom: PhantomData,\n                })\n            }\n        }\n        deserializer.deserialize_str(JsonWebTokenVisitor(PhantomData))\n    }\n}\nimpl<JE, JS, P, S> Serialize for JsonWebToken<JE, JS, P, S>\nwhere\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    P: Debug + DeserializeOwned + Serialize,\n    S: JsonWebTokenPayloadSerde<P>,\n{\n    fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>\n    where\n        SE: Serializer,\n    {\n        let signature_base64 = BASE64_URL_SAFE_NO_PAD.encode(&self.signature);\n        serializer.serialize_str(&format!(\"{}.{}\", self.signing_input, signature_base64))\n    }\n}\n"
  },
  {
    "path": "src/jwt/tests.rs",
    "content": "use crate::core::{\n    CoreJsonWebKey, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm,\n    CoreRsaPrivateSigningKey,\n};\nuse crate::jwt::{\n    InvalidJsonWebTokenTypeError, JsonWebToken, JsonWebTokenAccess, JsonWebTokenAlgorithm,\n    JsonWebTokenJsonPayloadSerde, JsonWebTokenPayloadSerde,\n};\nuse crate::{JsonWebKeyId, JsonWebTokenType};\n\nuse serde::{Deserialize, Serialize};\n\nuse std::string::ToString;\n\ntype CoreAlgorithm =\n    JsonWebTokenAlgorithm<CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>;\n\npub const TEST_JWT: &str =\n    \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZ\\\n         GFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGU\\\n         gcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlc\\\n         mUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e\\\n         5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3l\\\n         fWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV\\\n         0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41\\\n         Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg\";\n\nconst TEST_JWT_PAYLOAD: &str = \"It\\u{2019}s a dangerous business, Frodo, going out your \\\n                                    door. You step onto the road, and if you don't keep your feet, \\\n                                    there\\u{2019}s no knowing where you might be swept off \\\n                                    to.\";\n\npub const TEST_RSA_PUB_KEY: &str = \"{\n            \\\"kty\\\": \\\"RSA\\\",\n            \\\"kid\\\": \\\"bilbo.baggins@hobbiton.example\\\",\n            \\\"use\\\": \\\"sig\\\",\n            \\\"n\\\": \\\"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT\\\n                     -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV\\\n                     wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-\\\n                     oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde\\\n                     3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC\\\n                     LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g\\\n                     HdrNP5zw\\\",\n            \\\"e\\\": \\\"AQAB\\\"\n        }\";\n\npub const TEST_ED_PUB_KEY_ED25519: &str = r#\"{\n        \"kty\": \"OKP\",\n        \"use\": \"sig\",\n        \"alg\": \"EdDSA\",\n        \"crv\": \"Ed25519\",\n        \"x\": \"sfliRRhciU_d5qsuC5Vcydi-t8bRfxTg_4qulVatW4A\"\n    }\"#;\n\npub const TEST_EC_PUB_KEY_P256: &str = r#\"{\n        \"kty\": \"EC\",\n        \"kid\": \"bilbo.baggins@hobbiton.example\",\n        \"use\": \"sig\",\n        \"crv\": \"P-256\",\n        \"x\": \"t6PHivOTggpaX9lkMkis2p8kMhy-CktJAFTz6atReZw\",\n        \"y\": \"ODobXupKlD0DeM1yRd7bX4XFNBO1HOgCT1UCu0KY3lc\"\n    }\"#;\npub const TEST_EC_PUB_KEY_P384: &str = r#\"{\n        \"kty\": \"EC\",\n        \"kid\": \"bilbo.baggins@hobbiton.example\",\n        \"use\": \"sig\",\n        \"crv\" : \"P-384\",\n        \"x\": \"9ywsUbxX59kJXFRiWHcx97wRKNiF8Hc9F5wI08n8h2ek_qAl0veEc36k1Qz6KLiL\",\n        \"y\": \"6PWlqjRbaV7V8ohDscM243IneuLZmxDGLiGNA1w69fQhEDsvZtKLUQ5KiHLgR3op\"\n    }\"#;\n\n// This is the PEM form of the test private key from:\n// https://tools.ietf.org/html/rfc7520#section-3.4\npub const TEST_RSA_PRIV_KEY: &str = \"-----BEGIN RSA PRIVATE KEY-----\\n\\\n         MIIEowIBAAKCAQEAn4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8/KuKPEHLd4\\n\\\n         rHVTeT+O+XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz/AJmSCpMaJMRBSFKrKb2wqVwG\\n\\\n         U/NsYOYL+QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj+oBHqFEHYpP\\n\\\n         e7Tpe+OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzw\\n\\\n         OHrtIQbS0FVbb9k3+tVTU4fg/3L/vniUFAKwuCLqKnS2BYwdq/mzSnbLY7h/qixo\\n\\\n         R7jig3//kRhuaxwUkRz5iaiQkqgc5gHdrNP5zwIDAQABAoIBAG1lAvQfhBUSKPJK\\n\\\n         Rn4dGbshj7zDSr2FjbQf4pIh/ZNtHk/jtavyO/HomZKV8V0NFExLNi7DUUvvLiW7\\n\\\n         0PgNYq5MDEjJCtSd10xoHa4QpLvYEZXWO7DQPwCmRofkOutf+NqyDS0QnvFvp2d+\\n\\\n         Lov6jn5C5yvUFgw6qWiLAPmzMFlkgxbtjFAWMJB0zBMy2BqjntOJ6KnqtYRMQUxw\\n\\\n         TgXZDF4rhYVKtQVOpfg6hIlsaoPNrF7dofizJ099OOgDmCaEYqM++bUlEHxgrIVk\\n\\\n         wZz+bg43dfJCocr9O5YX0iXaz3TOT5cpdtYbBX+C/5hwrqBWru4HbD3xz8cY1TnD\\n\\\n         qQa0M8ECgYEA3Slxg/DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex/fp7AZ/9\\n\\\n         nRaO7HX/+SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr/WCsmGpeNqQn\\n\\\n         ev1T7IyEsnh8UMt+n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0kCgYEAuKE2\\n\\\n         dh+cTf6ERF4k4e/jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR/cu0Dm1MZwW\\n\\\n         mtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoB\\n\\\n         vyY898EXvRD+hdqRxHlSqAZ192zB3pVFJ0s7pFcCgYAHw9W9eS8muPYv4ZhDu/fL\\n\\\n         2vorDmD1JqFcHCxZTOnX1NWWAj5hXzmrU0hvWvFC0P4ixddHf5Nqd6+5E9G3k4E5\\n\\\n         2IwZCnylu3bqCWNh8pT8T3Gf5FQsfPT5530T2BcsoPhUaeCnP499D+rb2mTnFYeg\\n\\\n         mnTT1B/Ue8KGLFFfn16GKQKBgAiw5gxnbocpXPaO6/OKxFFZ+6c0OjxfN2PogWce\\n\\\n         TU/k6ZzmShdaRKwDFXisxRJeNQ5Rx6qgS0jNFtbDhW8E8WFmQ5urCOqIOYk28EBi\\n\\\n         At4JySm4v+5P7yYBh8B8YD2l9j57z/s8hJAxEbn/q8uHP2ddQqvQKgtsni+pHSk9\\n\\\n         XGBfAoGBANz4qr10DdM8DHhPrAb2YItvPVz/VwkBd1Vqj8zCpyIEKe/07oKOvjWQ\\n\\\n         SgkLDH9x2hBgY01SbP43CvPk0V72invu2TGkI/FXwXWJLLG7tDSgw4YyfhrYrHmg\\n\\\n         1Vre3XB9HH8MYBVB6UIexaAq4xSeoemRKTBesZro7OKjKT8/GmiO\\n\\\n         -----END RSA PRIVATE KEY-----\";\n\n#[test]\nfn test_jwt_algorithm_deserialization() {\n    assert_eq!(\n        serde_json::from_str::<CoreAlgorithm>(\"\\\"A128CBC-HS256\\\"\").expect(\"failed to deserialize\"),\n        JsonWebTokenAlgorithm::Encryption(CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256),\n    );\n    assert_eq!(\n        serde_json::from_str::<CoreAlgorithm>(\"\\\"A128GCM\\\"\").expect(\"failed to deserialize\"),\n        JsonWebTokenAlgorithm::Encryption(CoreJweContentEncryptionAlgorithm::Aes128Gcm),\n    );\n    assert_eq!(\n        serde_json::from_str::<CoreAlgorithm>(\"\\\"HS256\\\"\").expect(\"failed to deserialize\"),\n        JsonWebTokenAlgorithm::Signature(CoreJwsSigningAlgorithm::HmacSha256),\n    );\n    assert_eq!(\n        serde_json::from_str::<CoreAlgorithm>(\"\\\"RS256\\\"\").expect(\"failed to deserialize\"),\n        JsonWebTokenAlgorithm::Signature(CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256),\n    );\n    assert_eq!(\n        serde_json::from_str::<CoreAlgorithm>(\"\\\"none\\\"\").expect(\"failed to deserialize\"),\n        JsonWebTokenAlgorithm::None,\n    );\n\n    serde_json::from_str::<CoreAlgorithm>(\"\\\"invalid\\\"\")\n        .expect_err(\"deserialization should have failed\");\n}\n\n#[test]\nfn test_jwt_algorithm_serialization() {\n    assert_eq!(\n        serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Encryption(\n            CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256\n        ))\n        .expect(\"failed to serialize\"),\n        \"\\\"A128CBC-HS256\\\"\",\n    );\n    assert_eq!(\n        serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Encryption(\n            CoreJweContentEncryptionAlgorithm::Aes128Gcm\n        ))\n        .expect(\"failed to serialize\"),\n        \"\\\"A128GCM\\\"\",\n    );\n    assert_eq!(\n        serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Signature(\n            CoreJwsSigningAlgorithm::HmacSha256\n        ))\n        .expect(\"failed to serialize\"),\n        \"\\\"HS256\\\"\",\n    );\n    assert_eq!(\n        serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Signature(\n            CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256\n        ))\n        .expect(\"failed to serialize\"),\n        \"\\\"RS256\\\"\",\n    );\n    assert_eq!(\n        serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::None)\n            .expect(\"failed to serialize\"),\n        \"\\\"none\\\"\",\n    );\n}\n\n#[derive(Clone, Debug)]\npub struct JsonWebTokenStringPayloadSerde;\nimpl JsonWebTokenPayloadSerde<String> for JsonWebTokenStringPayloadSerde {\n    fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<String, DE> {\n        Ok(String::from_utf8(payload.to_owned()).unwrap())\n    }\n    fn serialize(payload: &String) -> Result<String, serde_json::Error> {\n        Ok(payload.to_string())\n    }\n}\n\n#[test]\nfn test_jwt_basic() {\n    fn verify_jwt<A>(jwt_access: A, key: &CoreJsonWebKey, expected_payload: &str)\n    where\n        A: JsonWebTokenAccess<CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, String>,\n        A::ReturnType: ToString,\n    {\n        {\n            let header = jwt_access.unverified_header();\n            assert_eq!(\n                header.alg,\n                JsonWebTokenAlgorithm::Signature(CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256)\n            );\n            assert_eq!(header.crit, None);\n            assert_eq!(header.cty, None);\n            assert_eq!(\n                header.kid,\n                Some(JsonWebKeyId::new(\n                    \"bilbo.baggins@hobbiton.example\".to_string()\n                ))\n            );\n            assert_eq!(header.typ, None);\n        }\n        assert_eq!(jwt_access.unverified_payload_ref(), expected_payload);\n\n        assert_eq!(\n            jwt_access\n                .payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, key)\n                .expect(\"failed to validate payload\")\n                .to_string(),\n            expected_payload\n        );\n    }\n\n    let key: CoreJsonWebKey =\n        serde_json::from_str(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    let jwt: JsonWebToken<\n        CoreJweContentEncryptionAlgorithm,\n        CoreJwsSigningAlgorithm,\n        String,\n        JsonWebTokenStringPayloadSerde,\n    > = serde_json::from_value(serde_json::Value::String(TEST_JWT.to_string()))\n        .expect(\"failed to deserialize\");\n\n    assert_eq!(\n        serde_json::to_value(&jwt).expect(\"failed to serialize\"),\n        serde_json::Value::String(TEST_JWT.to_string())\n    );\n\n    verify_jwt(&jwt, &key, TEST_JWT_PAYLOAD);\n    assert_eq!((&jwt).unverified_payload(), TEST_JWT_PAYLOAD);\n\n    verify_jwt(jwt, &key, TEST_JWT_PAYLOAD);\n}\n\n#[test]\nfn test_new_jwt() {\n    let signing_key = CoreRsaPrivateSigningKey::from_pem(\n        TEST_RSA_PRIV_KEY,\n        Some(JsonWebKeyId::new(\n            \"bilbo.baggins@hobbiton.example\".to_string(),\n        )),\n    )\n    .unwrap();\n    let new_jwt = JsonWebToken::<\n        CoreJweContentEncryptionAlgorithm,\n        _,\n        _,\n        JsonWebTokenStringPayloadSerde,\n    >::new(\n        TEST_JWT_PAYLOAD.to_owned(),\n        &signing_key,\n        &CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n    )\n    .unwrap();\n    assert_eq!(\n        serde_json::to_value(new_jwt).expect(\"failed to serialize\"),\n        serde_json::Value::String(TEST_JWT.to_string())\n    );\n}\n\n#[test]\nfn test_invalid_signature() {\n    let corrupted_jwt_str = TEST_JWT\n        .to_string()\n        .chars()\n        .take(TEST_JWT.len() - 1)\n        .collect::<String>()\n        + \"f\";\n    let jwt: JsonWebToken<\n        CoreJweContentEncryptionAlgorithm,\n        CoreJwsSigningAlgorithm,\n        String,\n        JsonWebTokenStringPayloadSerde,\n    > = serde_json::from_value(serde_json::Value::String(corrupted_jwt_str))\n        .expect(\"failed to deserialize\");\n    let key: CoreJsonWebKey =\n        serde_json::from_str(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    // JsonWebTokenAccess for reference.\n    (&jwt)\n        .payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, &key)\n        .expect_err(\"signature verification should have failed\");\n\n    // JsonWebTokenAccess for owned value.\n    jwt.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, &key)\n        .expect_err(\"signature verification should have failed\");\n}\n\n#[test]\nfn test_invalid_deserialization() {\n    #[derive(Debug, Deserialize, Serialize)]\n    struct TestPayload {\n        foo: String,\n    }\n\n    fn expect_deserialization_err<I: Into<String>>(jwt_str: I, pattern: &str) {\n        let err = serde_json::from_value::<\n            JsonWebToken<\n                CoreJweContentEncryptionAlgorithm,\n                CoreJwsSigningAlgorithm,\n                TestPayload,\n                JsonWebTokenJsonPayloadSerde,\n            >,\n        >(serde_json::Value::String(jwt_str.into()))\n        .expect_err(\"deserialization should have failed\");\n\n        assert!(\n            err.to_string().contains(pattern),\n            \"Error `{}` must contain string `{}`\",\n            err,\n            pattern,\n        );\n    }\n\n    // Too many dots\n    expect_deserialization_err(\"a.b.c.d\", \"found 4 parts (expected 3)\");\n\n    // Invalid header base64\n    expect_deserialization_err(\"a!.b.c\", \"Invalid base64url header encoding\");\n\n    // Invalid header utf-8 (after base64 decoding)\n    expect_deserialization_err(\"gA.b.c\", \"Error(\\\"expected value\\\", line: 1, column: 1)\");\n\n    // Invalid header JSON\n    expect_deserialization_err(\"bm90X2pzb24.b.c\", \"Failed to parse header JSON\");\n\n    let valid_header = \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9\";\n\n    // Invalid payload base64\n    expect_deserialization_err(\n        format!(\"{}.b!.c\", valid_header),\n        \"Invalid base64url payload encoding\",\n    );\n\n    // Invalid payload utf-8 (after base64 decoding)\n    expect_deserialization_err(\n        format!(\"{}.gA.c\", valid_header),\n        \"Error(\\\"expected value\\\", line: 1, column: 1)\",\n    );\n\n    // Invalid payload JSON\n    expect_deserialization_err(\n        format!(\"{}.bm90X2pzb24.c\", valid_header),\n        \"Failed to parse payload JSON\",\n    );\n\n    let valid_body = \"eyJmb28iOiAiYmFyIn0\";\n\n    // Invalid signature base64\n    expect_deserialization_err(\n        format!(\"{}.{}.c!\", valid_header, valid_body),\n        \"Invalid base64url signature encoding\",\n    );\n\n    let deserialized = serde_json::from_value::<\n        JsonWebToken<\n            CoreJweContentEncryptionAlgorithm,\n            CoreJwsSigningAlgorithm,\n            TestPayload,\n            JsonWebTokenJsonPayloadSerde,\n        >,\n    >(serde_json::Value::String(format!(\n        \"{}.{}.e2FiY30\",\n        valid_header, valid_body\n    )))\n    .expect(\"failed to deserialize\");\n    assert_eq!(deserialized.unverified_payload().foo, \"bar\");\n}\n\n#[test]\nfn test_json_web_token_type_normalization() {\n    fn assert_token_type_normalization(\n        jwt_type_string: &str,\n        expected_normalized_jwt_type_string: &str,\n    ) -> Result<(), InvalidJsonWebTokenTypeError> {\n        let jwt_type = JsonWebTokenType::new(jwt_type_string.to_string());\n        let normalized_jwt_type = jwt_type.normalize()?;\n\n        assert_eq!(*normalized_jwt_type, expected_normalized_jwt_type_string);\n        Ok(())\n    }\n\n    assert_token_type_normalization(\"jwt\", \"application/jwt\").unwrap();\n    assert_token_type_normalization(\"jwt;arg=some\", \"application/jwt;arg=some\").unwrap();\n    assert!(assert_token_type_normalization(\"jwt;arg=some/other\", \"\").is_err());\n    assert!(assert_token_type_normalization(\"/jwt;arg=some/other\", \"\").is_err());\n    assert!(assert_token_type_normalization(\"application/;arg=some/other\", \"\").is_err());\n    assert_token_type_normalization(\"application/jwt\", \"application/jwt\").unwrap();\n    assert_token_type_normalization(\n        \"application/jwt;arg=some/other\",\n        \"application/jwt;arg=some/other\",\n    )\n    .unwrap();\n    assert_token_type_normalization(\"special/type\", \"special/type\").unwrap();\n    assert_token_type_normalization(\"special/type;arg=some\", \"special/type;arg=some\").unwrap();\n    assert_token_type_normalization(\"s/t;arg=some/o\", \"s/t;arg=some/o\").unwrap();\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "#![warn(missing_docs)]\n#![allow(clippy::unreadable_literal, clippy::type_complexity)]\n#![cfg_attr(test, allow(clippy::cognitive_complexity))]\n//!\n//! [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) library.\n//!\n//! This library provides extensible, strongly-typed interfaces for the OpenID Connect protocol.\n//! For convenience, the [`core`] module provides type aliases for common usage that adheres to the\n//! [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html) spec. Users of\n//! this crate may define their own extensions and custom type parameters in lieu of using the\n//! [`core`] module.\n//!\n//! # Contents\n//!  * [Importing `openidconnect`: selecting an HTTP client interface](#importing-openidconnect-selecting-an-http-client-interface)\n//!  * [OpenID Connect Relying Party (Client) Interface](#openid-connect-relying-party-client-interface)\n//!    * [Examples](#examples)\n//!    * [Getting started: Authorization Code Grant w/ PKCE](#getting-started-authorization-code-grant-w-pkce)\n//!  * [OpenID Connect Provider (Server) Interface](#openid-connect-provider-server-interface)\n//!    * [OpenID Connect Discovery document](#openid-connect-discovery-document)\n//!    * [OpenID Connect Discovery JSON Web Key Set](#openid-connect-discovery-json-web-key-set)\n//!    * [OpenID Connect ID Token](#openid-connect-id-token)\n//!  * [Asynchronous API](#asynchronous-api)\n//!\n//! # Importing `openidconnect`: selecting an HTTP client interface\n//!\n//!\n//! This library offers a flexible HTTP client interface with two modes:\n//!  * **Synchronous (blocking)**\n//!\n//!    NOTE: Be careful not to use a blocking HTTP client within `async` Rust code, which may panic\n//!    or cause other issues. The\n//!    [`tokio::task::spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)\n//!    function may be useful in this situation.\n//!  * **Asynchronous**\n//!\n//! ## Security Warning\n//!\n//! To prevent\n//! [SSRF](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)\n//! vulnerabilities, be sure to configure the HTTP client **not to follow redirects**. For example,\n//! use [`redirect::Policy::none`](reqwest::redirect::Policy::none) when using\n//! [`reqwest`], or [`redirects(0)`](ureq::AgentBuilder::redirects) when using [`ureq`].\n//!\n//! ## HTTP Clients\n//!\n//! For the HTTP client modes described above, the following HTTP client implementations can be\n//! used:\n//!  * **[`reqwest`]**\n//!\n//!    The `reqwest` HTTP client supports both the synchronous and asynchronous modes and is enabled\n//!    by default.\n//!\n//!    Synchronous client: [`reqwest::blocking::Client`]\n//!    (requires the `reqwest-blocking` feature flag)\n//!\n//!    Asynchronous client: [`reqwest::Client`] (requires either\n//!    the `reqwest` or `reqwest-blocking` feature flags)\n//!\n//!  * **[`curl`]**\n//!\n//!    The `curl` HTTP client only supports the synchronous HTTP client mode and can be enabled in\n//!    `Cargo.toml` via the `curl` feature flag.\n//!\n//!    Synchronous client: [`CurlHttpClient`]\n//!\n//! * **[`ureq`]**\n//!\n//!    The `ureq` HTTP client is a simple HTTP client with minimal dependencies. It only supports\n//!    the synchronous HTTP client mode and can be enabled in `Cargo.toml` via the `ureq` feature\n//!    flag.\n//!\n//!    Synchronous client: [`ureq::Agent`]\n//!\n//!  * **Custom**\n//!\n//!    In addition to the clients above, users may define their own HTTP clients, which must accept\n//!    an [`HttpRequest`] and return an [`HttpResponse`] or error. Users writing their own clients\n//!    may wish to disable the default `reqwest` dependency by specifying\n//!    `default-features = false` in `Cargo.toml` (replacing `...` with the desired version of this\n//!    crate):\n//!    ```toml\n//!    openidconnect = { version = \"...\", default-features = false }\n//!    ```\n//!\n//!    Synchronous HTTP clients should implement the [`SyncHttpClient`] trait, which is\n//!    automatically implemented for any function/closure that implements:\n//!    ```rust,ignore\n//!    Fn(HttpRequest) -> Result<HttpResponse, E>\n//!    where\n//!      E: std::error::Error + 'static\n//!    ```\n//!\n//!    Asynchronous HTTP clients should implement the [`AsyncHttpClient`] trait, which is\n//!    automatically implemented for any function/closure that implements:\n//!    ```rust,ignore\n//!    Fn(HttpRequest) -> F\n//!    where\n//!      E: std::error::Error + 'static,\n//!      F: Future<Output = Result<HttpResponse, E>>,\n//!    ```\n//!\n//! # Comparing secrets securely\n//!\n//! OpenID Connect flows require comparing secrets received from providers. To do so securely\n//! while avoiding [timing side-channels](https://en.wikipedia.org/wiki/Timing_attack), the\n//! comparison must be done in constant time, either using a constant-time crate such as\n//! [`constant_time_eq`](https://crates.io/crates/constant_time_eq) (which could break if a future\n//! compiler version decides to be overly smart\n//! about its optimizations), or by first computing a cryptographically-secure hash (e.g., SHA-256)\n//! of both values and then comparing the hashes using `==`.\n//!\n//! The `timing-resistant-secret-traits` feature flag adds a safe (but comparatively expensive)\n//! [`PartialEq`] implementation to the secret types. Timing side-channels are why [`PartialEq`] is\n//! not auto-derived for this crate's secret types, and the lack of [`PartialEq`] is intended to\n//! prompt users to think more carefully about these comparisons.\n//!\n//! # OpenID Connect Relying Party (Client) Interface\n//!\n//! The [`Client`] struct provides the OpenID Connect Relying Party interface. The most common\n//! usage is provided by the [`core::CoreClient`] type alias.\n//!\n//! ## Examples\n//!\n//! * [Google](https://github.com/ramosbugs/openidconnect-rs/tree/main/examples/google.rs)\n//!\n//! ## Getting started: Authorization Code Grant w/ PKCE\n//!\n//! This is the most common OIDC/OAuth2 flow. PKCE is recommended whenever the client has no\n//! client secret or has a client secret that cannot remain confidential (e.g., native, mobile, or\n//! client-side web applications).\n//!\n//! ### Example\n//!\n//! ```rust,no_run\n//! use anyhow::anyhow;\n//! use openidconnect::{\n//!     AccessTokenHash,\n//!     AuthenticationFlow,\n//!     AuthorizationCode,\n//!     ClientId,\n//!     ClientSecret,\n//!     CsrfToken,\n//!     IssuerUrl,\n//!     Nonce,\n//!     OAuth2TokenResponse,\n//!     PkceCodeChallenge,\n//!     RedirectUrl,\n//!     Scope,\n//!     TokenResponse,\n//! };\n//! use openidconnect::core::{\n//!   CoreAuthenticationFlow,\n//!   CoreClient,\n//!   CoreProviderMetadata,\n//!   CoreResponseType,\n//!   CoreUserInfoClaims,\n//! };\n//! # #[cfg(feature = \"reqwest-blocking\")]\n//! use openidconnect::reqwest;\n//! use url::Url;\n//!\n//! # #[cfg(feature = \"reqwest-blocking\")]\n//! # fn err_wrapper() -> Result<(), anyhow::Error> {\n//! let http_client = reqwest::blocking::ClientBuilder::new()\n//!     // Following redirects opens the client up to SSRF vulnerabilities.\n//!     .redirect(reqwest::redirect::Policy::none())\n//!     .build()\n//!     .expect(\"Client should build\");\n//!\n//! // Use OpenID Connect Discovery to fetch the provider metadata.\n//! let provider_metadata = CoreProviderMetadata::discover(\n//!     &IssuerUrl::new(\"https://accounts.example.com\".to_string())?,\n//!     &http_client,\n//! )?;\n//!\n//! // Create an OpenID Connect client by specifying the client ID, client secret, authorization URL\n//! // and token URL.\n//! let client =\n//!     CoreClient::from_provider_metadata(\n//!         provider_metadata,\n//!         ClientId::new(\"client_id\".to_string()),\n//!         Some(ClientSecret::new(\"client_secret\".to_string())),\n//!     )\n//!     // Set the URL the user will be redirected to after the authorization process.\n//!     .set_redirect_uri(RedirectUrl::new(\"http://redirect\".to_string())?);\n//!\n//! // Generate a PKCE challenge.\n//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();\n//!\n//! // Generate the full authorization URL.\n//! let (auth_url, csrf_token, nonce) = client\n//!     .authorize_url(\n//!         CoreAuthenticationFlow::AuthorizationCode,\n//!         CsrfToken::new_random,\n//!         Nonce::new_random,\n//!     )\n//!     // Set the desired scopes.\n//!     .add_scope(Scope::new(\"read\".to_string()))\n//!     .add_scope(Scope::new(\"write\".to_string()))\n//!     // Set the PKCE code challenge.\n//!     .set_pkce_challenge(pkce_challenge)\n//!     .url();\n//!\n//! // This is the URL you should redirect the user to, in order to trigger the authorization\n//! // process.\n//! println!(\"Browse to: {}\", auth_url);\n//!\n//! // Once the user has been redirected to the redirect URL, you'll have access to the\n//! // authorization code. For security reasons, your code should verify that the `state`\n//! // parameter returned by the server matches `csrf_state`.\n//!\n//! // Now you can exchange it for an access token and ID token.\n//! let token_response =\n//!     client\n//!         .exchange_code(AuthorizationCode::new(\"some authorization code\".to_string()))?\n//!         // Set the PKCE code verifier.\n//!         .set_pkce_verifier(pkce_verifier)\n//!         .request(&http_client)?;\n//!\n//! // Extract the ID token claims after verifying its authenticity and nonce.\n//! let id_token = token_response\n//!   .id_token()\n//!   .ok_or_else(|| anyhow!(\"Server did not return an ID token\"))?;\n//! let id_token_verifier = client.id_token_verifier();\n//! let claims = id_token.claims(&id_token_verifier, &nonce)?;\n//!\n//! // Verify the access token hash to ensure that the access token hasn't been substituted for\n//! // another user's.\n//! if let Some(expected_access_token_hash) = claims.access_token_hash() {\n//!     let actual_access_token_hash = AccessTokenHash::from_token(\n//!         token_response.access_token(),\n//!         id_token.signing_alg()?,\n//!         id_token.signing_key(&id_token_verifier)?,\n//!     )?;\n//!     if actual_access_token_hash != *expected_access_token_hash {\n//!         return Err(anyhow!(\"Invalid access token\"));\n//!     }\n//! }\n//!\n//! // The authenticated user's identity is now available. See the IdTokenClaims struct for a\n//! // complete listing of the available claims.\n//! println!(\n//!     \"User {} with e-mail address {} has authenticated successfully\",\n//!     claims.subject().as_str(),\n//!     claims.email().map(|email| email.as_str()).unwrap_or(\"<not provided>\"),\n//! );\n//!\n//! // If available, we can use the user info endpoint to request additional information.\n//!\n//! // The user_info request uses the AccessToken returned in the token response. To parse custom\n//! // claims, use UserInfoClaims directly (with the desired type parameters) rather than using the\n//! // CoreUserInfoClaims type alias.\n//! let userinfo: CoreUserInfoClaims = client\n//!     .user_info(token_response.access_token().to_owned(), None)?\n//!     .request(&http_client)\n//!     .map_err(|err| anyhow!(\"Failed requesting user info: {}\", err))?;\n//!\n//! // See the OAuth2TokenResponse trait for a listing of other available fields such as\n//! // access_token() and refresh_token().\n//!\n//! # Ok(())\n//! # }\n//! ```\n//!\n//! # OpenID Connect Provider (Server) Interface\n//!\n//! This library does not implement a complete OpenID Connect Provider, which requires\n//! functionality such as credential and session management. However, it does provide\n//! strongly-typed interfaces for parsing and building OpenID Connect protocol messages.\n//!\n//! ## OpenID Connect Discovery document\n//!\n//! The [`ProviderMetadata`] struct implements the\n//! [OpenID Connect Discovery document](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig).\n//! This data structure should be serialized to JSON and served via the\n//! `GET .well-known/openid-configuration` path relative to your provider's issuer URL.\n//!\n//! ### Example\n//!\n//! ```rust,no_run\n//! use openidconnect::{\n//!     AuthUrl,\n//!     EmptyAdditionalProviderMetadata,\n//!     IssuerUrl,\n//!     JsonWebKeySetUrl,\n//!     ResponseTypes,\n//!     Scope,\n//!     TokenUrl,\n//!     UserInfoUrl,\n//! };\n//! use openidconnect::core::{\n//!     CoreClaimName,\n//!     CoreJwsSigningAlgorithm,\n//!     CoreProviderMetadata,\n//!     CoreResponseType,\n//!     CoreSubjectIdentifierType\n//! };\n//! use url::Url;\n//!\n//! # fn err_wrapper() -> Result<String, anyhow::Error> {\n//! let provider_metadata = CoreProviderMetadata::new(\n//!     // Parameters required by the OpenID Connect Discovery spec.\n//!     IssuerUrl::new(\"https://accounts.example.com\".to_string())?,\n//!     AuthUrl::new(\"https://accounts.example.com/authorize\".to_string())?,\n//!     // Use the JsonWebKeySet struct to serve the JWK Set at this URL.\n//!     JsonWebKeySetUrl::new(\"https://accounts.example.com/jwk\".to_string())?,\n//!     // Supported response types (flows).\n//!     vec![\n//!         // Recommended: support the code flow.\n//!         ResponseTypes::new(vec![CoreResponseType::Code]),\n//!         // Optional: support the implicit flow.\n//!         ResponseTypes::new(vec![CoreResponseType::Token, CoreResponseType::IdToken])\n//!         // Other flows including hybrid flows may also be specified here.\n//!     ],\n//!     // For user privacy, the Pairwise subject identifier type is preferred. This prevents\n//!     // distinct relying parties (clients) from knowing whether their users represent the same\n//!     // real identities. This identifier type is only useful for relying parties that don't\n//!     // receive the 'email', 'profile' or other personally-identifying scopes.\n//!     // The Public subject identifier type is also supported.\n//!     vec![CoreSubjectIdentifierType::Pairwise],\n//!     // Support the RS256 signature algorithm.\n//!     vec![CoreJwsSigningAlgorithm::RsaSsaPssSha256],\n//!     // OpenID Connect Providers may supply custom metadata by providing a struct that\n//!     // implements the AdditionalProviderMetadata trait. This requires manually using the\n//!     // generic ProviderMetadata struct rather than the CoreProviderMetadata type alias,\n//!     // however.\n//!     EmptyAdditionalProviderMetadata {},\n//! )\n//! // Specify the token endpoint (required for the code flow).\n//! .set_token_endpoint(Some(TokenUrl::new(\"https://accounts.example.com/token\".to_string())?))\n//! // Recommended: support the user info endpoint.\n//! .set_userinfo_endpoint(\n//!     Some(UserInfoUrl::new(\"https://accounts.example.com/userinfo\".to_string())?)\n//! )\n//! // Recommended: specify the supported scopes.\n//! .set_scopes_supported(Some(vec![\n//!     Scope::new(\"openid\".to_string()),\n//!     Scope::new(\"email\".to_string()),\n//!     Scope::new(\"profile\".to_string()),\n//! ]))\n//! // Recommended: specify the supported ID token claims.\n//! .set_claims_supported(Some(vec![\n//!     // Providers may also define an enum instead of using CoreClaimName.\n//!     CoreClaimName::new(\"sub\".to_string()),\n//!     CoreClaimName::new(\"aud\".to_string()),\n//!     CoreClaimName::new(\"email\".to_string()),\n//!     CoreClaimName::new(\"email_verified\".to_string()),\n//!     CoreClaimName::new(\"exp\".to_string()),\n//!     CoreClaimName::new(\"iat\".to_string()),\n//!     CoreClaimName::new(\"iss\".to_string()),\n//!     CoreClaimName::new(\"name\".to_string()),\n//!     CoreClaimName::new(\"given_name\".to_string()),\n//!     CoreClaimName::new(\"family_name\".to_string()),\n//!     CoreClaimName::new(\"picture\".to_string()),\n//!     CoreClaimName::new(\"locale\".to_string()),\n//! ]));\n//!\n//! serde_json::to_string(&provider_metadata).map_err(From::from)\n//! # }\n//! ```\n//!\n//! ## OpenID Connect Discovery JSON Web Key Set\n//!\n//! The JSON Web Key Set (JWKS) provides the public keys that relying parties (clients) use to\n//! verify the authenticity of ID tokens returned by this OpenID Connect Provider. The\n//! [`JsonWebKeySet`] data structure should be serialized as JSON and served at the URL specified\n//! in the `jwks_uri` field of the [`ProviderMetadata`] returned in the OpenID Connect Discovery\n//! document.\n//!\n//! ### Example\n//!\n//! ```rust,no_run\n//! use openidconnect::{JsonWebKeyId, PrivateSigningKey};\n//! use openidconnect::core::{CoreJsonWebKey, CoreJsonWebKeySet, CoreRsaPrivateSigningKey};\n//!\n//! # fn err_wrapper() -> Result<String, anyhow::Error> {\n//! # let rsa_pem = \"\";\n//! let jwks = CoreJsonWebKeySet::new(\n//!     vec![\n//!         // RSA keys may also be constructed directly using CoreJsonWebKey::new_rsa(). Providers\n//!         // aiming to support other key types may provide their own implementation of the\n//!         // JsonWebKey trait or submit a PR to add the desired support to this crate.\n//!         CoreRsaPrivateSigningKey::from_pem(\n//!             &rsa_pem,\n//!             Some(JsonWebKeyId::new(\"key1\".to_string()))\n//!         )\n//!         .expect(\"Invalid RSA private key\")\n//!         .as_verification_key()\n//!     ]\n//! );\n//!\n//! serde_json::to_string(&jwks).map_err(From::from)\n//! # }\n//! ```\n//!\n//! ## OpenID Connect ID Token\n//!\n//! The [`IdToken::new`] method is used for signing ID token claims, which can then be returned\n//! from the token endpoint as part of the [`StandardTokenResponse`] struct\n//! (or [`core::CoreTokenResponse`] type alias). The ID token can also be serialized to a string\n//! using the `IdToken::to_string` method and returned directly from the authorization endpoint\n//! when the implicit flow or certain hybrid flows are used. Note that in these flows, ID tokens\n//! must only be returned in the URL fragment, and never as a query parameter.\n//!\n//! The ID token contains a combination of the\n//! [OpenID Connect Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)\n//! (see [`StandardClaims`]) and claims specific to the\n//! [OpenID Connect ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)\n//! (see [`IdTokenClaims`]).\n//!\n//! ### Example\n//!\n//! ```rust,no_run\n//! use chrono::{Duration, Utc};\n//! use openidconnect::{\n//!     AccessToken,\n//!     Audience,\n//!     EmptyAdditionalClaims,\n//!     EmptyExtraTokenFields,\n//!     EndUserEmail,\n//!     IssuerUrl,\n//!     JsonWebKeyId,\n//!     StandardClaims,\n//!     SubjectIdentifier,\n//! };\n//! use openidconnect::core::{\n//!     CoreIdToken,\n//!     CoreIdTokenClaims,\n//!     CoreIdTokenFields,\n//!     CoreJwsSigningAlgorithm,\n//!     CoreRsaPrivateSigningKey,\n//!     CoreTokenResponse,\n//!     CoreTokenType,\n//! };\n//!\n//! # fn err_wrapper() -> Result<CoreTokenResponse, anyhow::Error> {\n//! # let rsa_pem = \"\";\n//! # let access_token = AccessToken::new(\"\".to_string());\n//! let id_token = CoreIdToken::new(\n//!     CoreIdTokenClaims::new(\n//!         // Specify the issuer URL for the OpenID Connect Provider.\n//!         IssuerUrl::new(\"https://accounts.example.com\".to_string())?,\n//!         // The audience is usually a single entry with the client ID of the client for whom\n//!         // the ID token is intended. This is a required claim.\n//!         vec![Audience::new(\"client-id-123\".to_string())],\n//!         // The ID token expiration is usually much shorter than that of the access or refresh\n//!         // tokens issued to clients.\n//!         Utc::now() + Duration::seconds(300),\n//!         // The issue time is usually the current time.\n//!         Utc::now(),\n//!         // Set the standard claims defined by the OpenID Connect Core spec.\n//!         StandardClaims::new(\n//!             // Stable subject identifiers are recommended in place of e-mail addresses or other\n//!             // potentially unstable identifiers. This is the only required claim.\n//!             SubjectIdentifier::new(\"5f83e0ca-2b8e-4e8c-ba0a-f80fe9bc3632\".to_string())\n//!         )\n//!         // Optional: specify the user's e-mail address. This should only be provided if the\n//!         // client has been granted the 'profile' or 'email' scopes.\n//!         .set_email(Some(EndUserEmail::new(\"bob@example.com\".to_string())))\n//!         // Optional: specify whether the provider has verified the user's e-mail address.\n//!         .set_email_verified(Some(true)),\n//!         // OpenID Connect Providers may supply custom claims by providing a struct that\n//!         // implements the AdditionalClaims trait. This requires manually using the\n//!         // generic IdTokenClaims struct rather than the CoreIdTokenClaims type alias,\n//!         // however.\n//!         EmptyAdditionalClaims {},\n//!     ),\n//!     // The private key used for signing the ID token. For confidential clients (those able\n//!     // to maintain a client secret), a CoreHmacKey can also be used, in conjunction\n//!     // with one of the CoreJwsSigningAlgorithm::HmacSha* signing algorithms. When using an\n//!     // HMAC-based signing algorithm, the UTF-8 representation of the client secret should\n//!     // be used as the HMAC key.\n//!     &CoreRsaPrivateSigningKey::from_pem(\n//!             &rsa_pem,\n//!             Some(JsonWebKeyId::new(\"key1\".to_string()))\n//!         )\n//!         .expect(\"Invalid RSA private key\"),\n//!     // Uses the RS256 signature algorithm. This crate supports any RS*, PS*, or HS*\n//!     // signature algorithm.\n//!     CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n//!     // When returning the ID token alongside an access token (e.g., in the Authorization Code\n//!     // flow), it is recommended to pass the access token here to set the `at_hash` claim\n//!     // automatically.\n//!     Some(&access_token),\n//!     // When returning the ID token alongside an authorization code (e.g., in the implicit\n//!     // flow), it is recommended to pass the authorization code here to set the `c_hash` claim\n//!     // automatically.\n//!     None,\n//! )?;\n//!\n//! Ok(CoreTokenResponse::new(\n//!     AccessToken::new(\"some_secret\".to_string()),\n//!     CoreTokenType::Bearer,\n//!     CoreIdTokenFields::new(Some(id_token), EmptyExtraTokenFields {}),\n//! ))\n//! # }\n//! ```\n//!\n//! # Asynchronous API\n//!\n//! An asynchronous API for async/await is also provided.\n//!\n//! ## Example\n//!\n//! ```rust,no_run\n//! use anyhow::anyhow;\n//! use openidconnect::{\n//!     AccessTokenHash,\n//!     AuthenticationFlow,\n//!     AuthorizationCode,\n//!     ClientId,\n//!     ClientSecret,\n//!     CsrfToken,\n//!     IssuerUrl,\n//!     Nonce,\n//!     OAuth2TokenResponse,\n//!     PkceCodeChallenge,\n//!     RedirectUrl,\n//!     Scope,\n//!     TokenResponse,\n//! };\n//! use openidconnect::core::{\n//!   CoreAuthenticationFlow,\n//!   CoreClient,\n//!   CoreProviderMetadata,\n//!   CoreResponseType,\n//! };\n//! # #[cfg(feature = \"reqwest\")]\n//! use openidconnect::reqwest;\n//! use url::Url;\n//!\n//!\n//! # #[cfg(feature = \"reqwest\")]\n//! # async fn err_wrapper() -> Result<(), anyhow::Error> {\n//! let http_client = reqwest::ClientBuilder::new()\n//!     // Following redirects opens the client up to SSRF vulnerabilities.\n//!     .redirect(reqwest::redirect::Policy::none())\n//!     .build()\n//!     .expect(\"Client should build\");\n//!\n//! // Use OpenID Connect Discovery to fetch the provider metadata.\n//! let provider_metadata = CoreProviderMetadata::discover_async(\n//!     IssuerUrl::new(\"https://accounts.example.com\".to_string())?,\n//!     &http_client,\n//! )\n//! .await?;\n//!\n//! // Create an OpenID Connect client by specifying the client ID, client secret, authorization URL\n//! // and token URL.\n//! let client =\n//!     CoreClient::from_provider_metadata(\n//!         provider_metadata,\n//!         ClientId::new(\"client_id\".to_string()),\n//!         Some(ClientSecret::new(\"client_secret\".to_string())),\n//!     )\n//!     // Set the URL the user will be redirected to after the authorization process.\n//!     .set_redirect_uri(RedirectUrl::new(\"http://redirect\".to_string())?);\n//!\n//! // Generate a PKCE challenge.\n//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();\n//!\n//! // Generate the full authorization URL.\n//! let (auth_url, csrf_token, nonce) = client\n//!     .authorize_url(\n//!         CoreAuthenticationFlow::AuthorizationCode,\n//!         CsrfToken::new_random,\n//!         Nonce::new_random,\n//!     )\n//!     // Set the desired scopes.\n//!     .add_scope(Scope::new(\"read\".to_string()))\n//!     .add_scope(Scope::new(\"write\".to_string()))\n//!     // Set the PKCE code challenge.\n//!     .set_pkce_challenge(pkce_challenge)\n//!     .url();\n//!\n//! // This is the URL you should redirect the user to, in order to trigger the authorization\n//! // process.\n//! println!(\"Browse to: {}\", auth_url);\n//!\n//! // Once the user has been redirected to the redirect URL, you'll have access to the\n//! // authorization code. For security reasons, your code should verify that the `state`\n//! // parameter returned by the server matches `csrf_state`.\n//!\n//! // Now you can exchange it for an access token and ID token.\n//! let token_response =\n//!     client\n//!         .exchange_code(AuthorizationCode::new(\"some authorization code\".to_string()))?\n//!         // Set the PKCE code verifier.\n//!         .set_pkce_verifier(pkce_verifier)\n//!         .request_async(&http_client)\n//!         .await?;\n//!\n//! // Extract the ID token claims after verifying its authenticity and nonce.\n//! let id_token = token_response\n//!   .id_token()\n//!   .ok_or_else(|| anyhow!(\"Server did not return an ID token\"))?;\n//! let id_token_verifier = client.id_token_verifier();\n//! let claims = id_token.claims(&id_token_verifier, &nonce)?;\n//!\n//! // Verify the access token hash to ensure that the access token hasn't been substituted for\n//! // another user's.\n//! if let Some(expected_access_token_hash) = claims.access_token_hash() {\n//!     let actual_access_token_hash = AccessTokenHash::from_token(\n//!         token_response.access_token(),\n//!         id_token.signing_alg()?,\n//!         id_token.signing_key(&id_token_verifier)?,\n//!     )?;\n//!     if actual_access_token_hash != *expected_access_token_hash {\n//!         return Err(anyhow!(\"Invalid access token\"));\n//!     }\n//! }\n//!\n//! // The authenticated user's identity is now available. See the IdTokenClaims struct for a\n//! // complete listing of the available claims.\n//! println!(\n//!     \"User {} with e-mail address {} has authenticated successfully\",\n//!     claims.subject().as_str(),\n//!     claims.email().map(|email| email.as_str()).unwrap_or(\"<not provided>\"),\n//! );\n//!\n//! // See the OAuth2TokenResponse trait for a listing of other available fields such as\n//! // access_token() and refresh_token().\n//!\n//! # Ok(())\n//! # }\n//! ```\n\nuse crate::jwt::{JsonWebToken, JsonWebTokenAccess, JsonWebTokenAlgorithm, JsonWebTokenHeader};\nuse crate::verification::{AudiencesClaim, IssuerClaim};\n\n// Defined first since other modules need the macros, and definition order is significant for\n// macros. This module is private.\n#[macro_use]\nmod macros;\n\n/// Baseline OpenID Connect implementation and types.\npub mod core;\n\n/// OpenID Connect Dynamic Client Registration.\npub mod registration;\n\n// Private modules since we may move types between different modules; these are exported publicly\n// via the pub use above.\nmod authorization;\nmod claims;\nmod client;\nmod discovery;\nmod helpers;\nmod id_token;\nmod logout;\nmod token;\nmod types;\nmod user_info;\nmod verification;\n\n// Private module for HTTP(S) utilities.\nmod http_utils;\n\n// Private module for JWT utilities.\nmod jwt;\n\npub use oauth2::{\n    AccessToken, AsyncHttpClient, AuthType, AuthUrl, AuthorizationCode,\n    ClientCredentialsTokenRequest, ClientId, ClientSecret, CodeTokenRequest, ConfigurationError,\n    CsrfToken, DeviceAccessTokenRequest, DeviceAuthorizationRequest, DeviceAuthorizationResponse,\n    DeviceAuthorizationUrl, DeviceCode, DeviceCodeErrorResponse, DeviceCodeErrorResponseType,\n    EmptyExtraDeviceAuthorizationFields, EmptyExtraTokenFields, EndUserVerificationUrl,\n    EndpointMaybeSet, EndpointNotSet, EndpointSet, EndpointState, ErrorResponse, ErrorResponseType,\n    ExtraDeviceAuthorizationFields, ExtraTokenFields, HttpClientError, HttpRequest, HttpResponse,\n    IntrospectionRequest, IntrospectionUrl, PasswordTokenRequest, PkceCodeChallenge,\n    PkceCodeChallengeMethod, PkceCodeVerifier, RedirectUrl, RefreshToken, RefreshTokenRequest,\n    RequestTokenError, ResourceOwnerPassword, ResourceOwnerUsername, RevocableToken,\n    RevocationErrorResponseType, RevocationRequest, RevocationUrl, Scope, StandardErrorResponse,\n    StandardTokenIntrospectionResponse, StandardTokenResponse, SyncHttpClient,\n    TokenIntrospectionResponse, TokenResponse as OAuth2TokenResponse, TokenType, TokenUrl,\n    UserCode, VerificationUriComplete,\n};\n\n/// Public re-exports of types used for HTTP client interfaces.\npub use oauth2::http;\npub use oauth2::url;\n\n#[cfg(all(feature = \"curl\", not(target_arch = \"wasm32\")))]\npub use oauth2::curl;\n\n#[cfg(all(feature = \"curl\", not(target_arch = \"wasm32\")))]\npub use oauth2::CurlHttpClient;\n\n#[cfg(all(feature = \"curl\", target_arch = \"wasm32\"))]\ncompile_error!(\"wasm32 is not supported with the `curl` feature. Use the `reqwest` backend or a custom backend for wasm32 support\");\n\n#[cfg(any(feature = \"reqwest\", feature = \"reqwest-blocking\"))]\npub use oauth2::reqwest;\n\n#[cfg(feature = \"ureq\")]\npub use oauth2::ureq;\n\npub use crate::authorization::{AuthenticationFlow, AuthorizationRequest};\npub use crate::claims::{\n    AdditionalClaims, AddressClaim, EmptyAdditionalClaims, GenderClaim, StandardClaims,\n};\npub use crate::client::Client;\npub use crate::discovery::{\n    AdditionalProviderMetadata, DiscoveryError, EmptyAdditionalProviderMetadata, ProviderMetadata,\n};\npub use crate::id_token::IdTokenFields;\npub use crate::id_token::{IdToken, IdTokenClaims};\npub use crate::jwt::{JsonWebTokenError, JsonWebTokenType, NormalizedJsonWebTokenType};\npub use crate::logout::{LogoutProviderMetadata, LogoutRequest, ProviderMetadataWithLogout};\npub use crate::token::TokenResponse;\n// Flatten the module hierarchy involving types. They're only separated to improve code\n// organization.\npub use crate::types::jwk::{\n    JsonWebKey, JsonWebKeyAlgorithm, JsonWebKeyId, JsonWebKeyType, JsonWebKeyUse,\n    JweContentEncryptionAlgorithm, JweKeyManagementAlgorithm, JwsSigningAlgorithm,\n    PrivateSigningKey,\n};\npub use crate::types::jwks::{JsonWebKeySet, JsonWebKeySetUrl};\npub use crate::types::localized::{LanguageTag, LocalizedClaim};\npub use crate::types::{\n    AccessTokenHash, AddressCountry, AddressLocality, AddressPostalCode, AddressRegion,\n    ApplicationType, Audience, AuthDisplay, AuthPrompt, AuthenticationContextClass,\n    AuthenticationMethodReference, AuthorizationCodeHash, ClaimName, ClaimType, ClientAuthMethod,\n    ClientConfigUrl, ClientContactEmail, ClientName, ClientUrl, EndSessionUrl, EndUserBirthday,\n    EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName,\n    EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone,\n    EndUserUsername, EndUserWebsiteUrl, FormattedAddress, GrantType, InitiateLoginUrl, IssuerUrl,\n    LoginHint, LogoUrl, LogoutHint, Nonce, OpPolicyUrl, OpTosUrl, PolicyUrl, PostLogoutRedirectUrl,\n    RegistrationAccessToken, RegistrationUrl, RequestUrl, ResponseMode, ResponseType,\n    ResponseTypes, SectorIdentifierUrl, ServiceDocUrl, SigningError, StreetAddress,\n    SubjectIdentifier, SubjectIdentifierType, ToSUrl,\n};\npub use crate::user_info::{\n    UserInfoClaims, UserInfoError, UserInfoJsonWebToken, UserInfoRequest, UserInfoResponseType,\n    UserInfoUrl,\n};\npub use crate::verification::{\n    ClaimsVerificationError, IdTokenVerifier, NonceVerifier, SignatureVerificationError,\n    UserInfoVerifier,\n};\n"
  },
  {
    "path": "src/logout.rs",
    "content": "use crate::core::{\n    CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClientAuthMethod, CoreGrantType,\n    CoreJsonWebKey, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm,\n    CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType,\n};\nuse crate::helpers::join_vec;\nuse crate::types::{LogoutHint, PostLogoutRedirectUrl};\nuse crate::{\n    AdditionalClaims, AdditionalProviderMetadata, ClientId, CsrfToken,\n    EmptyAdditionalProviderMetadata, EndSessionUrl, GenderClaim, IdToken,\n    JweContentEncryptionAlgorithm, JwsSigningAlgorithm, LanguageTag, ProviderMetadata,\n};\n\nuse serde::{Deserialize, Serialize};\nuse serde_with::skip_serializing_none;\nuse url::Url;\n\n/// Additional metadata for providers implementing [OpenID Connect RP-Initiated\n/// Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html).\n#[skip_serializing_none]\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]\npub struct LogoutProviderMetadata<A>\nwhere\n    A: AdditionalProviderMetadata,\n{\n    /// The end session endpoint as described in [OpenID Connect RP-Initiated\n    /// Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html).\n    pub end_session_endpoint: Option<EndSessionUrl>,\n    #[serde(bound = \"A: AdditionalProviderMetadata\", flatten)]\n    /// A field for an additional struct implementing AdditionalProviderMetadata.\n    pub additional_metadata: A,\n}\nimpl<A> AdditionalProviderMetadata for LogoutProviderMetadata<A> where A: AdditionalProviderMetadata {}\n\n/// Provider metadata returned by [OpenID Connect Discovery](\n/// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata)\n/// that returns [`ProviderMetadata::additional_metadata`] for providers\n/// implementing [OpenID Connect RP-Initiated Logout 1.0](\n/// https://openid.net/specs/openid-connect-rpinitiated-1_0.html).\npub type ProviderMetadataWithLogout = ProviderMetadata<\n    LogoutProviderMetadata<EmptyAdditionalProviderMetadata>,\n    CoreAuthDisplay,\n    CoreClientAuthMethod,\n    CoreClaimName,\n    CoreClaimType,\n    CoreGrantType,\n    CoreJweContentEncryptionAlgorithm,\n    CoreJweKeyManagementAlgorithm,\n    CoreJsonWebKey,\n    CoreResponseMode,\n    CoreResponseType,\n    CoreSubjectIdentifierType,\n>;\n\n/// A request to the end session endpoint.\npub struct LogoutRequest {\n    end_session_endpoint: EndSessionUrl,\n    parameters: LogoutRequestParameters,\n}\n\n#[derive(Default)]\nstruct LogoutRequestParameters {\n    id_token_hint: Option<String>,\n    logout_hint: Option<LogoutHint>,\n    client_id: Option<ClientId>,\n    post_logout_redirect_uri: Option<PostLogoutRedirectUrl>,\n    state: Option<CsrfToken>,\n    ui_locales: Vec<LanguageTag>,\n}\n\nimpl From<EndSessionUrl> for LogoutRequest {\n    fn from(value: EndSessionUrl) -> Self {\n        LogoutRequest {\n            end_session_endpoint: value,\n            parameters: Default::default(),\n        }\n    }\n}\n\nimpl LogoutRequest {\n    /// Provides an ID token previously issued by this OpenID Connect Provider as a hint about\n    /// the user's identity.\n    pub fn set_id_token_hint<AC, GC, JE, JS>(\n        mut self,\n        id_token_hint: &IdToken<AC, GC, JE, JS>,\n    ) -> Self\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n        JS: JwsSigningAlgorithm,\n    {\n        self.parameters.id_token_hint = Some(id_token_hint.to_string());\n        self\n    }\n\n    /// Provides the OpenID Connect Provider with a hint about the user's identity.\n    ///\n    /// The nature of this hint is specific to each provider.\n    pub fn set_logout_hint(mut self, logout_hint: LogoutHint) -> Self {\n        self.parameters.logout_hint = Some(logout_hint);\n        self\n    }\n\n    /// Provides the OpenID Connect Provider with the client identifier.\n    ///\n    /// When both this and `id_token_hint` are set, the provider must verify that\n    /// this client id matches the one used when the ID token was issued.\n    pub fn set_client_id(mut self, client_id: ClientId) -> Self {\n        self.parameters.client_id = Some(client_id);\n        self\n    }\n\n    /// Provides the OpenID Connect Provider with a URI to redirect to after\n    /// the logout has been performed.\n    pub fn set_post_logout_redirect_uri(mut self, redirect_uri: PostLogoutRedirectUrl) -> Self {\n        self.parameters.post_logout_redirect_uri = Some(redirect_uri);\n        self\n    }\n\n    /// Specify an opaque value that the OpenID Connect Provider should pass back\n    /// to your application using the state parameter when redirecting to post_logout_redirect_uri.\n    pub fn set_state(mut self, state: CsrfToken) -> Self {\n        self.parameters.state = Some(state);\n        self\n    }\n\n    /// Requests the preferred languages for the user interface presented by the OpenID Connect\n    /// Provider.\n    ///\n    /// Languages should be added in order of preference.\n    pub fn add_ui_locale(mut self, ui_locale: LanguageTag) -> Self {\n        self.parameters.ui_locales.push(ui_locale);\n        self\n    }\n\n    /// Returns the full logout URL. In order to logout, a GET request should be made to this URL\n    /// by the client's browser.\n    pub fn http_get_url(self) -> Url {\n        let mut url = self.end_session_endpoint.url().to_owned();\n        {\n            let mut query = url.query_pairs_mut();\n\n            macro_rules! add_pair {\n                ($name:ident, $acc:expr) => {\n                    if let Some($name) = self.parameters.$name {\n                        query.append_pair(stringify!($name), $acc);\n                    }\n                };\n            }\n\n            add_pair!(id_token_hint, id_token_hint.as_str());\n            add_pair!(logout_hint, logout_hint.secret());\n            add_pair!(client_id, client_id.as_str());\n            add_pair!(post_logout_redirect_uri, post_logout_redirect_uri.as_str());\n            add_pair!(state, state.secret());\n\n            if !self.parameters.ui_locales.is_empty() {\n                query.append_pair(\"ui_locales\", &join_vec(&self.parameters.ui_locales));\n            }\n        }\n\n        if url.query() == Some(\"\") {\n            url.set_query(None);\n        }\n\n        url\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::core::{\n        CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm,\n    };\n    use crate::types::{LogoutHint, PostLogoutRedirectUrl};\n    use crate::{\n        AuthUrl, ClientId, CsrfToken, EmptyAdditionalClaims, EndSessionUrl, IdToken, IssuerUrl,\n        JsonWebKeySetUrl, LanguageTag, LogoutProviderMetadata, LogoutRequest,\n        ProviderMetadataWithLogout,\n    };\n\n    use url::Url;\n\n    use std::str::FromStr;\n\n    #[test]\n    fn test_end_session_endpoint_deserialization() {\n        // Fetched from: https://rp.certification.openid.net:8080/openidconnect-rs/\n        //     rp-response_type-code/.well-known/openid-configuration\n        // But pared down\n        let json_response = \"{\\\n            \\\"issuer\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\\\",\\\n            \\\"authorization_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/authorization\\\",\\\n            \\\"jwks_uri\\\":\\\"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\\\",\\\n            \\\"response_types_supported\\\":[],\\\n            \\\"subject_types_supported\\\":[],\\\n            \\\"id_token_signing_alg_values_supported\\\": [],\\\n            \\\"end_session_endpoint\\\":\\\"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\\\",\\\n            \\\"version\\\":\\\"3.0\\\"}\";\n\n        let new_provider_metadata = ProviderMetadataWithLogout::new(\n            IssuerUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code\"\n                    .to_string(),\n            )\n            .unwrap(),\n            AuthUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/\\\n                 rp-response_type-code/authorization\"\n                    .to_string(),\n            )\n            .unwrap(),\n            JsonWebKeySetUrl::new(\n                \"https://rp.certification.openid.net:8080/static/jwks_3INbZl52IrrPCp2j.json\"\n                    .to_string(),\n            )\n            .unwrap(),\n            vec![],\n            vec![],\n            vec![],\n            LogoutProviderMetadata {\n                end_session_endpoint: Some(EndSessionUrl::new(\n                    \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\"\n                        .to_string()\n                ).unwrap()),\n                additional_metadata: Default::default(),\n            },\n        );\n\n        let provider_metadata: ProviderMetadataWithLogout =\n            serde_json::from_str(json_response).unwrap();\n        assert_eq!(provider_metadata, new_provider_metadata);\n\n        assert_eq!(\n            Some(EndSessionUrl::new(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\"\n                    .to_string()\n            ).unwrap()),\n            provider_metadata.additional_metadata().end_session_endpoint\n        );\n    }\n\n    #[test]\n    fn test_logout_request_with_no_parameters() {\n        let endpoint = EndSessionUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\"\n                .to_string()\n        ).unwrap();\n\n        let logout_url = LogoutRequest::from(endpoint).http_get_url();\n\n        assert_eq!(\n            Url::parse(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\"\n            ).unwrap(),\n            logout_url\n        );\n    }\n\n    #[test]\n    fn test_logout_request_with_all_parameters() {\n        let endpoint = EndSessionUrl::new(\n            \"https://rp.certification.openid.net:8080/openidconnect-rs/rp-response_type-code/end_session\"\n                .to_string()\n        ).unwrap();\n\n        let logout_url = LogoutRequest::from(endpoint)\n            .set_id_token_hint(\n                &IdToken::<\n                    EmptyAdditionalClaims,\n                    CoreGenderClaim,\n                    CoreJweContentEncryptionAlgorithm,\n                    CoreJwsSigningAlgorithm,\n                >::from_str(\n                    \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwcz\\\n                    ovL3JwLmNlcnRpZmljYXRpb24ub3BlbmlkLm5ldDo4MDgwLyIsImV4c\\\n                    CI6MTUxNjIzOTAyMiwiaWF0IjoxNTE2MjM5MDIyLCJzdWIiOiJhc2Rm\\\n                    In0.cPwX6csO2uBEOZLVAGR7x5rHLRfD36MHpPy3JTk6orM\",\n                )\n                .unwrap(),\n            )\n            .set_logout_hint(LogoutHint::new(\"johndoe\".to_string()))\n            .set_client_id(ClientId::new(\"asdf\".to_string()))\n            .set_post_logout_redirect_uri(\n                PostLogoutRedirectUrl::new(\"https://localhost:8000/\".to_string()).unwrap(),\n            )\n            .set_state(CsrfToken::new(\"asdf\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"en-US\".to_string()))\n            .add_ui_locale(LanguageTag::new(\"fr-FR\".to_string()))\n            .http_get_url();\n\n        assert_eq!(\n            Url::parse(\n                \"https://rp.certification.openid.net:8080/openidconnect-rs\\\n                /rp-response_type-code/end_session?id_token_hint=eyJhbGciO\\\n                iJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3JwLmNlcn\\\n                RpZmljYXRpb24ub3BlbmlkLm5ldDo4MDgwLyIsImV4cCI6MTUxNjIzOTAy\\\n                MiwiaWF0IjoxNTE2MjM5MDIyLCJzdWIiOiJhc2RmIn0.cPwX6csO2uBEOZ\\\n                LVAGR7x5rHLRfD36MHpPy3JTk6orM&logout_hint=johndoe&client_i\\\n                d=asdf&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A\\\n                8000%2F&state=asdf&ui_locales=en-US+fr-FR\"\n            )\n            .unwrap(),\n            logout_url\n        );\n    }\n}\n"
  },
  {
    "path": "src/macros.rs",
    "content": "/// Copied from oauth2-rs crate (not part of that crate's stable public interface).\nmacro_rules! new_type {\n    // Convenience pattern without an impl.\n    (\n        $(#[$attr:meta])*\n        $name:ident(\n            $(#[$type_attr:meta])*\n            $type:ty\n        )\n    ) => {\n        new_type![\n            @new_type_pub $(#[$attr])*,\n            $name(\n                $(#[$type_attr])*\n                $type\n            ),\n            concat!(\n                \"Create a new `\",\n                stringify!($name),\n                \"` to wrap the given `\",\n                stringify!($type),\n                \"`.\"\n            ),\n            impl {}\n        ];\n    };\n    // Convenience pattern without an impl.\n    (\n        $(#[$attr:meta])*\n        pub(crate) $name:ident(\n            $(#[$type_attr:meta])*\n            $type:ty\n        )\n    ) => {\n        new_type![\n            @new_type_pub_crate $(#[$attr])*,\n            $name(\n                $(#[$type_attr])*\n                $type\n            ),\n            concat!(\n                \"Create a new `\",\n                stringify!($name),\n                \"` to wrap the given `\",\n                stringify!($type),\n                \"`.\"\n            ),\n            impl {}\n        ];\n    };\n    // Main entry point with an impl.\n    (\n        $(#[$attr:meta])*\n        $name:ident(\n            $(#[$type_attr:meta])*\n            $type:ty\n        )\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        new_type![\n            @new_type_pub $(#[$attr])*,\n            $name(\n                $(#[$type_attr])*\n                $type\n            ),\n            concat!(\n                \"Create a new `\",\n                stringify!($name),\n                \"` to wrap the given `\",\n                stringify!($type),\n                \"`.\"\n            ),\n            impl {\n                $($item)*\n            }\n        ];\n    };\n    // Main entry point with an impl.\n    (\n        $(#[$attr:meta])*\n        pub(crate) $name:ident(\n            $(#[$type_attr:meta])*\n            $type:ty\n        )\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        new_type![\n            @new_type_pub_crate $(#[$attr])*,\n            $name(\n                $(#[$type_attr])*\n                $type\n            ),\n            concat!(\n                \"Create a new `\",\n                stringify!($name),\n                \"` to wrap the given `\",\n                stringify!($type),\n                \"`.\"\n            ),\n            impl {\n                $($item)*\n            }\n        ];\n    };\n    // Actual implementation, after stringifying the #[doc] attr.\n    (\n        @new_type_pub $(#[$attr:meta])*,\n        $name:ident(\n            $(#[$type_attr:meta])*\n            $type:ty\n        ),\n        $new_doc:expr,\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        $(#[$attr])*\n        #[derive(Clone, Debug, PartialEq, Eq)]\n        pub struct $name(\n            $(#[$type_attr])*\n            $type\n        );\n        impl $name {\n            $($item)*\n\n            #[allow(dead_code)]\n            #[doc = $new_doc]\n            pub fn new(s: $type) -> Self {\n                $name(s)\n            }\n        }\n        impl std::ops::Deref for $name {\n            type Target = $type;\n            fn deref(&self) -> &$type {\n                &self.0\n            }\n        }\n        impl From<$name> for $type {\n            fn from(t: $name) -> $type {\n                t.0\n            }\n        }\n    };\n    // Actual implementation, after stringifying the #[doc] attr.\n    (\n        @new_type_pub_crate $(#[$attr:meta])*,\n        $name:ident(\n            $(#[$type_attr:meta])*\n            $type:ty\n        ),\n        $new_doc:expr,\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        $(#[$attr])*\n        #[derive(Clone, Debug, PartialEq, Eq)]\n        pub(crate) struct $name(\n            $(#[$type_attr])*\n            $type\n        );\n        impl $name {\n            $($item)*\n\n            #[doc = $new_doc]\n            pub const fn new(s: $type) -> Self {\n                $name(s)\n            }\n        }\n        impl std::ops::Deref for $name {\n            type Target = $type;\n            fn deref(&self) -> &$type {\n                &self.0\n            }\n        }\n        impl From<$name> for $type {\n            fn from(t: $name) -> $type {\n                t.0\n            }\n        }\n    };\n}\n\n/// Copied from oauth2-rs crate (not part of that crate's stable public interface).\nmacro_rules! new_secret_type {\n    (\n        $(#[$attr:meta])*\n        $name:ident($type:ty)\n    ) => {\n        new_secret_type![\n            $(#[$attr])*\n            $name($type)\n            impl {}\n        ];\n    };\n    (\n        $(#[$attr:meta])*\n        $name:ident($type:ty)\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        new_secret_type![\n            $(#[$attr])*,\n            $name($type),\n            concat!(\n                \"Create a new `\",\n                stringify!($name),\n                \"` to wrap the given `\",\n                stringify!($type),\n                \"`.\"\n            ),\n            concat!(\"Get the secret contained within this `\", stringify!($name), \"`.\"),\n            impl {\n                $($item)*\n            }\n        ];\n    };\n    (\n        $(#[$attr:meta])*,\n        $name:ident($type:ty),\n        $new_doc:expr,\n        $secret_doc:expr,\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        $(\n            #[$attr]\n        )*\n        #[cfg_attr(feature = \"timing-resistant-secret-traits\", derive(Eq))]\n        pub struct $name($type);\n        impl $name {\n            $($item)*\n\n            #[doc = $new_doc]\n            pub fn new(s: $type) -> Self {\n                $name(s)\n            }\n\n            #[doc = $secret_doc]\n            ///\n            /// # Security Warning\n            ///\n            /// Leaking this value may compromise the security of the OAuth2 flow.\n            pub fn secret(&self) -> &$type { &self.0 }\n\n            #[doc = $secret_doc]\n            ///\n            /// # Security Warning\n            ///\n            /// Leaking this value may compromise the security of the OAuth2 flow.\n            pub fn into_secret(self) -> $type { self.0 }\n        }\n        impl Debug for $name {\n            fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n                write!(f, concat!(stringify!($name), \"([redacted])\"))\n            }\n        }\n\n        #[cfg(any(test, feature = \"timing-resistant-secret-traits\"))]\n        impl PartialEq for $name {\n            fn eq(&self, other: &Self) -> bool {\n                <sha2::Sha256 as sha2::Digest>::digest(&self.0)\n                  == <sha2::Sha256 as sha2::Digest>::digest(&other.0)\n            }\n        }\n\n        #[cfg(feature = \"timing-resistant-secret-traits\")]\n        impl std::hash::Hash for $name {\n            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n                <sha2::Sha256 as sha2::Digest>::digest(&self.0).hash(state)\n            }\n        }\n\n    };\n}\n\n/// Creates a URL-specific new type\n///\n/// Types created by this macro enforce during construction that the contained value represents a\n/// syntactically valid URL. However, comparisons and hashes of these types are based on the string\n/// representation given during construction, disregarding any canonicalization performed by the\n/// underlying `Url` struct. OpenID Connect requires certain URLs (e.g., ID token issuers) to be\n/// compared exactly, without canonicalization.\n///\n/// In addition to the raw string representation, these types include a `url` method to retrieve a\n/// parsed `Url` struct.\nmacro_rules! new_url_type {\n    // Convenience pattern without an impl.\n    (\n        $(#[$attr:meta])*\n        $name:ident\n    ) => {\n        new_url_type![\n            @new_type_pub $(#[$attr])*,\n            $name,\n            concat!(\"Create a new `\", stringify!($name), \"` from a `String` to wrap a URL.\"),\n            concat!(\"Create a new `\", stringify!($name), \"` from a `Url` to wrap a URL.\"),\n            concat!(\"Return this `\", stringify!($name), \"` as a parsed `Url`.\"),\n            impl {}\n        ];\n    };\n    // Main entry point with an impl.\n    (\n        $(#[$attr:meta])*\n        $name:ident\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        new_url_type![\n            @new_type_pub $(#[$attr])*,\n            $name,\n            concat!(\"Create a new `\", stringify!($name), \"` from a `String` to wrap a URL.\"),\n            concat!(\"Create a new `\", stringify!($name), \"` from a `Url` to wrap a URL.\"),\n            concat!(\"Return this `\", stringify!($name), \"` as a parsed `Url`.\"),\n            impl {\n                $($item)*\n            }\n        ];\n    };\n    // Actual implementation, after stringifying the #[doc] attr.\n    (\n        @new_type_pub $(#[$attr:meta])*,\n        $name:ident,\n        $new_doc:expr,\n        $from_url_doc:expr,\n        $url_doc:expr,\n        impl {\n            $($item:tt)*\n        }\n    ) => {\n        $(#[$attr])*\n        #[derive(Clone)]\n        pub struct $name(url::Url, String);\n        impl $name {\n            #[doc = $new_doc]\n            pub fn new(url: String) -> Result<Self, ::url::ParseError> {\n                Ok($name(url::Url::parse(&url)?, url))\n            }\n            #[doc = $from_url_doc]\n            pub fn from_url(url: url::Url) -> Self {\n                let s = url.to_string();\n                Self(url, s)\n            }\n            #[doc = $url_doc]\n            pub fn url(&self) -> &url::Url {\n                return &self.0;\n            }\n            $($item)*\n        }\n        impl std::ops::Deref for $name {\n            type Target = String;\n            fn deref(&self) -> &String {\n                &self.1\n            }\n        }\n        impl ::std::fmt::Display for $name {\n            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {\n                write!(f, \"{}\", self.1)\n            }\n        }\n        impl From<$name> for url::Url {\n            fn from(t: $name) -> url::Url {\n                t.0\n            }\n        }\n        impl ::std::fmt::Debug for $name {\n            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {\n                let mut debug_trait_builder = f.debug_tuple(stringify!($name));\n                debug_trait_builder.field(&self.1);\n                debug_trait_builder.finish()\n            }\n        }\n        impl<'de> ::serde::Deserialize<'de> for $name {\n            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n            where\n                D: ::serde::de::Deserializer<'de>,\n            {\n                struct UrlVisitor;\n                impl<'de> ::serde::de::Visitor<'de> for UrlVisitor {\n                    type Value = $name;\n\n                    fn expecting(\n                        &self,\n                        formatter: &mut ::std::fmt::Formatter\n                    ) -> ::std::fmt::Result {\n                        formatter.write_str(stringify!($name))\n                    }\n\n                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>\n                    where\n                        E: ::serde::de::Error,\n                    {\n                        $name::new(v.to_string()).map_err(E::custom)\n                    }\n                }\n                deserializer.deserialize_str(UrlVisitor {})\n            }\n        }\n        impl ::serde::Serialize for $name {\n            fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>\n            where\n                SE: ::serde::Serializer,\n            {\n                serializer.serialize_str(&self.1)\n            }\n        }\n        impl ::std::hash::Hash for $name {\n            fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) -> () {\n                ::std::hash::Hash::hash(&(self.1), state);\n            }\n        }\n        impl Ord for $name {\n            fn cmp(&self, other: &$name) -> ::std::cmp::Ordering {\n                self.1.cmp(&other.1)\n            }\n        }\n        impl PartialOrd for $name {\n            fn partial_cmp(&self, other: &$name) -> Option<::std::cmp::Ordering> {\n                Some(self.cmp(other))\n            }\n        }\n        impl PartialEq for $name {\n            fn eq(&self, other: &$name) -> bool {\n                self.1 == other.1\n            }\n        }\n        impl Eq for $name {}\n    };\n}\n\nmacro_rules! serialize_fields {\n    (@case $self:ident $map:ident Option(Seconds($field:ident))) => {\n        if let Some(ref $field) = $self.$field {\n            $map.serialize_entry(stringify!($field), &$field.as_secs())?;\n        }\n    };\n    (@case $self:ident $map:ident Option(DateTime(Seconds($field:ident)))) => {\n        if let Some(ref $field) = $self.$field {\n            $map.serialize_entry(stringify!($field), &crate::helpers::Timestamp::from_utc(&$field))?;\n        }\n    };\n    (@case $self:ident $map:ident Option($field:ident)) => {\n        if let Some(ref $field) = $self.$field {\n            $map.serialize_entry(stringify!($field), $field)?;\n        }\n    };\n    (@case $self:ident $map:ident LanguageTag($field:ident)) => {\n        if let Some(ref field_map) = $self.$field {\n            use itertools::sorted;\n            let sorted_field_map = sorted(field_map.iter());\n            for (language_tag_opt, $field) in sorted_field_map {\n                if let Some(ref language_tag) = language_tag_opt {\n                    $map.serialize_entry(\n                        &format!(concat!(stringify!($field), \"#{}\"), language_tag.as_ref()),\n                        &$field\n                    )?;\n                } else {\n                    $map.serialize_entry(stringify!($field), &$field)?;\n                }\n            }\n        }\n    };\n    (@case $self:ident $map:ident $field:ident) => {\n        $map.serialize_entry(stringify!($field), &$self.$field)?;\n    };\n    // Main entry point\n    (\n        $self:ident -> $serializer:ident {\n            $([$($entry:tt)+])+\n        }\n    ) => {\n        let mut map = $serializer.serialize_map(None)?;\n        $(\n            serialize_fields![@case $self map $($entry)+];\n        )+\n        map.end()\n    };\n}\n\nmacro_rules! field_getters {\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < bool >) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> Option<bool> {\n            $zero.$field\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < bool > { $($body:tt)+ }) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> Option<bool> {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < DateTime < Utc >>) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> Option<DateTime<Utc>> {\n            $zero.$field\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < DateTime < Utc >> { $($body:tt)+ }) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> Option<DateTime<Utc>> {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < $type:ty >) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> Option<&$type> {\n            $zero.$field.as_ref()\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < $type:ty > { $($body:tt)+ }) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> Option<$type> {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident DateTime < Utc >) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> DateTime<Utc> {\n            $zero.$field\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident $type:ty) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> &$type {\n            &$zero.$field\n        }\n    };\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident $type:ty { $($body:tt)+ }) => {\n        #[doc = $doc]\n        $vis fn $field(&$self) -> $type {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < bool >) => {\n        #[doc = $doc]\n        fn $field(&$self) -> Option<bool> {\n            $zero.$field()\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < bool > { $($body:tt)+ }) => {\n        #[doc = $doc]\n        fn $field(&$self) -> Option<bool> {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < DateTime < Utc >>) => {\n        #[doc = $doc]\n        fn $field(&$self) -> Option<DateTime<Utc>> {\n            $zero.$field()\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < DateTime < Utc >> { $($body:tt)+ }) => {\n        #[doc = $doc]\n        fn $field(&$self) -> Option<DateTime<Utc>> {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < $type:ty >) => {\n        #[doc = $doc]\n        fn $field(&$self) -> Option<&$type> {\n            $zero.$field()\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < $type:ty > { $($body:tt)+ }) => {\n        #[doc = $doc]\n        fn $field(&$self) -> Option<$type> {\n            $($body)+\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() DateTime < Utc >) => {\n        #[doc = $doc]\n        fn $field(&$self) -> DateTime<Utc> {\n            $zero.$field()\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() $type:ty) => {\n        #[doc = $doc]\n        fn $field(&$self) -> &$type {\n            &$zero.$field()\n        }\n    };\n    (@case [$doc:expr] $self:ident [$zero:expr] $field:ident() $type:ty { $($body:tt)+ }) => {\n        #[doc = $doc]\n        fn $field(&$self) -> $type {\n            $($body)+\n        }\n    };\n    // Main entry points\n    (\n        $vis:vis $self:ident [$zero:expr] [$doc:expr] {\n            $(\n                $field:ident[$($entry:tt)+] [$doc_field:expr],\n            )+\n        }\n    ) => {\n        $(\n            field_getters![\n                @case\n                [concat!(\"Returns the `\", $doc_field, \"` \", $doc, \".\")]\n                $vis $self [$zero] $field $($entry)+\n            ];\n        )+\n    };\n    (\n        $vis:vis $self:ident [$zero:expr]() [$doc:expr] {\n            $(\n                $field:ident[$($entry:tt)+] [$doc_field:expr],\n            )+\n        }\n    ) => {\n        $(\n            field_getters![\n                @case\n                [concat!(\"Returns the `\", $doc_field, \"` \", $doc, \".\")]\n                $vis $self [$zero] $field() $($entry)+\n            ];\n        )+\n    };\n}\n\nmacro_rules! field_setters {\n    (@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $setter:ident $field:ident $type:ty [$doc_field:expr]) => {\n        field_setters![\n            @case2\n            [concat!(\"Sets the `\", $doc_field, \"` \", $doc, \".\")]\n            $vis $self [$zero] $setter $field $type\n        ];\n    };\n    (@case2 [$doc:expr] $vis:vis $self:ident [$zero:expr] $setter:ident $field:ident $type:ty) => {\n        #[doc = $doc]\n        $vis fn $setter(\n            mut $self,\n            $field: $type\n        ) -> Self {\n            $zero.$field = $field;\n            $self\n        }\n    };\n    // Main entry point\n    (\n        $vis:vis $self:ident [$zero:expr] [$doc:expr] {\n            $setter:ident -> $field:ident[$($entry:tt)+] [$doc_field:expr]\n        }\n    ) => {\n        field_setters![\n            @case [$doc] $vis $self [$zero] $setter $field $($entry)+ [$doc_field]\n        ];\n    };\n}\n\nmacro_rules! field_getters_setters {\n    (\n        @single $vis:vis $self:ident [$zero:expr] [$doc:expr]\n        [$setter:ident -> $field:ident[$($entry:tt)+] [$field_doc:expr], $($rest:tt)*]\n    ) => {\n        field_getters![$vis $self [$zero] [$doc] { $field[$($entry)+] [$field_doc], }];\n        field_setters![\n            $vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [$field_doc] }\n        ];\n        field_getters_setters![@single $vis $self [$zero] [$doc] [$($rest)*]];\n    };\n    (\n        @single $vis:vis $self:ident [$zero:expr]() [$doc:expr]\n        [$setter:ident -> $field:ident[$($entry:tt)+] [$field_doc:expr], $($rest:tt)*]\n    ) => {\n        field_getters![$vis $self [$zero]() [$doc] { $field[$($entry)+] [$field_doc], }];\n        field_setters![\n            $vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [$field_doc] }\n        ];\n        field_getters_setters![@single $vis $self [$zero]() [$doc] [$($rest)*]];\n    };\n    (\n        @single $vis:vis $self:ident [$zero:expr] [$doc:expr]\n        [$setter:ident -> $field:ident[$($entry:tt)+], $($rest:tt)*]\n    ) => {\n        field_getters![$vis $self [$zero] [$doc] { $field[$($entry)+] [stringify!($field)], }];\n        field_setters![\n            $vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [stringify!($field)] }\n        ];\n        field_getters_setters![@single $vis $self [$zero] [$doc] [$($rest)*]];\n    };\n    (\n        @single $vis:vis $self:ident [$zero:expr]() [$doc:expr]\n        [$setter:ident -> $field:ident[$($entry:tt)+], $($rest:tt)*]\n    ) => {\n        field_getters![$vis $self [$zero]() [$doc] { $field[$($entry)+] [stringify!($field)], }];\n        field_setters![\n            $vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [stringify!($field)] }\n        ];\n        field_getters_setters![@single $vis $self [$zero]() [$doc] [$($rest)*]];\n    };\n    // Base case.\n    (@single $vis:vis $self:ident [$zero:expr] [$doc:expr] []) => {};\n    // Main entry points.\n    (\n        $vis:vis $self:ident [$zero:expr] [$doc:expr] {\n            $setter:ident -> $field:ident[$($entry:tt)+] $($rest:tt)*\n        }\n    ) => {\n        field_getters_setters![\n            @single\n            $vis $self [$zero] [$doc] [$setter -> $field[$($entry)+] $($rest)*]\n        ];\n    };\n    (\n        $vis:vis $self:ident [$zero:expr]() [$doc:expr] {\n            $setter:ident -> $field:ident[$($entry:tt)+] $($rest:tt)*\n        }\n    ) => {\n        field_getters_setters![\n            @single\n            $vis $self [$zero]() [$doc] [$setter -> $field[$($entry)+] $($rest)*]\n        ];\n    };\n}\n\nmacro_rules! deserialize_from_str {\n    ($type:path) => {\n        impl<'de> serde::Deserialize<'de> for $type {\n            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n            where\n                D: serde::de::Deserializer<'de>,\n            {\n                let variant_str = String::deserialize(deserializer)?;\n                Ok(Self::from_str(&variant_str))\n            }\n        }\n    };\n}\n\nmacro_rules! serialize_as_str {\n    ($type:path) => {\n        impl serde::ser::Serialize for $type {\n            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n            where\n                S: serde::ser::Serializer,\n            {\n                serializer.serialize_str(self.as_ref())\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "src/registration/mod.rs",
    "content": "use crate::helpers::{DeserializeMapField, Timestamp};\nuse crate::http_utils::{auth_bearer, check_content_type, MIME_TYPE_JSON};\nuse crate::types::localized::split_language_tag_key;\nuse crate::types::{\n    ApplicationType, AuthenticationContextClass, ClientAuthMethod, ClientConfigUrl,\n    ClientContactEmail, ClientName, ClientUrl, GrantType, InitiateLoginUrl, LogoUrl, PolicyUrl,\n    RegistrationAccessToken, RegistrationUrl, RequestUrl, ResponseType, ResponseTypes,\n    SectorIdentifierUrl, SubjectIdentifierType, ToSUrl,\n};\nuse crate::{\n    AccessToken, AsyncHttpClient, ClientId, ClientSecret, ErrorResponseType, HttpRequest,\n    HttpResponse, JsonWebKey, JsonWebKeySet, JsonWebKeySetUrl, JweContentEncryptionAlgorithm,\n    JweKeyManagementAlgorithm, JwsSigningAlgorithm, LocalizedClaim, RedirectUrl,\n    StandardErrorResponse, SyncHttpClient,\n};\n\nuse chrono::{DateTime, Utc};\nuse http::header::{HeaderValue, ACCEPT, CONTENT_TYPE};\nuse http::method::Method;\nuse http::status::StatusCode;\nuse serde::de::{DeserializeOwned, Deserializer, MapAccess, Visitor};\nuse serde::ser::SerializeMap;\nuse serde::{Deserialize, Serialize, Serializer};\nuse serde_with::{serde_as, skip_serializing_none};\nuse thiserror::Error;\n\nuse std::fmt::{Debug, Formatter, Result as FormatterResult};\nuse std::future::Future;\nuse std::marker::PhantomData;\nuse std::time::Duration;\n\n#[cfg(test)]\nmod tests;\n\n/// Trait for adding extra fields to [`ClientMetadata`].\npub trait AdditionalClientMetadata: Debug + DeserializeOwned + Serialize {}\n\n// In order to support serde flatten, this must be an empty struct rather than an empty\n// tuple struct.\n/// Empty (default) extra [`ClientMetadata`] fields.\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]\npub struct EmptyAdditionalClientMetadata {}\nimpl AdditionalClientMetadata for EmptyAdditionalClientMetadata {}\n\n/// Client metadata used in dynamic client registration.\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\npub struct ClientMetadata<A, AT, CA, G, JE, JK, K, RT, S>\nwhere\n    A: AdditionalClientMetadata,\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    // To avoid implementing a custom deserializer that handles both language tags and flatten,\n    // we wrap the language tag handling in its own flattened struct.\n    #[serde(bound = \"AT: ApplicationType\", flatten)]\n    standard_metadata: StandardClientMetadata<AT, CA, G, JE, JK, K, RT, S>,\n\n    #[serde(bound = \"A: AdditionalClientMetadata\", flatten)]\n    additional_metadata: A,\n}\nimpl<A, AT, CA, G, JE, JK, K, RT, S> ClientMetadata<A, AT, CA, G, JE, JK, K, RT, S>\nwhere\n    A: AdditionalClientMetadata,\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    /// Instantiates new client metadata.\n    pub fn new(redirect_uris: Vec<RedirectUrl>, additional_metadata: A) -> Self {\n        Self {\n            standard_metadata: StandardClientMetadata {\n                redirect_uris,\n                response_types: None,\n                grant_types: None,\n                application_type: None,\n                contacts: None,\n                client_name: None,\n                logo_uri: None,\n                client_uri: None,\n                policy_uri: None,\n                tos_uri: None,\n                jwks_uri: None,\n                jwks: None,\n                sector_identifier_uri: None,\n                subject_type: None,\n                id_token_signed_response_alg: None,\n                id_token_encrypted_response_alg: None,\n                id_token_encrypted_response_enc: None,\n                userinfo_signed_response_alg: None,\n                userinfo_encrypted_response_alg: None,\n                userinfo_encrypted_response_enc: None,\n                request_object_signing_alg: None,\n                request_object_encryption_alg: None,\n                request_object_encryption_enc: None,\n                token_endpoint_auth_method: None,\n                token_endpoint_auth_signing_alg: None,\n                default_max_age: None,\n                require_auth_time: None,\n                default_acr_values: None,\n                initiate_login_uri: None,\n                request_uris: None,\n            },\n            additional_metadata,\n        }\n    }\n    field_getters_setters![\n        pub self [self.standard_metadata] [\"client metadata value\"] {\n            set_redirect_uris -> redirect_uris[Vec<RedirectUrl>],\n            set_response_types -> response_types[Option<Vec<ResponseTypes<RT>>>],\n            set_grant_types -> grant_types[Option<Vec<G>>],\n            set_application_type -> application_type[Option<AT>],\n            set_contacts -> contacts[Option<Vec<ClientContactEmail>>],\n            set_client_name -> client_name[Option<LocalizedClaim<ClientName>>],\n            set_logo_uri -> logo_uri[Option<LocalizedClaim<LogoUrl>>],\n            set_client_uri -> client_uri[Option<LocalizedClaim<ClientUrl>>],\n            set_policy_uri -> policy_uri[Option<LocalizedClaim<PolicyUrl>>],\n            set_tos_uri -> tos_uri[Option<LocalizedClaim<ToSUrl>>],\n            set_jwks_uri -> jwks_uri[Option<JsonWebKeySetUrl>],\n            set_jwks -> jwks[Option<JsonWebKeySet<K>>],\n            set_sector_identifier_uri -> sector_identifier_uri[Option<SectorIdentifierUrl>],\n            set_subject_type -> subject_type[Option<S>],\n            set_id_token_signed_response_alg -> id_token_signed_response_alg[Option<K::SigningAlgorithm>],\n            set_id_token_encrypted_response_alg -> id_token_encrypted_response_alg[Option<JK>],\n            set_id_token_encrypted_response_enc -> id_token_encrypted_response_enc[Option<JE>],\n            set_userinfo_signed_response_alg -> userinfo_signed_response_alg[Option<K::SigningAlgorithm>],\n            set_userinfo_encrypted_response_alg -> userinfo_encrypted_response_alg[Option<JK>],\n            set_userinfo_encrypted_response_enc -> userinfo_encrypted_response_enc[Option<JE>],\n            set_request_object_signing_alg -> request_object_signing_alg[Option<K::SigningAlgorithm>],\n            set_request_object_encryption_alg -> request_object_encryption_alg[Option<JK>],\n            set_request_object_encryption_enc -> request_object_encryption_enc[Option<JE>],\n            set_token_endpoint_auth_method -> token_endpoint_auth_method[Option<CA>],\n            set_token_endpoint_auth_signing_alg -> token_endpoint_auth_signing_alg[Option<K::SigningAlgorithm>],\n            set_default_max_age -> default_max_age[Option<Duration>],\n            set_require_auth_time -> require_auth_time[Option<bool>],\n            set_default_acr_values -> default_acr_values[Option<Vec<AuthenticationContextClass>>],\n            set_initiate_login_uri -> initiate_login_uri[Option<InitiateLoginUrl>],\n            set_request_uris -> request_uris[Option<Vec<RequestUrl>>],\n        }\n    ];\n\n    /// Returns additional client metadata fields.\n    pub fn additional_metadata(&self) -> &A {\n        &self.additional_metadata\n    }\n    /// Returns mutable additional client metadata fields.\n    pub fn additional_metadata_mut(&mut self) -> &mut A {\n        &mut self.additional_metadata\n    }\n}\n\n#[derive(Clone, Debug, PartialEq)]\nstruct StandardClientMetadata<AT, CA, G, JE, JK, K, RT, S>\nwhere\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    redirect_uris: Vec<RedirectUrl>,\n    response_types: Option<Vec<ResponseTypes<RT>>>,\n    grant_types: Option<Vec<G>>,\n    application_type: Option<AT>,\n    contacts: Option<Vec<ClientContactEmail>>,\n    client_name: Option<LocalizedClaim<ClientName>>,\n    logo_uri: Option<LocalizedClaim<LogoUrl>>,\n    client_uri: Option<LocalizedClaim<ClientUrl>>,\n    policy_uri: Option<LocalizedClaim<PolicyUrl>>,\n    tos_uri: Option<LocalizedClaim<ToSUrl>>,\n    jwks_uri: Option<JsonWebKeySetUrl>,\n    jwks: Option<JsonWebKeySet<K>>,\n    sector_identifier_uri: Option<SectorIdentifierUrl>,\n    subject_type: Option<S>,\n    id_token_signed_response_alg: Option<K::SigningAlgorithm>,\n    id_token_encrypted_response_alg: Option<JK>,\n    id_token_encrypted_response_enc: Option<JE>,\n    userinfo_signed_response_alg: Option<K::SigningAlgorithm>,\n    userinfo_encrypted_response_alg: Option<JK>,\n    userinfo_encrypted_response_enc: Option<JE>,\n    request_object_signing_alg: Option<K::SigningAlgorithm>,\n    request_object_encryption_alg: Option<JK>,\n    request_object_encryption_enc: Option<JE>,\n    token_endpoint_auth_method: Option<CA>,\n    token_endpoint_auth_signing_alg: Option<K::SigningAlgorithm>,\n    default_max_age: Option<Duration>,\n    require_auth_time: Option<bool>,\n    default_acr_values: Option<Vec<AuthenticationContextClass>>,\n    initiate_login_uri: Option<InitiateLoginUrl>,\n    request_uris: Option<Vec<RequestUrl>>,\n}\nimpl<'de, AT, CA, G, JE, JK, K, RT, S> Deserialize<'de>\n    for StandardClientMetadata<AT, CA, G, JE, JK, K, RT, S>\nwhere\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    /// Special deserializer that supports [RFC 5646](https://tools.ietf.org/html/rfc5646) language\n    /// tags associated with human-readable client metadata fields.\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct MetadataVisitor<\n            AT: ApplicationType,\n            CA: ClientAuthMethod,\n            G: GrantType,\n            JE: JweContentEncryptionAlgorithm<\n                KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n            >,\n            JK: JweKeyManagementAlgorithm,\n            K: JsonWebKey,\n            RT: ResponseType,\n            S: SubjectIdentifierType,\n        >(PhantomData<(AT, CA, G, JE, JK, K, RT, S)>);\n        impl<'de, AT, CA, G, JE, JK, K, RT, S> Visitor<'de> for MetadataVisitor<AT, CA, G, JE, JK, K, RT, S>\n        where\n            AT: ApplicationType,\n            CA: ClientAuthMethod,\n            G: GrantType,\n            JE: JweContentEncryptionAlgorithm<\n                KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n            >,\n            JK: JweKeyManagementAlgorithm,\n            K: JsonWebKey,\n            RT: ResponseType,\n            S: SubjectIdentifierType,\n        {\n            type Value = StandardClientMetadata<AT, CA, G, JE, JK, K, RT, S>;\n\n            fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {\n                formatter.write_str(\"struct StandardClientMetadata\")\n            }\n            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>\n            where\n                V: MapAccess<'de>,\n            {\n                // NB: The non-localized fields are actually Option<Option<_>> here so that we can\n                // distinguish between omitted fields and fields explicitly set to `null`. The\n                // latter is necessary so that we can detect duplicate fields (e.g., if a key is\n                // present both with a null value and a non-null value, that's an error).\n                let mut redirect_uris = None;\n                let mut response_types = None;\n                let mut grant_types = None;\n                let mut application_type = None;\n                let mut contacts = None;\n                let mut client_name = None;\n                let mut logo_uri = None;\n                let mut client_uri = None;\n                let mut policy_uri = None;\n                let mut tos_uri = None;\n                let mut jwks_uri = None;\n                let mut jwks = None;\n                let mut sector_identifier_uri = None;\n                let mut subject_type = None;\n                let mut id_token_signed_response_alg = None;\n                let mut id_token_encrypted_response_alg = None;\n                let mut id_token_encrypted_response_enc = None;\n                let mut userinfo_signed_response_alg = None;\n                let mut userinfo_encrypted_response_alg = None;\n                let mut userinfo_encrypted_response_enc = None;\n                let mut request_object_signing_alg = None;\n                let mut request_object_encryption_alg = None;\n                let mut request_object_encryption_enc = None;\n                let mut token_endpoint_auth_method = None;\n                let mut token_endpoint_auth_signing_alg = None;\n                let mut default_max_age = None;\n                let mut require_auth_time = None;\n                let mut default_acr_values = None;\n                let mut initiate_login_uri = None;\n                let mut request_uris = None;\n\n                macro_rules! field_case {\n                    ($field:ident, $typ:ty, $language_tag:ident) => {{\n                        $field = Some(<$typ>::deserialize_map_field(\n                            &mut map,\n                            stringify!($field),\n                            $language_tag,\n                            $field,\n                        )?);\n                    }};\n                }\n\n                while let Some(key) = map.next_key::<String>()? {\n                    let (field_name, language_tag) = split_language_tag_key(&key);\n                    match field_name {\n                        \"redirect_uris\" => field_case!(redirect_uris, Vec<_>, language_tag),\n                        \"response_types\" => field_case!(response_types, Option<_>, language_tag),\n                        \"grant_types\" => field_case!(grant_types, Option<_>, language_tag),\n                        \"application_type\" => {\n                            field_case!(application_type, Option<_>, language_tag)\n                        }\n                        \"contacts\" => field_case!(contacts, Option<_>, language_tag),\n                        \"client_name\" => {\n                            field_case!(client_name, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"logo_uri\" => {\n                            field_case!(logo_uri, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"client_uri\" => {\n                            field_case!(client_uri, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"policy_uri\" => {\n                            field_case!(policy_uri, LocalizedClaim<Option<_>>, language_tag)\n                        }\n                        \"tos_uri\" => field_case!(tos_uri, LocalizedClaim<Option<_>>, language_tag),\n                        \"jwks_uri\" => field_case!(jwks_uri, Option<_>, language_tag),\n                        \"jwks\" => field_case!(jwks, Option<_>, language_tag),\n                        \"sector_identifier_uri\" => {\n                            field_case!(sector_identifier_uri, Option<_>, language_tag)\n                        }\n                        \"subject_type\" => field_case!(subject_type, Option<_>, language_tag),\n                        \"id_token_signed_response_alg\" => {\n                            field_case!(id_token_signed_response_alg, Option<_>, language_tag)\n                        }\n                        \"id_token_encrypted_response_alg\" => {\n                            field_case!(id_token_encrypted_response_alg, Option<_>, language_tag)\n                        }\n                        \"id_token_encrypted_response_enc\" => {\n                            field_case!(id_token_encrypted_response_enc, Option<_>, language_tag)\n                        }\n                        \"userinfo_signed_response_alg\" => {\n                            field_case!(userinfo_signed_response_alg, Option<_>, language_tag)\n                        }\n                        \"userinfo_encrypted_response_alg\" => {\n                            field_case!(userinfo_encrypted_response_alg, Option<_>, language_tag)\n                        }\n                        \"userinfo_encrypted_response_enc\" => {\n                            field_case!(userinfo_encrypted_response_enc, Option<_>, language_tag)\n                        }\n                        \"request_object_signing_alg\" => {\n                            field_case!(request_object_signing_alg, Option<_>, language_tag)\n                        }\n                        \"request_object_encryption_alg\" => {\n                            field_case!(request_object_encryption_alg, Option<_>, language_tag)\n                        }\n                        \"request_object_encryption_enc\" => {\n                            field_case!(request_object_encryption_enc, Option<_>, language_tag)\n                        }\n                        \"token_endpoint_auth_method\" => {\n                            field_case!(token_endpoint_auth_method, Option<_>, language_tag)\n                        }\n                        \"token_endpoint_auth_signing_alg\" => {\n                            field_case!(token_endpoint_auth_signing_alg, Option<_>, language_tag)\n                        }\n                        \"default_max_age\" => {\n                            field_case!(default_max_age, Option<u64>, language_tag)\n                        }\n                        \"require_auth_time\" => {\n                            field_case!(require_auth_time, Option<_>, language_tag)\n                        }\n                        \"default_acr_values\" => {\n                            field_case!(default_acr_values, Option<_>, language_tag)\n                        }\n                        \"initiate_login_uri\" => {\n                            field_case!(initiate_login_uri, Option<_>, language_tag)\n                        }\n                        \"request_uris\" => field_case!(request_uris, Option<_>, language_tag),\n\n                        // Ignore unknown fields.\n                        _ => {\n                            map.next_value::<serde::de::IgnoredAny>()?;\n                            continue;\n                        }\n                    };\n                }\n\n                Ok(StandardClientMetadata {\n                    redirect_uris: redirect_uris\n                        .ok_or_else(|| serde::de::Error::missing_field(\"redirect_uris\"))?,\n                    response_types: response_types.flatten(),\n                    grant_types: grant_types.flatten(),\n                    application_type: application_type.flatten(),\n                    contacts: contacts.flatten(),\n                    client_name: client_name.and_then(LocalizedClaim::flatten_or_none),\n                    logo_uri: logo_uri.and_then(LocalizedClaim::flatten_or_none),\n                    client_uri: client_uri.and_then(LocalizedClaim::flatten_or_none),\n                    policy_uri: policy_uri.and_then(LocalizedClaim::flatten_or_none),\n                    tos_uri: tos_uri.and_then(LocalizedClaim::flatten_or_none),\n                    jwks_uri: jwks_uri.flatten(),\n                    jwks: jwks.flatten(),\n                    sector_identifier_uri: sector_identifier_uri.flatten(),\n                    subject_type: subject_type.flatten(),\n                    id_token_signed_response_alg: id_token_signed_response_alg.flatten(),\n                    id_token_encrypted_response_alg: id_token_encrypted_response_alg.flatten(),\n                    id_token_encrypted_response_enc: id_token_encrypted_response_enc.flatten(),\n                    userinfo_signed_response_alg: userinfo_signed_response_alg.flatten(),\n                    userinfo_encrypted_response_alg: userinfo_encrypted_response_alg.flatten(),\n                    userinfo_encrypted_response_enc: userinfo_encrypted_response_enc.flatten(),\n                    request_object_signing_alg: request_object_signing_alg.flatten(),\n                    request_object_encryption_alg: request_object_encryption_alg.flatten(),\n                    request_object_encryption_enc: request_object_encryption_enc.flatten(),\n                    token_endpoint_auth_method: token_endpoint_auth_method.flatten(),\n                    token_endpoint_auth_signing_alg: token_endpoint_auth_signing_alg.flatten(),\n                    default_max_age: default_max_age.flatten().map(Duration::from_secs),\n                    require_auth_time: require_auth_time.flatten(),\n                    default_acr_values: default_acr_values.flatten(),\n                    initiate_login_uri: initiate_login_uri.flatten(),\n                    request_uris: request_uris.flatten(),\n                })\n            }\n        }\n        deserializer.deserialize_map(MetadataVisitor(PhantomData))\n    }\n}\nimpl<AT, CA, G, JE, JK, K, RT, S> Serialize for StandardClientMetadata<AT, CA, G, JE, JK, K, RT, S>\nwhere\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    #[allow(clippy::cognitive_complexity)]\n    fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>\n    where\n        SE: Serializer,\n    {\n        serialize_fields! {\n            self -> serializer {\n                [redirect_uris]\n                [Option(response_types)]\n                [Option(grant_types)]\n                [Option(application_type)]\n                [Option(contacts)]\n                [LanguageTag(client_name)]\n                [LanguageTag(logo_uri)]\n                [LanguageTag(client_uri)]\n                [LanguageTag(policy_uri)]\n                [LanguageTag(tos_uri)]\n                [Option(jwks_uri)]\n                [Option(jwks)]\n                [Option(sector_identifier_uri)]\n                [Option(subject_type)]\n                [Option(id_token_signed_response_alg)]\n                [Option(id_token_encrypted_response_alg)]\n                [Option(id_token_encrypted_response_enc)]\n                [Option(userinfo_signed_response_alg)]\n                [Option(userinfo_encrypted_response_alg)]\n                [Option(userinfo_encrypted_response_enc)]\n                [Option(request_object_signing_alg)]\n                [Option(request_object_encryption_alg)]\n                [Option(request_object_encryption_enc)]\n                [Option(token_endpoint_auth_method)]\n                [Option(token_endpoint_auth_signing_alg)]\n                [Option(Seconds(default_max_age))]\n                [Option(require_auth_time)]\n                [Option(default_acr_values)]\n                [Option(initiate_login_uri)]\n                [Option(request_uris)]\n            }\n        }\n    }\n}\n\n/// Dynamic client registration request.\n#[derive(Clone, Debug)]\npub struct ClientRegistrationRequest<AC, AR, AT, CA, ET, G, JE, JK, K, RT, S>\nwhere\n    AC: AdditionalClientMetadata,\n    AR: AdditionalClientRegistrationResponse,\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    ET: RegisterErrorResponseType,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    client_metadata: ClientMetadata<AC, AT, CA, G, JE, JK, K, RT, S>,\n    initial_access_token: Option<AccessToken>,\n    _phantom: PhantomData<(AR, ET)>,\n}\nimpl<AC, AR, AT, CA, ET, G, JE, JK, K, RT, S>\n    ClientRegistrationRequest<AC, AR, AT, CA, ET, G, JE, JK, K, RT, S>\nwhere\n    AC: AdditionalClientMetadata,\n    AR: AdditionalClientRegistrationResponse,\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    ET: RegisterErrorResponseType + Send + Sync,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType + Send + Sync,\n{\n    /// Instantiates a new dynamic client registration request.\n    pub fn new(redirect_uris: Vec<RedirectUrl>, additional_metadata: AC) -> Self {\n        Self {\n            client_metadata: ClientMetadata::new(redirect_uris, additional_metadata),\n            initial_access_token: None,\n            _phantom: PhantomData,\n        }\n    }\n\n    /// Submits this request to the specified registration endpoint using the specified synchronous\n    /// HTTP client.\n    pub fn register<C>(\n        &self,\n        registration_endpoint: &RegistrationUrl,\n        http_client: &C,\n    ) -> Result<\n        ClientRegistrationResponse<AC, AR, AT, CA, G, JE, JK, K, RT, S>,\n        ClientRegistrationError<ET, <C as SyncHttpClient>::Error>,\n    >\n    where\n        C: SyncHttpClient,\n    {\n        self.prepare_registration(registration_endpoint)\n            .and_then(|http_request| {\n                http_client\n                    .call(http_request)\n                    .map_err(ClientRegistrationError::Request)\n            })\n            .and_then(Self::register_response)\n    }\n\n    /// Submits this request to the specified registration endpoint using the specified asynchronous\n    /// HTTP client.\n    pub fn register_async<'c, C>(\n        &'c self,\n        registration_endpoint: &'c RegistrationUrl,\n        http_client: &'c C,\n    ) -> impl Future<\n        Output = Result<\n            ClientRegistrationResponse<AC, AR, AT, CA, G, JE, JK, K, RT, S>,\n            ClientRegistrationError<ET, <C as AsyncHttpClient<'c>>::Error>,\n        >,\n    > + 'c\n    where\n        Self: 'c,\n        C: AsyncHttpClient<'c>,\n    {\n        Box::pin(async move {\n            let http_request = self.prepare_registration(registration_endpoint)?;\n            let http_response = http_client\n                .call(http_request)\n                .await\n                .map_err(ClientRegistrationError::Request)?;\n            Self::register_response(http_response)\n        })\n    }\n\n    fn prepare_registration<RE>(\n        &self,\n        registration_endpoint: &RegistrationUrl,\n    ) -> Result<HttpRequest, ClientRegistrationError<ET, RE>>\n    where\n        RE: std::error::Error + 'static,\n    {\n        let request_json = serde_json::to_string(self.client_metadata())\n            .map_err(ClientRegistrationError::Serialize)?\n            .into_bytes();\n\n        let auth_header_opt = self.initial_access_token().map(auth_bearer);\n\n        let mut request = http::Request::builder()\n            .uri(registration_endpoint.to_string())\n            .method(Method::POST)\n            .header(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON))\n            .header(CONTENT_TYPE, HeaderValue::from_static(MIME_TYPE_JSON));\n        if let Some((header, value)) = auth_header_opt {\n            request = request.header(header, value);\n        }\n\n        request.body(request_json).map_err(|err| {\n            ClientRegistrationError::Other(format!(\"failed to prepare request: {err}\"))\n        })\n    }\n\n    fn register_response<RE>(\n        http_response: HttpResponse,\n    ) -> Result<\n        ClientRegistrationResponse<AC, AR, AT, CA, G, JE, JK, K, RT, S>,\n        ClientRegistrationError<ET, RE>,\n    >\n    where\n        RE: std::error::Error + 'static,\n    {\n        // TODO: check for WWW-Authenticate response header if bearer auth was used (see\n        //   https://tools.ietf.org/html/rfc6750#section-3)\n        // TODO: other necessary response validation? check spec\n\n        // Spec says that a successful response SHOULD use 201 Created, and a registration error\n        // condition returns (no \"SHOULD\") 400 Bad Request. For now, only accept these two status\n        // codes. We may need to relax the success status to improve interoperability.\n        if http_response.status() != StatusCode::CREATED\n            && http_response.status() != StatusCode::BAD_REQUEST\n        {\n            return Err(ClientRegistrationError::Response(\n                http_response.status(),\n                http_response.body().to_owned(),\n                \"unexpected HTTP status code\".to_string(),\n            ));\n        }\n\n        check_content_type(http_response.headers(), MIME_TYPE_JSON).map_err(|err_msg| {\n            ClientRegistrationError::Response(\n                http_response.status(),\n                http_response.body().to_owned(),\n                err_msg,\n            )\n        })?;\n\n        let response_body =\n            String::from_utf8(http_response.body().to_owned()).map_err(|parse_error| {\n                ClientRegistrationError::Other(format!(\n                    \"couldn't parse response as UTF-8: {}\",\n                    parse_error\n                ))\n            })?;\n\n        if http_response.status() == StatusCode::BAD_REQUEST {\n            let response_error: StandardErrorResponse<ET> = serde_path_to_error::deserialize(\n                &mut serde_json::Deserializer::from_str(&response_body),\n            )\n            .map_err(ClientRegistrationError::Parse)?;\n            return Err(ClientRegistrationError::ServerResponse(response_error));\n        }\n\n        serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_str(&response_body))\n            .map_err(ClientRegistrationError::Parse)\n    }\n\n    /// Returns the client metadata associated with this registration request.\n    pub fn client_metadata(&self) -> &ClientMetadata<AC, AT, CA, G, JE, JK, K, RT, S> {\n        &self.client_metadata\n    }\n\n    /// Returns the initial access token associated with this registration request.\n    pub fn initial_access_token(&self) -> Option<&AccessToken> {\n        self.initial_access_token.as_ref()\n    }\n    /// Sets the initial access token for this request.\n    pub fn set_initial_access_token(mut self, access_token: Option<AccessToken>) -> Self {\n        self.initial_access_token = access_token;\n        self\n    }\n\n    field_getters_setters![\n        pub self [self.client_metadata.standard_metadata] [\"client metadata value\"] {\n            set_redirect_uris -> redirect_uris[Vec<RedirectUrl>],\n            set_response_types -> response_types[Option<Vec<ResponseTypes<RT>>>],\n            set_grant_types -> grant_types[Option<Vec<G>>],\n            set_application_type -> application_type[Option<AT>],\n            set_contacts -> contacts[Option<Vec<ClientContactEmail>>],\n            set_client_name -> client_name[Option<LocalizedClaim<ClientName>>],\n            set_logo_uri -> logo_uri[Option<LocalizedClaim<LogoUrl>>],\n            set_client_uri -> client_uri[Option<LocalizedClaim<ClientUrl>>],\n            set_policy_uri -> policy_uri[Option<LocalizedClaim<PolicyUrl>>],\n            set_tos_uri -> tos_uri[Option<LocalizedClaim<ToSUrl>>],\n            set_jwks_uri -> jwks_uri[Option<JsonWebKeySetUrl>],\n            set_jwks -> jwks[Option<JsonWebKeySet<K>>],\n            set_sector_identifier_uri -> sector_identifier_uri[Option<SectorIdentifierUrl>],\n            set_subject_type -> subject_type[Option<S>],\n            set_id_token_signed_response_alg -> id_token_signed_response_alg[Option<K::SigningAlgorithm>],\n            set_id_token_encrypted_response_alg -> id_token_encrypted_response_alg[Option<JK>],\n            set_id_token_encrypted_response_enc -> id_token_encrypted_response_enc[Option<JE>],\n            set_userinfo_signed_response_alg -> userinfo_signed_response_alg[Option<K::SigningAlgorithm>],\n            set_userinfo_encrypted_response_alg -> userinfo_encrypted_response_alg[Option<JK>],\n            set_userinfo_encrypted_response_enc -> userinfo_encrypted_response_enc[Option<JE>],\n            set_request_object_signing_alg -> request_object_signing_alg[Option<K::SigningAlgorithm>],\n            set_request_object_encryption_alg -> request_object_encryption_alg[Option<JK>],\n            set_request_object_encryption_enc -> request_object_encryption_enc[Option<JE>],\n            set_token_endpoint_auth_method -> token_endpoint_auth_method[Option<CA>],\n            set_token_endpoint_auth_signing_alg -> token_endpoint_auth_signing_alg[Option<K::SigningAlgorithm>],\n            set_default_max_age -> default_max_age[Option<Duration>],\n            set_require_auth_time -> require_auth_time[Option<bool>],\n            set_default_acr_values -> default_acr_values[Option<Vec<AuthenticationContextClass>>],\n            set_initiate_login_uri -> initiate_login_uri[Option<InitiateLoginUrl>],\n            set_request_uris -> request_uris[Option<Vec<RequestUrl>>],\n        }\n    ];\n\n    /// Returns additional client metadata fields.\n    pub fn additional_metadata(&self) -> &AC {\n        &self.client_metadata.additional_metadata\n    }\n    /// Returns mutable additional client metadata fields.\n    pub fn additional_metadata_mut(&mut self) -> &mut AC {\n        &mut self.client_metadata.additional_metadata\n    }\n}\n\n/// Trait for adding extra fields to the [`ClientRegistrationResponse`].\npub trait AdditionalClientRegistrationResponse: Debug + DeserializeOwned + Serialize {}\n\n// In order to support serde flatten, this must be an empty struct rather than an empty\n// tuple struct.\n/// Empty (default) extra [`ClientRegistrationResponse`] fields.\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]\npub struct EmptyAdditionalClientRegistrationResponse {}\nimpl AdditionalClientRegistrationResponse for EmptyAdditionalClientRegistrationResponse {}\n\n/// Response to a dynamic client registration request.\n#[serde_as]\n#[skip_serializing_none]\n#[derive(Debug, Deserialize, Serialize)]\npub struct ClientRegistrationResponse<AC, AR, AT, CA, G, JE, JK, K, RT, S>\nwhere\n    AC: AdditionalClientMetadata,\n    AR: AdditionalClientRegistrationResponse,\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    client_id: ClientId,\n    client_secret: Option<ClientSecret>,\n    registration_access_token: Option<RegistrationAccessToken>,\n    registration_client_uri: Option<ClientConfigUrl>,\n    #[serde_as(as = \"Option<Timestamp>\")]\n    client_id_issued_at: Option<DateTime<Utc>>,\n    #[serde_as(as = \"Option<Timestamp>\")]\n    client_secret_expires_at: Option<DateTime<Utc>>,\n    #[serde(bound = \"AC: AdditionalClientMetadata\", flatten)]\n    client_metadata: ClientMetadata<AC, AT, CA, G, JE, JK, K, RT, S>,\n\n    #[serde(bound = \"AR: AdditionalClientRegistrationResponse\", flatten)]\n    additional_response: AR,\n}\nimpl<AC, AR, AT, CA, G, JE, JK, K, RT, S>\n    ClientRegistrationResponse<AC, AR, AT, CA, G, JE, JK, K, RT, S>\nwhere\n    AC: AdditionalClientMetadata,\n    AR: AdditionalClientRegistrationResponse,\n    AT: ApplicationType,\n    CA: ClientAuthMethod,\n    G: GrantType,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    JK: JweKeyManagementAlgorithm,\n    K: JsonWebKey,\n    RT: ResponseType,\n    S: SubjectIdentifierType,\n{\n    /// Instantiates a new dynamic client registration response.\n    pub fn new(\n        client_id: ClientId,\n        redirect_uris: Vec<RedirectUrl>,\n        additional_metadata: AC,\n        additional_response: AR,\n    ) -> Self {\n        Self {\n            client_id,\n            client_secret: None,\n            registration_access_token: None,\n            registration_client_uri: None,\n            client_id_issued_at: None,\n            client_secret_expires_at: None,\n            client_metadata: ClientMetadata::new(redirect_uris, additional_metadata),\n            additional_response,\n        }\n    }\n\n    /// Instantiates a new dynamic client registration response using the specified client metadata.\n    pub fn from_client_metadata(\n        client_id: ClientId,\n        client_metadata: ClientMetadata<AC, AT, CA, G, JE, JK, K, RT, S>,\n        additional_response: AR,\n    ) -> Self {\n        Self {\n            client_id,\n            client_secret: None,\n            registration_access_token: None,\n            registration_client_uri: None,\n            client_id_issued_at: None,\n            client_secret_expires_at: None,\n            client_metadata,\n            additional_response,\n        }\n    }\n\n    field_getters_setters![\n        pub self [self] [\"response field\"] {\n            set_client_id -> client_id[ClientId],\n            set_client_secret -> client_secret[Option<ClientSecret>],\n            set_registration_access_token\n              -> registration_access_token[Option<RegistrationAccessToken>],\n            set_registration_client_uri -> registration_client_uri[Option<ClientConfigUrl>],\n            set_client_id_issued_at -> client_id_issued_at[Option<DateTime<Utc>>],\n            set_client_secret_expires_at -> client_secret_expires_at[Option<DateTime<Utc>>],\n        }\n    ];\n\n    field_getters_setters![\n        pub self [self.client_metadata.standard_metadata] [\"client metadata value\"] {\n            set_redirect_uris -> redirect_uris[Vec<RedirectUrl>],\n            set_response_types -> response_types[Option<Vec<ResponseTypes<RT>>>],\n            set_grant_types -> grant_types[Option<Vec<G>>],\n            set_application_type -> application_type[Option<AT>],\n            set_contacts -> contacts[Option<Vec<ClientContactEmail>>],\n            set_client_name -> client_name[Option<LocalizedClaim<ClientName>>],\n            set_logo_uri -> logo_uri[Option<LocalizedClaim<LogoUrl>>],\n            set_client_uri -> client_uri[Option<LocalizedClaim<ClientUrl>>],\n            set_policy_uri -> policy_uri[Option<LocalizedClaim<PolicyUrl>>],\n            set_tos_uri -> tos_uri[Option<LocalizedClaim<ToSUrl>>],\n            set_jwks_uri -> jwks_uri[Option<JsonWebKeySetUrl>],\n            set_jwks -> jwks[Option<JsonWebKeySet<K>>],\n            set_sector_identifier_uri -> sector_identifier_uri[Option<SectorIdentifierUrl>],\n            set_subject_type -> subject_type[Option<S>],\n            set_id_token_signed_response_alg -> id_token_signed_response_alg[Option<K::SigningAlgorithm>],\n            set_id_token_encrypted_response_alg -> id_token_encrypted_response_alg[Option<JK>],\n            set_id_token_encrypted_response_enc -> id_token_encrypted_response_enc[Option<JE>],\n            set_userinfo_signed_response_alg -> userinfo_signed_response_alg[Option<K::SigningAlgorithm>],\n            set_userinfo_encrypted_response_alg -> userinfo_encrypted_response_alg[Option<JK>],\n            set_userinfo_encrypted_response_enc -> userinfo_encrypted_response_enc[Option<JE>],\n            set_request_object_signing_alg -> request_object_signing_alg[Option<K::SigningAlgorithm>],\n            set_request_object_encryption_alg -> request_object_encryption_alg[Option<JK>],\n            set_request_object_encryption_enc -> request_object_encryption_enc[Option<JE>],\n            set_token_endpoint_auth_method -> token_endpoint_auth_method[Option<CA>],\n            set_token_endpoint_auth_signing_alg -> token_endpoint_auth_signing_alg[Option<K::SigningAlgorithm>],\n            set_default_max_age -> default_max_age[Option<Duration>],\n            set_require_auth_time -> require_auth_time[Option<bool>],\n            set_default_acr_values -> default_acr_values[Option<Vec<AuthenticationContextClass>>],\n            set_initiate_login_uri -> initiate_login_uri[Option<InitiateLoginUrl>],\n            set_request_uris -> request_uris[Option<Vec<RequestUrl>>],\n        }\n    ];\n\n    /// Returns additional client metadata fields.\n    pub fn additional_metadata(&self) -> &AC {\n        &self.client_metadata.additional_metadata\n    }\n    /// Returns mutable additional client metadata fields.\n    pub fn additional_metadata_mut(&mut self) -> &mut AC {\n        &mut self.client_metadata.additional_metadata\n    }\n\n    /// Returns additional response fields.\n    pub fn additional_response(&self) -> &AR {\n        &self.additional_response\n    }\n    /// Returns mutable additional response fields.\n    pub fn additional_response_mut(&mut self) -> &mut AR {\n        &mut self.additional_response\n    }\n}\n\n// TODO: implement client configuration endpoint request (Section 4)\n\n/// Trait representing an error returned by the dynamic client registration endpoint.\npub trait RegisterErrorResponseType: ErrorResponseType + 'static {}\n\n/// Error registering a client.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum ClientRegistrationError<T, RE>\nwhere\n    RE: std::error::Error + 'static,\n    T: RegisterErrorResponseType,\n{\n    /// An unexpected error occurred.\n    #[error(\"Other error: {0}\")]\n    Other(String),\n    /// Failed to parse server response.\n    #[error(\"Failed to parse server response\")]\n    Parse(#[source] serde_path_to_error::Error<serde_json::Error>),\n    /// An error occurred while sending the request or receiving the response (e.g., network\n    /// connectivity failed).\n    #[error(\"Request failed\")]\n    Request(#[source] RE),\n    /// Server returned an invalid response.\n    #[error(\"Server returned invalid response with status {0}: {2}\")]\n    Response(StatusCode, Vec<u8>, String),\n    /// Failed to serialize client metadata.\n    #[error(\"Failed to serialize client metadata\")]\n    Serialize(#[source] serde_json::Error),\n    /// Server returned an error.\n    #[error(\"Server returned error: {0}\")]\n    ServerResponse(StandardErrorResponse<T>),\n}\n"
  },
  {
    "path": "src/registration/tests.rs",
    "content": "use crate::core::{\n    CoreApplicationType, CoreClientAuthMethod, CoreClientMetadata, CoreClientRegistrationResponse,\n    CoreGrantType, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm,\n    CoreJwsSigningAlgorithm, CoreResponseType, CoreSubjectIdentifierType,\n};\nuse crate::jwt::tests::TEST_RSA_PUB_KEY;\nuse crate::{\n    AuthenticationContextClass, ClientConfigUrl, ClientContactEmail, ClientName, ClientUrl,\n    JsonWebKeySet, JsonWebKeySetUrl, LanguageTag, LogoUrl, PolicyUrl, RequestUrl, ResponseTypes,\n    SectorIdentifierUrl, ToSUrl,\n};\nuse crate::{ClientId, RedirectUrl};\n\nuse chrono::{TimeZone, Utc};\nuse itertools::sorted;\n\nuse std::time::Duration;\n\n#[test]\nfn test_metadata_serialization() {\n    // `jwks_uri` and `jwks` aren't supposed to be used together, but this test is just for\n    // serialization/deserialization.\n    let json_response = format!(\"{{\n            \\\"redirect_uris\\\": [\\\"https://example.com/redirect-1\\\", \\\"https://example.com/redirect-2\\\"],\n            \\\"response_types\\\": [\\\"code\\\", \\\"code token id_token\\\"],\n            \\\"grant_types\\\": [\\\"authorization_code\\\", \\\"client_credentials\\\", \\\"implicit\\\", \\\n                \\\"password\\\", \\\"refresh_token\\\"],\n            \\\"application_type\\\": \\\"web\\\",\n            \\\"contacts\\\": [\\\"user@example.com\\\", \\\"admin@openidconnect.local\\\"],\n            \\\"client_name\\\": \\\"Example\\\",\n            \\\"client_name#es\\\": \\\"Ejemplo\\\",\n            \\\"logo_uri\\\": \\\"https://example.com/logo.png\\\",\n            \\\"logo_uri#fr\\\": \\\"https://example.com/logo-fr.png\\\",\n            \\\"client_uri\\\": \\\"https://example.com/client-app\\\",\n            \\\"client_uri#de\\\": \\\"https://example.com/client-app-de\\\",\n            \\\"policy_uri\\\": \\\"https://example.com/policy\\\",\n            \\\"policy_uri#sr-Latn\\\": \\\"https://example.com/policy-sr-latin\\\",\n            \\\"tos_uri\\\": \\\"https://example.com/tos\\\",\n            \\\"tos_uri#sr-Cyrl\\\": \\\"https://example.com/tos-sr-cyrl\\\",\n            \\\"jwks_uri\\\": \\\"https://example.com/jwks\\\",\n            \\\"jwks\\\": {{\\\"keys\\\": [{}]}},\n            \\\"sector_identifier_uri\\\": \\\"https://example.com/sector\\\",\n            \\\"subject_type\\\": \\\"pairwise\\\",\n            \\\"id_token_signed_response_alg\\\": \\\"HS256\\\",\n            \\\"id_token_encrypted_response_alg\\\": \\\"RSA1_5\\\",\n            \\\"id_token_encrypted_response_enc\\\": \\\"A128CBC-HS256\\\",\n            \\\"userinfo_signed_response_alg\\\": \\\"RS384\\\",\n            \\\"userinfo_encrypted_response_alg\\\": \\\"RSA-OAEP\\\",\n            \\\"userinfo_encrypted_response_enc\\\": \\\"A256CBC-HS512\\\",\n            \\\"request_object_signing_alg\\\": \\\"ES512\\\",\n            \\\"request_object_encryption_alg\\\": \\\"ECDH-ES+A128KW\\\",\n            \\\"request_object_encryption_enc\\\": \\\"A256GCM\\\",\n            \\\"token_endpoint_auth_method\\\": \\\"client_secret_basic\\\",\n            \\\"token_endpoint_auth_signing_alg\\\": \\\"PS512\\\",\n            \\\"default_max_age\\\": 3600,\n            \\\"require_auth_time\\\": true,\n            \\\"default_acr_values\\\": [\\\"0\\\", \\\"urn:mace:incommon:iap:silver\\\", \\\n                \\\"urn:mace:incommon:iap:bronze\\\"],\n            \\\"initiate_login_uri\\\": \\\"https://example.com/login\\\",\n            \\\"request_uris\\\": [\\\"https://example.com/request-1\\\", \\\"https://example.com/request-2\\\"]\n        }}\", TEST_RSA_PUB_KEY);\n\n    let client_metadata: CoreClientMetadata = serde_json::from_str(&json_response).unwrap();\n\n    assert_eq!(\n        *client_metadata.redirect_uris(),\n        vec![\n            RedirectUrl::new(\"https://example.com/redirect-1\".to_string()).unwrap(),\n            RedirectUrl::new(\"https://example.com/redirect-2\".to_string()).unwrap(),\n        ]\n    );\n    assert_eq!(\n        *client_metadata.response_types().unwrap(),\n        vec![\n            ResponseTypes::new(vec![CoreResponseType::Code]),\n            ResponseTypes::new(vec![\n                CoreResponseType::Code,\n                CoreResponseType::Token,\n                CoreResponseType::IdToken,\n            ]),\n        ]\n    );\n    assert_eq!(\n        client_metadata.grant_types().unwrap(),\n        &vec![\n            CoreGrantType::AuthorizationCode,\n            CoreGrantType::ClientCredentials,\n            CoreGrantType::Implicit,\n            CoreGrantType::Password,\n            CoreGrantType::RefreshToken,\n        ]\n    );\n    assert_eq!(\n        *client_metadata.application_type().unwrap(),\n        CoreApplicationType::Web\n    );\n    assert_eq!(\n        *client_metadata.contacts().unwrap(),\n        vec![\n            ClientContactEmail::new(\"user@example.com\".to_string()),\n            ClientContactEmail::new(\"admin@openidconnect.local\".to_string()),\n        ]\n    );\n    assert_eq!(\n        sorted(client_metadata.client_name().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, ClientName)>>(),\n        vec![\n            (None, ClientName::new(\"Example\".to_string())),\n            (\n                Some(LanguageTag::new(\"es\".to_string())),\n                ClientName::new(\"Ejemplo\".to_string()),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(client_metadata.logo_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, LogoUrl)>>(),\n        vec![\n            (\n                None,\n                LogoUrl::new(\"https://example.com/logo.png\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"fr\".to_string())),\n                LogoUrl::new(\"https://example.com/logo-fr.png\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(client_metadata.client_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, ClientUrl)>>(),\n        vec![\n            (\n                None,\n                ClientUrl::new(\"https://example.com/client-app\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"de\".to_string())),\n                ClientUrl::new(\"https://example.com/client-app-de\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(client_metadata.policy_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, PolicyUrl)>>(),\n        vec![\n            (\n                None,\n                PolicyUrl::new(\"https://example.com/policy\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"sr-Latn\".to_string())),\n                PolicyUrl::new(\"https://example.com/policy-sr-latin\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(client_metadata.tos_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, ToSUrl)>>(),\n        vec![\n            (\n                None,\n                ToSUrl::new(\"https://example.com/tos\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"sr-Cyrl\".to_string())),\n                ToSUrl::new(\"https://example.com/tos-sr-cyrl\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        *client_metadata.jwks_uri().unwrap(),\n        JsonWebKeySetUrl::new(\"https://example.com/jwks\".to_string()).unwrap()\n    );\n    assert_eq!(\n        client_metadata.jwks(),\n        Some(&JsonWebKeySet::new(vec![serde_json::from_str(\n            TEST_RSA_PUB_KEY\n        )\n        .unwrap()],))\n    );\n    assert_eq!(\n        *client_metadata.sector_identifier_uri().unwrap(),\n        SectorIdentifierUrl::new(\"https://example.com/sector\".to_string()).unwrap()\n    );\n    assert_eq!(\n        *client_metadata.subject_type().unwrap(),\n        CoreSubjectIdentifierType::Pairwise\n    );\n    assert_eq!(\n        *client_metadata.id_token_signed_response_alg().unwrap(),\n        CoreJwsSigningAlgorithm::HmacSha256\n    );\n    assert_eq!(\n        *client_metadata.id_token_encrypted_response_alg().unwrap(),\n        CoreJweKeyManagementAlgorithm::RsaPkcs1V15\n    );\n    assert_eq!(\n        *client_metadata.id_token_encrypted_response_enc().unwrap(),\n        CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256\n    );\n    assert_eq!(\n        *client_metadata.userinfo_signed_response_alg().unwrap(),\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384\n    );\n    assert_eq!(\n        *client_metadata.userinfo_encrypted_response_alg().unwrap(),\n        CoreJweKeyManagementAlgorithm::RsaOaep\n    );\n    assert_eq!(\n        *client_metadata.userinfo_encrypted_response_enc().unwrap(),\n        CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512\n    );\n    assert_eq!(\n        *client_metadata.request_object_signing_alg().unwrap(),\n        CoreJwsSigningAlgorithm::EcdsaP521Sha512\n    );\n    assert_eq!(\n        *client_metadata.request_object_encryption_alg().unwrap(),\n        CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap128\n    );\n    assert_eq!(\n        *client_metadata.request_object_encryption_enc().unwrap(),\n        CoreJweContentEncryptionAlgorithm::Aes256Gcm\n    );\n    assert_eq!(\n        *client_metadata.token_endpoint_auth_method().unwrap(),\n        CoreClientAuthMethod::ClientSecretBasic\n    );\n    assert_eq!(\n        *client_metadata.token_endpoint_auth_signing_alg().unwrap(),\n        CoreJwsSigningAlgorithm::RsaSsaPssSha512\n    );\n    assert_eq!(\n        *client_metadata.default_max_age().unwrap(),\n        Duration::from_secs(3600)\n    );\n    assert!(client_metadata.require_auth_time().unwrap());\n    assert_eq!(\n        *client_metadata.default_acr_values().unwrap(),\n        vec![\n            AuthenticationContextClass::new(\"0\".to_string()),\n            AuthenticationContextClass::new(\"urn:mace:incommon:iap:silver\".to_string()),\n            AuthenticationContextClass::new(\"urn:mace:incommon:iap:bronze\".to_string()),\n        ]\n    );\n    assert_eq!(\n        *client_metadata.sector_identifier_uri().unwrap(),\n        SectorIdentifierUrl::new(\"https://example.com/sector\".to_string()).unwrap()\n    );\n    assert_eq!(\n        *client_metadata.request_uris().unwrap(),\n        vec![\n            RequestUrl::new(\"https://example.com/request-1\".to_string()).unwrap(),\n            RequestUrl::new(\"https://example.com/request-2\".to_string()).unwrap(),\n        ]\n    );\n    let serialized_json = serde_json::to_string(&client_metadata).unwrap();\n\n    assert_eq!(\n        client_metadata,\n        serde_json::from_str(&serialized_json).unwrap()\n    );\n}\n\n#[test]\nfn test_metadata_serialization_minimal() {\n    let json_response = \"{\\\"redirect_uris\\\": [\\\"https://example.com/redirect-1\\\"]}\";\n\n    let client_metadata: CoreClientMetadata = serde_json::from_str(json_response).unwrap();\n\n    assert_eq!(\n        *client_metadata.redirect_uris(),\n        vec![RedirectUrl::new(\"https://example.com/redirect-1\".to_string()).unwrap(),]\n    );\n    assert_eq!(client_metadata.response_types(), None);\n    assert_eq!(client_metadata.grant_types(), None);\n    assert_eq!(client_metadata.application_type(), None);\n    assert_eq!(client_metadata.contacts(), None);\n    assert_eq!(client_metadata.client_name(), None);\n    assert_eq!(client_metadata.logo_uri(), None);\n    assert_eq!(client_metadata.client_uri(), None);\n    assert_eq!(client_metadata.policy_uri(), None);\n    assert_eq!(client_metadata.tos_uri(), None);\n    assert_eq!(client_metadata.jwks_uri(), None);\n    assert_eq!(client_metadata.jwks(), None);\n    assert_eq!(client_metadata.sector_identifier_uri(), None);\n    assert_eq!(client_metadata.subject_type(), None);\n    assert_eq!(client_metadata.id_token_signed_response_alg(), None);\n    assert_eq!(client_metadata.id_token_encrypted_response_alg(), None);\n    assert_eq!(client_metadata.id_token_encrypted_response_enc(), None);\n    assert_eq!(client_metadata.userinfo_signed_response_alg(), None);\n    assert_eq!(client_metadata.userinfo_encrypted_response_alg(), None);\n    assert_eq!(client_metadata.userinfo_encrypted_response_enc(), None);\n    assert_eq!(client_metadata.request_object_signing_alg(), None);\n    assert_eq!(client_metadata.request_object_encryption_alg(), None);\n    assert_eq!(client_metadata.request_object_encryption_enc(), None);\n    assert_eq!(client_metadata.token_endpoint_auth_method(), None);\n    assert_eq!(client_metadata.token_endpoint_auth_signing_alg(), None);\n    assert_eq!(client_metadata.default_max_age(), None);\n    assert_eq!(client_metadata.require_auth_time(), None);\n    assert_eq!(client_metadata.default_acr_values(), None);\n    assert_eq!(client_metadata.sector_identifier_uri(), None);\n    assert_eq!(client_metadata.request_uris(), None);\n\n    let serialized_json = serde_json::to_string(&client_metadata).unwrap();\n\n    assert_eq!(\n        client_metadata,\n        serde_json::from_str(&serialized_json).unwrap()\n    );\n}\n\n#[test]\nfn test_response_serialization() {\n    let json_response = format!(\"{{\n            \\\"client_id\\\": \\\"abcdefgh\\\",\n            \\\"client_secret\\\": \\\"shhhh\\\",\n            \\\"registration_access_token\\\": \\\"use_me_to_update_registration\\\",\n            \\\"registration_client_uri\\\": \\\"https://example-provider.com/registration\\\",\n            \\\"client_id_issued_at\\\": 1523953306,\n            \\\"client_secret_expires_at\\\": 1526545306,\n            \\\"redirect_uris\\\": [\\\"https://example.com/redirect-1\\\", \\\"https://example.com/redirect-2\\\"],\n            \\\"response_types\\\": [\\\"code\\\", \\\"code token id_token\\\"],\n            \\\"grant_types\\\": [\\\"authorization_code\\\", \\\"client_credentials\\\", \\\"implicit\\\", \\\n                \\\"password\\\", \\\"refresh_token\\\"],\n            \\\"application_type\\\": \\\"web\\\",\n            \\\"contacts\\\": [\\\"user@example.com\\\", \\\"admin@openidconnect.local\\\"],\n            \\\"client_name\\\": \\\"Example\\\",\n            \\\"client_name#es\\\": \\\"Ejemplo\\\",\n            \\\"logo_uri\\\": \\\"https://example.com/logo.png\\\",\n            \\\"logo_uri#fr\\\": \\\"https://example.com/logo-fr.png\\\",\n            \\\"client_uri\\\": \\\"https://example.com/client-app\\\",\n            \\\"client_uri#de\\\": \\\"https://example.com/client-app-de\\\",\n            \\\"policy_uri\\\": \\\"https://example.com/policy\\\",\n            \\\"policy_uri#sr-Latn\\\": \\\"https://example.com/policy-sr-latin\\\",\n            \\\"tos_uri\\\": \\\"https://example.com/tos\\\",\n            \\\"tos_uri#sr-Cyrl\\\": \\\"https://example.com/tos-sr-cyrl\\\",\n            \\\"jwks_uri\\\": \\\"https://example.com/jwks\\\",\n            \\\"jwks\\\": {{\\\"keys\\\": [{}]}},\n            \\\"sector_identifier_uri\\\": \\\"https://example.com/sector\\\",\n            \\\"subject_type\\\": \\\"pairwise\\\",\n            \\\"id_token_signed_response_alg\\\": \\\"HS256\\\",\n            \\\"id_token_encrypted_response_alg\\\": \\\"RSA1_5\\\",\n            \\\"id_token_encrypted_response_enc\\\": \\\"A128CBC-HS256\\\",\n            \\\"userinfo_signed_response_alg\\\": \\\"RS384\\\",\n            \\\"userinfo_encrypted_response_alg\\\": \\\"RSA-OAEP\\\",\n            \\\"userinfo_encrypted_response_enc\\\": \\\"A256CBC-HS512\\\",\n            \\\"request_object_signing_alg\\\": \\\"ES512\\\",\n            \\\"request_object_encryption_alg\\\": \\\"ECDH-ES+A128KW\\\",\n            \\\"request_object_encryption_enc\\\": \\\"A256GCM\\\",\n            \\\"token_endpoint_auth_method\\\": \\\"client_secret_basic\\\",\n            \\\"token_endpoint_auth_signing_alg\\\": \\\"PS512\\\",\n            \\\"default_max_age\\\": 3600,\n            \\\"require_auth_time\\\": true,\n            \\\"default_acr_values\\\": [\\\"0\\\", \\\"urn:mace:incommon:iap:silver\\\", \\\n                \\\"urn:mace:incommon:iap:bronze\\\"],\n            \\\"initiate_login_uri\\\": \\\"https://example.com/login\\\",\n            \\\"request_uris\\\": [\\\"https://example.com/request-1\\\", \\\"https://example.com/request-2\\\"]\n        }}\", TEST_RSA_PUB_KEY);\n\n    let registration_response: CoreClientRegistrationResponse =\n        serde_json::from_str(&json_response).unwrap();\n\n    assert_eq!(\n        *registration_response.client_id(),\n        ClientId::new(\"abcdefgh\".to_string())\n    );\n    assert_eq!(\n        *registration_response.client_secret().unwrap().secret(),\n        \"shhhh\"\n    );\n    assert_eq!(\n        *registration_response\n            .registration_access_token()\n            .unwrap()\n            .secret(),\n        \"use_me_to_update_registration\",\n    );\n    assert_eq!(\n        *registration_response.registration_client_uri().unwrap(),\n        ClientConfigUrl::new(\"https://example-provider.com/registration\".to_string()).unwrap()\n    );\n    assert_eq!(\n        registration_response.client_id_issued_at().unwrap(),\n        Utc.timestamp_opt(1523953306, 0)\n            .single()\n            .expect(\"valid timestamp\")\n    );\n    assert_eq!(\n        registration_response.client_secret_expires_at().unwrap(),\n        Utc.timestamp_opt(1526545306, 0)\n            .single()\n            .expect(\"valid timestamp\")\n    );\n    assert_eq!(\n        *registration_response.redirect_uris(),\n        vec![\n            RedirectUrl::new(\"https://example.com/redirect-1\".to_string()).unwrap(),\n            RedirectUrl::new(\"https://example.com/redirect-2\".to_string()).unwrap(),\n        ]\n    );\n    assert_eq!(\n        *registration_response.response_types().unwrap(),\n        vec![\n            ResponseTypes::new(vec![CoreResponseType::Code]),\n            ResponseTypes::new(vec![\n                CoreResponseType::Code,\n                CoreResponseType::Token,\n                CoreResponseType::IdToken,\n            ]),\n        ]\n    );\n    assert_eq!(\n        registration_response.grant_types().unwrap(),\n        &vec![\n            CoreGrantType::AuthorizationCode,\n            CoreGrantType::ClientCredentials,\n            CoreGrantType::Implicit,\n            CoreGrantType::Password,\n            CoreGrantType::RefreshToken,\n        ]\n    );\n    assert_eq!(\n        *registration_response.application_type().unwrap(),\n        CoreApplicationType::Web\n    );\n    assert_eq!(\n        *registration_response.contacts().unwrap(),\n        vec![\n            ClientContactEmail::new(\"user@example.com\".to_string()),\n            ClientContactEmail::new(\"admin@openidconnect.local\".to_string()),\n        ]\n    );\n    assert_eq!(\n        sorted(registration_response.client_name().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, ClientName)>>(),\n        vec![\n            (None, ClientName::new(\"Example\".to_string())),\n            (\n                Some(LanguageTag::new(\"es\".to_string())),\n                ClientName::new(\"Ejemplo\".to_string()),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(registration_response.logo_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, LogoUrl)>>(),\n        vec![\n            (\n                None,\n                LogoUrl::new(\"https://example.com/logo.png\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"fr\".to_string())),\n                LogoUrl::new(\"https://example.com/logo-fr.png\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(registration_response.client_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, ClientUrl)>>(),\n        vec![\n            (\n                None,\n                ClientUrl::new(\"https://example.com/client-app\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"de\".to_string())),\n                ClientUrl::new(\"https://example.com/client-app-de\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(registration_response.policy_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, PolicyUrl)>>(),\n        vec![\n            (\n                None,\n                PolicyUrl::new(\"https://example.com/policy\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"sr-Latn\".to_string())),\n                PolicyUrl::new(\"https://example.com/policy-sr-latin\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        sorted(registration_response.tos_uri().unwrap().clone())\n            .collect::<Vec<(Option<LanguageTag>, ToSUrl)>>(),\n        vec![\n            (\n                None,\n                ToSUrl::new(\"https://example.com/tos\".to_string()).unwrap(),\n            ),\n            (\n                Some(LanguageTag::new(\"sr-Cyrl\".to_string())),\n                ToSUrl::new(\"https://example.com/tos-sr-cyrl\".to_string()).unwrap(),\n            ),\n        ]\n    );\n    assert_eq!(\n        *registration_response.jwks_uri().unwrap(),\n        JsonWebKeySetUrl::new(\"https://example.com/jwks\".to_string()).unwrap()\n    );\n    assert_eq!(\n        registration_response.jwks(),\n        Some(&JsonWebKeySet::new(vec![serde_json::from_str(\n            TEST_RSA_PUB_KEY\n        )\n        .unwrap()],)),\n    );\n    assert_eq!(\n        *registration_response.sector_identifier_uri().unwrap(),\n        SectorIdentifierUrl::new(\"https://example.com/sector\".to_string()).unwrap()\n    );\n    assert_eq!(\n        *registration_response.subject_type().unwrap(),\n        CoreSubjectIdentifierType::Pairwise\n    );\n    assert_eq!(\n        *registration_response\n            .id_token_signed_response_alg()\n            .unwrap(),\n        CoreJwsSigningAlgorithm::HmacSha256\n    );\n    assert_eq!(\n        *registration_response\n            .id_token_encrypted_response_alg()\n            .unwrap(),\n        CoreJweKeyManagementAlgorithm::RsaPkcs1V15\n    );\n    assert_eq!(\n        *registration_response\n            .id_token_encrypted_response_enc()\n            .unwrap(),\n        CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256\n    );\n    assert_eq!(\n        *registration_response\n            .userinfo_signed_response_alg()\n            .unwrap(),\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha384\n    );\n    assert_eq!(\n        *registration_response\n            .userinfo_encrypted_response_alg()\n            .unwrap(),\n        CoreJweKeyManagementAlgorithm::RsaOaep\n    );\n    assert_eq!(\n        *registration_response\n            .userinfo_encrypted_response_enc()\n            .unwrap(),\n        CoreJweContentEncryptionAlgorithm::Aes256CbcHmacSha512\n    );\n    assert_eq!(\n        *registration_response.request_object_signing_alg().unwrap(),\n        CoreJwsSigningAlgorithm::EcdsaP521Sha512\n    );\n    assert_eq!(\n        *registration_response\n            .request_object_encryption_alg()\n            .unwrap(),\n        CoreJweKeyManagementAlgorithm::EcdhEsAesKeyWrap128\n    );\n    assert_eq!(\n        *registration_response\n            .request_object_encryption_enc()\n            .unwrap(),\n        CoreJweContentEncryptionAlgorithm::Aes256Gcm\n    );\n    assert_eq!(\n        *registration_response.token_endpoint_auth_method().unwrap(),\n        CoreClientAuthMethod::ClientSecretBasic\n    );\n    assert_eq!(\n        *registration_response\n            .token_endpoint_auth_signing_alg()\n            .unwrap(),\n        CoreJwsSigningAlgorithm::RsaSsaPssSha512\n    );\n    assert_eq!(\n        *registration_response.default_max_age().unwrap(),\n        Duration::from_secs(3600)\n    );\n    assert!(registration_response.require_auth_time().unwrap());\n    assert_eq!(\n        *registration_response.default_acr_values().unwrap(),\n        vec![\n            AuthenticationContextClass::new(\"0\".to_string()),\n            AuthenticationContextClass::new(\"urn:mace:incommon:iap:silver\".to_string()),\n            AuthenticationContextClass::new(\"urn:mace:incommon:iap:bronze\".to_string()),\n        ]\n    );\n    assert_eq!(\n        *registration_response.sector_identifier_uri().unwrap(),\n        SectorIdentifierUrl::new(\"https://example.com/sector\".to_string()).unwrap()\n    );\n    assert_eq!(\n        *registration_response.request_uris().unwrap(),\n        vec![\n            RequestUrl::new(\"https://example.com/request-1\".to_string()).unwrap(),\n            RequestUrl::new(\"https://example.com/request-2\".to_string()).unwrap(),\n        ]\n    );\n    let serialized_json = serde_json::to_string(&registration_response).unwrap();\n\n    let deserialized: CoreClientRegistrationResponse =\n        serde_json::from_str(&serialized_json).unwrap();\n    assert_eq!(registration_response.client_id, deserialized.client_id);\n    assert_eq!(\n        registration_response.client_secret.unwrap().secret(),\n        deserialized.client_secret.unwrap().secret(),\n    );\n    assert_eq!(\n        registration_response\n            .registration_access_token\n            .unwrap()\n            .secret(),\n        deserialized.registration_access_token.unwrap().secret(),\n    );\n    assert_eq!(\n        registration_response.registration_client_uri,\n        deserialized.registration_client_uri,\n    );\n    assert_eq!(\n        registration_response.client_id_issued_at,\n        deserialized.client_id_issued_at,\n    );\n    assert_eq!(\n        registration_response.client_secret_expires_at,\n        deserialized.client_secret_expires_at,\n    );\n    assert_eq!(\n        registration_response.client_metadata,\n        deserialized.client_metadata,\n    );\n    assert_eq!(\n        registration_response.additional_response,\n        deserialized.additional_response,\n    );\n}\n"
  },
  {
    "path": "src/token.rs",
    "content": "use crate::{\n    AdditionalClaims, ExtraTokenFields, GenderClaim, IdToken, IdTokenFields,\n    JweContentEncryptionAlgorithm, JwsSigningAlgorithm, OAuth2TokenResponse, StandardTokenResponse,\n    TokenType,\n};\n\n/// Extends the base OAuth2 token response with an ID token.\npub trait TokenResponse<AC, GC, JE, JS>: OAuth2TokenResponse\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    /// Returns the ID token provided by the token response.\n    ///\n    /// OpenID Connect authorization servers should always return this field, but it is optional\n    /// to allow for interoperability with authorization servers that only support OAuth2.\n    fn id_token(&self) -> Option<&IdToken<AC, GC, JE, JS>>;\n}\n\nimpl<AC, EF, GC, JE, JS, TT> TokenResponse<AC, GC, JE, JS>\n    for StandardTokenResponse<IdTokenFields<AC, EF, GC, JE, JS>, TT>\nwhere\n    AC: AdditionalClaims,\n    EF: ExtraTokenFields,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n    TT: TokenType,\n{\n    fn id_token(&self) -> Option<&IdToken<AC, GC, JE, JS>> {\n        self.extra_fields().id_token()\n    }\n}\n"
  },
  {
    "path": "src/types/jwk.rs",
    "content": "use crate::{SignatureVerificationError, SigningError};\n\nuse serde::de::DeserializeOwned;\nuse serde::{Deserialize, Serialize};\n\nuse std::fmt::Debug;\nuse std::hash::Hash;\n\nnew_type![\n    /// ID of a JSON Web Key.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    JsonWebKeyId(String)\n];\n\n/// JSON Web Key.\npub trait JsonWebKey: Clone + Debug + DeserializeOwned + Serialize + 'static {\n    /// Allowed key usage.\n    type KeyUse: JsonWebKeyUse;\n\n    /// JSON Web Signature (JWS) algorithm.\n    type SigningAlgorithm: JwsSigningAlgorithm;\n\n    /// Returns the key ID, or `None` if no key ID is specified.\n    fn key_id(&self) -> Option<&JsonWebKeyId>;\n\n    /// Returns the key type (e.g., RSA).\n    fn key_type(&self) -> &<Self::SigningAlgorithm as JwsSigningAlgorithm>::KeyType;\n\n    /// Returns the allowed key usage (e.g., signing or encryption), or `None` if no usage is\n    /// specified.\n    fn key_use(&self) -> Option<&Self::KeyUse>;\n\n    /// Returns the algorithm (e.g. ES512) this key must be used with, or `Unspecified` if\n    /// no algorithm constraint was given, or unsupported if the algorithm is not for signing.\n    ///\n    /// It's not sufficient to tell whether a key can be used for signing, as key use also has to be validated.\n    fn signing_alg(&self) -> JsonWebKeyAlgorithm<&Self::SigningAlgorithm>;\n\n    /// Initializes a new symmetric key or shared signing secret from the specified raw bytes.\n    fn new_symmetric(key: Vec<u8>) -> Self;\n\n    /// Verifies the given `signature` using the given signature algorithm (`signature_alg`) over\n    /// the given `message`.\n    ///\n    /// Returns `Ok` if the signature is valid, or an `Err` otherwise.\n    fn verify_signature(\n        &self,\n        signature_alg: &Self::SigningAlgorithm,\n        message: &[u8],\n        signature: &[u8],\n    ) -> Result<(), SignatureVerificationError>;\n\n    /// Hashes the given `bytes` using the hash function associated with the specified signing\n    /// algorithm and returns the hashed bytes.\n    ///\n    /// Certain signing algorithms (e.g., `EdDSA`) use different hash functions depending on the\n    /// type of key (e.g., whether the `Ed25519` or `Ed448` curve is used), so this method is\n    /// implemented on the corresponding public key instead of the [`JwsSigningAlgorithm`] trait\n    /// to allow the implementation to determine the proper hash function to use.\n    /// If hashing fails or this key/signing algorithm does not have an associated hash function, an\n    /// `Err` is returned with a string describing the cause of the error. An error is also returned\n    /// if the specified signature algorithm is incompatible with this key (e.g., passing `EdDSA`\n    /// with an RSA key).\n    fn hash_bytes(&self, bytes: &[u8], alg: &Self::SigningAlgorithm) -> Result<Vec<u8>, String>;\n}\n\n/// Encodes a JWK key's alg field compatibility with either signing or encryption operations.\n#[derive(Debug)]\npub enum JsonWebKeyAlgorithm<A: Debug> {\n    /// the alg field allows this kind of operation to be performed with this algorithm only\n    Algorithm(A),\n    /// there is no alg field\n    Unspecified,\n    /// the alg field's algorithm is incompatible with this kind of operation\n    Unsupported,\n}\n\n/// Private or symmetric key for signing.\npub trait PrivateSigningKey {\n    /// Corresponding type of JSON Web Key used for verifying signatures produced by this key.\n    type VerificationKey: JsonWebKey;\n\n    /// Signs the given `message` using the given signature algorithm.\n    fn sign(\n        &self,\n        signature_alg: &<Self::VerificationKey as JsonWebKey>::SigningAlgorithm,\n        message: &[u8],\n    ) -> Result<Vec<u8>, SigningError>;\n\n    /// Converts this key to a JSON Web Key that can be used for verifying signatures.\n    fn as_verification_key(&self) -> Self::VerificationKey;\n}\n\n/// Key type (e.g., RSA).\npub trait JsonWebKeyType:\n    Clone + Debug + DeserializeOwned + PartialEq + Serialize + 'static\n{\n}\n\n/// Allowed key usage.\npub trait JsonWebKeyUse: Debug + DeserializeOwned + Serialize + 'static {\n    /// Returns true if the associated key may be used for digital signatures, or false otherwise.\n    fn allows_signature(&self) -> bool;\n\n    /// Returns true if the associated key may be used for encryption, or false otherwise.\n    fn allows_encryption(&self) -> bool;\n}\n\n/// JSON Web Encryption (JWE) content encryption algorithm.\npub trait JweContentEncryptionAlgorithm:\n    Clone + Debug + DeserializeOwned + Serialize + 'static\n{\n    /// Key type (e.g., RSA).\n    type KeyType: JsonWebKeyType;\n\n    /// Returns the type of key required to use this encryption algorithm.\n    fn key_type(&self) -> Result<Self::KeyType, String>;\n}\n\n/// JSON Web Encryption (JWE) key management algorithm.\npub trait JweKeyManagementAlgorithm: Debug + DeserializeOwned + Serialize + 'static {\n    // TODO: add a key_type() method\n}\n\n/// JSON Web Signature (JWS) algorithm.\npub trait JwsSigningAlgorithm:\n    Clone + Debug + DeserializeOwned + Eq + Hash + PartialEq + Serialize + 'static\n{\n    /// Key type (e.g., RSA).\n    type KeyType: JsonWebKeyType;\n\n    /// Returns the type of key required to use this signature algorithm, or `None` if this\n    /// algorithm does not require a key.\n    fn key_type(&self) -> Option<Self::KeyType>;\n\n    /// Returns true if the signature algorithm uses a shared secret (symmetric key).\n    fn uses_shared_secret(&self) -> bool;\n\n    /// Returns the RS256 algorithm.\n    ///\n    /// This is the default algorithm for OpenID Connect ID tokens and must be supported by all\n    /// implementations.\n    fn rsa_sha_256() -> Self;\n}\n"
  },
  {
    "path": "src/types/jwks.rs",
    "content": "use crate::http_utils::{check_content_type, MIME_TYPE_JSON, MIME_TYPE_JWKS};\nuse crate::types::jwk::{JsonWebKey, JsonWebKeyId, JwsSigningAlgorithm};\nuse crate::{\n    AsyncHttpClient, DiscoveryError, HttpRequest, HttpResponse, JsonWebKeyUse, SyncHttpClient,\n};\n\nuse http::header::ACCEPT;\nuse http::{HeaderValue, Method, StatusCode};\nuse serde::{Deserialize, Serialize};\nuse serde_with::{serde_as, VecSkipError};\n\nuse std::future::Future;\n\nnew_url_type![\n    /// JSON Web Key Set URL.\n    JsonWebKeySetUrl\n];\n\n/// JSON Web Key Set.\n#[serde_as]\n#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]\npub struct JsonWebKeySet<K>\nwhere\n    K: JsonWebKey,\n{\n    // FIXME: write a test that ensures duplicate object member names cause an error\n    // (see https://tools.ietf.org/html/rfc7517#section-5)\n    #[serde(bound = \"K: JsonWebKey\")]\n    // Ignores invalid keys rather than failing. That way, clients can function using the keys that\n    // they do understand, which is fine if they only ever get JWTs signed with those keys.\n    #[serde_as(as = \"VecSkipError<_>\")]\n    keys: Vec<K>,\n}\n\n/// Checks whether a JWK key can be used with a given signing algorithm.\npub(crate) fn check_key_compatibility<K>(\n    key: &K,\n    signing_algorithm: &K::SigningAlgorithm,\n) -> Result<(), &'static str>\nwhere\n    K: JsonWebKey,\n{\n    // if this key isn't suitable for signing\n    if let Some(use_) = key.key_use() {\n        if !use_.allows_signature() {\n            return Err(\"key usage not permitted for digital signatures\");\n        }\n    }\n\n    // if this key doesn't have the right key type\n    if signing_algorithm.key_type().as_ref() != Some(key.key_type()) {\n        return Err(\"key type does not match signature algorithm\");\n    }\n\n    match key.signing_alg() {\n        // if no specific algorithm is mandated, any will do\n        crate::JsonWebKeyAlgorithm::Unspecified => Ok(()),\n        crate::JsonWebKeyAlgorithm::Unsupported => Err(\"key algorithm is not a signing algorithm\"),\n        crate::JsonWebKeyAlgorithm::Algorithm(key_alg) if key_alg == signing_algorithm => Ok(()),\n        crate::JsonWebKeyAlgorithm::Algorithm(_) => Err(\"incompatible key algorithm\"),\n    }\n}\n\nimpl<K> JsonWebKeySet<K>\nwhere\n    K: JsonWebKey,\n{\n    /// Create a new JSON Web Key Set.\n    pub fn new(keys: Vec<K>) -> Self {\n        Self { keys }\n    }\n\n    /// Return a list of suitable keys, given a key ID and signature algorithm\n    pub(crate) fn filter_keys(\n        &self,\n        key_id: Option<&JsonWebKeyId>,\n        signature_alg: &K::SigningAlgorithm,\n    ) -> Vec<&K> {\n        self.keys()\n        .iter()\n        .filter(|key|\n            // Either the JWT doesn't include a 'kid' (in which case any 'kid'\n            // is acceptable), or the 'kid' matches the key's ID.\n            if key_id.is_some() && key_id != key.key_id() {\n                false\n            } else {\n                check_key_compatibility(*key, signature_alg).is_ok()\n            }\n        )\n        .collect()\n    }\n\n    /// Fetch a remote JSON Web Key Set from the specified `url` using the given `http_client`\n    /// (e.g., [`reqwest::blocking::Client`](crate::reqwest::blocking::Client) or\n    /// [`CurlHttpClient`](crate::CurlHttpClient)).\n    pub fn fetch<C>(\n        url: &JsonWebKeySetUrl,\n        http_client: &C,\n    ) -> Result<Self, DiscoveryError<<C as SyncHttpClient>::Error>>\n    where\n        C: SyncHttpClient,\n    {\n        http_client\n            .call(Self::fetch_request(url).map_err(|err| {\n                DiscoveryError::Other(format!(\"failed to prepare request: {err}\"))\n            })?)\n            .map_err(DiscoveryError::Request)\n            .and_then(Self::fetch_response)\n    }\n\n    /// Fetch a remote JSON Web Key Set from the specified `url` using the given async `http_client`\n    /// (e.g., [`reqwest::Client`](crate::reqwest::Client)).\n    pub fn fetch_async<'c, C>(\n        url: &JsonWebKeySetUrl,\n        http_client: &'c C,\n    ) -> impl Future<Output = Result<Self, DiscoveryError<<C as AsyncHttpClient<'c>>::Error>>> + 'c\n    where\n        Self: 'c,\n        C: AsyncHttpClient<'c>,\n    {\n        let fetch_request = Self::fetch_request(url)\n            .map_err(|err| DiscoveryError::Other(format!(\"failed to prepare request: {err}\")));\n        Box::pin(async move {\n            http_client\n                .call(fetch_request?)\n                .await\n                .map_err(DiscoveryError::Request)\n                .and_then(Self::fetch_response)\n        })\n    }\n\n    fn fetch_request(url: &JsonWebKeySetUrl) -> Result<HttpRequest, http::Error> {\n        http::Request::builder()\n            .uri(url.to_string())\n            .method(Method::GET)\n            .header(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON))\n            .body(Vec::new())\n    }\n\n    fn fetch_response<RE>(http_response: HttpResponse) -> Result<Self, DiscoveryError<RE>>\n    where\n        RE: std::error::Error + 'static,\n    {\n        if http_response.status() != StatusCode::OK {\n            return Err(DiscoveryError::Response(\n                http_response.status(),\n                http_response.body().to_owned(),\n                format!(\"HTTP status code {}\", http_response.status()),\n            ));\n        }\n\n        check_content_type(http_response.headers(), MIME_TYPE_JSON)\n            .or_else(|err| {\n                check_content_type(http_response.headers(), MIME_TYPE_JWKS).map_err(|_| err)\n            })\n            .map_err(|err_msg| {\n                DiscoveryError::Response(\n                    http_response.status(),\n                    http_response.body().to_owned(),\n                    err_msg,\n                )\n            })?;\n\n        serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(\n            http_response.body(),\n        ))\n        .map_err(DiscoveryError::Parse)\n    }\n\n    /// Return the keys in this JSON Web Key Set.\n    pub fn keys(&self) -> &Vec<K> {\n        &self.keys\n    }\n}\nimpl<K> Clone for JsonWebKeySet<K>\nwhere\n    K: JsonWebKey,\n{\n    fn clone(&self) -> Self {\n        Self::new(self.keys.clone())\n    }\n}\nimpl<K> Default for JsonWebKeySet<K>\nwhere\n    K: JsonWebKey,\n{\n    fn default() -> Self {\n        Self::new(Vec::new())\n    }\n}\n"
  },
  {
    "path": "src/types/localized.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse std::borrow::Cow;\nuse std::collections::HashMap;\nuse std::fmt::Display;\n\nnew_type![\n    /// Language tag adhering to RFC 5646 (e.g., `fr` or `fr-CA`).\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    LanguageTag(String)\n];\nimpl AsRef<str> for LanguageTag {\n    fn as_ref(&self) -> &str {\n        self\n    }\n}\nimpl Display for LanguageTag {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n\npub(crate) fn split_language_tag_key(key: &str) -> (&str, Option<LanguageTag>) {\n    let mut lang_tag_sep = key.splitn(2, '#');\n\n    // String::splitn(2) always returns at least one element.\n    let field_name = lang_tag_sep.next().unwrap();\n\n    let language_tag = lang_tag_sep\n        .next()\n        .filter(|language_tag| !language_tag.is_empty())\n        .map(|language_tag| LanguageTag::new(language_tag.to_string()));\n\n    (field_name, language_tag)\n}\n\npub(crate) fn join_language_tag_key<'a>(\n    field_name: &'a str,\n    language_tag: Option<&LanguageTag>,\n) -> Cow<'a, str> {\n    if let Some(language_tag) = language_tag {\n        Cow::Owned(format!(\"{field_name}#{language_tag}\"))\n    } else {\n        Cow::Borrowed(field_name)\n    }\n}\n\n/// A [locale-aware](https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsLanguages)\n/// claim.\n///\n/// This structure associates one more `Option<LanguageTag>` locales with the corresponding\n/// claims values.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct LocalizedClaim<T>(HashMap<LanguageTag, T>, Option<T>);\nimpl<T> LocalizedClaim<T> {\n    /// Initialize an empty claim.\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Returns true if the claim contains a value for the specified locale.\n    pub fn contains_key(&self, locale: Option<&LanguageTag>) -> bool {\n        if let Some(l) = locale {\n            self.0.contains_key(l)\n        } else {\n            self.1.is_some()\n        }\n    }\n\n    /// Returns the entry for the specified locale or `None` if there is no such entry.\n    pub fn get(&self, locale: Option<&LanguageTag>) -> Option<&T> {\n        if let Some(l) = locale {\n            self.0.get(l)\n        } else {\n            self.1.as_ref()\n        }\n    }\n\n    /// Returns an iterator over the locales and claim value entries.\n    pub fn iter(&self) -> impl Iterator<Item = (Option<&LanguageTag>, &T)> {\n        self.1\n            .iter()\n            .map(|value| (None, value))\n            .chain(self.0.iter().map(|(locale, value)| (Some(locale), value)))\n    }\n\n    /// Inserts or updates an entry for the specified locale.\n    ///\n    /// Returns the current value associated with the given locale, or `None` if there is no\n    /// such entry.\n    pub fn insert(&mut self, locale: Option<LanguageTag>, value: T) -> Option<T> {\n        if let Some(l) = locale {\n            self.0.insert(l, value)\n        } else {\n            self.1.replace(value)\n        }\n    }\n\n    /// Removes an entry for the specified locale.\n    ///\n    /// Returns the current value associated with the given locale, or `None` if there is no\n    /// such entry.\n    pub fn remove(&mut self, locale: Option<&LanguageTag>) -> Option<T> {\n        if let Some(l) = locale {\n            self.0.remove(l)\n        } else {\n            self.1.take()\n        }\n    }\n}\nimpl<T> LocalizedClaim<Option<T>> {\n    pub(crate) fn flatten_or_none(self) -> Option<LocalizedClaim<T>> {\n        let flattened_tagged = self\n            .0\n            .into_iter()\n            .filter_map(|(k, v)| v.map(|v| (k, v)))\n            .collect::<HashMap<_, _>>();\n        let flattened_default = self.1.flatten();\n\n        if flattened_tagged.is_empty() && flattened_default.is_none() {\n            None\n        } else {\n            Some(LocalizedClaim(flattened_tagged, flattened_default))\n        }\n    }\n}\n\nimpl<T> Default for LocalizedClaim<T> {\n    fn default() -> Self {\n        Self(HashMap::new(), None)\n    }\n}\nimpl<T> From<T> for LocalizedClaim<T> {\n    fn from(default: T) -> Self {\n        Self(HashMap::new(), Some(default))\n    }\n}\nimpl<T> FromIterator<(Option<LanguageTag>, T)> for LocalizedClaim<T> {\n    fn from_iter<I: IntoIterator<Item = (Option<LanguageTag>, T)>>(iter: I) -> Self {\n        let mut temp: HashMap<Option<LanguageTag>, T> = iter.into_iter().collect();\n        let default = temp.remove(&None);\n        Self(\n            temp.into_iter()\n                .filter_map(|(locale, value)| locale.map(|l| (l, value)))\n                .collect(),\n            default,\n        )\n    }\n}\nimpl<T> IntoIterator for LocalizedClaim<T>\nwhere\n    T: 'static,\n{\n    type Item = <LocalizedClaimIterator<T> as Iterator>::Item;\n    type IntoIter = LocalizedClaimIterator<T>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        LocalizedClaimIterator {\n            inner: Box::new(\n                self.1.into_iter().map(|value| (None, value)).chain(\n                    self.0\n                        .into_iter()\n                        .map(|(locale, value)| (Some(locale), value)),\n                ),\n            ),\n        }\n    }\n}\n\n/// Owned iterator over a LocalizedClaim.\npub struct LocalizedClaimIterator<T> {\n    inner: Box<dyn Iterator<Item = (Option<LanguageTag>, T)>>,\n}\nimpl<T> Iterator for LocalizedClaimIterator<T> {\n    type Item = (Option<LanguageTag>, T);\n    fn next(&mut self) -> Option<Self::Item> {\n        self.inner.next()\n    }\n}\n"
  },
  {
    "path": "src/types/mod.rs",
    "content": "use crate::types::jwk::JsonWebKey;\nuse crate::{AccessToken, AuthorizationCode};\n\nuse base64::prelude::BASE64_URL_SAFE_NO_PAD;\nuse base64::Engine;\nuse oauth2::helpers::deserialize_space_delimited_vec;\nuse rand::{thread_rng, Rng};\nuse serde::de::DeserializeOwned;\nuse serde::{Deserialize, Serialize};\nuse thiserror::Error;\nuse url::Url;\n\nuse std::fmt::Debug;\nuse std::hash::Hash;\nuse std::ops::Deref;\n\npub(crate) mod jwk;\npub(crate) mod jwks;\npub(crate) mod localized;\n\n#[cfg(test)]\nmod tests;\n\n/// Client application type.\npub trait ApplicationType: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// How the Authorization Server displays the authentication and consent user interface pages to\n/// the End-User.\npub trait AuthDisplay: AsRef<str> + Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Whether the Authorization Server should prompt the End-User for reauthentication and consent.\npub trait AuthPrompt: AsRef<str> + 'static {}\n\n/// Claim name.\npub trait ClaimName: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Claim type (e.g., normal, aggregated, or distributed).\npub trait ClaimType: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Client authentication method.\npub trait ClientAuthMethod: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Grant type.\npub trait GrantType: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Error signing a message.\n#[derive(Clone, Debug, Error, PartialEq, Eq)]\n#[non_exhaustive]\npub enum SigningError {\n    /// Failed to sign the message using the given key and parameters.\n    #[error(\"Crypto error\")]\n    CryptoError,\n    /// Unsupported signature algorithm.\n    #[error(\"Unsupported signature algorithm: {0}\")]\n    UnsupportedAlg(String),\n    /// An unexpected error occurred.\n    #[error(\"Other error: {0}\")]\n    Other(String),\n}\n\n/// Response mode indicating how the OpenID Connect Provider should return the Authorization\n/// Response to the Relying Party (client).\npub trait ResponseMode: Debug + DeserializeOwned + Serialize + 'static {}\n\n/// Response type indicating the desired authorization processing flow, including what\n/// parameters are returned from the endpoints used.\npub trait ResponseType: AsRef<str> + Debug + DeserializeOwned + Serialize + 'static {\n    /// Converts this OpenID Connect response type to an [`oauth2::ResponseType`] used by the\n    /// underlying [`oauth2`] crate.\n    fn to_oauth2(&self) -> oauth2::ResponseType;\n}\n\n/// Subject identifier type returned by an OpenID Connect Provider to uniquely identify its users.\npub trait SubjectIdentifierType: Debug + DeserializeOwned + Serialize + 'static {}\n\nnew_type![\n    /// Set of authentication methods or procedures that are considered to be equivalent to each\n    /// other in a particular context.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AuthenticationContextClass(String)\n];\nimpl AsRef<str> for AuthenticationContextClass {\n    fn as_ref(&self) -> &str {\n        self\n    }\n}\n\nnew_type![\n    /// Identifier for an authentication method (e.g., `password` or `totp`).\n    ///\n    /// Defining specific AMR identifiers is beyond the scope of the OpenID Connect Core spec.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AuthenticationMethodReference(String)\n];\n\nnew_type![\n    /// Access token hash.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AccessTokenHash(String)\n    impl {\n        /// Initialize a new access token hash from an [`AccessToken`] and signature algorithm.\n        pub fn from_token<K>(\n            access_token: &AccessToken,\n            alg: &K::SigningAlgorithm,\n            key: &K,\n        ) -> Result<Self, SigningError>\n        where\n            K: JsonWebKey,\n        {\n            key.hash_bytes(access_token.secret().as_bytes(), alg)\n                .map(|hash| Self::new(BASE64_URL_SAFE_NO_PAD.encode(&hash[0..hash.len() / 2])))\n                .map_err(SigningError::UnsupportedAlg)\n        }\n    }\n];\n\nnew_type![\n    /// Country portion of address.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AddressCountry(String)\n];\n\nnew_type![\n    /// Locality portion of address.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AddressLocality(String)\n];\n\nnew_type![\n    /// Postal code portion of address.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AddressPostalCode(String)\n];\n\nnew_type![\n    /// Region portion of address.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AddressRegion(String)\n];\n\nnew_type![\n    /// Audience claim value.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    Audience(String)\n];\n\nnew_type![\n    /// Authorization code hash.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    AuthorizationCodeHash(String)\n    impl {\n        /// Initialize a new authorization code hash from an [`AuthorizationCode`] and signature\n        /// algorithm.\n        pub fn from_code<K>(\n            code: &AuthorizationCode,\n            alg: &K::SigningAlgorithm,\n            key: &K,\n        ) -> Result<Self, SigningError>\n        where\n            K: JsonWebKey,\n        {\n            key.hash_bytes(code.secret().as_bytes(), alg)\n                .map(|hash| Self::new(BASE64_URL_SAFE_NO_PAD.encode(&hash[0..hash.len() / 2])))\n                .map_err(SigningError::UnsupportedAlg)\n        }\n    }\n];\n\nnew_type![\n    /// OpenID Connect client name.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    ClientName(String)\n];\n\nnew_url_type![\n    /// Client configuration endpoint URL.\n    ClientConfigUrl\n];\n\nnew_url_type![\n    /// Client homepage URL.\n    ClientUrl\n];\n\nnew_type![\n    /// Client contact e-mail address.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    ClientContactEmail(String)\n];\n\nnew_url_type![\n    /// URL for the [OpenID Connect RP-Initiated Logout 1.0](\n    /// https://openid.net/specs/openid-connect-rpinitiated-1_0.html) end session endpoint.\n    EndSessionUrl\n];\n\nnew_type![\n    /// End user's birthday, represented as an\n    /// [ISO 8601:2004](https://www.iso.org/standard/40874.html) `YYYY-MM-DD` format.\n    ///\n    /// The year MAY be `0000`, indicating that it is omitted. To represent only the year, `YYYY`\n    /// format is allowed. Note that depending on the underlying platform's date related function,\n    /// providing just year can result in varying month and day, so the implementers need to take\n    /// this factor into account to correctly process the dates.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserBirthday(String)\n];\n\nnew_type![\n    /// End user's e-mail address.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserEmail(String)\n];\n\nnew_type![\n    /// End user's family name.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserFamilyName(String)\n];\n\nnew_type![\n    /// End user's given name.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserGivenName(String)\n];\n\nnew_type![\n    /// End user's middle name.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserMiddleName(String)\n];\n\nnew_type![\n    /// End user's name.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserName(String)\n];\n\nnew_type![\n    /// End user's nickname.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserNickname(String)\n];\n\nnew_type![\n    /// End user's phone number.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserPhoneNumber(String)\n];\n\nnew_type![\n    /// URL of end user's profile picture.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserPictureUrl(String)\n];\n\nnew_type![\n    /// URL of end user's profile page.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserProfileUrl(String)\n];\n\nnew_type![\n    /// End user's time zone as a string from the\n    /// [time zone database](https://www.iana.org/time-zones).\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserTimezone(String)\n];\n\nnew_type![\n    /// URL of end user's website.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserWebsiteUrl(String)\n];\n\nnew_type![\n    /// End user's username.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    EndUserUsername(String)\n];\n\nnew_type![\n    /// Full mailing address, formatted for display or use on a mailing label.\n    ///\n    /// This field MAY contain multiple lines, separated by newlines. Newlines can be represented\n    /// either as a carriage return/line feed pair (`\"\\r\\n\"`) or as a single line feed character\n    /// (`\"\\n\"`).\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    FormattedAddress(String)\n];\n\nnew_url_type![\n    /// URI using the `https` scheme that a third party can use to initiate a login by the Relying\n    /// Party.\n    InitiateLoginUrl\n];\n\nnew_url_type![\n    /// URL using the `https` scheme with no query or fragment component that the OP asserts as its\n    /// Issuer Identifier.\n    IssuerUrl\n    impl {\n        /// Parse a string as a URL, with this URL as the base URL.\n        ///\n        /// See [`Url::parse`].\n        pub fn join(&self, suffix: &str) -> Result<Url, url::ParseError> {\n            if let Some('/') = self.1.chars().next_back() {\n                Url::parse(&(self.1.clone() + suffix))\n            } else {\n                Url::parse(&(self.1.clone() + \"/\" + suffix))\n            }\n        }\n    }\n];\n\nnew_secret_type![\n    /// Hint about the login identifier the End-User might use to log in.\n    ///\n    /// The use of this parameter is left to the OpenID Connect Provider's discretion.\n    #[derive(Clone, Deserialize, Serialize)]\n    LoginHint(String)\n];\n\nnew_secret_type![\n    /// Hint about the logout identifier the End-User might use to log out.\n    ///\n    /// The use of this parameter is left to the OpenID Connect Provider's discretion.\n    #[derive(Clone, Deserialize, Serialize)]\n    LogoutHint(String)\n];\n\nnew_url_type![\n    /// URL that references a logo for the Client application.\n    LogoUrl\n];\n\nnew_secret_type![\n    /// String value used to associate a client session with an ID Token, and to mitigate replay\n    /// attacks.\n    #[derive(Clone, Deserialize, Serialize)]\n    Nonce(String)\n    impl {\n        /// Generate a new random, base64-encoded 128-bit nonce.\n        pub fn new_random() -> Self {\n            Nonce::new_random_len(16)\n        }\n        /// Generate a new random, base64-encoded nonce of the specified length.\n        ///\n        /// # Arguments\n        ///\n        /// * `num_bytes` - Number of random bytes to generate, prior to base64-encoding.\n        pub fn new_random_len(num_bytes: u32) -> Self {\n            let random_bytes: Vec<u8> = (0..num_bytes).map(|_| thread_rng().gen::<u8>()).collect();\n            Nonce::new(BASE64_URL_SAFE_NO_PAD.encode(random_bytes))\n        }\n    }\n];\n\nnew_url_type![\n    /// URL providing the OpenID Connect Provider's data usage policies for client applications.\n    OpPolicyUrl\n];\n\nnew_url_type![\n    /// URL providing the OpenID Connect Provider's Terms of Service.\n    OpTosUrl\n];\n\nnew_url_type![\n    /// URL providing a client application's data usage policy.\n    PolicyUrl\n];\n\nnew_url_type![\n    /// The post logout redirect URL, which should be passed to the end session endpoint\n    /// of providers implementing [OpenID Connect RP-Initiated Logout 1.0](\n    /// https://openid.net/specs/openid-connect-rpinitiated-1_0.html).\n    PostLogoutRedirectUrl\n];\n\nnew_secret_type![\n    /// Access token used by a client application to access the Client Registration endpoint.\n    #[derive(Clone, Deserialize, Serialize)]\n    RegistrationAccessToken(String)\n];\n\nnew_url_type![\n    /// URL of the Client Registration endpoint.\n    RegistrationUrl\n];\n\nnew_url_type![\n    /// URL used to pass request parameters as JWTs by reference.\n    RequestUrl\n];\n\n/// Informs the Authorization Server of the desired authorization processing flow, including what\n/// parameters are returned from the endpoints used.\n///\n/// See [OAuth 2.0 Multiple Response Type Encoding Practices](\n///     http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseTypesAndModes)\n/// for further details.\n#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]\npub struct ResponseTypes<RT: ResponseType>(\n    #[serde(\n        deserialize_with = \"deserialize_space_delimited_vec\",\n        serialize_with = \"crate::helpers::serialize_space_delimited_vec\"\n    )]\n    Vec<RT>,\n);\nimpl<RT: ResponseType> ResponseTypes<RT> {\n    /// Create a new [`ResponseTypes<RT>`] to wrap the given [`Vec<RT>`].\n    pub fn new(s: Vec<RT>) -> Self {\n        ResponseTypes::<RT>(s)\n    }\n}\nimpl<RT: ResponseType> Deref for ResponseTypes<RT> {\n    type Target = Vec<RT>;\n    fn deref(&self) -> &Vec<RT> {\n        &self.0\n    }\n}\n\nnew_url_type![\n    /// URL for retrieving redirect URIs that should receive identical pairwise subject identifiers.\n    SectorIdentifierUrl\n];\n\nnew_url_type![\n    /// URL for developer documentation for an OpenID Connect Provider.\n    ServiceDocUrl\n];\n\nnew_type![\n    /// A user's street address.\n    ///\n    /// Full street address component, which MAY include house number, street name, Post Office Box,\n    /// and multi-line extended street address information. This field MAY contain multiple lines,\n    /// separated by newlines. Newlines can be represented either as a carriage return/line feed\n    /// pair (`\\r\\n`) or as a single line feed character (`\\n`).\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    StreetAddress(String)\n];\n\nnew_type![\n    /// Locally unique and never reassigned identifier within the Issuer for the End-User, which is\n    /// intended to be consumed by the client application.\n    #[derive(Deserialize, Hash, Ord, PartialOrd, Serialize)]\n    SubjectIdentifier(String)\n];\n\nnew_url_type![\n    /// URL for the relying party's Terms of Service.\n    ToSUrl\n];\n"
  },
  {
    "path": "src/types/tests.rs",
    "content": "use crate::IssuerUrl;\n\n#[test]\nfn test_issuer_url_append() {\n    assert_eq!(\n        \"http://example.com/.well-known/openid-configuration\",\n        IssuerUrl::new(\"http://example.com\".to_string())\n            .unwrap()\n            .join(\".well-known/openid-configuration\")\n            .unwrap()\n            .to_string()\n    );\n    assert_eq!(\n        \"http://example.com/.well-known/openid-configuration\",\n        IssuerUrl::new(\"http://example.com/\".to_string())\n            .unwrap()\n            .join(\".well-known/openid-configuration\")\n            .unwrap()\n            .to_string()\n    );\n    assert_eq!(\n        \"http://example.com/x/.well-known/openid-configuration\",\n        IssuerUrl::new(\"http://example.com/x\".to_string())\n            .unwrap()\n            .join(\".well-known/openid-configuration\")\n            .unwrap()\n            .to_string()\n    );\n    assert_eq!(\n        \"http://example.com/x/.well-known/openid-configuration\",\n        IssuerUrl::new(\"http://example.com/x/\".to_string())\n            .unwrap()\n            .join(\".well-known/openid-configuration\")\n            .unwrap()\n            .to_string()\n    );\n}\n\n#[test]\nfn test_url_serialize() {\n    let issuer_url =\n        IssuerUrl::new(\"http://example.com/.well-known/openid-configuration\".to_string()).unwrap();\n    let serialized_url = serde_json::to_string(&issuer_url).unwrap();\n\n    assert_eq!(\n        \"\\\"http://example.com/.well-known/openid-configuration\\\"\",\n        serialized_url\n    );\n\n    let deserialized_url = serde_json::from_str(&serialized_url).unwrap();\n    assert_eq!(issuer_url, deserialized_url);\n\n    assert_eq!(\n        serde_json::to_string(&IssuerUrl::new(\"http://example.com\".to_string()).unwrap()).unwrap(),\n        \"\\\"http://example.com\\\"\",\n    );\n}\n\n#[cfg(feature = \"accept-string-booleans\")]\n#[test]\nfn test_string_bool_parse() {\n    use crate::helpers::Boolean;\n\n    fn test_case(input: &str, expect: bool) {\n        let value: Boolean = serde_json::from_str(input).unwrap();\n        assert_eq!(value.0, expect);\n    }\n    test_case(\"true\", true);\n    test_case(\"false\", false);\n    test_case(\"\\\"true\\\"\", true);\n    test_case(\"\\\"false\\\"\", false);\n    assert!(serde_json::from_str::<Boolean>(\"\\\"maybe\\\"\").is_err());\n}\n"
  },
  {
    "path": "src/user_info.rs",
    "content": "use crate::helpers::{deserialize_string_or_vec_opt, FilteredFlatten};\nuse crate::http_utils::{auth_bearer, content_type_has_essence, MIME_TYPE_JSON, MIME_TYPE_JWT};\nuse crate::jwt::{JsonWebTokenError, JsonWebTokenJsonPayloadSerde};\nuse crate::verification::UserInfoVerifier;\nuse crate::{\n    AccessToken, AdditionalClaims, AddressClaim, AsyncHttpClient, Audience, AudiencesClaim,\n    AuthDisplay, AuthPrompt, ClaimsVerificationError, Client, EndUserBirthday, EndUserEmail,\n    EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName, EndUserNickname,\n    EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone, EndUserUsername,\n    EndUserWebsiteUrl, EndpointState, ErrorResponse, GenderClaim, HttpRequest, HttpResponse,\n    IssuerClaim, IssuerUrl, JsonWebKey, JsonWebToken, JweContentEncryptionAlgorithm,\n    JwsSigningAlgorithm, LanguageTag, LocalizedClaim, PrivateSigningKey, RevocableToken,\n    SignatureVerificationError, StandardClaims, SubjectIdentifier, SyncHttpClient,\n    TokenIntrospectionResponse, TokenResponse,\n};\n\nuse chrono::{DateTime, Utc};\nuse http::header::{HeaderValue, ACCEPT, CONTENT_TYPE};\nuse http::method::Method;\nuse http::status::StatusCode;\nuse serde::{Deserialize, Serialize};\nuse serde_with::skip_serializing_none;\nuse thiserror::Error;\n\nuse std::future::Future;\nuse std::str;\n\nimpl<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\n    Client<\n        AC,\n        AD,\n        GC,\n        JE,\n        K,\n        P,\n        TE,\n        TR,\n        TIR,\n        RT,\n        TRE,\n        HasAuthUrl,\n        HasDeviceAuthUrl,\n        HasIntrospectionUrl,\n        HasRevocationUrl,\n        HasTokenUrl,\n        HasUserInfoUrl,\n    >\nwhere\n    AC: AdditionalClaims,\n    AD: AuthDisplay,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n    P: AuthPrompt,\n    TE: ErrorResponse + 'static,\n    TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,\n    TIR: TokenIntrospectionResponse,\n    RT: RevocableToken,\n    TRE: ErrorResponse + 'static,\n    HasAuthUrl: EndpointState,\n    HasDeviceAuthUrl: EndpointState,\n    HasIntrospectionUrl: EndpointState,\n    HasRevocationUrl: EndpointState,\n    HasTokenUrl: EndpointState,\n    HasUserInfoUrl: EndpointState,\n{\n    pub(crate) fn user_info_impl<'a>(\n        &'a self,\n        userinfo_endpoint: &'a UserInfoUrl,\n        access_token: AccessToken,\n        expected_subject: Option<SubjectIdentifier>,\n    ) -> UserInfoRequest<'a, JE, K> {\n        UserInfoRequest {\n            url: userinfo_endpoint,\n            access_token,\n            require_signed_response: false,\n            response_type: UserInfoResponseType::Json,\n            signed_response_verifier: UserInfoVerifier::new(\n                self.client_id.clone(),\n                self.issuer.clone(),\n                self.jwks.clone(),\n                expected_subject,\n            ),\n        }\n    }\n}\n\n/// User info request.\npub struct UserInfoRequest<'a, JE, K>\nwhere\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n{\n    pub(crate) url: &'a UserInfoUrl,\n    pub(crate) access_token: AccessToken,\n    pub(crate) require_signed_response: bool,\n    pub(crate) signed_response_verifier: UserInfoVerifier<'static, JE, K>,\n    pub(crate) response_type: UserInfoResponseType,\n}\nimpl<'a, JE, K> UserInfoRequest<'a, JE, K>\nwhere\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n{\n    /// Submits this request to the associated user info endpoint using the specified synchronous\n    /// HTTP client.\n    pub fn request<AC, GC, C>(\n        self,\n        http_client: &C,\n    ) -> Result<UserInfoClaims<AC, GC>, UserInfoError<<C as SyncHttpClient>::Error>>\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        C: SyncHttpClient,\n    {\n        http_client\n            .call(\n                self.prepare_request().map_err(|err| {\n                    UserInfoError::Other(format!(\"failed to prepare request: {err}\"))\n                })?,\n            )\n            .map_err(UserInfoError::Request)\n            .and_then(|http_response| self.user_info_response(http_response))\n    }\n\n    /// Submits this request to the associated user info endpoint using the specified asynchronous\n    /// HTTP client.\n    pub fn request_async<'c, AC, C, GC>(\n        self,\n        http_client: &'c C,\n    ) -> impl Future<\n        Output = Result<UserInfoClaims<AC, GC>, UserInfoError<<C as AsyncHttpClient<'c>>::Error>>,\n    > + 'c\n    where\n        Self: 'c,\n        AC: AdditionalClaims,\n        C: AsyncHttpClient<'c>,\n        GC: GenderClaim,\n    {\n        Box::pin(async move {\n            let http_response = http_client\n                .call(self.prepare_request().map_err(|err| {\n                    UserInfoError::Other(format!(\"failed to prepare request: {err}\"))\n                })?)\n                .await\n                .map_err(UserInfoError::Request)?;\n\n            self.user_info_response(http_response)\n        })\n    }\n\n    fn prepare_request(&self) -> Result<HttpRequest, http::Error> {\n        let (auth_header, auth_value) = auth_bearer(&self.access_token);\n        let accept_value = match self.response_type {\n            UserInfoResponseType::Jwt => MIME_TYPE_JWT,\n            _ => MIME_TYPE_JSON,\n        };\n\n        http::Request::builder()\n            .uri(self.url.to_string())\n            .method(Method::GET)\n            .header(ACCEPT, HeaderValue::from_static(accept_value))\n            .header(auth_header, auth_value)\n            .body(Vec::new())\n    }\n\n    fn user_info_response<AC, GC, RE>(\n        self,\n        http_response: HttpResponse,\n    ) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        RE: std::error::Error + 'static,\n    {\n        if http_response.status() != StatusCode::OK {\n            return Err(UserInfoError::Response(\n                http_response.status(),\n                http_response.body().to_owned(),\n                \"unexpected HTTP status code\".to_string(),\n            ));\n        }\n\n        match http_response\n            .headers()\n            .get(CONTENT_TYPE)\n            .map(ToOwned::to_owned)\n            .unwrap_or_else(|| HeaderValue::from_static(MIME_TYPE_JSON))\n        {\n            ref content_type if content_type_has_essence(content_type, MIME_TYPE_JSON) => {\n                if self.require_signed_response {\n                    return Err(UserInfoError::ClaimsVerification(\n                        ClaimsVerificationError::SignatureVerification(\n                            SignatureVerificationError::NoSignature,\n                        ),\n                    ));\n                }\n                UserInfoClaims::from_json(\n                    http_response.body(),\n                    self.signed_response_verifier.expected_subject(),\n                )\n            }\n            ref content_type if content_type_has_essence(content_type, MIME_TYPE_JWT) => {\n                let jwt_str = String::from_utf8(http_response.body().to_owned()).map_err(|_| {\n                    UserInfoError::Other(\"response body has invalid UTF-8 encoding\".to_string())\n                })?;\n                serde_path_to_error::deserialize::<\n                    _,\n                    UserInfoJsonWebToken<AC, GC, JE, K::SigningAlgorithm>,\n                >(serde_json::Value::String(jwt_str))\n                .map_err(UserInfoError::Parse)?\n                .claims(&self.signed_response_verifier)\n                .map_err(UserInfoError::ClaimsVerification)\n            }\n            ref content_type => Err(UserInfoError::Response(\n                http_response.status(),\n                http_response.body().to_owned(),\n                format!(\"unexpected response Content-Type: `{:?}`\", content_type),\n            )),\n        }\n    }\n\n    /// Specifies whether to require the user info response to be a signed JSON Web Token (JWT).\n    pub fn require_signed_response(mut self, require_signed_response: bool) -> Self {\n        self.require_signed_response = require_signed_response;\n        self\n    }\n\n    /// Specifies whether to require the issuer of the signed JWT response to match the expected\n    /// issuer URL for this provider.\n    ///\n    /// This option has no effect on unsigned JSON responses.\n    pub fn require_issuer_match(mut self, iss_required: bool) -> Self {\n        self.signed_response_verifier = self\n            .signed_response_verifier\n            .require_issuer_match(iss_required);\n        self\n    }\n\n    /// Specifies whether to require the audience of the signed JWT response to match the expected\n    /// audience (client ID).\n    ///\n    /// This option has no effect on unsigned JSON responses.\n    pub fn require_audience_match(mut self, aud_required: bool) -> Self {\n        self.signed_response_verifier = self\n            .signed_response_verifier\n            .require_audience_match(aud_required);\n        self\n    }\n\n    /// Specifies the expected response type by setting the `Accept` header. Note that the server can ignore this header.\n    pub fn set_response_type(mut self, response_type: UserInfoResponseType) -> Self {\n        self.response_type = response_type;\n        self\n    }\n}\n\n/// User info claims.\n#[derive(Clone, Debug, Serialize)]\npub struct UserInfoClaims<AC: AdditionalClaims, GC: GenderClaim>(UserInfoClaimsImpl<AC, GC>);\nimpl<AC, GC> UserInfoClaims<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    /// Initializes user info claims.\n    pub fn new(standard_claims: StandardClaims<GC>, additional_claims: AC) -> Self {\n        Self(UserInfoClaimsImpl {\n            issuer: None,\n            audiences: None,\n            standard_claims,\n            additional_claims: additional_claims.into(),\n        })\n    }\n\n    /// Initializes user info claims from the provided raw JSON response.\n    ///\n    /// If an `expected_subject` is provided, this function verifies that the user info claims\n    /// contain the expected subject and returns an error otherwise.\n    pub fn from_json<RE>(\n        user_info_json: &[u8],\n        expected_subject: Option<&SubjectIdentifier>,\n    ) -> Result<Self, UserInfoError<RE>>\n    where\n        RE: std::error::Error + 'static,\n    {\n        let user_info = serde_path_to_error::deserialize::<_, UserInfoClaimsImpl<AC, GC>>(\n            &mut serde_json::Deserializer::from_slice(user_info_json),\n        )\n        .map_err(UserInfoError::Parse)?;\n\n        // This is the only verification we need to do for JSON-based user info claims, so don't\n        // bother with the complexity of a separate verifier object.\n        if expected_subject\n            .iter()\n            .all(|expected_subject| user_info.standard_claims.sub == **expected_subject)\n        {\n            Ok(Self(user_info))\n        } else {\n            Err(UserInfoError::ClaimsVerification(\n                ClaimsVerificationError::InvalidSubject(format!(\n                    \"expected `{}` (found `{}`)\",\n                    // This can only happen when expected_subject is not None.\n                    expected_subject.unwrap().as_str(),\n                    user_info.standard_claims.sub.as_str(),\n                )),\n            ))\n        }\n    }\n\n    field_getters_setters![\n        pub self [self.0] [\"claim\"] {\n            set_issuer -> issuer[Option<IssuerUrl>],\n            set_audiences -> audiences[Option<Vec<Audience>>] [\"aud\"],\n        }\n    ];\n\n    /// Returns the `sub` claim.\n    pub fn subject(&self) -> &SubjectIdentifier {\n        &self.0.standard_claims.sub\n    }\n    /// Sets the `sub` claim.\n    pub fn set_subject(&mut self, subject: SubjectIdentifier) {\n        self.0.standard_claims.sub = subject\n    }\n\n    field_getters_setters![\n        pub self [self.0.standard_claims] [\"claim\"] {\n            set_name -> name[Option<LocalizedClaim<EndUserName>>],\n            set_given_name -> given_name[Option<LocalizedClaim<EndUserGivenName>>],\n            set_family_name ->\n                family_name[Option<LocalizedClaim<EndUserFamilyName>>],\n            set_middle_name ->\n                middle_name[Option<LocalizedClaim<EndUserMiddleName>>],\n            set_nickname -> nickname[Option<LocalizedClaim<EndUserNickname>>],\n            set_preferred_username -> preferred_username[Option<EndUserUsername>],\n            set_profile -> profile[Option<LocalizedClaim<EndUserProfileUrl>>],\n            set_picture -> picture[Option<LocalizedClaim<EndUserPictureUrl>>],\n            set_website -> website[Option<LocalizedClaim<EndUserWebsiteUrl>>],\n            set_email -> email[Option<EndUserEmail>],\n            set_email_verified -> email_verified[Option<bool>],\n            set_gender -> gender[Option<GC>],\n            set_birthday -> birthday[Option<EndUserBirthday>],\n            set_birthdate -> birthdate[Option<EndUserBirthday>],\n            set_zoneinfo -> zoneinfo[Option<EndUserTimezone>],\n            set_locale -> locale[Option<LanguageTag>],\n            set_phone_number -> phone_number[Option<EndUserPhoneNumber>],\n            set_phone_number_verified -> phone_number_verified[Option<bool>],\n            set_address -> address[Option<AddressClaim>],\n            set_updated_at -> updated_at[Option<DateTime<Utc>>],\n        }\n    ];\n\n    /// Returns the standard claims as a `StandardClaims` object.\n    pub fn standard_claims(&self) -> &StandardClaims<GC> {\n        &self.0.standard_claims\n    }\n\n    /// Returns additional user info claims.\n    pub fn additional_claims(&self) -> &AC {\n        self.0.additional_claims.as_ref()\n    }\n    /// Returns mutable additional user info claims.\n    pub fn additional_claims_mut(&mut self) -> &mut AC {\n        self.0.additional_claims.as_mut()\n    }\n}\n\n#[skip_serializing_none]\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub(crate) struct UserInfoClaimsImpl<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    #[serde(rename = \"iss\")]\n    pub issuer: Option<IssuerUrl>,\n    // We always serialize as an array, which is valid according to the spec.\n    #[serde(\n        default,\n        rename = \"aud\",\n        deserialize_with = \"deserialize_string_or_vec_opt\"\n    )]\n    pub audiences: Option<Vec<Audience>>,\n\n    #[serde(bound = \"GC: GenderClaim\", flatten)]\n    pub standard_claims: StandardClaims<GC>,\n\n    #[serde(bound = \"AC: AdditionalClaims\", flatten)]\n    pub additional_claims: FilteredFlatten<StandardClaims<GC>, AC>,\n}\nimpl<AC, GC> AudiencesClaim for UserInfoClaimsImpl<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn audiences(&self) -> Option<&Vec<Audience>> {\n        self.audiences.as_ref()\n    }\n}\nimpl<'a, AC, GC> AudiencesClaim for &'a UserInfoClaimsImpl<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn audiences(&self) -> Option<&Vec<Audience>> {\n        self.audiences.as_ref()\n    }\n}\n\nimpl<AC, GC> IssuerClaim for UserInfoClaimsImpl<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn issuer(&self) -> Option<&IssuerUrl> {\n        self.issuer.as_ref()\n    }\n}\nimpl<'a, AC, GC> IssuerClaim for &'a UserInfoClaimsImpl<AC, GC>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n{\n    fn issuer(&self) -> Option<&IssuerUrl> {\n        self.issuer.as_ref()\n    }\n}\n\n/// JSON Web Token (JWT) containing user info claims.\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct UserInfoJsonWebToken<\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n>(\n    #[serde(bound = \"AC: AdditionalClaims\")]\n    JsonWebToken<JE, JS, UserInfoClaimsImpl<AC, GC>, JsonWebTokenJsonPayloadSerde>,\n);\nimpl<AC, GC, JE, JS> UserInfoJsonWebToken<AC, GC, JE, JS>\nwhere\n    AC: AdditionalClaims,\n    GC: GenderClaim,\n    JE: JweContentEncryptionAlgorithm<KeyType = JS::KeyType>,\n    JS: JwsSigningAlgorithm,\n{\n    /// Initializes a new signed JWT containing the specified claims, signed with the specified key\n    /// and signing algorithm.\n    pub fn new<S>(\n        claims: UserInfoClaims<AC, GC>,\n        signing_key: &S,\n        alg: JS,\n    ) -> Result<Self, JsonWebTokenError>\n    where\n        S: PrivateSigningKey,\n        <S as PrivateSigningKey>::VerificationKey: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        Ok(Self(JsonWebToken::new(claims.0, signing_key, &alg)?))\n    }\n\n    /// Verifies and returns the user info claims.\n    pub fn claims<K>(\n        self,\n        verifier: &UserInfoVerifier<JE, K>,\n    ) -> Result<UserInfoClaims<AC, GC>, ClaimsVerificationError>\n    where\n        K: JsonWebKey<SigningAlgorithm = JS>,\n    {\n        Ok(UserInfoClaims(verifier.verified_claims(self.0)?))\n    }\n}\n\nnew_url_type![\n    /// URL for a provider's user info endpoint.\n    UserInfoUrl\n];\n\n/// Indicates via the `Accept` header the body response type the server should use to return the user info. Note that the server can ignore this header.\n///\n/// Defaults to Json.\n#[derive(Clone, Debug)]\n#[non_exhaustive]\npub enum UserInfoResponseType {\n    /// Sets the `Accept` header to `application/json`.\n    Json,\n    /// Sets the `Accept` header to `application/jwt`.\n    Jwt,\n}\n\n/// Error retrieving user info.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum UserInfoError<RE>\nwhere\n    RE: std::error::Error + 'static,\n{\n    /// Failed to verify user info claims.\n    #[error(\"Failed to verify claims\")]\n    ClaimsVerification(#[source] ClaimsVerificationError),\n    /// Failed to parse server response.\n    #[error(\"Failed to parse server response\")]\n    Parse(#[source] serde_path_to_error::Error<serde_json::Error>),\n    /// An error occurred while sending the request or receiving the response (e.g., network\n    /// connectivity failed).\n    #[error(\"Request failed\")]\n    Request(#[source] RE),\n    /// Server returned an invalid response.\n    #[error(\"Server returned invalid response: {2}\")]\n    Response(StatusCode, Vec<u8>, String),\n    /// An unexpected error occurred.\n    #[error(\"Other error: {0}\")]\n    Other(String),\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::core::CoreGenderClaim;\n    use crate::{AdditionalClaims, UserInfoClaims};\n\n    use serde::{Deserialize, Serialize};\n\n    use std::collections::HashMap;\n\n    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\n    struct TestClaims {\n        pub tfa_method: String,\n    }\n    impl AdditionalClaims for TestClaims {}\n\n    #[test]\n    fn test_additional_claims() {\n        let claims =\n            UserInfoClaims::<TestClaims, CoreGenderClaim>::from_json::<crate::reqwest::Error>(\n                \"{\n                \\\"iss\\\": \\\"https://server.example.com\\\",\n                \\\"sub\\\": \\\"24400320\\\",\n                \\\"aud\\\": [\\\"s6BhdRkqt3\\\"],\n                \\\"tfa_method\\\": \\\"u2f\\\"\n            }\"\n                .as_bytes(),\n                None,\n            )\n            .expect(\"failed to deserialize\");\n        assert_eq!(claims.additional_claims().tfa_method, \"u2f\");\n        assert_eq!(\n            serde_json::to_string(&claims).expect(\"failed to serialize\"),\n            \"{\\\n             \\\"iss\\\":\\\"https://server.example.com\\\",\\\n             \\\"aud\\\":[\\\"s6BhdRkqt3\\\"],\\\n             \\\"sub\\\":\\\"24400320\\\",\\\n             \\\"tfa_method\\\":\\\"u2f\\\"\\\n             }\",\n        );\n\n        UserInfoClaims::<TestClaims, CoreGenderClaim>::from_json::<crate::reqwest::Error>(\n            \"{\n                \\\"iss\\\": \\\"https://server.example.com\\\",\n                \\\"sub\\\": \\\"24400320\\\",\n                \\\"aud\\\": [\\\"s6BhdRkqt3\\\"]\n            }\"\n            .as_bytes(),\n            None,\n        )\n        .expect_err(\"missing claim should fail to deserialize\");\n    }\n\n    #[derive(Debug, Deserialize, Serialize)]\n    struct AllOtherClaims(HashMap<String, serde_json::Value>);\n    impl AdditionalClaims for AllOtherClaims {}\n\n    #[test]\n    fn test_catch_all_additional_claims() {\n        let claims =\n            UserInfoClaims::<AllOtherClaims, CoreGenderClaim>::from_json::<crate::reqwest::Error>(\n                \"{\n                \\\"iss\\\": \\\"https://server.example.com\\\",\n                \\\"sub\\\": \\\"24400320\\\",\n                \\\"aud\\\": [\\\"s6BhdRkqt3\\\"],\n                \\\"tfa_method\\\": \\\"u2f\\\",\n                \\\"updated_at\\\": 1000\n            }\"\n                .as_bytes(),\n                None,\n            )\n            .expect(\"failed to deserialize\");\n\n        assert_eq!(claims.additional_claims().0.len(), 1);\n        assert_eq!(claims.additional_claims().0[\"tfa_method\"], \"u2f\");\n    }\n}\n"
  },
  {
    "path": "src/verification/mod.rs",
    "content": "use crate::jwt::{JsonWebToken, JsonWebTokenJsonPayloadSerde, NormalizedJsonWebTokenType};\nuse crate::user_info::UserInfoClaimsImpl;\nuse crate::{\n    AdditionalClaims, Audience, AuthenticationContextClass, ClientId, ClientSecret, GenderClaim,\n    IdTokenClaims, IssuerUrl, JsonWebKey, JsonWebKeyId, JsonWebKeySet, JsonWebTokenAccess,\n    JsonWebTokenAlgorithm, JsonWebTokenHeader, JsonWebTokenType, JweContentEncryptionAlgorithm,\n    JwsSigningAlgorithm, Nonce, SubjectIdentifier,\n};\n\nuse chrono::{DateTime, Utc};\nuse serde::de::DeserializeOwned;\nuse serde::Serialize;\nuse sha2::{Digest, Sha256};\nuse thiserror::Error;\n\nuse std::collections::HashSet;\nuse std::fmt::Debug;\nuse std::marker::PhantomData;\nuse std::ops::Deref;\nuse std::sync::Arc;\n\n#[cfg(test)]\nmod tests;\n\npub(crate) trait AudiencesClaim {\n    fn audiences(&self) -> Option<&Vec<Audience>>;\n}\n\npub(crate) trait IssuerClaim {\n    fn issuer(&self) -> Option<&IssuerUrl>;\n}\n\n/// Error verifying claims.\n#[derive(Clone, Debug, Error, PartialEq, Eq)]\n#[non_exhaustive]\npub enum ClaimsVerificationError {\n    /// Claims have expired.\n    #[error(\"Expired: {0}\")]\n    Expired(String),\n    /// Audience claim is invalid.\n    #[error(\"Invalid audiences: {0}\")]\n    InvalidAudience(String),\n    /// Authorization context class reference (`acr`) claim is invalid.\n    #[error(\"Invalid authorization context class reference: {0}\")]\n    InvalidAuthContext(String),\n    /// User authenticated too long ago.\n    #[error(\"Invalid authentication time: {0}\")]\n    InvalidAuthTime(String),\n    /// Issuer claim is invalid.\n    #[error(\"Invalid issuer: {0}\")]\n    InvalidIssuer(String),\n    /// Nonce is invalid.\n    #[error(\"Invalid nonce: {0}\")]\n    InvalidNonce(String),\n    /// Subject claim is invalid.\n    #[error(\"Invalid subject: {0}\")]\n    InvalidSubject(String),\n    /// An unexpected error occurred.\n    #[error(\"{0}\")]\n    Other(String),\n    /// Failed to verify the claims signature.\n    #[error(\"Signature verification failed\")]\n    SignatureVerification(#[source] SignatureVerificationError),\n    /// Unsupported argument or value.\n    #[error(\"Unsupported: {0}\")]\n    Unsupported(String),\n}\n\n/// Error verifying claims signature.\n#[derive(Clone, Debug, Error, PartialEq, Eq)]\n#[non_exhaustive]\npub enum SignatureVerificationError {\n    /// More than one key matches the supplied key constraints (e.g., key ID).\n    #[error(\"Ambiguous key identification: {0}\")]\n    AmbiguousKeyId(String),\n    /// Invalid signature for the supplied claims and signing key.\n    #[error(\"Crypto error: {0}\")]\n    CryptoError(String),\n    /// The supplied signature algorithm is disallowed by the verifier.\n    #[error(\"Disallowed signature algorithm: {0}\")]\n    DisallowedAlg(String),\n    /// The supplied key cannot be used in this context. This may occur if the key type does not\n    /// match the signature type (e.g., an RSA key used to validate an HMAC) or the JWK usage\n    /// disallows signatures.\n    #[error(\"Invalid cryptographic key: {0}\")]\n    InvalidKey(String),\n    /// The signing key needed for verifying the\n    /// [JSON Web Token](https://tools.ietf.org/html/rfc7519)'s signature/MAC could not be found.\n    /// This error can occur if the key ID (`kid`) specified in the JWT's\n    /// [JOSE header](https://tools.ietf.org/html/rfc7519#section-5) does not match the ID of any\n    /// key in the OpenID Connect provider's JSON Web Key Set (JWKS), typically retrieved from\n    /// the provider's [JWKS document](\n    /// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). To support\n    /// [rotation of asymmetric signing keys](\n    /// http://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys), client applications\n    /// should consider refreshing the JWKS document (via\n    /// [`JsonWebKeySet::fetch`][crate::JsonWebKeySet::fetch]).\n    ///\n    /// This error can also occur if the identified\n    /// [JSON Web Key](https://tools.ietf.org/html/rfc7517) is of the wrong type (e.g., an RSA key\n    /// when the JOSE header specifies an ECDSA algorithm) or does not support signing.\n    #[error(\"No matching key found\")]\n    NoMatchingKey,\n    /// No signature present but claims must be signed.\n    #[error(\"No signature found\")]\n    NoSignature,\n    /// Unsupported signature algorithm.\n    #[error(\"Unsupported signature algorithm: {0}\")]\n    UnsupportedAlg(String),\n    /// An unexpected error occurred.\n    #[error(\"Other error: {0}\")]\n    Other(String),\n}\n\n// This struct is intentionally private.\n#[derive(Clone)]\npub(crate) struct JwtClaimsVerifier<'a, K>\nwhere\n    K: JsonWebKey,\n{\n    allowed_jose_types: Option<HashSet<NormalizedJsonWebTokenType>>,\n    allowed_algs: Option<HashSet<K::SigningAlgorithm>>,\n    aud_match_required: bool,\n    client_id: ClientId,\n    client_secret: Option<ClientSecret>,\n    iss_required: bool,\n    issuer: IssuerUrl,\n    is_signature_check_enabled: bool,\n    other_aud_verifier_fn: Arc<dyn Fn(&Audience) -> bool + 'a + Send + Sync>,\n    signature_keys: JsonWebKeySet<K>,\n}\nimpl<'a, K> JwtClaimsVerifier<'a, K>\nwhere\n    K: JsonWebKey,\n{\n    pub fn new(client_id: ClientId, issuer: IssuerUrl, signature_keys: JsonWebKeySet<K>) -> Self {\n        JwtClaimsVerifier {\n            allowed_algs: Some(\n                [K::SigningAlgorithm::rsa_sha_256()]\n                    .iter()\n                    .cloned()\n                    .collect(),\n            ),\n            allowed_jose_types: Some(HashSet::from([\n                JsonWebTokenType::new(\"application/jwt\".to_string())\n                    .normalize()\n                    .expect(\"application/jwt should be a valid JWT type\"), // used by many IdP, but not standardized\n                JsonWebTokenType::new(\"application/jose\".to_string())\n                    .normalize()\n                    .expect(\"application/jose should be a valid JWT type\"), // standard as defined in https://tools.ietf.org/html/rfc7515#section-4.1.9\n                                                                            // we do not support JOSE+JSON, so we omit this here in the default configuration\n            ])),\n            aud_match_required: true,\n            client_id,\n            client_secret: None,\n            iss_required: true,\n            issuer,\n            is_signature_check_enabled: true,\n            // Secure default: reject all other audiences as untrusted, since any other audience\n            // can potentially impersonate the user when by sending its copy of these claims\n            // to this relying party.\n            other_aud_verifier_fn: Arc::new(|_| false),\n            signature_keys,\n        }\n    }\n\n    pub fn require_audience_match(mut self, aud_required: bool) -> Self {\n        self.aud_match_required = aud_required;\n        self\n    }\n\n    pub fn require_issuer_match(mut self, iss_required: bool) -> Self {\n        self.iss_required = iss_required;\n        self\n    }\n\n    pub fn require_signature_check(mut self, sig_required: bool) -> Self {\n        self.is_signature_check_enabled = sig_required;\n        self\n    }\n\n    pub fn set_allowed_algs<I>(mut self, algs: I) -> Self\n    where\n        I: IntoIterator<Item = K::SigningAlgorithm>,\n    {\n        self.allowed_algs = Some(algs.into_iter().collect());\n        self\n    }\n    pub fn allow_any_alg(mut self) -> Self {\n        self.allowed_algs = None;\n        self\n    }\n\n    /// Allows setting specific JOSE types. The verifier will check against them during verification.\n    ///\n    /// See [RFC 7515 section 4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9) for more details.\n    pub fn set_allowed_jose_types<I>(mut self, types: I) -> Self\n    where\n        I: IntoIterator<Item = NormalizedJsonWebTokenType>,\n    {\n        self.allowed_jose_types = Some(types.into_iter().collect());\n        self\n    }\n    pub fn allow_all_jose_types(mut self) -> Self {\n        self.allowed_jose_types = None;\n        self\n    }\n\n    pub fn set_client_secret(mut self, client_secret: ClientSecret) -> Self {\n        self.client_secret = Some(client_secret);\n        self\n    }\n\n    pub fn set_other_audience_verifier_fn<T>(mut self, other_aud_verifier_fn: T) -> Self\n    where\n        T: Fn(&Audience) -> bool + 'a + Send + Sync,\n    {\n        self.other_aud_verifier_fn = Arc::new(other_aud_verifier_fn);\n        self\n    }\n\n    fn validate_jose_header<JE>(\n        &self,\n        jose_header: &JsonWebTokenHeader<JE, K::SigningAlgorithm>,\n    ) -> Result<(), ClaimsVerificationError>\n    where\n        JE: JweContentEncryptionAlgorithm<\n            KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n        >,\n    {\n        // The 'typ' header field must either be omitted or have the canonicalized value JWT.\n        // see https://tools.ietf.org/html/rfc7519#section-5.1\n        if let Some(ref jwt_type) = jose_header.typ {\n            if let Some(allowed_jose_types) = &self.allowed_jose_types {\n                // Check according to https://tools.ietf.org/html/rfc7515#section-4.1.9\n                // See https://tools.ietf.org/html/rfc2045#section-5.1 for the full Content-Type Header Field spec.\n                //\n                // For sake of simplicity, we do not support matching on application types with parameters like\n                // application/example;part=\"1/2\". If you know your parameters exactly, just set the whole Content Type manually.\n                let valid_jwt_type = if let Ok(normalized_jwt_type) = jwt_type.normalize() {\n                    allowed_jose_types.contains(&normalized_jwt_type)\n                } else {\n                    false\n                };\n\n                if !valid_jwt_type {\n                    return Err(ClaimsVerificationError::Unsupported(format!(\n                        \"unexpected or unsupported JWT type `{}`\",\n                        **jwt_type\n                    )));\n                }\n            }\n        }\n\n        // The 'cty' header field must be omitted, since it's only used for JWTs that contain\n        // content types other than JSON-encoded claims. This may include nested JWTs, such as if\n        // JWE encryption is used. This is currently unsupported.\n        if let Some(ref content_type) = jose_header.cty {\n            if content_type.to_uppercase() == \"JWT\" {\n                return Err(ClaimsVerificationError::Unsupported(\n                    \"nested JWT's are not currently supported\".to_string(),\n                ));\n            } else {\n                return Err(ClaimsVerificationError::Unsupported(format!(\n                    \"unexpected or unsupported JWT content type `{}`\",\n                    **content_type\n                )));\n            }\n        }\n\n        // If 'crit' fields are specified, we must reject any we do not understand. Since this\n        // implementation doesn't understand any of them, unconditionally reject the JWT. Note that\n        // the spec prohibits this field from containing any of the standard headers or being empty.\n        if jose_header.crit.is_some() {\n            // https://tools.ietf.org/html/rfc7515#appendix-E\n            return Err(ClaimsVerificationError::Unsupported(\n                \"critical JWT header fields are unsupported\".to_string(),\n            ));\n        }\n        Ok(())\n    }\n\n    pub fn verified_claims<A, C, JE, T>(&self, jwt: A) -> Result<T, ClaimsVerificationError>\n    where\n        A: JsonWebTokenAccess<JE, K::SigningAlgorithm, C, ReturnType = T>,\n        C: AudiencesClaim + Debug + DeserializeOwned + IssuerClaim + Serialize,\n        JE: JweContentEncryptionAlgorithm<\n            KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n        >,\n        T: AudiencesClaim + IssuerClaim,\n    {\n        {\n            let jose_header = jwt.unverified_header();\n            self.validate_jose_header(jose_header)?;\n\n            // The code below roughly follows the validation steps described in\n            // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation\n\n            // 1. If the ID Token is encrypted, decrypt it using the keys and algorithms that the Client\n            //    specified during Registration that the OP was to use to encrypt the ID Token. If\n            //    encryption was negotiated with the OP at Registration time and the ID Token is not\n            //    encrypted, the RP SHOULD reject it.\n\n            if let JsonWebTokenAlgorithm::Encryption(ref encryption_alg) = jose_header.alg {\n                return Err(ClaimsVerificationError::Unsupported(format!(\n                    \"JWE encryption is not currently supported (found algorithm `{}`)\",\n                    serde_plain::to_string(encryption_alg).unwrap_or_else(|err| panic!(\n                        \"encryption alg {:?} failed to serialize to a string: {}\",\n                        encryption_alg, err\n                    )),\n                )));\n            }\n        }\n\n        // TODO: Add encryption (JWE) support\n        {\n            // 2. The Issuer Identifier for the OpenID Provider (which is typically obtained during\n            //    Discovery) MUST exactly match the value of the iss (issuer) Claim.\n            let unverified_claims = jwt.unverified_payload_ref();\n            if self.iss_required {\n                if let Some(issuer) = unverified_claims.issuer() {\n                    if *issuer != self.issuer {\n                        return Err(ClaimsVerificationError::InvalidIssuer(format!(\n                            \"expected `{}` (found `{}`)\",\n                            *self.issuer, **issuer\n                        )));\n                    }\n                } else {\n                    return Err(ClaimsVerificationError::InvalidIssuer(\n                        \"missing issuer claim\".to_string(),\n                    ));\n                }\n            }\n\n            // 3. The Client MUST validate that the aud (audience) Claim contains its client_id value\n            //    registered at the Issuer identified by the iss (issuer) Claim as an audience. The aud\n            //    (audience) Claim MAY contain an array with more than one element. The ID Token MUST be\n            //    rejected if the ID Token does not list the Client as a valid audience, or if it\n            //    contains additional audiences not trusted by the Client.\n            if self.aud_match_required {\n                if let Some(audiences) = unverified_claims.audiences() {\n                    if !audiences\n                        .iter()\n                        .any(|aud| (**aud).deref() == self.client_id.deref())\n                    {\n                        return Err(ClaimsVerificationError::InvalidAudience(format!(\n                            \"must contain `{}` (found audiences: {})\",\n                            *self.client_id,\n                            audiences\n                                .iter()\n                                .map(|aud| format!(\"`{}`\", Deref::deref(aud)))\n                                .collect::<Vec<_>>()\n                                .join(\", \")\n                        )));\n                    } else if audiences.len() > 1 {\n                        audiences\n                            .iter()\n                            .filter(|aud| (**aud).deref() != self.client_id.deref())\n                            .find(|aud| !(self.other_aud_verifier_fn)(aud))\n                            .map(|aud| {\n                                Err(ClaimsVerificationError::InvalidAudience(format!(\n                                    \"`{}` is not a trusted audience\",\n                                    **aud,\n                                )))\n                            })\n                            .unwrap_or(Ok(()))?;\n                    }\n                } else {\n                    return Err(ClaimsVerificationError::InvalidAudience(\n                        \"missing audiences claim\".to_string(),\n                    ));\n                }\n            }\n        }\n        // Steps 4--5 (azp claim validation) are specific to the ID token.\n\n        // 6. If the ID Token is received via direct communication between the Client and the Token\n        //    Endpoint (which it is in this flow), the TLS server validation MAY be used to validate\n        //    the issuer in place of checking the token signature. The Client MUST validate the\n        //    signature of all other ID Tokens according to JWS [JWS] using the algorithm specified\n        //    in the JWT alg Header Parameter. The Client MUST use the keys provided by the Issuer.\n        if !self.is_signature_check_enabled {\n            return Ok(jwt.unverified_payload());\n        }\n\n        // Borrow the header again. We had to drop the reference above to allow for the\n        // early exit calling jwt.unverified_claims(), which takes ownership of the JWT.\n        let signature_alg = jwt\n            .signing_alg()\n            .map_err(ClaimsVerificationError::SignatureVerification)?\n            .to_owned();\n\n        // 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client\n        //    in the id_token_signed_response_alg parameter during Registration.\n        if let Some(ref allowed_algs) = self.allowed_algs {\n            if !allowed_algs.contains(&signature_alg) {\n                return Err(ClaimsVerificationError::SignatureVerification(\n                    SignatureVerificationError::DisallowedAlg(format!(\n                        \"algorithm `{}` is not one of: {}\",\n                        serde_plain::to_string(&signature_alg).unwrap_or_else(|err| panic!(\n                            \"signature alg {:?} failed to serialize to a string: {}\",\n                            signature_alg, err,\n                        )),\n                        allowed_algs\n                            .iter()\n                            .map(\n                                |alg| serde_plain::to_string(alg).unwrap_or_else(|err| panic!(\n                                    \"signature alg {:?} failed to serialize to a string: {}\",\n                                    alg, err,\n                                ))\n                            )\n                            .collect::<Vec<_>>()\n                            .join(\", \"),\n                    )),\n                ));\n            }\n        }\n\n        // NB: We must *not* trust the 'kid' (key ID) or 'alg' (algorithm) fields present in the\n        // JOSE header, as an attacker could manipulate these while forging the JWT. The code\n        // below must be secure regardless of how these fields are manipulated.\n\n        if signature_alg.uses_shared_secret() {\n            // 8. If the JWT alg Header Parameter uses a MAC based algorithm such as HS256,\n            //    HS384, or HS512, the octets of the UTF-8 representation of the client_secret\n            //    corresponding to the client_id contained in the aud (audience) Claim are used\n            //    as the key to validate the signature. For MAC based algorithms, the behavior\n            //    is unspecified if the aud is multi-valued or if an azp value is present that\n            //    is different than the aud value.\n            if let Some(ref client_secret) = self.client_secret {\n                let key = K::new_symmetric(client_secret.secret().clone().into_bytes());\n                return jwt\n                    .payload(&signature_alg, &key)\n                    .map_err(ClaimsVerificationError::SignatureVerification);\n            } else {\n                // The client secret isn't confidential for public clients, so anyone can forge a\n                // JWT with a valid signature.\n                return Err(ClaimsVerificationError::SignatureVerification(\n                    SignatureVerificationError::DisallowedAlg(\n                        \"symmetric signatures are disallowed for public clients\".to_string(),\n                    ),\n                ));\n            }\n        }\n\n        // Section 10.1 of OpenID Connect Core 1.0 states that the JWT must include a key ID\n        // if the JWK set contains more than one public key.\n\n        let public_key = self\n            .signing_key(jwt.unverified_header().kid.as_ref(), &signature_alg)\n            .map_err(ClaimsVerificationError::SignatureVerification)?;\n\n        jwt.payload(&signature_alg.clone(), public_key)\n            .map_err(ClaimsVerificationError::SignatureVerification)\n\n        // Steps 9--13 are specific to the ID token.\n    }\n\n    pub(crate) fn signing_key<'b>(\n        &'b self,\n        key_id: Option<&JsonWebKeyId>,\n        signature_alg: &K::SigningAlgorithm,\n    ) -> Result<&'b K, SignatureVerificationError> {\n        // See if any key has a matching key ID (if supplied) and compatible type.\n        let public_keys = self.signature_keys.filter_keys(key_id, signature_alg);\n        if public_keys.is_empty() {\n            Err(SignatureVerificationError::NoMatchingKey)\n        } else if public_keys.len() == 1 {\n            Ok(public_keys.first().expect(\"unreachable\"))\n        } else {\n            Err(SignatureVerificationError::AmbiguousKeyId(format!(\n                \"JWK set must only contain one eligible public key, but found {} eligible keys: {}\",\n                public_keys.len(),\n                public_keys\n                    .iter()\n                    .map(|key| format!(\n                        \"{} ({})\",\n                        key.key_id()\n                            .map(|kid| format!(\"`{}`\", **kid))\n                            .unwrap_or_else(|| \"null ID\".to_string()),\n                        serde_plain::to_string(key.key_type()).unwrap_or_else(|err| panic!(\n                            \"key type {:?} failed to serialize to a string: {}\",\n                            key.key_type(),\n                            err,\n                        ))\n                    ))\n                    .collect::<Vec<_>>()\n                    .join(\", \")\n            )))\n        }\n    }\n}\n\n/// Trait for verifying ID token nonces.\npub trait NonceVerifier {\n    /// Verifies the nonce.\n    ///\n    /// Returns `Ok(())` if the nonce is valid, or a string describing the error otherwise.\n    fn verify(self, nonce: Option<&Nonce>) -> Result<(), String>;\n}\n\nimpl NonceVerifier for &Nonce {\n    fn verify(self, nonce: Option<&Nonce>) -> Result<(), String> {\n        if let Some(claims_nonce) = nonce {\n            // Avoid timing side-channel.\n            if Sha256::digest(claims_nonce.secret()) != Sha256::digest(self.secret()) {\n                return Err(\"nonce mismatch\".to_string());\n            }\n        } else {\n            return Err(\"missing nonce claim\".to_string());\n        }\n        Ok(())\n    }\n}\n\nimpl<F> NonceVerifier for F\nwhere\n    F: FnOnce(Option<&Nonce>) -> Result<(), String>,\n{\n    fn verify(self, nonce: Option<&Nonce>) -> Result<(), String> {\n        self(nonce)\n    }\n}\n\n/// ID token verifier.\n#[derive(Clone)]\npub struct IdTokenVerifier<'a, K>\nwhere\n    K: JsonWebKey,\n{\n    acr_verifier_fn:\n        Arc<dyn Fn(Option<&AuthenticationContextClass>) -> Result<(), String> + 'a + Send + Sync>,\n    #[allow(clippy::type_complexity)]\n    auth_time_verifier_fn:\n        Arc<dyn Fn(Option<DateTime<Utc>>) -> Result<(), String> + 'a + Send + Sync>,\n    iat_verifier_fn: Arc<dyn Fn(DateTime<Utc>) -> Result<(), String> + 'a + Send + Sync>,\n    pub(crate) jwt_verifier: JwtClaimsVerifier<'a, K>,\n    time_fn: Arc<dyn Fn() -> DateTime<Utc> + 'a + Send + Sync>,\n}\nimpl<'a, K> IdTokenVerifier<'a, K>\nwhere\n    K: JsonWebKey,\n{\n    fn new(jwt_verifier: JwtClaimsVerifier<'a, K>) -> Self {\n        IdTokenVerifier {\n            // By default, accept authorization context reference (acr claim).\n            acr_verifier_fn: Arc::new(|_| Ok(())),\n            auth_time_verifier_fn: Arc::new(|_| Ok(())),\n            // By default, accept any issued time (iat claim).\n            iat_verifier_fn: Arc::new(|_| Ok(())),\n            jwt_verifier,\n            // By default, use the current system time.\n            time_fn: Arc::new(Utc::now),\n        }\n    }\n\n    /// Initializes a new verifier for a public client (i.e., one without a client secret).\n    pub fn new_public_client(\n        client_id: ClientId,\n        issuer: IssuerUrl,\n        signature_keys: JsonWebKeySet<K>,\n    ) -> Self {\n        Self::new(JwtClaimsVerifier::new(client_id, issuer, signature_keys))\n    }\n\n    /// Initializes a no-op verifier that performs no signature, audience, or issuer verification.\n    /// The token's expiration time is still checked, and the token is otherwise required to conform to the expected format.\n    pub fn new_insecure_without_verification() -> Self {\n        let empty_issuer = IssuerUrl::new(\"https://0.0.0.0\".to_owned())\n            .expect(\"Creating empty issuer url mustn't fail\");\n        Self::new_public_client(\n            ClientId::new(String::new()),\n            empty_issuer,\n            JsonWebKeySet::new(vec![]),\n        )\n        .insecure_disable_signature_check()\n        .require_audience_match(false)\n        .require_issuer_match(false)\n    }\n\n    /// Initializes a new verifier for a confidential client (i.e., one with a client secret).\n    ///\n    /// A confidential client verifier is required in order to verify ID tokens signed using a\n    /// shared secret algorithm such as `HS256`, `HS384`, or `HS512`. For these algorithms, the\n    /// client secret is the shared secret.\n    pub fn new_confidential_client(\n        client_id: ClientId,\n        client_secret: ClientSecret,\n        issuer: IssuerUrl,\n        signature_keys: JsonWebKeySet<K>,\n    ) -> Self {\n        Self::new(\n            JwtClaimsVerifier::new(client_id, issuer, signature_keys)\n                .set_client_secret(client_secret),\n        )\n    }\n\n    /// Specifies which JSON Web Signature algorithms are supported.\n    pub fn set_allowed_algs<I>(mut self, algs: I) -> Self\n    where\n        I: IntoIterator<Item = K::SigningAlgorithm>,\n    {\n        self.jwt_verifier = self.jwt_verifier.set_allowed_algs(algs);\n        self\n    }\n\n    /// Specifies that any signature algorithm is supported.\n    pub fn allow_any_alg(mut self) -> Self {\n        self.jwt_verifier = self.jwt_verifier.allow_any_alg();\n        self\n    }\n\n    /// Allows setting specific JOSE types. The verifier will check against them during verification.\n    ///\n    /// See [RFC 7515 section 4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9) for more details.\n    pub fn set_allowed_jose_types<I>(mut self, types: I) -> Self\n    where\n        I: IntoIterator<Item = NormalizedJsonWebTokenType>,\n    {\n        self.jwt_verifier = self.jwt_verifier.set_allowed_jose_types(types);\n        self\n    }\n\n    /// Allow all JSON Web Token Header types.\n    pub fn allow_all_jose_types(mut self) -> Self {\n        self.jwt_verifier = self.jwt_verifier.allow_all_jose_types();\n        self\n    }\n\n    /// Specifies a function for verifying the `acr` claim.\n    ///\n    /// The function should return `Ok(())` if the claim is valid, or a string describing the error\n    /// otherwise.\n    pub fn set_auth_context_verifier_fn<T>(mut self, acr_verifier_fn: T) -> Self\n    where\n        T: Fn(Option<&AuthenticationContextClass>) -> Result<(), String> + 'a + Send + Sync,\n    {\n        self.acr_verifier_fn = Arc::new(acr_verifier_fn);\n        self\n    }\n\n    /// Specifies a function for verifying the `auth_time` claim.\n    ///\n    /// The function should return `Ok(())` if the claim is valid, or a string describing the error\n    /// otherwise.\n    pub fn set_auth_time_verifier_fn<T>(mut self, auth_time_verifier_fn: T) -> Self\n    where\n        T: Fn(Option<DateTime<Utc>>) -> Result<(), String> + 'a + Send + Sync,\n    {\n        self.auth_time_verifier_fn = Arc::new(auth_time_verifier_fn);\n        self\n    }\n\n    /// Enables signature verification.\n    ///\n    /// Signature verification is enabled by default, so this function is only useful if\n    /// [`IdTokenVerifier::insecure_disable_signature_check`] was previously invoked.\n    pub fn enable_signature_check(mut self) -> Self {\n        self.jwt_verifier = self.jwt_verifier.require_signature_check(true);\n        self\n    }\n\n    /// Disables signature verification.\n    ///\n    /// # Security Warning\n    ///\n    /// Unverified ID tokens may be subject to forgery. See [Section 16.3](\n    /// https://openid.net/specs/openid-connect-core-1_0.html#TokenManufacture) for more\n    /// information.\n    pub fn insecure_disable_signature_check(mut self) -> Self {\n        self.jwt_verifier = self.jwt_verifier.require_signature_check(false);\n        self\n    }\n\n    /// Specifies whether the issuer claim must match the expected issuer URL for the provider.\n    pub fn require_issuer_match(mut self, iss_required: bool) -> Self {\n        self.jwt_verifier = self.jwt_verifier.require_issuer_match(iss_required);\n        self\n    }\n\n    /// Specifies whether the audience claim must match this client's client ID.\n    pub fn require_audience_match(mut self, aud_required: bool) -> Self {\n        self.jwt_verifier = self.jwt_verifier.require_audience_match(aud_required);\n        self\n    }\n\n    /// Specifies a function for returning the current time.\n    ///\n    /// This function is used for verifying the ID token expiration time.\n    pub fn set_time_fn<T>(mut self, time_fn: T) -> Self\n    where\n        T: Fn() -> DateTime<Utc> + 'a + Send + Sync,\n    {\n        self.time_fn = Arc::new(time_fn);\n        self\n    }\n\n    /// Specifies a function for verifying the ID token issue time.\n    ///\n    /// The function should return `Ok(())` if the claim is valid, or a string describing the error\n    /// otherwise.\n    pub fn set_issue_time_verifier_fn<T>(mut self, iat_verifier_fn: T) -> Self\n    where\n        T: Fn(DateTime<Utc>) -> Result<(), String> + 'a + Send + Sync,\n    {\n        self.iat_verifier_fn = Arc::new(iat_verifier_fn);\n        self\n    }\n\n    /// Specifies a function for verifying audiences included in the `aud` claim that differ from\n    /// this client's client ID.\n    ///\n    /// The function should return `true` if the audience is trusted, or `false` otherwise.\n    ///\n    /// [Section 3.1.3.7](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation)\n    /// states that *\"The ID Token MUST be rejected if the ID Token does not list the Client as a\n    /// valid audience, or if it contains additional audiences not trusted by the Client.\"*\n    pub fn set_other_audience_verifier_fn<T>(mut self, other_aud_verifier_fn: T) -> Self\n    where\n        T: Fn(&Audience) -> bool + 'a + Send + Sync,\n    {\n        self.jwt_verifier = self\n            .jwt_verifier\n            .set_other_audience_verifier_fn(other_aud_verifier_fn);\n        self\n    }\n\n    pub(crate) fn verified_claims<'b, AC, GC, JE, N>(\n        &self,\n        jwt: &'b JsonWebToken<\n            JE,\n            K::SigningAlgorithm,\n            IdTokenClaims<AC, GC>,\n            JsonWebTokenJsonPayloadSerde,\n        >,\n        nonce_verifier: N,\n    ) -> Result<&'b IdTokenClaims<AC, GC>, ClaimsVerificationError>\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        JE: JweContentEncryptionAlgorithm<\n            KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n        >,\n        N: NonceVerifier,\n    {\n        // The code below roughly follows the validation steps described in\n        // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation\n\n        // Steps 1--3 are handled by the generic JwtClaimsVerifier.\n        let partially_verified_claims = self.jwt_verifier.verified_claims(jwt)?;\n\n        self.verify_claims(partially_verified_claims, nonce_verifier)?;\n        Ok(partially_verified_claims)\n    }\n\n    pub(crate) fn verified_claims_owned<AC, GC, JE, N>(\n        &self,\n        jwt: JsonWebToken<\n            JE,\n            K::SigningAlgorithm,\n            IdTokenClaims<AC, GC>,\n            JsonWebTokenJsonPayloadSerde,\n        >,\n        nonce_verifier: N,\n    ) -> Result<IdTokenClaims<AC, GC>, ClaimsVerificationError>\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        JE: JweContentEncryptionAlgorithm<\n            KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n        >,\n        N: NonceVerifier,\n    {\n        // The code below roughly follows the validation steps described in\n        // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation\n\n        // Steps 1--3 are handled by the generic JwtClaimsVerifier.\n        let partially_verified_claims = self.jwt_verifier.verified_claims(jwt)?;\n\n        self.verify_claims(&partially_verified_claims, nonce_verifier)?;\n        Ok(partially_verified_claims)\n    }\n\n    fn verify_claims<AC, GC, N>(\n        &self,\n        partially_verified_claims: &'_ IdTokenClaims<AC, GC>,\n        nonce_verifier: N,\n    ) -> Result<(), ClaimsVerificationError>\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n        N: NonceVerifier,\n    {\n        // 4. If the ID Token contains multiple audiences, the Client SHOULD verify that an azp\n        //    Claim is present.\n\n        // There is significant confusion and contradiction in the OpenID Connect Core spec around\n        // the azp claim. See https://bitbucket.org/openid/connect/issues/973/ for a detailed\n        // discussion. Given the lack of clarity around how this claim should be used, we defer\n        // any verification of it here until a use case becomes apparent. If such a use case does\n        // arise, we most likely want to allow clients to pass in a function for validating the\n        // azp claim rather than introducing logic that affects all clients of this library.\n\n        // This naive implementation of the spec would almost certainly not be useful in practice:\n        /*\n        let azp_required = partially_verified_claims.audiences().len() > 1;\n\n        // 5. If an azp (authorized party) Claim is present, the Client SHOULD verify that its\n        //    client_id is the Claim Value.\n        if let Some(authorized_party) = partially_verified_claims.authorized_party() {\n            if *authorized_party != self.client_id {\n                return Err(ClaimsVerificationError::InvalidAudience(format!(\n                    \"authorized party must match client ID `{}` (found `{}`\",\n                    *self.client_id, **authorized_party\n                )));\n            }\n        } else if azp_required {\n            return Err(ClaimsVerificationError::InvalidAudience(format!(\n                \"missing authorized party claim but multiple audiences found\"\n            )));\n        }\n        */\n\n        // Steps 6--8 are handled by the generic JwtClaimsVerifier.\n\n        // 9. The current time MUST be before the time represented by the exp Claim.\n        let cur_time = (*self.time_fn)();\n        if cur_time >= partially_verified_claims.expiration() {\n            return Err(ClaimsVerificationError::Expired(format!(\n                \"ID token expired at {} (current time is {})\",\n                partially_verified_claims.expiration(),\n                cur_time\n            )));\n        }\n\n        // 10. The iat Claim can be used to reject tokens that were issued too far away from the\n        //     current time, limiting the amount of time that nonces need to be stored to prevent\n        //     attacks. The acceptable range is Client specific.\n        (*self.iat_verifier_fn)(partially_verified_claims.issue_time())\n            .map_err(ClaimsVerificationError::Expired)?;\n\n        // 11. If a nonce value was sent in the Authentication Request, a nonce Claim MUST be\n        //     present and its value checked to verify that it is the same value as the one that was\n        //     sent in the Authentication Request. The Client SHOULD check the nonce value for\n        //     replay attacks. The precise method for detecting replay attacks is Client specific.\n        nonce_verifier\n            .verify(partially_verified_claims.nonce())\n            .map_err(ClaimsVerificationError::InvalidNonce)?;\n\n        // 12. If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value\n        //     is appropriate. The meaning and processing of acr Claim Values is out of scope for\n        //     this specification.\n        (*self.acr_verifier_fn)(partially_verified_claims.auth_context_ref())\n            .map_err(ClaimsVerificationError::InvalidAuthContext)?;\n\n        // 13. If the auth_time Claim was requested, either through a specific request for this\n        //     Claim or by using the max_age parameter, the Client SHOULD check the auth_time Claim\n        //     value and request re-authentication if it determines too much time has elapsed since\n        //     the last End-User authentication.\n        (*self.auth_time_verifier_fn)(partially_verified_claims.auth_time())\n            .map_err(ClaimsVerificationError::InvalidAuthTime)?;\n\n        Ok(())\n    }\n}\n\n/// User info verifier.\n#[derive(Clone)]\npub struct UserInfoVerifier<'a, JE, K>\nwhere\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n{\n    jwt_verifier: JwtClaimsVerifier<'a, K>,\n    expected_subject: Option<SubjectIdentifier>,\n    _phantom: PhantomData<JE>,\n}\nimpl<'a, JE, K> UserInfoVerifier<'a, JE, K>\nwhere\n    JE: JweContentEncryptionAlgorithm<\n        KeyType = <K::SigningAlgorithm as JwsSigningAlgorithm>::KeyType,\n    >,\n    K: JsonWebKey,\n{\n    /// Instantiates a user info verifier.\n    pub fn new(\n        client_id: ClientId,\n        issuer: IssuerUrl,\n        signature_keys: JsonWebKeySet<K>,\n        expected_subject: Option<SubjectIdentifier>,\n    ) -> Self {\n        UserInfoVerifier {\n            jwt_verifier: JwtClaimsVerifier::new(client_id, issuer, signature_keys),\n            expected_subject,\n            _phantom: PhantomData,\n        }\n    }\n\n    pub(crate) fn expected_subject(&self) -> Option<&SubjectIdentifier> {\n        self.expected_subject.as_ref()\n    }\n\n    /// Specifies whether the issuer claim must match the expected issuer URL for the provider.\n    pub fn require_issuer_match(mut self, iss_required: bool) -> Self {\n        self.jwt_verifier = self.jwt_verifier.require_issuer_match(iss_required);\n        self\n    }\n\n    /// Specifies whether the audience claim must match this client's client ID.\n    pub fn require_audience_match(mut self, aud_required: bool) -> Self {\n        self.jwt_verifier = self.jwt_verifier.require_audience_match(aud_required);\n        self\n    }\n\n    pub(crate) fn verified_claims<AC, GC>(\n        &self,\n        user_info_jwt: JsonWebToken<\n            JE,\n            K::SigningAlgorithm,\n            UserInfoClaimsImpl<AC, GC>,\n            JsonWebTokenJsonPayloadSerde,\n        >,\n    ) -> Result<UserInfoClaimsImpl<AC, GC>, ClaimsVerificationError>\n    where\n        AC: AdditionalClaims,\n        GC: GenderClaim,\n    {\n        let user_info = self.jwt_verifier.verified_claims(user_info_jwt)?;\n        if self\n            .expected_subject\n            .iter()\n            .all(|expected_subject| user_info.standard_claims.sub == *expected_subject)\n        {\n            Ok(user_info)\n        } else {\n            Err(ClaimsVerificationError::InvalidSubject(format!(\n                \"expected `{}` (found `{}`)\",\n                // This can only happen when self.expected_subject is not None.\n                self.expected_subject.as_ref().unwrap().as_str(),\n                user_info.standard_claims.sub.as_str()\n            )))\n        }\n    }\n}\n"
  },
  {
    "path": "src/verification/tests.rs",
    "content": "use crate::core::{\n    CoreIdToken, CoreIdTokenClaims, CoreIdTokenVerifier, CoreJsonWebKey, CoreJsonWebKeySet,\n    CoreJsonWebKeyType, CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm,\n    CoreJwsSigningAlgorithm, CoreRsaPrivateSigningKey, CoreUserInfoClaims,\n    CoreUserInfoJsonWebToken, CoreUserInfoVerifier,\n};\nuse crate::helpers::{Base64UrlEncodedBytes, Timestamp};\nuse crate::jwt::tests::{TEST_RSA_PRIV_KEY, TEST_RSA_PUB_KEY};\nuse crate::jwt::{\n    JsonWebToken, JsonWebTokenHeader, JsonWebTokenJsonPayloadSerde, JsonWebTokenType,\n};\nuse crate::verification::{AudiencesClaim, IssuerClaim, JwtClaimsVerifier};\nuse crate::{\n    AccessToken, Audience, AuthenticationContextClass, AuthorizationCode, ClaimsVerificationError,\n    ClientId, ClientSecret, EndUserName, IssuerUrl, JsonWebKeyId, Nonce,\n    SignatureVerificationError, StandardClaims, SubjectIdentifier, UserInfoError,\n};\n\nuse chrono::{TimeZone, Utc};\nuse serde::{Deserialize, Serialize};\n\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\n\ntype CoreJsonWebTokenHeader =\n    JsonWebTokenHeader<CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>;\n\ntype CoreJwtClaimsVerifier<'a> = JwtClaimsVerifier<'a, CoreJsonWebKey>;\n\nfn assert_unsupported<T>(result: Result<T, ClaimsVerificationError>, expected_substr: &str) {\n    match result {\n        Err(ClaimsVerificationError::Unsupported(msg)) => {\n            assert!(msg.contains(expected_substr))\n        }\n        Err(err) => panic!(\"unexpected error: {:?}\", err),\n        Ok(_) => panic!(\"validation should fail\"),\n    }\n}\n\n#[test]\nfn test_jose_header() {\n    let client_id = ClientId::new(\"my_client\".to_string());\n    let issuer = IssuerUrl::new(\"https://example.com\".to_string()).unwrap();\n    let verifier = CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![]),\n    );\n\n    // Happy path JWT\n    verifier\n        .validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\"{\\\"alg\\\":\\\"RS256\\\", \\\"typ\\\":\\\"JWT\\\"}\")\n                .expect(\"failed to deserialize\"),\n        )\n        .expect(\"JWT type should be allowed but is not\");\n\n    verifier\n        .validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\"{\\\"alg\\\":\\\"RS256\\\", \\\"typ\\\":\\\"jwt\\\"}\")\n                .expect(\"failed to deserialize\"),\n        )\n        .expect(\"JWT type should be allowed but is not\");\n\n    verifier\n        .validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                \"{\\\"alg\\\":\\\"RS256\\\", \\\"typ\\\":\\\"application/JWT\\\"}\",\n            )\n            .expect(\"failed to deserialize\"),\n        )\n        .expect(\"JWT type should be allowed but is not\");\n\n    // Happy path JOSE\n    verifier\n        .validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                \"{\\\"alg\\\":\\\"RS256\\\", \\\"typ\\\":\\\"jose\\\"}\",\n            )\n            .expect(\"failed to deserialize\"),\n        )\n        .expect(\"JWT type should be allowed but is not\");\n\n    verifier\n        .validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                \"{\\\"alg\\\":\\\"RS256\\\", \\\"typ\\\":\\\"JOSE\\\"}\",\n            )\n            .expect(\"failed to deserialize\"),\n        )\n        .expect(\"JWT type should be allowed but is not\");\n\n    // Unexpected JWT type.\n    assert_unsupported(\n        verifier.validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"NOT_A_JWT\\\"}\",\n            )\n            .expect(\"failed to deserialize\"),\n        ),\n        \"unsupported JWT type\",\n    );\n\n    // No typ at all.\n    verifier\n        .validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\"{\\\"alg\\\":\\\"RS256\\\"}\")\n                .expect(\"failed to deserialize\"),\n        )\n        .expect(\"JWT type should be allowed but is not\");\n\n    // Specific JWT type from list.\n    {\n        let custom_verifier = CoreJwtClaimsVerifier::new(\n            client_id.clone(),\n            issuer.clone(),\n            CoreJsonWebKeySet::new(vec![]),\n        )\n        .set_allowed_jose_types(vec![\n            JsonWebTokenType::new(\"application/NOT_A_JWT\".to_string())\n                .normalize()\n                .unwrap(),\n            JsonWebTokenType::new(\"APPLICATION/AT+jwt\".to_string())\n                .normalize()\n                .unwrap(),\n            JsonWebTokenType::new(\"X-special-app/jwt;param=some\".to_string())\n                .normalize()\n                .unwrap(),\n            JsonWebTokenType::new(\"X-special-app/jwt\".to_string())\n                .normalize()\n                .unwrap(),\n        ]);\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"NOT_A_JWT\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"application/NOT_A_JWT\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        assert_unsupported(\n            custom_verifier.validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"application/NOT_A_JWT;bla=test\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            ),\n            \"unsupported JWT type\",\n        );\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"application/at+jwt\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        assert_unsupported(\n            custom_verifier.validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"NOT_A_JWT_REALLY\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            ),\n            \"unsupported JWT type\",\n        );\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"X-special-app/jwt\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"X-special-app/jwt;param=some\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        assert_unsupported(\n            custom_verifier.validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"X-special-app/jwt;param=other\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            ),\n            \"unsupported JWT type\",\n        );\n    }\n\n    // Allow all JWT types.\n    {\n        let custom_verifier = CoreJwtClaimsVerifier::new(\n            client_id.clone(),\n            issuer.clone(),\n            CoreJsonWebKeySet::new(vec![]),\n        )\n        .allow_all_jose_types();\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"NOT_A_JWT\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"application/at+jwt\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"application/at+jwt;oidc=cool\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n\n        custom_verifier\n            .validate_jose_header(\n                &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                    \"{\\\"alg\\\":\\\"RS256\\\",\\\"typ\\\":\\\"NOT_A_JWT_REALLY\\\"}\",\n                )\n                .expect(\"failed to deserialize\"),\n            )\n            .expect(\"JWT type should be allowed but is not\");\n    }\n\n    // Nested JWTs.\n    assert_unsupported(\n        verifier.validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\"{\\\"alg\\\":\\\"RS256\\\",\\\"cty\\\":\\\"JWT\\\"}\")\n                .expect(\"failed to deserialize\"),\n        ),\n        \"nested JWT\",\n    );\n    assert_unsupported(\n        verifier.validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                \"{\\\"alg\\\":\\\"RS256\\\",\\\"cty\\\":\\\"NOT_A_JWT\\\"}\",\n            )\n            .expect(\"failed to deserialize\"),\n        ),\n        \"unsupported JWT content type\",\n    );\n\n    // Critical fields. Adapted from https://tools.ietf.org/html/rfc7515#appendix-E\n    assert_unsupported(\n        verifier.validate_jose_header(\n            &serde_json::from_str::<CoreJsonWebTokenHeader>(\n                \"{\\\n                     \\\"alg\\\":\\\"RS256\\\",\\\n                     \\\"crit\\\":[\\\"http://example.invalid/UNDEFINED\\\"],\\\n                     \\\"http://example.invalid/UNDEFINED\\\":true\\\n                     }\",\n            )\n            .expect(\"failed to deserialize\"),\n        ),\n        \"critical JWT header fields are unsupported\",\n    );\n}\n\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\nstruct TestClaims {\n    aud: Option<Vec<Audience>>,\n    iss: Option<IssuerUrl>,\n    payload: String,\n}\nimpl AudiencesClaim for TestClaims {\n    fn audiences(&self) -> Option<&Vec<Audience>> {\n        self.aud.as_ref()\n    }\n}\nimpl IssuerClaim for TestClaims {\n    fn issuer(&self) -> Option<&IssuerUrl> {\n        self.iss.as_ref()\n    }\n}\ntype TestClaimsJsonWebToken = JsonWebToken<\n    CoreJweContentEncryptionAlgorithm,\n    CoreJwsSigningAlgorithm,\n    TestClaims,\n    JsonWebTokenJsonPayloadSerde,\n>;\n\n#[test]\nfn test_jwt_verified_claims() {\n    let rsa_key =\n        serde_json::from_str::<CoreJsonWebKey>(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    let client_id = ClientId::new(\"my_client\".to_string());\n    let issuer = IssuerUrl::new(\"https://example.com\".to_string()).unwrap();\n    let verifier = CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n    );\n\n    // Invalid JOSE header.\n    assert_unsupported(\n        verifier.verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJBMjU2R0NNIiwiY3R5IjoiSldUIn0.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Im\\\n                     h0dHBzOi8vZXhhbXBsZS5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.YmFkX2hhc2g\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\"),\n        ),\n        \"nested JWT\",\n    );\n\n    // JWE-encrypted JWT.\n    assert_unsupported(\n        verifier.verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJBMjU2R0NNIn0.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbX\\\n                     BsZS5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.YmFkX2hhc2g\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\"),\n        ),\n        \"JWE encryption\",\n    );\n\n    // Wrong issuer.\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vYXR0YWNrZXIuY\\\n                 29tIiwicGF5bG9hZCI6ImhlbGxvIHdvcmxkIn0.YmFkX2hhc2g\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::InvalidIssuer(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Missing issuer.\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sInBheWxvYWQiOiJoZWxsbyB3b3JsZCJ9.\\\n                 YmFkX2hhc2g\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::InvalidIssuer(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Ignore missing issuer.\n    verifier\n            .clone()\n            .require_issuer_match(false)\n            .verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sInBheWxvYWQiOiJoZWxsbyB3b3JsZCJ9.\\\n                 nv09al63NNDfb8cF3IozegXKbPaUC08zknRPKmQ5qKgXv80hjVxknkpRz7BxocB3JYTBjhYd0gyN9wAuJj\\\n                 byZ1QaUC14HOB83awAGbehy5yFLkLadTfPT7-siBCvE2V7AF73a_21YvwdkKmJ-RaKWHzFnG8CDmioma3X\\\n                 cWyrsdRLgvUkrWllajLRo8DCIXQ8OuZo1_o4n17PSlPxSkhKIrgaWCvG6tan40Y_1DZOFv47bx4hQUGd-J\\\n                 h2aEjiwn65WV3M_Xb2vQMP7VgYNVaNlfxzpL4yDASItbPMWaXBt3ZUa_IOGoSx2GMnPkrQ4xp56qUth6U7\\\n                 esWPqRSqqolnHg\"\n                    .to_string(),\n            )).expect(\"failed to deserialize\"),\n        ).expect(\"verification should succeed\");\n\n    // Wrong audience.\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsib3RoZXJfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                 S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.YmFkX2hhc2g\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::InvalidAudience(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Missing audience.\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwicGF5bG9hZCI6ImhlbGxvI\\\n                 HdvcmxkIn0.YmFkX2hhc2g\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::InvalidAudience(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Ignore missing audience.\n    verifier\n        .clone()\n        .require_audience_match(false)\n        .verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwicGF5bG9hZCI6Imhlb\\\n                     GxvIHdvcmxkIn0.lP-Z_zGPNoKIbLQsnrZc2LAc5qJrKyb7t07ZtJUKVhcwHiCUou4bBhq5RHlElCh\\\n                     0ElRRP6I25lp6UszkRvIC46UV3GVze0x73kVkHSvCVI7MO75LbL9BRqrm5b4CN2zCiFBY8-EwTXnJd\\\n                     Ri0d_U8K29TV24L2I-Z5ZILebwUue1N59AGDjx2yYLFx5NOw3TUsPyscG62aZAT321pL_jcYwTWTWw\\\n                     2FYm07zguwx-PUTZwGXlJiOgXQqRIbY_1bS3I_D8UWsmEB3DmV0f9z-iklgIPFawa4wHaE-hpzBAEx\\\n                     pSieyOavA5pl0Se3XRYA-CkdDVgzG0Pt4IdnxFanfUXTw\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\"),\n        )\n        .expect(\"verification should succeed\");\n\n    // Multiple audiences, where one is a match (default = reject)\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiYXVkMSIsIm15X2NsaWVudCIsImF1ZDIiXSwiaXNzIjoia\\\n                 HR0cHM6Ly9leGFtcGxlLmNvbSIsInBheWxvYWQiOiJoZWxsbyB3b3JsZCJ9.N9ibisEe0kKLe1GDWM\\\n                 ON3PmYqbL73dag-loM8pjKJNinF9SB7n4JuSu4FrNkeW4F1Cz8MIbLuWfKvDa_4v_3FstMA3GODZWH\\\n                 BVIiuNFay2ovCfGFyykwe47dF_47g_OM5AkJc_teE5MN8lPh9V5zYCy3ON3zZ3acFPJMOPTdbU56xD\\\n                 eFe7lil6DmV4JU9A52t5ZkJILFaIuxxXJUIDmqpPTvHkggh_QOj9C2US9bgg5b543JwT4j-HbDp51L\\\n                 dDB4k3azOssT1ddtoAuuDOctnraMKUtqffJXexxfwA1uM6EIofSrK5v11xwgTciL9xDXAvav_G2buP\\\n                 ol1bjGLa2t0Q\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::InvalidAudience(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Multiple audiences, where one is a match (allowed)\n    verifier\n        .clone()\n        .set_other_audience_verifier_fn(|aud| **aud == \"aud1\" || **aud == \"aud2\")\n        .verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiYXVkMSIsIm15X2NsaWVudCIsImF1ZDIiXSwiaXNzIjoia\\\n                 HR0cHM6Ly9leGFtcGxlLmNvbSIsInBheWxvYWQiOiJoZWxsbyB3b3JsZCJ9.N9ibisEe0kKLe1GDWM\\\n                 ON3PmYqbL73dag-loM8pjKJNinF9SB7n4JuSu4FrNkeW4F1Cz8MIbLuWfKvDa_4v_3FstMA3GODZWH\\\n                 BVIiuNFay2ovCfGFyykwe47dF_47g_OM5AkJc_teE5MN8lPh9V5zYCy3ON3zZ3acFPJMOPTdbU56xD\\\n                 eFe7lil6DmV4JU9A52t5ZkJILFaIuxxXJUIDmqpPTvHkggh_QOj9C2US9bgg5b543JwT4j-HbDp51L\\\n                 dDB4k3azOssT1ddtoAuuDOctnraMKUtqffJXexxfwA1uM6EIofSrK5v11xwgTciL9xDXAvav_G2buP\\\n                 ol1bjGLa2t0Q\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\"),\n        )\n        .expect(\"verification should succeed\");\n\n    // Multiple audiences, where none is a match\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiYXVkMSIsImF1ZDIiXSwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlL\\\n                 mNvbSIsInBheWxvYWQiOiJoZWxsbyB3b3JsZCJ9.YmFkX2hhc2g\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::InvalidAudience(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Disable signature check.\n    verifier\n        .clone()\n        .require_signature_check(false)\n        .verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                     S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.YmFkX2hhc2g\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\"),\n        )\n        .expect(\"verification should succeed\");\n\n    // \"none\" algorithm (unsigned JWT).\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJub25lIn0.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                 S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::NoSignature,\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    let valid_rs256_jwt =\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                 S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.UZ7vmAsDmOBzeB6e2_0POUfyhMRZKM6WSKz3\\\n                 jB2QdmO-eZ9605EzhkJufJQ8515ryWnHv-gUHtZHQi3zilrzhBwvE2cVP83Gv2XIL1EKaMMmfISeEB\\\n                 ShWez_FvqxN_bamh5yTROhWmoZTmof-MweBCHgINcsEd7K4e_BHHgq3aaRBpvSFlL_z4l_1NwNcTBo\\\n                 kqjNScKZITk42AbsSuGR39L94BWLhz6WXQZ_Sn6R1Ro6roOm1b7E82jJiQEtlseQiCCvPR2JJ6LgW6\\\n                 XTMzQ0vCqSh1A7U_IBDsjY_yag8_X3xxFh2URCtHJ47ZSjqfv6hq7OAq8tmVecOVgfIvABOg\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\");\n    // Default algs + RS256 -> allowed\n    verifier\n        .verified_claims(valid_rs256_jwt.clone())\n        .expect(\"verification should succeed\");\n\n    let verifier_with_client_secret = CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![]),\n    )\n    .set_client_secret(ClientSecret::new(\"my_secret\".to_string()));\n    let valid_hs256_jwt =\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                 S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.dTXvSWen74_rC4oiWw0ziLZNe4KZk8Jw2VZe\\\n                 N6vLCDo\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\");\n\n    // Default algs + HS256 -> disallowed\n    match verifier_with_client_secret.verified_claims(valid_hs256_jwt.clone()) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::DisallowedAlg(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // none algs + RS256 -> allowed\n    verifier\n        .clone()\n        .allow_any_alg()\n        .verified_claims(valid_rs256_jwt.clone())\n        .expect(\"verification should succeed\");\n\n    // none algs + HS256 -> allowed\n    verifier_with_client_secret\n        .clone()\n        .allow_any_alg()\n        .verified_claims(valid_hs256_jwt.clone())\n        .expect(\"verification should succeed\");\n\n    // none algs + none -> disallowed\n    match verifier.clone().allow_any_alg().verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJub25lIn0.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                 S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::NoSignature,\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // HS256 + no client secret -> disallowed\n    match verifier\n        .clone()\n        .allow_any_alg()\n        .verified_claims(valid_hs256_jwt.clone())\n    {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::DisallowedAlg(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // HS256 + valid signature\n    verifier_with_client_secret\n        .clone()\n        .set_allowed_algs(vec![CoreJwsSigningAlgorithm::HmacSha256])\n        .verified_claims(valid_hs256_jwt)\n        .expect(\"verification should succeed\");\n\n    // HS256 + invalid signature\n    match verifier_with_client_secret\n        .clone()\n        .set_allowed_algs(vec![CoreJwsSigningAlgorithm::HmacSha256])\n        .verified_claims(\n            serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n                \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                     S5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.dTXvSWen74_rC4oiWw0ziLZNe4KZk8Jw2VZe\\\n                     N6vLCEo\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\"),\n        ) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::CryptoError(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // No public keys\n    match CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![]),\n    )\n    .verified_claims(valid_rs256_jwt.clone())\n    {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::NoMatchingKey,\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    let kid = JsonWebKeyId::new(\"bilbo.baggins@hobbiton.example\".to_string());\n    let n = Base64UrlEncodedBytes::new(vec![\n        159, 129, 15, 180, 3, 130, 115, 208, 37, 145, 228, 7, 63, 49, 210, 182, 0, 27, 130, 206,\n        219, 77, 146, 240, 80, 22, 93, 71, 207, 202, 184, 163, 196, 28, 183, 120, 172, 117, 83,\n        121, 63, 142, 249, 117, 118, 141, 26, 35, 116, 216, 113, 37, 100, 195, 188, 215, 123, 158,\n        164, 52, 84, 72, 153, 64, 124, 255, 0, 153, 146, 10, 147, 26, 36, 196, 65, 72, 82, 171, 41,\n        189, 176, 169, 92, 6, 83, 243, 108, 96, 230, 11, 249, 11, 98, 88, 221, 165, 111, 55, 4,\n        123, 165, 194, 209, 208, 41, 175, 156, 157, 64, 186, 199, 170, 65, 199, 138, 13, 209, 6,\n        138, 221, 105, 158, 128, 143, 234, 1, 30, 161, 68, 29, 138, 79, 123, 180, 233, 123, 227,\n        159, 85, 241, 221, 212, 78, 156, 75, 163, 53, 21, 151, 3, 212, 211, 75, 96, 62, 101, 20,\n        122, 79, 35, 214, 211, 192, 153, 108, 117, 237, 238, 132, 106, 130, 209, 144, 174, 16, 120,\n        60, 150, 28, 240, 56, 122, 237, 33, 6, 210, 208, 85, 91, 111, 217, 55, 250, 213, 83, 83,\n        135, 224, 255, 114, 255, 190, 120, 148, 20, 2, 176, 184, 34, 234, 42, 116, 182, 5, 140, 29,\n        171, 249, 179, 74, 118, 203, 99, 184, 127, 170, 44, 104, 71, 184, 226, 131, 127, 255, 145,\n        24, 110, 107, 28, 20, 145, 28, 249, 137, 168, 144, 146, 168, 28, 230, 1, 221, 172, 211,\n        249, 207,\n    ]);\n    let e = Base64UrlEncodedBytes::new(vec![1, 0, 1]);\n\n    // Wrong key type (symmetric key)\n    match CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![CoreJsonWebKey {\n            kty: CoreJsonWebKeyType::Symmetric,\n            use_: Some(CoreJsonWebKeyUse::Signature),\n            kid: Some(kid.clone()),\n            n: None,\n            e: None,\n            k: Some(Base64UrlEncodedBytes::new(vec![1, 2, 3, 4])),\n            crv: None,\n            x: None,\n            y: None,\n            d: None,\n            alg: None,\n        }]),\n    )\n    .verified_claims(valid_rs256_jwt.clone())\n    {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::NoMatchingKey,\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Correct public key, but with signing disallowed\n    match CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![CoreJsonWebKey {\n            kty: CoreJsonWebKeyType::RSA,\n            use_: Some(CoreJsonWebKeyUse::Encryption),\n            kid: Some(kid),\n            n: Some(n),\n            e: Some(e),\n            k: None,\n            crv: None,\n            x: None,\n            y: None,\n            d: None,\n            alg: None,\n        }]),\n    )\n    .verified_claims(valid_rs256_jwt.clone())\n    {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::NoMatchingKey,\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Wrong key ID\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiIsImtpZCI6Indyb25nX2tleSJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6I\\\n                 mh0dHBzOi8vZXhhbXBsZS5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.lVLomyIyO8WmyS1VZWPu\\\n                 cGhRTUyK9RCw90fJC5CfDWUCgt1CBn-aP_ieWWBGfjb4ccR4dl57OYxdLl0Day8QN5pTCBud9QKpQ0rKQX\\\n                 K8eBlOW8uSosx8q5pwU_bRyy-XuKJiPlDCOwTEHOp_hOgZFGjoN27MH3Xm8kc0iT3PgyqQ46-wsqHY9S02\\\n                 hdJORX7vqYwQLZF8_k_L8K0IG_dC-1Co0g5oAf37oVSdl8hE-ScQ9K-AiSpS-cGYyldbMhyKNDL3ry2cuI\\\n                 EUgYSIznkVFuM7RrEdNK222z5PF11ijYx-TM7BIDggbcIyJm-UqpmvVaJImmj5FNkMzuHYznLtdg\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::NoMatchingKey,\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Client secret + public key\n    verifier\n        .clone()\n        .set_client_secret(ClientSecret::new(\"my_secret\".to_string()))\n        .verified_claims(valid_rs256_jwt.clone())\n        .expect(\"verification should succeed\");\n\n    // Multiple matching public keys: no KID specified\n    match CoreJwtClaimsVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![rsa_key.clone(), rsa_key.clone()]),\n    )\n    .verified_claims(valid_rs256_jwt.clone())\n    {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::AmbiguousKeyId(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // Multiple matching public keys: KID specified\n    match CoreJwtClaimsVerifier::new(\n        client_id,\n        issuer,\n        CoreJsonWebKeySet::new(vec![rsa_key.clone(), rsa_key]),\n    )\n    .verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.eyJhdWQiO\\\n                 lsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJwYXlsb2FkIjoiaGVsbG8gd29\\\n                 ybGQifQ.jH0v2fQGvH2MD0jn5pQP6W6AF5rJlizyofdyRUIt7E3GraGA1LYDiLAVIfhST3uwJopP-TgtBk\\\n                 zc-zyJSvgTR63S8iI1YlHypItpx7r4I9ydzo8GSN5RrZudcU2esY4uEnLbVl17ZVNu4IyTExeKJ0sPM0Hj\\\n                 qkOA4XaP2cJwsK-bookNHSA8NRE6adRMrHAKJbor5jrGjpkZAKHbnQFK-wu-nEV_OjS9jpN_FboRZVcDTZ\\\n                 GFzeFbqFqHdRn6UWPFnVpVnUhih16UjNH1om6gwc0uFoPWTDxJlXQCFbHMhZtgCbUkXQBH7twPMc4YUziw\\\n                 S8GIRKCcXjdrP5oyxmcitQ\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::AmbiguousKeyId(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // RS256 + valid signature\n    verifier\n        .verified_claims(valid_rs256_jwt)\n        .expect(\"verification should succeed\");\n\n    // RS256 + invalid signature\n    match verifier.verified_claims(\n        serde_json::from_value::<TestClaimsJsonWebToken>(serde_json::Value::String(\n            \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb\\\n                 20iLCJwYXlsb2FkIjoiaGVsbG8gd29ybGQifQ.YmFkX2hhc2g\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\"),\n    ) {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::CryptoError(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n}\n\ntype CoreIdTokenJwt = JsonWebToken<\n    CoreJweContentEncryptionAlgorithm,\n    CoreJwsSigningAlgorithm,\n    CoreIdTokenClaims,\n    JsonWebTokenJsonPayloadSerde,\n>;\n\n#[test]\nfn test_id_token_verified_claims() {\n    let rsa_key =\n        serde_json::from_str::<CoreJsonWebKey>(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    let client_id = ClientId::new(\"my_client\".to_string());\n    let issuer = IssuerUrl::new(\"https://example.com\".to_string()).unwrap();\n    let mock_current_time = AtomicUsize::new(1544932149);\n    let mock_is_valid_issue_time = AtomicBool::new(true);\n    // Extra scope needed to ensure closures are destroyed before the values they borrow.\n    {\n        let public_client_verifier = CoreIdTokenVerifier::new_public_client(\n            client_id.clone(),\n            issuer.clone(),\n            CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n        )\n        .set_time_fn(|| {\n            Timestamp::Seconds(mock_current_time.load(Ordering::Relaxed).into())\n                .to_utc()\n                .unwrap()\n        })\n        .set_issue_time_verifier_fn(|_| {\n            if mock_is_valid_issue_time.load(Ordering::Relaxed) {\n                Ok(())\n            } else {\n                Err(\"Invalid iat claim\".to_string())\n            }\n        });\n\n        let insecure_verifier = CoreIdTokenVerifier::new_insecure_without_verification()\n            .set_time_fn(|| {\n                Timestamp::Seconds(mock_current_time.load(Ordering::Relaxed).into())\n                    .to_utc()\n                    .unwrap()\n            });\n\n        // This JWTs below have an issue time of 1544928549 and an expiration time of 1544932149.\n\n        let test_jwt_without_nonce =\n            serde_json::from_value::<CoreIdTokenJwt>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                     S5jb20iLCJzdWIiOiJzdWJqZWN0IiwiZXhwIjoxNTQ0OTMyMTQ5LCJpYXQiOjE1NDQ5Mjg1NDl9.nN\\\n                     aTxNwclnTHd1Q9POkddm5wB1w3wJ-gwQWHomhimttk3SWQTLhxI0SSjWrHahGxlfkjufJlSyt-t_VO\\\n                     SdcROvIYZTDznDfFZz3oSOev-p9XiZ-EZTS-U6N11Y923sDQjbTMeukz1F3ZFEfn5Mv2xjdEoJccCe\\\n                     7SaGuDmVqMqTLXMtsw9NCE_KDd0oKSwDzbJIBBPEfG3JjbKg0Dln7ENHg9wzoNFQzPXrkKzjneBgD3\\\n                     vuwFCV5y-e8xUBdLaLZF1kdkDZJIA48uRROLlWjsM8pEptosA5QK07luQCZNqcaZWEczoGXeQs8PyA\\\n                     zkNV7JEmti3bJnWSN-ud4cFU0LiQ\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\");\n\n        // Invalid JWT claims\n        match public_client_verifier.verified_claims(\n                &serde_json::from_value::<CoreIdTokenJwt>(serde_json::Value::String(\n                    \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vYXR0YWNrZ\\\n                     XIuY29tIiwic3ViIjoic3ViamVjdCIsImV4cCI6MTU0NDkzMjE0OSwiaWF0IjoxNTQ0OTI4NTQ5LCJ\\\n                     ub25jZSI6InRoZV9ub25jZSIsImFjciI6InRoZV9hY3IifQ.Pkicxk0dTU5BkSxgqTON6lE7A7ir3l\\\n                     aADRyoeRoCNDX3AOx7BXCbfzbda6HJiPskN2nu56w0q-0OdkDSIHls-2xTUlLEJv2Bv0BLYwV5ZVJ8\\\n                     hoc-rTd0_oLUb5NzyD80RyVByjVMK8bh6cwysTnr8QDxsEiFZbFo3mVJob2yjPZnNOdcNJWPcVVueP\\\n                     8vqMJnx5kHih1gKZpWj_dMN9b2AW6zVLOInW3Ox__gx6fsFFz7rjxItG-PTY_OQMzthqeHUyq4o9y7\\\n                     Jv8mB_jFkTZGVKHTPpObHV-qptJ_rnlwvF_mP5GARBLng-4Yd7nmSr31onYL48QDjGOrwPqQ-IyaCQ\"\n                        .to_string(),\n                ))\n                    .expect(\"failed to deserialize\"), |_: Option<&Nonce>| Ok(())) {\n                Err(ClaimsVerificationError::InvalidIssuer(_)) => {}\n                other => panic!(\"unexpected result: {:?}\", other),\n            }\n\n        // TODO: disallowed algs\n\n        // Expired token\n        mock_current_time.store(1544928549 + 3600, Ordering::Relaxed);\n        match public_client_verifier\n            .verified_claims(&test_jwt_without_nonce, |_: Option<&Nonce>| Ok(()))\n        {\n            Err(ClaimsVerificationError::Expired(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n        mock_current_time.store(1544928549 + 1, Ordering::Relaxed);\n\n        // Invalid issue time\n        mock_is_valid_issue_time.store(false, Ordering::Relaxed);\n        match public_client_verifier\n            .verified_claims(&test_jwt_without_nonce, |_: Option<&Nonce>| Ok(()))\n        {\n            Err(ClaimsVerificationError::Expired(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n        mock_is_valid_issue_time.store(true, Ordering::Relaxed);\n\n        let valid_nonce = Nonce::new(\"the_nonce\".to_string());\n\n        // Successful verification w/o checking nonce\n        public_client_verifier\n            .verified_claims(&test_jwt_without_nonce, |_: Option<&Nonce>| Ok(()))\n            .expect(\"verification should succeed\");\n\n        // Missing nonce\n        match public_client_verifier.verified_claims(&test_jwt_without_nonce, &valid_nonce) {\n            Err(ClaimsVerificationError::InvalidNonce(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        // Missing nonce w/ closure\n        match public_client_verifier.verified_claims(\n            &test_jwt_without_nonce,\n            |nonce: Option<&Nonce>| {\n                if nonce.iter().any(|n| n.secret() == valid_nonce.secret()) {\n                    Ok(())\n                } else {\n                    Err(\"invalid nonce\".to_string())\n                }\n            },\n        ) {\n            Err(ClaimsVerificationError::InvalidNonce(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        let test_jwt_with_nonce =\n            serde_json::from_value::<CoreIdTokenJwt>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                     S5jb20iLCJzdWIiOiJzdWJqZWN0IiwiZXhwIjoxNTQ0OTMyMTQ5LCJpYXQiOjE1NDQ5Mjg1NDksIm5\\\n                     vbmNlIjoidGhlX25vbmNlIiwiYWNyIjoidGhlX2FjciIsImF1dGhfdGltZSI6MTU0NDkyODU0OH0.W\\\n                     XA7SS9aMh_6rvBEgQce5D2J84OqphmmnCLGgEKRTN5G-UuQTNOBp8VS5_4f3xgzMEEMvGJJauJoALk\\\n                     muUeHB-N_ESrkmB3tgDzBSYBa7kuYPHUPYpdjZM2UVolqI9RYyHaWwKjL_Io5YyAazB5lH5ibPaiBl\\\n                     UNKGs3cmVsEB22UGMFKM6cek7GinrHQe_aJQsMU839-c2zzlEyFSeI8QBphQtG6AN82IPkNRv8QWmw\\\n                     ZjUiB5a-W73Z3gURYMNs7f32BjAUNoJzW0Qj34vzD2djoSHhltE0wHKBzPqGhUM1Y3A-a3q-LS2g1h\\\n                     6qgXb_KQ_Mmok8v8ld0cW_aYRLfNg\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\");\n\n        // Invalid nonce\n        match public_client_verifier.verified_claims(\n            &test_jwt_with_nonce,\n            &Nonce::new(\"different_nonce\".to_string()),\n        ) {\n            Err(ClaimsVerificationError::InvalidNonce(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        let verified_claims = public_client_verifier\n            .clone()\n            .set_auth_context_verifier_fn(|acr| {\n                assert_eq!(**acr.unwrap(), \"the_acr\");\n                Err(\"Invalid acr claim\".to_string())\n            })\n            .verified_claims(&test_jwt_with_nonce, &valid_nonce);\n\n        // Invalid AuthenticationContextClass reference\n        match verified_claims {\n            Err(ClaimsVerificationError::InvalidAuthContext(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        let test_jwt_without_auth_time =\n            serde_json::from_value::<CoreIdTokenJwt>(serde_json::Value::String(\n                \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                     S5jb20iLCJzdWIiOiJzdWJqZWN0IiwiZXhwIjoxNTQ0OTMyMTQ5LCJpYXQiOjE1NDQ5Mjg1NDksIm5\\\n                     vbmNlIjoidGhlX25vbmNlIiwiYWNyIjoidGhlX2FjciJ9.c_lU1VRasTg0mB4lwdOzbzvFS_XShMLN\\\n                     lAPUpHBaMtCSPtI71L2x3hIByfkqIrAED-Qc_am2gNJ20bifidlkTOO6nyaBrJuaSjwT8aqajEbXon\\\n                     5JFswwPvqCIWjd0eV5dXC1MZunpd7ANXSC7Qw16v3m_crc9wcI_fLFCzuAKrWYokGvNy0gr1CxcgVg\\\n                     aE9qR0eqaatetzCuaOJhYOq4njrRlGZWtbj5Q56q3zhxJ_yS8K8gv1QcB4sHjUyXIj21jzjUD87zVG\\\n                     dJsn8E-nFJSltBdQhEaLksTBH6ZZhkeGicQ8cEPnNeS4L1vfVyAd_cjl64JHLmzw8RUp8XuoF9nA\"\n                    .to_string(),\n            ))\n            .expect(\"failed to deserialize\");\n\n        // Missing auth_time (ok)\n        public_client_verifier\n            .verified_claims(&test_jwt_without_auth_time, |_: Option<&Nonce>| Ok(()))\n            .expect(\"verification should succeed\");\n\n        let verified_claims = public_client_verifier\n            .clone()\n            .set_auth_time_verifier_fn(|auth_time| {\n                assert!(auth_time.is_none());\n                Err(\"Invalid auth_time claim\".to_string())\n            })\n            .verified_claims(&test_jwt_without_auth_time, |_: Option<&Nonce>| Ok(()));\n\n        // Missing auth_time (error)\n        match verified_claims {\n            Err(ClaimsVerificationError::InvalidAuthTime(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        let verified_claims = public_client_verifier\n            .clone()\n            .set_auth_time_verifier_fn(|auth_time| {\n                assert_eq!(\n                    auth_time.unwrap(),\n                    Timestamp::Seconds(1544928548.into()).to_utc().unwrap(),\n                );\n                Err(\"Invalid auth_time claim\".to_string())\n            })\n            .verified_claims(&test_jwt_with_nonce, &valid_nonce);\n\n        // Invalid auth_time\n        match verified_claims {\n            Err(ClaimsVerificationError::InvalidAuthTime(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        // Successful verification with nonce, acr, and auth_time specified (no expected Nonce)\n        public_client_verifier\n            .verified_claims(&test_jwt_with_nonce, |_: Option<&Nonce>| Ok(()))\n            .expect(\"verification should succeed\");\n        insecure_verifier\n            .verified_claims(&test_jwt_with_nonce, |_: Option<&Nonce>| Ok(()))\n            .expect(\"verification should succeed\");\n\n        // Successful verification with nonce, acr, and auth_time specified (w/ expected Nonce)\n        public_client_verifier\n            .verified_claims(&test_jwt_with_nonce, &valid_nonce)\n            .expect(\"verification should succeed\");\n        insecure_verifier\n            .verified_claims(&test_jwt_with_nonce, &valid_nonce)\n            .expect(\"verification should succeed\");\n\n        // Successful verification with nonce, acr, and auth_time specified (w/ closure)\n        public_client_verifier\n            .verified_claims(&test_jwt_with_nonce, |nonce: Option<&Nonce>| {\n                if nonce.iter().any(|n| n.secret() == valid_nonce.secret()) {\n                    Ok(())\n                } else {\n                    Err(\"invalid nonce\".to_string())\n                }\n            })\n            .expect(\"verification should succeed\");\n        insecure_verifier\n            .verified_claims(&test_jwt_with_nonce, |nonce: Option<&Nonce>| {\n                if nonce.iter().any(|n| n.secret() == valid_nonce.secret()) {\n                    Ok(())\n                } else {\n                    Err(\"invalid nonce\".to_string())\n                }\n            })\n            .expect(\"verification should succeed\");\n\n        // HS256 w/ default algs\n        let test_jwt_hs256 = serde_json::from_value::<CoreIdTokenJwt>(serde_json::Value::String(\n            \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhbXBsZ\\\n                     S5jb20iLCJzdWIiOiJzdWJqZWN0IiwiZXhwIjoxNTQ0OTMyMTQ5LCJpYXQiOjE1NDQ5Mjg1NDksIm5\\\n                     vbmNlIjoidGhlX25vbmNlIn0.xUnSwSbcHsHWyJxwKGg69BIo_CktcyN5BVulGDb_QzE\"\n                .to_string(),\n        ))\n        .expect(\"failed to deserialize\");\n        let private_client_verifier = CoreIdTokenVerifier::new_confidential_client(\n            client_id.clone(),\n            ClientSecret::new(\"my_secret\".to_string()),\n            issuer.clone(),\n            CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n        )\n        .set_time_fn(|| {\n            Timestamp::Seconds(mock_current_time.load(Ordering::Relaxed).into())\n                .to_utc()\n                .unwrap()\n        });\n        match private_client_verifier.verified_claims(&test_jwt_hs256, &valid_nonce) {\n            Err(ClaimsVerificationError::SignatureVerification(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n        insecure_verifier\n            .clone()\n            .verified_claims(&test_jwt_hs256, &valid_nonce)\n            .expect(\"verification should succeed\");\n\n        // HS256 w/ set_allowed_algs\n        private_client_verifier\n            .clone()\n            .set_allowed_algs(vec![CoreJwsSigningAlgorithm::HmacSha256])\n            .verified_claims(&test_jwt_hs256, &valid_nonce)\n            .expect(\"verification should succeed\");\n\n        // HS256 w/ allow_any_alg\n        private_client_verifier\n            .clone()\n            .allow_any_alg()\n            .verified_claims(&test_jwt_hs256, &valid_nonce)\n            .expect(\"verification should succeed\");\n\n        // Invalid signature\n        let private_client_verifier_with_other_secret =\n            CoreIdTokenVerifier::new_confidential_client(\n                client_id,\n                ClientSecret::new(\"other_secret\".to_string()),\n                issuer,\n                CoreJsonWebKeySet::new(vec![rsa_key]),\n            )\n            .allow_any_alg()\n            .set_time_fn(|| {\n                Timestamp::Seconds(mock_current_time.load(Ordering::Relaxed).into())\n                    .to_utc()\n                    .unwrap()\n            });\n        match private_client_verifier_with_other_secret\n            .verified_claims(&test_jwt_hs256, &valid_nonce)\n        {\n            Err(ClaimsVerificationError::SignatureVerification(_)) => {}\n            other => panic!(\"unexpected result: {:?}\", other),\n        }\n\n        // Invalid signature w/ signature check disabled\n        private_client_verifier_with_other_secret\n            .clone()\n            .insecure_disable_signature_check()\n            .verified_claims(&test_jwt_hs256, &valid_nonce)\n            .expect(\"verification should succeed\");\n    };\n}\n\n#[test]\nfn test_new_id_token() {\n    let client_id = ClientId::new(\"my_client\".to_string());\n    let issuer = IssuerUrl::new(\"https://example.com\".to_string()).unwrap();\n    let nonce = Nonce::new(\"the_nonce\".to_string());\n    let rsa_priv_key = CoreRsaPrivateSigningKey::from_pem(TEST_RSA_PRIV_KEY, None).unwrap();\n\n    let id_token = CoreIdToken::new(\n        CoreIdTokenClaims::new(\n            issuer.clone(),\n            vec![Audience::new((*client_id).clone())],\n            Utc.timestamp_opt(1544932149, 0)\n                .single()\n                .expect(\"valid timestamp\"),\n            Utc.timestamp_opt(1544928549, 0)\n                .single()\n                .expect(\"valid timestamp\"),\n            StandardClaims::new(SubjectIdentifier::new(\"subject\".to_string())),\n            Default::default(),\n        )\n        .set_nonce(Some(nonce.clone()))\n        .set_auth_context_ref(Some(AuthenticationContextClass::new(\"the_acr\".to_string())))\n        .set_auth_time(Some(\n            Utc.timestamp_opt(1544928548, 0)\n                .single()\n                .expect(\"valid timestamp\"),\n        )),\n        &rsa_priv_key,\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n        Some(&AccessToken::new(\"the_access_token\".to_string())),\n        Some(&AuthorizationCode::new(\n            \"the_authorization_code\".to_string(),\n        )),\n    )\n    .unwrap();\n\n    let serialized_jwt: serde_json::Value = serde_json::to_value(&id_token).unwrap();\n    let expected_serialized_jwt =\n        \"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiYXVkIjpbIm15X2NsaWVudCJdL\\\n             CJleHAiOjE1NDQ5MzIxNDksImlhdCI6MTU0NDkyODU0OSwiYXV0aF90aW1lIjoxNTQ0OTI4NTQ4LCJub25jZSI\\\n             6InRoZV9ub25jZSIsImFjciI6InRoZV9hY3IiLCJhdF9oYXNoIjoiWjNJQUNVR00tbXhIV3lZUXZpSzhFUSIsI\\\n             mNfaGFzaCI6Imo2OW1CZmFIbmRMM1Y1RmNoak9LVXciLCJzdWIiOiJzdWJqZWN0In0.CHCWFcIqbCZhZwZH4oY\\\n             _mlcRy5aUQQtlNI0VHNYxiILn9ppRHLL4Bn_LMn9VP8tGXkfZWxCgP25ZTyBXXKfk0fQvnukVdyM0bCOpQbiBg\\\n             5gB9c46l_f-ZznDoHWonpnKky2Gmzk3ocb3TCUQ9GSeRXAzRdRNWTT0ElWNBsLWU4j2IIdnghM78gkXwOC76Rk\\\n             pshgB73ubtuHGdIf5L9Ec3hifHlVjzKuvedAM4SIOjdBOelgtBlF3463ufX_Ut91CjP5TzLMsuK3Lh_vyo8ttn\\\n             S41rBDuetR2ENvR0yj5RjkX_SPY3V0yCW8_NPPu1CHu_1oL0Nma0ohCbF3vnUJcwg\";\n    assert_eq!(expected_serialized_jwt, serialized_jwt.as_str().unwrap());\n\n    let rsa_pub_key =\n        serde_json::from_str::<CoreJsonWebKey>(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    let mock_current_time = AtomicUsize::new(1544932148);\n    let time_fn = || {\n        Timestamp::Seconds(mock_current_time.load(Ordering::Relaxed).into())\n            .to_utc()\n            .unwrap()\n    };\n    let verifier = CoreIdTokenVerifier::new_public_client(\n        client_id,\n        issuer,\n        CoreJsonWebKeySet::new(vec![rsa_pub_key]),\n    )\n    .set_time_fn(time_fn);\n    let claims = id_token.claims(&verifier, &nonce).unwrap();\n    let unverified = id_token\n        .claims(\n            &CoreIdTokenVerifier::new_insecure_without_verification().set_time_fn(time_fn),\n            &nonce,\n        )\n        .unwrap();\n    assert_eq!(claims, unverified);\n}\n\n#[test]\nfn test_user_info_verified_claims() {\n    let rsa_key =\n        serde_json::from_str::<CoreJsonWebKey>(TEST_RSA_PUB_KEY).expect(\"deserialization failed\");\n\n    let client_id = ClientId::new(\"my_client\".to_string());\n    let issuer = IssuerUrl::new(\"https://example.com\".to_string()).unwrap();\n    let sub = SubjectIdentifier::new(\"the_subject\".to_string());\n\n    let verifier = CoreUserInfoVerifier::new(\n        client_id.clone(),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n        Some(sub.clone()),\n    );\n\n    let json_claims = \"{\\\n                           \\\"sub\\\": \\\"the_subject\\\",\\\n                           \\\"name\\\": \\\"Jane Doe\\\"\\\n                           }\";\n\n    // JSON response (default args)\n    assert_eq!(\n        CoreUserInfoClaims::from_json::<crate::reqwest::Error>(json_claims.as_bytes(), Some(&sub))\n            .expect(\"verification should succeed\")\n            .name()\n            .unwrap()\n            .iter()\n            .collect::<Vec<_>>(),\n        vec![(None, &EndUserName::new(\"Jane Doe\".to_string()))],\n    );\n\n    // Invalid subject\n    match CoreUserInfoClaims::from_json::<crate::reqwest::Error>(\n        json_claims.as_bytes(),\n        Some(&SubjectIdentifier::new(\"wrong_subject\".to_string())),\n    ) {\n        Err(UserInfoError::ClaimsVerification(ClaimsVerificationError::InvalidSubject(_))) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    let jwt_claims = serde_json::from_value::<CoreUserInfoJsonWebToken>(serde_json::Value::String(\n        \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhb\\\n                 XBsZS5jb20iLCJzdWIiOiJ0aGVfc3ViamVjdCIsIm5hbWUiOiJKYW5lIERvZSJ9.aX7VpexLAd\\\n                 43HtC1cFTot3jmqsr105rB50mzTcS1TXzWcxLbqYf1K7Kf-S1oP-ZCL_dnL9-nu3iDK_vRa6xT\\\n                 nGGt3I1JwhoIv6znSS3JOPT1wtekyD-sLcUwqsJHWBBiTSBwlmGG_kVRuGkBtXgVZ9aGlqg9u1\\\n                 FlxvyGUJ5q1o9gdb8mKql5ojgsThTNo9qdW3lPIVsiDO-n4mMp4HuOp1re4ZDDkHxiExjtLQAV\\\n                 kR4q3SlhJC2mkr4mw3_0a2AW52ocWDiwY_lPcdmohmwFaB8aHlivYLFnmKGQIatEW-KDaW5fFo\\\n                 JYreNkplo4FvzXYyxgxAsqHjHMI8MZVEa1IA\"\n            .to_string(),\n    ))\n    .expect(\"failed to deserialize\");\n\n    // Valid JWT response (default args)\n    jwt_claims\n        .clone()\n        .claims(&verifier)\n        .expect(\"verification should succeed\");\n\n    // JWT response with invalid signature\n    match serde_json::from_value::<CoreUserInfoJsonWebToken>(serde_json::Value::String(\n        \"eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsibXlfY2xpZW50Il0sImlzcyI6Imh0dHBzOi8vZXhhb\\\n             XBsZS5jb20iLCJzdWIiOiJ0aGVfc3ViamVjdCIsIm5hbWUiOiJKYW5lIERvZSJ9.bX7VpexLAd\\\n             43HtC1cFTot3jmqsr105rB50mzTcS1TXzWcxLbqYf1K7Kf-S1oP-ZCL_dnL9-nu3iDK_vRa6xT\\\n             nGGt3I1JwhoIv6znSS3JOPT1wtekyD-sLcUwqsJHWBBiTSBwlmGG_kVRuGkBtXgVZ9aGlqg9u1\\\n             FlxvyGUJ5q1o9gdb8mKql5ojgsThTNo9qdW3lPIVsiDO-n4mMp4HuOp1re4ZDDkHxiExjtLQAV\\\n             kR4q3SlhJC2mkr4mw3_0a2AW52ocWDiwY_lPcdmohmwFaB8aHlivYLFnmKGQIatEW-KDaW5fFo\\\n             JYreNkplo4FvzXYyxgxAsqHjHMI8MZVEa1IA\"\n            .to_string(),\n    ))\n    .expect(\"failed to deserialize\")\n    .claims(&verifier)\n    {\n        Err(ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::CryptoError(_),\n        )) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // JWT response with invalid issuer claim (error)\n    match jwt_claims.clone().claims(&CoreUserInfoVerifier::new(\n        client_id.clone(),\n        IssuerUrl::new(\"https://attacker.com\".to_string()).unwrap(),\n        CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n        Some(sub.clone()),\n    )) {\n        Err(ClaimsVerificationError::InvalidIssuer(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // JWT response with invalid issuer claim (allowed)\n    jwt_claims\n        .clone()\n        .claims(\n            &CoreUserInfoVerifier::new(\n                client_id,\n                IssuerUrl::new(\"https://attacker.com\".to_string()).unwrap(),\n                CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n                Some(sub.clone()),\n            )\n            .require_issuer_match(false),\n        )\n        .expect(\"verification should succeed\");\n\n    // JWT response with invalid audience claim (error)\n    match jwt_claims.clone().claims(&CoreUserInfoVerifier::new(\n        ClientId::new(\"wrong_client\".to_string()),\n        issuer.clone(),\n        CoreJsonWebKeySet::new(vec![rsa_key.clone()]),\n        Some(sub.clone()),\n    )) {\n        Err(ClaimsVerificationError::InvalidAudience(_)) => {}\n        other => panic!(\"unexpected result: {:?}\", other),\n    }\n\n    // JWT response with invalid audience claim (allowed)\n    jwt_claims\n        .claims(\n            &CoreUserInfoVerifier::new(\n                ClientId::new(\"wrong_client\".to_string()),\n                issuer,\n                CoreJsonWebKeySet::new(vec![rsa_key]),\n                Some(sub),\n            )\n            .require_audience_match(false),\n        )\n        .expect(\"verification should succeed\");\n}\n\n#[test]\nfn test_new_user_info_claims() {\n    let claims = CoreUserInfoClaims::new(\n        StandardClaims {\n            sub: SubjectIdentifier::new(\"the_subject\".to_string()),\n            name: Some(EndUserName::new(\"John Doe\".to_string()).into()),\n            given_name: None,\n            family_name: None,\n            middle_name: None,\n            nickname: None,\n            preferred_username: None,\n            profile: None,\n            picture: None,\n            website: None,\n            email: None,\n            email_verified: None,\n            gender: None,\n            birthday: None,\n            birthdate: None,\n            zoneinfo: None,\n            locale: None,\n            phone_number: None,\n            phone_number_verified: None,\n            address: None,\n            updated_at: Some(\n                Utc.timestamp_opt(1544928548, 0)\n                    .single()\n                    .expect(\"valid timestamp\"),\n            ),\n        },\n        Default::default(),\n    );\n\n    assert_eq!(\n        \"{\\\"sub\\\":\\\"the_subject\\\",\\\"name\\\":\\\"John Doe\\\",\\\"updated_at\\\":1544928548}\",\n        serde_json::to_string(&claims).unwrap()\n    );\n\n    let rsa_priv_key = CoreRsaPrivateSigningKey::from_pem(TEST_RSA_PRIV_KEY, None).unwrap();\n    let claims_jwt = CoreUserInfoJsonWebToken::new(\n        claims,\n        &rsa_priv_key,\n        CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,\n    )\n    .unwrap();\n    assert_eq!(\n        \"eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0aGVfc3ViamVjdCIsIm5hbWUiOiJKb2huIERvZSIsInVwZGF0ZWRfY\\\n             XQiOjE1NDQ5Mjg1NDh9.nJ7Buckt_p_ACXkyVRCQLqyaW8KhDsk5H9Nu7PdNf4daEcEWm-lGjoSTAfAbDPgHAZ\\\n             78knomgLgDxiGWrj1qdFTIEFep32I3q18VBP_DcMdyuQafipK6T98RgZFWP8YnxlxLPHeJQlRsdMpemHK4vxas\\\n             ZD4A4aIn0K7z5J9RvrR3L7DWnc3fJQ0VU2v5QLePyqNWnFxks5eyl8Ios8JrZhwr4Q8GES8Q4Iw8Sz6W9vYpHK\\\n             2r1YdaACMM4g_TTtV91lpjn-Li2-HxW9NERdLvYvF6HwGIwbss26trp2yjNTARlxBUT6LR7y82oPIJKXIKL1GD\\\n             YeSLeErhb6oTQ0a5gQ\",\n        serde_json::to_value(claims_jwt).unwrap().as_str().unwrap()\n    );\n}\n"
  },
  {
    "path": "tests/rp_certification_code.rs",
    "content": "#![allow(clippy::expect_fun_call)]\n\nuse crate::rp_common::{\n    get_provider_metadata, http_client, init_log, issuer_url, register_client, PanicIfFail,\n};\n\nuse http::header::LOCATION;\nuse http::method::Method;\nuse log::{debug, error, info};\nuse openidconnect::core::{\n    CoreClient, CoreClientAuthMethod, CoreClientRegistrationRequest,\n    CoreClientRegistrationResponse, CoreIdToken, CoreIdTokenClaims, CoreIdTokenVerifier,\n    CoreJsonWebKeySet, CoreJwsSigningAlgorithm, CoreProviderMetadata, CoreResponseType,\n    CoreUserInfoClaims,\n};\nuse openidconnect::Nonce;\nuse openidconnect::{\n    AccessToken, AuthType, AuthenticationFlow, AuthorizationCode, ClaimsVerificationError,\n    CsrfToken, EndpointMaybeSet, EndpointNotSet, EndpointSet, HttpClientError, OAuth2TokenResponse,\n    RequestTokenError, Scope, SignatureVerificationError, UserInfoError,\n};\nuse reqwest::{blocking::Client, redirect::Policy};\nuse url::Url;\n\nuse std::collections::HashMap;\n\nmod rp_common;\n\nstruct TestState {\n    access_token: Option<AccessToken>,\n    authorization_code: Option<AuthorizationCode>,\n    client: CoreClient<\n        EndpointSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointNotSet,\n        EndpointMaybeSet,\n        EndpointMaybeSet,\n    >,\n    id_token: Option<CoreIdToken>,\n    nonce: Option<Nonce>,\n    provider_metadata: CoreProviderMetadata,\n    registration_response: CoreClientRegistrationResponse,\n}\nimpl TestState {\n    pub fn init<F>(test_id: &'static str, reg_request_fn: F) -> Self\n    where\n        F: FnOnce(CoreClientRegistrationRequest) -> CoreClientRegistrationRequest,\n    {\n        init_log(test_id);\n\n        let _issuer_url = issuer_url(test_id);\n        let provider_metadata = get_provider_metadata(test_id);\n        let registration_response = register_client(&provider_metadata, reg_request_fn);\n\n        let redirect_uri = registration_response.redirect_uris()[0].clone();\n        let client: CoreClient<_, _, _, _, _, _> = CoreClient::from_provider_metadata(\n            provider_metadata.clone(),\n            registration_response.client_id().to_owned(),\n            registration_response.client_secret().cloned(),\n        )\n        .set_redirect_uri(redirect_uri);\n\n        TestState {\n            access_token: None,\n            authorization_code: None,\n            client,\n            id_token: None,\n            nonce: None,\n            provider_metadata,\n            registration_response,\n        }\n    }\n\n    pub fn access_token(&self) -> &AccessToken {\n        self.access_token.as_ref().expect(\"no access_token\")\n    }\n\n    pub fn authorize(mut self, scopes: &[Scope]) -> Self {\n        let (authorization_code, nonce) = {\n            let mut authorization_request = self.client.authorize_url(\n                AuthenticationFlow::AuthorizationCode::<CoreResponseType>,\n                CsrfToken::new_random,\n                Nonce::new_random,\n            );\n            authorization_request =\n                scopes\n                    .iter()\n                    .fold(authorization_request, |mut authorization_request, scope| {\n                        authorization_request = authorization_request.add_scope(scope.clone());\n                        authorization_request\n                    });\n            let (url, state, nonce) = authorization_request.url();\n            log_debug!(\"Authorize URL: {:?}\", url);\n\n            let http_client = Client::builder().redirect(Policy::none()).build().unwrap();\n            let redirect_response = http_client\n                .execute(\n                    http_client\n                        .request(Method::GET, url.as_str())\n                        .build()\n                        .unwrap(),\n                )\n                .unwrap();\n            assert!(redirect_response.status().is_redirection());\n            let redirected_url = Url::parse(\n                redirect_response\n                    .headers()\n                    .get(LOCATION)\n                    .unwrap()\n                    .to_str()\n                    .unwrap(),\n            )\n            .unwrap();\n\n            log_debug!(\"Authorization Server redirected to: {:?}\", redirected_url);\n\n            let mut query_params = HashMap::new();\n            redirected_url.query_pairs().for_each(|(key, value)| {\n                query_params.insert(key, value);\n            });\n            log_debug!(\n                \"Authorization Server returned query params: {:?}\",\n                query_params\n            );\n\n            assert_eq!(\n                self.provider_metadata.issuer().as_str(),\n                query_params.get(\"iss\").unwrap()\n            );\n            assert_eq!(state.secret(), query_params.get(\"state\").unwrap());\n\n            log_info!(\"Successfully received authentication response from Authorization Server\");\n\n            let authorization_code =\n                AuthorizationCode::new(query_params.get(\"code\").unwrap().to_string());\n            log_debug!(\n                \"Authorization Server returned authorization code: {}\",\n                authorization_code.secret()\n            );\n\n            (authorization_code, nonce)\n        };\n\n        self.authorization_code = Some(authorization_code);\n        self.nonce = Some(nonce);\n\n        self\n    }\n\n    pub fn exchange_code(mut self) -> Self {\n        let token_response = self\n            .client\n            .exchange_code(\n                self.authorization_code\n                    .take()\n                    .expect(\"no authorization_code\"),\n            )\n            .panic_if_fail(\"should have a token endpoint\")\n            .request(&http_client)\n            .panic_if_fail(\"failed to exchange authorization code for token\");\n        log_debug!(\n            \"Authorization Server returned token response: {:?}\",\n            token_response\n        );\n\n        self.access_token = Some(token_response.access_token().clone());\n\n        let id_token = (*token_response\n            .extra_fields()\n            .id_token()\n            .expect(\"no id_token\"))\n        .clone();\n        self.id_token = Some(id_token);\n\n        self\n    }\n\n    pub fn id_token(&self) -> &CoreIdToken {\n        self.id_token.as_ref().expect(\"no id_token\")\n    }\n\n    pub fn id_token_verifier(&self, jwks: CoreJsonWebKeySet) -> CoreIdTokenVerifier {\n        CoreIdTokenVerifier::new_confidential_client(\n            self.registration_response.client_id().clone(),\n            self.registration_response\n                .client_secret()\n                .expect(\"no client_secret\")\n                .clone(),\n            self.provider_metadata.issuer().clone(),\n            jwks,\n        )\n    }\n\n    pub fn id_token_claims(&self) -> &CoreIdTokenClaims {\n        let verifier = self.id_token_verifier(self.jwks());\n        self.id_token()\n            .claims(&verifier, self.nonce.as_ref().expect(\"no nonce\"))\n            .panic_if_fail(\"failed to validate claims\")\n    }\n\n    pub fn id_token_claims_failure(&self) -> ClaimsVerificationError {\n        let verifier = self.id_token_verifier(self.jwks());\n        self.id_token()\n            .claims(&verifier, self.nonce.as_ref().expect(\"no nonce\"))\n            .expect_err(\"claims verification succeeded but was expected to fail\")\n    }\n\n    pub fn jwks(&self) -> CoreJsonWebKeySet {\n        CoreJsonWebKeySet::fetch(self.provider_metadata.jwks_uri(), &http_client)\n            .panic_if_fail(\"failed to fetch JWK set\")\n    }\n\n    pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {\n        self.client = self.client.set_auth_type(auth_type);\n        self\n    }\n\n    pub fn user_info_claims(&self) -> CoreUserInfoClaims {\n        self.client\n            .user_info(\n                self.access_token().to_owned(),\n                Some(self.id_token_claims().subject().clone()),\n            )\n            .unwrap()\n            .require_signed_response(false)\n            .request(&http_client)\n            .panic_if_fail(\"failed to get UserInfo\")\n    }\n\n    pub fn user_info_claims_failure(&self) -> UserInfoError<HttpClientError<reqwest::Error>> {\n        let user_info_result: Result<CoreUserInfoClaims, _> = self\n            .client\n            .user_info(\n                self.access_token().to_owned(),\n                Some(self.id_token_claims().subject().clone()),\n            )\n            .unwrap()\n            .require_signed_response(false)\n            .request(&http_client);\n        match user_info_result {\n            Err(err) => err,\n            _ => panic!(\"claims verification succeeded but was expected to fail\"),\n        }\n    }\n}\n\n#[test]\n#[ignore]\nfn rp_response_type_code() {\n    let test_state = TestState::init(\"rp-response_type-code\", |reg| reg).authorize(&[]);\n    assert!(\n        test_state\n            .authorization_code\n            .expect(\"no authorization_code\")\n            .secret()\n            != \"\"\n    );\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_scope_userinfo_claims() {\n    let user_info_scopes = [\"profile\", \"email\", \"address\", \"phone\"]\n        .iter()\n        .map(|scope| Scope::new((*scope).to_string()))\n        .collect::<Vec<_>>();\n    let test_state = TestState::init(\"rp-scope-userinfo-claims\", |reg| reg)\n        .authorize(&user_info_scopes)\n        .exchange_code();\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    let user_info_claims = test_state.user_info_claims();\n    log_debug!(\"UserInfo response: {:?}\", user_info_claims);\n\n    assert_eq!(id_token_claims.subject(), user_info_claims.subject());\n    assert!(!user_info_claims\n        .email()\n        .expect(\"no email returned by UserInfo endpoint\")\n        .is_empty());\n    assert!(!user_info_claims\n        .address()\n        .expect(\"no address returned by UserInfo endpoint\")\n        .street_address\n        .as_ref()\n        .expect(\"no street address returned by UserInfo endpoint\")\n        .is_empty());\n    assert!(!user_info_claims\n        .phone_number()\n        .expect(\"no phone_number returned by UserInfo endpoint\")\n        .is_empty());\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_nonce_invalid() {\n    let test_state = TestState::init(\"rp-nonce-invalid\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    match test_state.id_token_claims_failure() {\n        ClaimsVerificationError::InvalidNonce(_) => {\n            log_error!(\"ID token contains invalid nonce (expected result)\")\n        }\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_token_endpoint_client_secret_basic() {\n    let test_state = TestState::init(\"rp-token_endpoint-client_secret_basic\", |reg| {\n        reg.set_token_endpoint_auth_method(Some(CoreClientAuthMethod::ClientSecretBasic))\n    })\n    .set_auth_type(AuthType::BasicAuth)\n    .authorize(&[])\n    .exchange_code();\n\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_token_endpoint_client_secret_post() {\n    let test_state = TestState::init(\"rp-token_endpoint-client_secret_post\", |reg| {\n        reg.set_token_endpoint_auth_method(Some(CoreClientAuthMethod::ClientSecretPost))\n    })\n    .set_auth_type(AuthType::RequestBody)\n    .authorize(&[])\n    .exchange_code();\n\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_kid_absent_single_jwks() {\n    let test_state = TestState::init(\"rp-id_token-kid-absent-single-jwks\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_iat() {\n    let mut test_state = TestState::init(\"rp-id_token-iat\", |reg| reg).authorize(&[]);\n\n    let token_response = test_state\n        .client\n        .exchange_code(\n            test_state\n                .authorization_code\n                .take()\n                .expect(\"no authorization_code\"),\n        )\n        .panic_if_fail(\"should have a token endpoint\")\n        .request(&http_client);\n\n    match token_response {\n        Err(RequestTokenError::Parse(_, _)) => {\n            log_error!(\"ID token failed to parse without `iat` claim (expected result)\")\n        }\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_aud() {\n    let test_state = TestState::init(\"rp-id_token-aud\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    match test_state.id_token_claims_failure() {\n        ClaimsVerificationError::InvalidAudience(_) => {\n            log_error!(\"ID token has invalid audience (expected result)\")\n        }\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_kid_absent_multiple_jwks() {\n    let test_state = TestState::init(\"rp-id_token-kid-absent-multiple-jwks\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    match test_state.id_token_claims_failure() {\n        ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::AmbiguousKeyId(_),\n        ) => log_error!(\"ID token has ambiguous key identification without KID (expected result)\"),\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_sig_none() {\n    let test_state = TestState::init(\"rp-id_token-sig-none\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    let verifier = test_state\n        .id_token_verifier(test_state.jwks())\n        .insecure_disable_signature_check();\n\n    let id_token_claims = test_state\n        .id_token()\n        .claims(&verifier, test_state.nonce.as_ref().expect(\"no nonce\"))\n        .panic_if_fail(\"failed to validate claims\");\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_sig_rs256() {\n    let test_state = TestState::init(\"rp-id_token-sig-rs256\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_sig_hs256() {\n    let test_state = TestState::init(\"rp-id_token-sig-hs256\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    let verifier = test_state\n        .id_token_verifier(test_state.jwks())\n        .set_allowed_algs(vec![CoreJwsSigningAlgorithm::HmacSha256]);\n    let id_token_claims = test_state\n        .id_token()\n        .claims(&verifier, test_state.nonce.as_ref().expect(\"no nonce\"))\n        .panic_if_fail(\"failed to validate claims\");\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_sub() {\n    let mut test_state = TestState::init(\"rp-id_token-sub\", |reg| reg).authorize(&[]);\n\n    let token_response = test_state\n        .client\n        .exchange_code(\n            test_state\n                .authorization_code\n                .take()\n                .expect(\"no authorization_code\"),\n        )\n        .panic_if_fail(\"should have a token endpoint\")\n        .request(&http_client);\n\n    match token_response {\n        Err(RequestTokenError::Parse(_, _)) => {\n            log_error!(\"ID token failed to parse without `sub` claim (expected result)\")\n        }\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_bad_sig_rs256() {\n    let test_state = TestState::init(\"rp-id_token-bad-sig-rs256\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    match test_state.id_token_claims_failure() {\n        ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::CryptoError(_),\n        ) => log_error!(\"ID token has invalid signature (expected result)\"),\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_bad_sig_hs256() {\n    let test_state = TestState::init(\"rp-id_token-bad-sig-hs256\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    let verifier = test_state\n        .id_token_verifier(test_state.jwks())\n        .set_allowed_algs(vec![CoreJwsSigningAlgorithm::HmacSha256]);\n    let id_token_err = test_state\n        .id_token()\n        .claims(&verifier, test_state.nonce.as_ref().expect(\"no nonce\"))\n        .expect_err(\"claims verification succeeded but was expected to fail\");\n    match id_token_err {\n        ClaimsVerificationError::SignatureVerification(\n            SignatureVerificationError::CryptoError(_),\n        ) => log_error!(\"ID token has invalid signature (expected result)\"),\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_id_token_issuer_mismatch() {\n    let test_state = TestState::init(\"rp-id_token-issuer-mismatch\", |reg| reg)\n        .authorize(&[])\n        .exchange_code();\n\n    match test_state.id_token_claims_failure() {\n        ClaimsVerificationError::InvalidIssuer(_) => {\n            log_error!(\"ID token has invalid issuer (expected result)\")\n        }\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_userinfo_bad_sub_claim() {\n    let test_state = TestState::init(\"rp-userinfo-bad-sub-claim\", |reg| reg)\n        .authorize(&[Scope::new(\"profile\".to_string())])\n        .exchange_code();\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    match test_state.user_info_claims_failure() {\n        UserInfoError::ClaimsVerification(ClaimsVerificationError::InvalidSubject(_)) => {\n            log_error!(\"UserInfo response has invalid subject (expected result)\")\n        }\n        other => panic!(\"Unexpected result verifying ID token claims: {:?}\", other),\n    }\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_userinfo_bearer_header() {\n    let test_state = TestState::init(\"rp-userinfo-bearer-header\", |reg| reg)\n        .authorize(&[Scope::new(\"profile\".to_string())])\n        .exchange_code();\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    let user_info_claims = test_state.user_info_claims();\n    log_debug!(\"UserInfo response: {:?}\", user_info_claims);\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_userinfo_sig() {\n    let test_state = TestState::init(\"rp-userinfo-sig\", |reg| {\n        reg.set_userinfo_signed_response_alg(Some(CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256))\n    })\n    .authorize(&[Scope::new(\"profile\".to_string())])\n    .exchange_code();\n    let id_token_claims = test_state.id_token_claims();\n    log_debug!(\"ID token: {:?}\", id_token_claims);\n\n    let user_info_claims: CoreUserInfoClaims = test_state\n        .client\n        .user_info(\n            test_state.access_token().to_owned(),\n            Some(id_token_claims.subject().clone()),\n        )\n        .unwrap()\n        // For some reason, the test suite omits these claims even though the Core spec says\n        // that the RP SHOULD verify these.\n        .require_audience_match(false)\n        .require_issuer_match(false)\n        .request(&http_client)\n        .panic_if_fail(\"failed to get UserInfo\");\n\n    log_debug!(\"UserInfo response: {:?}\", user_info_claims);\n    log_info!(\"SUCCESS\");\n}\n"
  },
  {
    "path": "tests/rp_certification_dynamic.rs",
    "content": "#![allow(clippy::cognitive_complexity)]\n\nuse crate::rp_common::{\n    get_provider_metadata, init_log, issuer_url, register_client, CERTIFICATION_BASE_URL, RP_NAME,\n};\n\nuse log::{debug, info};\n\nmod rp_common;\n\n#[test]\n#[ignore]\nfn rp_discovery_openid_configuration() {\n    const TEST_ID: &str = \"rp-discovery-openid-configuration\";\n    init_log(TEST_ID);\n\n    let _issuer_url = issuer_url(TEST_ID);\n    let provider_metadata = get_provider_metadata(TEST_ID);\n\n    macro_rules! log_field {\n        ($field:ident) => {\n            log_container_field!(provider_metadata.$field);\n        };\n    }\n\n    log_info!(\n        \"Successfully retrieved provider metadata from {:?}\",\n        _issuer_url\n    );\n    log_field!(issuer);\n    log_field!(authorization_endpoint);\n    log_field!(token_endpoint);\n    log_field!(userinfo_endpoint);\n    log_field!(jwks_uri);\n    log_field!(registration_endpoint);\n    log_field!(scopes_supported);\n    log_field!(response_types_supported);\n    log_field!(response_modes_supported);\n    log_field!(grant_types_supported);\n    log_field!(acr_values_supported);\n    log_field!(subject_types_supported);\n    log_field!(id_token_signing_alg_values_supported);\n    log_field!(id_token_encryption_alg_values_supported);\n    log_field!(id_token_encryption_enc_values_supported);\n    log_field!(userinfo_signing_alg_values_supported);\n    log_field!(userinfo_encryption_alg_values_supported);\n    log_field!(userinfo_encryption_enc_values_supported);\n    log_field!(request_object_signing_alg_values_supported);\n    log_field!(request_object_encryption_alg_values_supported);\n    log_field!(request_object_encryption_enc_values_supported);\n    log_field!(token_endpoint_auth_methods_supported);\n    log_field!(token_endpoint_auth_signing_alg_values_supported);\n    log_field!(display_values_supported);\n    log_field!(claim_types_supported);\n    log_field!(claims_supported);\n    log_field!(service_documentation);\n    log_field!(claims_locales_supported);\n    log_field!(ui_locales_supported);\n    log_field!(claims_parameter_supported);\n    log_field!(request_parameter_supported);\n    log_field!(request_uri_parameter_supported);\n    log_field!(require_request_uri_registration);\n    log_field!(op_policy_uri);\n    log_field!(op_tos_uri);\n\n    log_debug!(\"Provider metadata: {:?}\", provider_metadata);\n\n    log_info!(\"SUCCESS\");\n}\n\n#[test]\n#[ignore]\nfn rp_registration_dynamic() {\n    const TEST_ID: &str = \"rp-registration-dynamic\";\n    init_log(TEST_ID);\n\n    let _issuer_url = issuer_url(TEST_ID);\n    let provider_metadata = get_provider_metadata(TEST_ID);\n    let registration_response = register_client(&provider_metadata, |reg| reg);\n\n    macro_rules! log_field {\n        ($field:ident) => {\n            log_container_field!(registration_response.$field);\n        };\n    }\n\n    log_field!(client_id);\n    log_field!(client_secret);\n    log_field!(registration_access_token);\n    log_field!(registration_client_uri);\n    log_field!(client_id_issued_at);\n    log_field!(client_secret_expires_at);\n    log_field!(redirect_uris);\n    log_field!(response_types);\n    log_field!(grant_types);\n    log_field!(application_type);\n    log_field!(contacts);\n    log_field!(client_name);\n    log_field!(logo_uri);\n    log_field!(client_uri);\n    log_field!(policy_uri);\n    log_field!(tos_uri);\n    log_field!(jwks_uri);\n    log_field!(jwks);\n    log_field!(sector_identifier_uri);\n    log_field!(subject_type);\n    log_field!(id_token_signed_response_alg);\n    log_field!(id_token_encrypted_response_alg);\n    log_field!(id_token_encrypted_response_enc);\n    log_field!(userinfo_signed_response_alg);\n    log_field!(userinfo_encrypted_response_alg);\n    log_field!(userinfo_encrypted_response_enc);\n    log_field!(request_object_signing_alg);\n    log_field!(request_object_encryption_alg);\n    log_field!(request_object_encryption_enc);\n    log_field!(token_endpoint_auth_method);\n    log_field!(token_endpoint_auth_signing_alg);\n    log_field!(default_max_age);\n    log_field!(require_auth_time);\n    log_field!(default_acr_values);\n    log_field!(initiate_login_uri);\n    log_field!(request_uris);\n\n    log_debug!(\"Registration response: {:?}\", registration_response);\n\n    assert_eq!(\n        format!(\n            \"{}/{}/registration?client_id={}\",\n            CERTIFICATION_BASE_URL,\n            RP_NAME,\n            **registration_response.client_id()\n        ),\n        registration_response\n            .registration_client_uri()\n            .unwrap()\n            .to_string()\n    );\n\n    log_info!(\"SUCCESS\");\n}\n"
  },
  {
    "path": "tests/rp_common.rs",
    "content": "#![allow(clippy::cognitive_complexity, clippy::expect_fun_call)]\n\nuse log::{error, warn};\nuse openidconnect::core::{\n    CoreApplicationType, CoreClientRegistrationRequest, CoreClientRegistrationResponse,\n    CoreProviderMetadata,\n};\nuse openidconnect::{\n    ClientContactEmail, ClientName, HttpClientError, HttpRequest, HttpResponse, IssuerUrl,\n    RedirectUrl,\n};\n\nuse std::cell::RefCell;\nuse std::sync::Once;\nuse std::time::Duration;\n\npub const CERTIFICATION_BASE_URL: &str = \"https://rp.certification.openid.net:8080\";\npub const RP_CONTACT_EMAIL: &str = \"ramos@cs.stanford.edu\";\npub const RP_NAME: &str = \"openidconnect-rs\";\npub const RP_REDIRECT_URI: &str = \"http://localhost:8080\";\n\nstatic INIT_LOG: Once = Once::new();\n\nthread_local! {\n    static TEST_ID: RefCell<&'static str> = const { RefCell::new(\"UNINITIALIZED_TEST_ID\") };\n}\n\npub fn get_test_id() -> &'static str {\n    TEST_ID.with(|id| *id.borrow())\n}\n\npub fn set_test_id(test_id: &'static str) {\n    TEST_ID.with(|id| *id.borrow_mut() = test_id);\n}\n\n#[macro_export]\nmacro_rules! log_error {\n    ($($args:tt)+) => {\n        error!(\"[{}] {}\", rp_common::get_test_id(), format!($($args)+))\n    }\n}\n#[macro_export]\nmacro_rules! log_info {\n    ($($args:tt)+) => {\n        info!(\"[{}] {}\", rp_common::get_test_id(), format!($($args)+));\n    }\n}\n#[macro_export]\nmacro_rules! log_debug {\n    ($($args:tt)+) => {\n        debug!(\"[{}] {}\", rp_common::get_test_id(), format!($($args)+));\n    }\n}\n\n#[macro_export]\nmacro_rules! log_container_field {\n    ($container:ident. $field:ident) => {\n        log_info!(\n            concat!(\"  \", stringify!($field), \" = {:?}\"),\n            $container.$field()\n        );\n    };\n}\n\nfn _init_log() {\n    color_backtrace::install();\n    env_logger::init();\n}\n\npub fn init_log(test_id: &'static str) {\n    INIT_LOG.call_once(_init_log);\n    set_test_id(test_id);\n}\n\n// FIXME: just clone `request` directly once we update `http` to 1.0, which implements `Clone`.\n#[cfg(feature = \"reqwest-blocking\")]\npub(crate) fn clone_request(request: &HttpRequest) -> HttpRequest {\n    let mut request_copy = http::Request::builder()\n        .method(request.method().to_owned())\n        .uri(request.uri().to_owned())\n        .version(request.version());\n\n    for (name, value) in request.headers() {\n        request_copy = request_copy.header(name, value);\n    }\n    request_copy.body(request.body().to_owned()).unwrap()\n}\n\npub fn http_client(request: HttpRequest) -> Result<HttpResponse, HttpClientError<reqwest::Error>> {\n    retry::retry(\n        (0..5).map(|i| {\n            if i != 0 {\n                warn!(\"Retrying HTTP request ({}/5)\", i + 1)\n            }\n            Duration::from_millis(500)\n        }),\n        || -> Result<HttpResponse, HttpClientError<reqwest::Error>> {\n            #[cfg(feature = \"reqwest-blocking\")]\n            {\n                use openidconnect::SyncHttpClient;\n                reqwest::blocking::Client::default().call(clone_request(&request))\n            }\n            #[cfg(not(feature = \"reqwest-blocking\"))]\n            {\n                let _ = &request;\n                panic!(\"reqwest-blocking feature is required\")\n            }\n        },\n    )\n    .map_err(|err| match err {\n        retry::Error::Operation { error, .. } => error,\n        retry::Error::Internal(msg) => panic!(\"unexpected error: {msg}\"),\n    })\n}\n\npub trait PanicIfFail<T, F>\nwhere\n    F: std::error::Error,\n{\n    fn panic_if_fail(self, msg: &'static str) -> T;\n}\nimpl<T, F> PanicIfFail<T, F> for Result<T, F>\nwhere\n    F: std::error::Error,\n{\n    fn panic_if_fail(self, msg: &'static str) -> T {\n        match self {\n            Ok(ret) => ret,\n            Err(fail) => {\n                let mut err_msg = format!(\"Panic: {}\", msg);\n\n                let mut cur_fail: Option<&dyn std::error::Error> = Some(&fail);\n                while let Some(cause) = cur_fail {\n                    err_msg += &format!(\"\\n    caused by: {}\", cause);\n                    cur_fail = cause.source();\n                }\n                error!(\"[{}] {}\", get_test_id(), err_msg);\n                panic!(\"{}\", msg);\n            }\n        }\n    }\n}\n\npub fn issuer_url(test_id: &str) -> IssuerUrl {\n    IssuerUrl::new(format!(\n        \"{}/{}/{}\",\n        CERTIFICATION_BASE_URL, RP_NAME, test_id\n    ))\n    .expect(\"Failed to parse issuer URL\")\n}\n\npub fn get_provider_metadata(test_id: &str) -> CoreProviderMetadata {\n    let _issuer_url = issuer_url(test_id);\n    CoreProviderMetadata::discover(&_issuer_url, &http_client).expect(&format!(\n        \"Failed to fetch provider metadata from {:?}\",\n        _issuer_url\n    ))\n}\n\npub fn register_client<F>(\n    provider_metadata: &CoreProviderMetadata,\n    request_fn: F,\n) -> CoreClientRegistrationResponse\nwhere\n    F: FnOnce(CoreClientRegistrationRequest) -> CoreClientRegistrationRequest,\n{\n    let registration_request_pre = CoreClientRegistrationRequest::new(\n        vec![RedirectUrl::new(RP_REDIRECT_URI.to_string()).unwrap()],\n        Default::default(),\n    )\n    .set_application_type(Some(CoreApplicationType::Native))\n    .set_client_name(Some(\n        vec![(None, ClientName::new(RP_NAME.to_string()))]\n            .into_iter()\n            .collect(),\n    ))\n    .set_contacts(Some(vec![ClientContactEmail::new(\n        RP_CONTACT_EMAIL.to_string(),\n    )]));\n\n    let registration_request_post = request_fn(registration_request_pre);\n\n    let registration_endpoint = provider_metadata\n        .registration_endpoint()\n        .expect(\"provider does not support dynamic registration\");\n    registration_request_post\n        .register(registration_endpoint, &http_client)\n        .expect(&format!(\n            \"Failed to register client at {:?}\",\n            registration_endpoint\n        ))\n}\n"
  }
]