From a24c8b4ede55e238e39f7e7f60e5ff4b94870705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Fri, 8 Sep 2023 12:07:59 +0200 Subject: [PATCH] WIP: Elixir Bindings --- .formatter.exs | 8 +- .github/workflows/branch_main.yml | 2 + .github/workflows/part_docs.yml | 17 ++- .github/workflows/part_test.yml | 87 ++++++++++- .github/workflows/part_tool_versioning.yml | 8 + .github/workflows/pr.yml | 2 + .github/workflows/tag-beta.yml | 1 + .github/workflows/tag-stable.yml | 1 + .tool-versions | 1 + lib/oidcc.ex | 3 + lib/oidcc/provider_configuration.ex | 97 ++++++++++++ lib/oidcc/provider_configuration/worker.ex | 139 ++++++++++++++++++ mix.exs | 69 +++++++++ mix.lock | 11 ++ rebar.config | 25 ++-- .../provider_configuration/worker_test.exs | 72 +++++++++ test/oidcc/provider_configuration_test.exs | 14 ++ test/test_helper.exs | 1 + 18 files changed, 532 insertions(+), 26 deletions(-) create mode 100644 lib/oidcc.ex create mode 100644 lib/oidcc/provider_configuration.ex create mode 100644 lib/oidcc/provider_configuration/worker.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 test/oidcc/provider_configuration/worker_test.exs create mode 100644 test/oidcc/provider_configuration_test.exs create mode 100644 test/test_helper.exs diff --git a/.formatter.exs b/.formatter.exs index 067c54d..2db4bcc 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,3 +1,9 @@ [ - inputs: ["conformance/**/*.{ex,exs}"] + inputs: [ + "lib/**/*.ex", + "test/**/*.exs", + "conformance/**/*.{ex,exs}", + ".formatter.exs", + "mix.exs" + ] ] diff --git a/.github/workflows/branch_main.yml b/.github/workflows/branch_main.yml index bdd8a7e..90f03d0 100644 --- a/.github/workflows/branch_main.yml +++ b/.github/workflows/branch_main.yml @@ -22,6 +22,7 @@ jobs: with: otpVersion: "${{ needs.detectToolVersions.outputs.otpVersion }}" rebarVersion: "${{ needs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: "${{ needs.detectToolVersions.outputs.elixirVersion }}" docs: name: "Docs" @@ -32,3 +33,4 @@ jobs: with: otpVersion: "${{ needs.detectToolVersions.outputs.otpVersion }}" rebarVersion: "${{ needs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: "${{ needs.detectToolVersions.outputs.elixirVersion }}" diff --git a/.github/workflows/part_docs.yml b/.github/workflows/part_docs.yml index 3f5a6d1..ff6ac9a 100644 --- a/.github/workflows/part_docs.yml +++ b/.github/workflows/part_docs.yml @@ -7,6 +7,9 @@ on: rebarVersion: required: true type: string + elixirVersion: + required: true + type: string releaseName: required: false type: string @@ -26,13 +29,21 @@ jobs: with: otp-version: ${{ inputs.otpVersion }} rebar3-version: ${{ inputs.rebarVersion }} + elixir-version: ${{ inputs.elixirVersion }} - uses: actions/cache@v3 with: path: _build - key: docs-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} + key: docs-build-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('rebar.lock') }} + restore-keys: | + docs-build-{{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + - uses: actions/cache@v3 + with: + path: deps + key: docs-deps-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('rebar.lock') }} restore-keys: | - docs-{{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- - - run: rebar3 ex_doc + docs-bdepsuild-{{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + - run: mix deps.get + - run: mix docs - uses: actions/upload-artifact@v3 with: name: docs diff --git a/.github/workflows/part_test.yml b/.github/workflows/part_test.yml index 51b67e7..60d80b0 100644 --- a/.github/workflows/part_test.yml +++ b/.github/workflows/part_test.yml @@ -7,6 +7,9 @@ on: rebarVersion: required: true type: string + elixirVersion: + required: true + type: string name: "Test" @@ -14,8 +17,8 @@ env: ERL_AFLAGS: "-enable-feature all" jobs: - format: - name: Check Formatting + rebar_format: + name: Check Rebar Formatting runs-on: ubuntu-latest @@ -29,11 +32,39 @@ jobs: - uses: actions/cache@v3 with: path: _build - key: format-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} + key: rebar_format-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} restore-keys: | - format-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- + rebar_format-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- - run: rebar3 fmt --check + mix_format: + name: Check Mix Formatting + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + id: setupBEAM + with: + otp-version: ${{ inputs.otpVersion }} + rebar3-version: ${{ inputs.rebarVersion }} + elixir-version: ${{ inputs.elixirVersion }} + - uses: actions/cache@v3 + with: + path: _build + key: mix_format-build-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} + restore-keys: | + mix_format-build-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- + - uses: actions/cache@v3 + with: + path: deps + key: mix_format-deps-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} + restore-keys: | + mix_format-deps-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- + - run: mix deps.get + - run: mix format --check-formatted + eunit: name: Run EUnit @@ -89,12 +120,44 @@ jobs: name: ct-coverage path: _build/test/cover/ct.coverdata + mix_test: + name: Run Mix Tests + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + id: setupBEAM + with: + otp-version: ${{ inputs.otpVersion }} + rebar3-version: ${{ inputs.rebarVersion }} + elixir-version: ${{ inputs.elixirVersion }} + - uses: actions/cache@v3 + with: + path: _build + key: mix_test-build-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} + restore-keys: | + mix_test-build-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- + - uses: actions/cache@v3 + with: + path: deps + key: mix_test-deps-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ hashFiles('rebar.lock') }} + restore-keys: | + mix_test-deps-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- + - run: mix deps.get + - run: mix test --cover --export-coverage mix_test + - uses: actions/upload-artifact@v3 + with: + name: mix_test-coverage + path: cover/mix_test.coverdata + coverage: name: Process Test Coverage runs-on: ubuntu-latest - needs: ["eunit", "conformance"] + needs: ["eunit", "conformance", "mix_test"] steps: - uses: actions/checkout@v3 @@ -117,6 +180,10 @@ jobs: with: name: eunit-coverage path: _build/test/cover/ + - uses: actions/download-artifact@v3 + with: + name: mix_test-coverage + path: _build/test/cover/ - run: rebar3 cover - uses: actions/upload-artifact@v3 with: @@ -128,7 +195,7 @@ jobs: runs-on: ubuntu-latest - needs: ["eunit", "conformance"] + needs: ["eunit", "conformance", "mix_test"] steps: - uses: actions/checkout@v3 @@ -151,6 +218,10 @@ jobs: with: name: eunit-coverage path: _build/test/cover/ + - uses: actions/download-artifact@v3 + with: + name: mix_test-coverage + path: _build/test/cover/ - uses: actions/upload-artifact@v3 with: name: coverage-report @@ -179,6 +250,8 @@ jobs: lint-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- - run: rebar3 lint + # TODO: Credo + dialyzer: name: Dialyzer @@ -199,6 +272,8 @@ jobs: dialyzer-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}- - run: rebar3 dialyzer + # TODO: Dialyxir + hank: name: Hank diff --git a/.github/workflows/part_tool_versioning.yml b/.github/workflows/part_tool_versioning.yml index f68db69..59150b3 100644 --- a/.github/workflows/part_tool_versioning.yml +++ b/.github/workflows/part_tool_versioning.yml @@ -7,6 +7,9 @@ on: rebarVersion: description: "The .tool-versions Rebar version" value: "${{ jobs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: + description: "The .tool-versions Elixir version" + value: "${{ jobs.detectToolVersions.outputs.elixirVersion }}" name: "Detect Tool Versions" @@ -19,6 +22,7 @@ jobs: outputs: otpVersion: "${{ env.OTP_VERSION }}" rebarVersion: "${{ env.REBAR_VERSION }}" + elixirVersion: "${{ env.ELIXIR_VERSION }}" steps: - uses: actions/checkout@v3 @@ -32,3 +36,7 @@ jobs: REBAR_VERSION="$(cat .tool-versions | grep rebar | cut -d' ' -f2-)" echo Rebar: $REBAR_VERSION echo "REBAR_VERSION=${REBAR_VERSION}" >> $GITHUB_ENV + + ELIXIR_VERSION="$(cat .tool-versions | grep elixir | cut -d' ' -f2-)" + echo Rebar: $ELIXIR_VERSION + echo "ELIXIR_VERSION=${ELIXIR_VERSION}" >> $GITHUB_ENV diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a7f950f..a3bad32 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -21,6 +21,7 @@ jobs: with: otpVersion: "${{ needs.detectToolVersions.outputs.otpVersion }}" rebarVersion: "${{ needs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: "${{ needs.detectToolVersions.outputs.elixirVersion }}" docs: name: "Docs" @@ -31,3 +32,4 @@ jobs: with: otpVersion: "${{ needs.detectToolVersions.outputs.otpVersion }}" rebarVersion: "${{ needs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: "${{ needs.detectToolVersions.outputs.elixirVersion }}" diff --git a/.github/workflows/tag-beta.yml b/.github/workflows/tag-beta.yml index c61fcc6..8b23575 100644 --- a/.github/workflows/tag-beta.yml +++ b/.github/workflows/tag-beta.yml @@ -27,4 +27,5 @@ jobs: with: otpVersion: "${{ needs.detectToolVersions.outputs.otpVersion }}" rebarVersion: "${{ needs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: "${{ needs.detectToolVersions.outputs.elixirVersion }}" releaseName: "${{ github.ref_name }}" diff --git a/.github/workflows/tag-stable.yml b/.github/workflows/tag-stable.yml index af31c20..f4beb93 100644 --- a/.github/workflows/tag-stable.yml +++ b/.github/workflows/tag-stable.yml @@ -28,4 +28,5 @@ jobs: with: otpVersion: "${{ needs.detectToolVersions.outputs.otpVersion }}" rebarVersion: "${{ needs.detectToolVersions.outputs.rebarVersion }}" + elixirVersion: "${{ needs.detectToolVersions.outputs.elixirVersion }}" releaseName: "${{ github.ref_name }}" diff --git a/.tool-versions b/.tool-versions index c07058f..34e95a5 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,3 @@ erlang 26.0.2 rebar 3.22.1 +elixir 1.15.5 diff --git a/lib/oidcc.ex b/lib/oidcc.ex new file mode 100644 index 0000000..a1c1786 --- /dev/null +++ b/lib/oidcc.ex @@ -0,0 +1,3 @@ +defmodule Oidcc do + @moduledoc "foo" +end diff --git a/lib/oidcc/provider_configuration.ex b/lib/oidcc/provider_configuration.ex new file mode 100644 index 0000000..a35568d --- /dev/null +++ b/lib/oidcc/provider_configuration.ex @@ -0,0 +1,97 @@ +defmodule Oidcc.ProviderConfiguration do + @moduledoc """ + Tooling to load and parse Openid Configuration + """ + + import Record, only: [defrecordp: 3, extract: 2] + + defrecordp :configuration, + :oidcc_provider_configuration, + extract(:oidcc_provider_configuration, + from: "include/oidcc_provider_configuration.hrl" + ) + + defstruct extract(:oidcc_provider_configuration, + from: "include/oidcc_provider_configuration.hrl" + ) + + @typedoc """ + Configuration Struct + + For details on the fields see: + * https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + * https://datatracker.ietf.org/doc/html/draft-jones-oauth-discovery-01#section-4.1 + """ + @type t() :: %__MODULE__{ + issuer: :uri_string.uri_string(), + authorization_endpoint: :uri_string.uri_string(), + token_endpoint: :uri_string.uri_string() | :undefined, + userinfo_endpoint: :uri_string.uri_string() | :undefined, + jwks_uri: :uri_string.uri_string() | :undefined, + registration_endpoint: :uri_string.uri_string() | :undefined, + scopes_supported: [String.t()] | :undefined, + response_types_supported: [String.t()], + response_modes_supported: [String.t()], + grant_types_supported: [String.t()], + acr_values_supported: [String.t()] | :undefined, + subject_types_supported: [:pairwise | :public], + id_token_signing_alg_values_supported: [String.t()], + id_token_encryption_alg_values_supported: [String.t()] | :undefined, + id_token_encryption_enc_values_supported: [String.t()] | :undefined, + userinfo_signing_alg_values_supported: [String.t()] | :undefined, + userinfo_encryption_alg_values_supported: [String.t()] | :undefined, + userinfo_encryption_enc_values_supported: [String.t()] | :undefined, + request_object_signing_alg_values_supported: [String.t()] | :undefined, + request_object_encryption_alg_values_supported: [String.t()] | :undefined, + request_object_encryption_enc_values_supported: [String.t()] | :undefined, + token_endpoint_auth_methods_supported: [String.t()], + token_endpoint_auth_signing_alg_values_supported: [String.t()] | :undefined, + display_values_supported: [String.t()] | :undefined, + claim_types_supported: [:normal | :aggregated | :distributed], + claims_supported: [String.t()] | :undefined, + service_documentation: :uri_string.uri_string() | :undefined, + claims_locales_supported: [String.t()] | :undefined, + ui_locales_supported: [String.t()] | :undefined, + claims_parameter_supported: boolean(), + request_parameter_supported: boolean(), + request_uri_parameter_supported: boolean(), + require_request_uri_registration: boolean(), + op_policy_uri: :uri_string.uri_string() | :undefined, + op_tos_uri: :uri_string.uri_string() | :undefined, + revocation_endpoint: :uri_string.uri_string() | :undefined, + revocation_endpoint_auth_methods_supported: [String.t()], + revocation_endpoint_auth_signing_alg_values_supported: [String.t()] | :undefined, + introspection_endpoint: :uri_string.uri_string() | :undefined, + introspection_endpoint_auth_methods_supported: [String.t()], + introspection_endpoint_auth_signing_alg_values_supported: [String.t()] | :undefined, + code_challenge_methods_supported: [String.t()] | :undefined, + extra_fields: %{String.t() => term()} + } + + @doc false + @spec record_to_struct(record :: :oidcc_provider_configuration.configuration()) :: t() + def record_to_struct(record), do: struct!(__MODULE__, configuration(record)) + + @doc """ + Load OpenID Configuration + + ## Examples + + iex> {:ok, { + ...> %ProviderConfiguration{issuer: "https://accounts.google.com"}, + ...> _expiry + ...> }} = Oidcc.ProviderConfiguration.load_configuration("https://accounts.google.com") + """ + @spec load_configuration( + issuer :: :uri_string.uri_string(), + opts :: :oidcc_provider_configuration.opts() + ) :: + {:ok, {configuration :: t(), expiry :: pos_integer()}} + | {:error, :oidcc_provider_configuration.error()} + def load_configuration(issuer, opts \\ %{}) do + with {:ok, {configuration, expiry}} <- + :oidcc_provider_configuration.load_configuration(issuer, opts) do + {:ok, {record_to_struct(configuration), expiry}} + end + end +end diff --git a/lib/oidcc/provider_configuration/worker.ex b/lib/oidcc/provider_configuration/worker.ex new file mode 100644 index 0000000..acd3a8b --- /dev/null +++ b/lib/oidcc/provider_configuration/worker.ex @@ -0,0 +1,139 @@ +defmodule Oidcc.ProviderConfiguration.Worker do + @moduledoc """ + OIDC Config Provider Worker + + Loads and continuously refreshes the OIDC configuration and JWKs + + ## Usage in Supervisor + + ```elixir + Supervisor.init([ + {Oidcc.ProviderConfiguration.Worker, %{issuer: "https://accounts.google.com/"}} + ], strategy: :one_for_one) + ``` + """ + + alias Oidcc.ProviderConfiguration + + @typedoc """ + See `:oidcc_provider_configuration_worker.opts/0` + """ + @type opts() :: %{ + optional(:name) => GenServer.name(), + required(:issuer) => :uri_string.uri_string(), + optional(:provider_configuration_opts) => :oidcc_provider_configuration.opts() + } + + @doc """ + Start Configuration Worker + + ## Examples + + iex> {:ok, _pid} = + ...> Oidcc.ProviderConfiguration.Worker.start_link(%{ + ...> issuer: "https://accounts.google.com/", + ...> name: MyApp.GoogleConfigProvider + ...> }) + """ + @spec start_link(opts :: :oidcc_provider_configuration_worker.opts()) :: GenServer.on_start() + def start_link(opts) + + def start_link(%{name: name} = opts) when is_atom(name), + do: start_link(%{opts | name: {:local, name}}) + + def start_link(opts), do: :oidcc_provider_configuration_worker.start_link(opts) + + @spec child_spec(opts :: :oidcc_provider_configuration_worker.opts()) :: Supervisor.child_spec() + def child_spec(opts), + do: + Supervisor.child_spec( + %{ + id: __MODULE__, + start: {__MODULE__, :start_link, [opts]} + }, + [] + ) + + @doc """ + Get Configuration + + ## Examples + + iex> {:ok, pid} = + ...> Oidcc.ProviderConfiguration.Worker.start_link(%{ + ...> issuer: "https://accounts.google.com/" + ...> }) + ...> %Oidcc.ProviderConfiguration{issuer: "https://accounts.google.com"} = + ...> Oidcc.ProviderConfiguration.Worker.get_provider_configuration(pid) + """ + @spec get_provider_configuration(name :: GenServer.name()) :: ProviderConfiguration.t() + def get_provider_configuration(name), + do: + name + |> :oidcc_provider_configuration_worker.get_provider_configuration() + |> ProviderConfiguration.record_to_struct() + + @doc """ + Get Parsed Jwks + + ## Examples + + iex> {:ok, pid} = + ...> Oidcc.ProviderConfiguration.Worker.start_link(%{ + ...> issuer: "https://accounts.google.com/" + ...> }) + ...> %JOSE.JWK{} = + ...> Oidcc.ProviderConfiguration.Worker.get_jwks(pid) + """ + @spec get_jwks(name :: GenServer.name()) :: JOSE.JWK.t() + def get_jwks(name), + do: + name + |> :oidcc_provider_configuration_worker.get_jwks() + |> JOSE.JWK.from_record() + + @doc """ + Refresh Configuration + + ## Examples + + iex> {:ok, pid} = + ...> Oidcc.ProviderConfiguration.Worker.start_link(%{ + ...> issuer: "https://accounts.google.com/" + ...> }) + ...> :ok = Oidcc.ProviderConfiguration.Worker.refresh_configuration(pid) + """ + @spec refresh_configuration(name :: GenServer.name()) :: :ok + def refresh_configuration(name), + do: :oidcc_provider_configuration_worker.refresh_configuration(name) + + @doc """ + Refresh JWKs + + ## Examples + + iex> {:ok, pid} = + ...> Oidcc.ProviderConfiguration.Worker.start_link(%{ + ...> issuer: "https://accounts.google.com/" + ...> }) + ...> :ok = Oidcc.ProviderConfiguration.Worker.refresh_jwks(pid) + """ + @spec refresh_jwks(name :: GenServer.name()) :: :ok + def refresh_jwks(name), + do: :oidcc_provider_configuration_worker.refresh_jwks(name) + + @doc """ + Refresh JWKs if the provided `Kid` is not matching any currently loaded keys + + ## Examples + + iex> {:ok, pid} = + ...> Oidcc.ProviderConfiguration.Worker.start_link(%{ + ...> issuer: "https://accounts.google.com/" + ...> }) + ...> :ok = Oidcc.ProviderConfiguration.Worker.refresh_jwks_for_unknown_kid(pid, "kid") + """ + @spec refresh_jwks_for_unknown_kid(name :: GenServer.name(), kid :: String.t()) :: :ok + def refresh_jwks_for_unknown_kid(name, kid), + do: :oidcc_provider_configuration_worker.refresh_jwks_for_unknown_kid(name, kid) +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..56fa8f0 --- /dev/null +++ b/mix.exs @@ -0,0 +1,69 @@ +defmodule Oidcc.Mixfile do + use Mix.Project + + {:ok, [{:application, :oidcc, props}]} = :file.consult(~c"src/oidcc.app.src") + @props Keyword.take(props, [:applications, :description, :env, :mod, :licenses, :vsn]) + + def project() do + [ + app: :oidcc, + version: to_string(@props[:vsn]), + elixir: "~> 1.15", + erlc_options: erlc_options(Mix.env()), + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + deps: deps(), + name: "Oidcc", + source_url: "https://github.com/Erlang-Openid/oidcc", + docs: &docs/0, + description: to_string(@props[:description]), + package: package(), + aliases: [docs: ["compile", &rebar3_doc_chunks/1, "docs"]] + ] + end + + def application() do + [extra_applications: [:inets, :ssl]] + end + + defp deps() do + [ + {:telemetry, "~> 1.2"}, + {:jose, "~> 1.11"}, + {:jsx, "~> 3.1"}, + {:ex_doc, "~> 0.29.4", only: :dev, runtime: false} + ] + end + + defp erlc_options(:prod), do: [] + + defp erlc_options(_enc), + do: [:debug_info, :warn_unused_import, :warn_export_vars, :warnings_as_errors, :verbose] + + defp package() do + [ + maintainers: ["Jonatan Männchen"], + build_tools: ["rebar3", "mix"], + files: [ + "include", + "lib", + "LICENSE*", + "mix.exs", + "README*", + "rebar.config", + "src" + ], + licenses: @props[:licenses], + links: %{"Github" => "https://github.com/Erlang-Openid/oidcc"} + ] + end + + defp docs do + {ref, 0} = System.cmd("git", ["rev-parse", "--verify", "--quiet", "HEAD"]) + [source_ref: ref, main: "Oidcc", extras: ["README.md"]] + end + + defp rebar3_doc_chunks(_args) do + {_out, 0} = System.cmd("rebar3", ["edoc"], into: IO.stream()) + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..381cb5a --- /dev/null +++ b/mix.lock @@ -0,0 +1,11 @@ +%{ + "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, + "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, + "jsx": {:hex, :jsx, "3.1.0", "d12516baa0bb23a59bb35dccaf02a1bd08243fcbb9efe24f2d9d056ccff71268", [:rebar3], [], "hexpm", "0c5cc8fdc11b53cc25cf65ac6705ad39e54ecc56d1c22e4adb8f5a53fb9427f3"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, +} diff --git a/rebar.config b/rebar.config index 097c514..da41bdb 100644 --- a/rebar.config +++ b/rebar.config @@ -6,25 +6,20 @@ {project_plugins, [ coveralls, - rebar3_ex_doc, erlfmt, rebar3_hank, - rebar3_hex, - rebar3_lint, - rebar3_sbom + rebar3_lint ]}. {validate_app_modules, true}. -{dialyzer, [{warnings, [no_return, unmatched_returns, error_handling, no_underspecs]}]}. - -{ex_doc, [ - {extras, ["README.md", "LICENSE"]}, - {main, "README.md"}, - {source_url, "https://github.com/Erlang-Openid/oidcc"} -]}. - -{hex, [{doc, #{provider => ex_doc}}]}. +{edoc_opts, + [ + {doclet, edoc_doclet_chunks}, + {layout, edoc_layout_chunks}, + {preprocess, true}, + {dir, "_build/dev/lib/oidcc/doc"} + ]}. {hank, [{ignore, [{"test/**/*_SUITE.erl", [unnecessary_function_arguments]}, "include/**/*.hrl"]}]}. @@ -40,6 +35,4 @@ {cover_opts, [verbose]}. -{shell, - % {config, "config/sys.config"}, - [{apps, [oidcc]}]}. +{shell, [{apps, [oidcc]}]}. diff --git a/test/oidcc/provider_configuration/worker_test.exs b/test/oidcc/provider_configuration/worker_test.exs new file mode 100644 index 0000000..404525c --- /dev/null +++ b/test/oidcc/provider_configuration/worker_test.exs @@ -0,0 +1,72 @@ +defmodule Oidcc.ProviderConfiguration.WorkerTest do + use ExUnit.Case + + alias Oidcc.ProviderConfiguration + alias Oidcc.ProviderConfiguration.Worker + + doctest Worker + + describe inspect(&Worker.start_link/1) do + test "works" do + start_supervised!( + {Worker, %{issuer: "https://accounts.google.com/", name: __MODULE__.GoogleProvider}} + ) + end + end + + describe inspect(&Worker.get_provider_configuration/1) do + test "works" do + pid = + start_supervised!( + {Worker, %{issuer: "https://accounts.google.com/", name: __MODULE__.GoogleProvider}} + ) + + assert %ProviderConfiguration{issuer: "https://accounts.google.com"} = + Worker.get_provider_configuration(pid) + end + end + + describe inspect(&Worker.get_jwks/1) do + test "works" do + start_supervised!( + {Worker, %{issuer: "https://accounts.google.com/", name: __MODULE__.GoogleProvider}} + ) + + assert %JOSE.JWK{} = + Worker.get_jwks(__MODULE__.GoogleProvider) + end + end + + describe inspect(&Worker.refresh_configuration/1) do + test "works" do + pid = + start_supervised!( + {Worker, %{issuer: "https://accounts.google.com/", name: __MODULE__.GoogleProvider}} + ) + + assert :ok = Worker.refresh_configuration(pid) + end + end + + describe inspect(&Worker.refresh_jwks/1) do + test "works" do + pid = + start_supervised!( + {Worker, %{issuer: "https://accounts.google.com/", name: __MODULE__.GoogleProvider}} + ) + + assert :ok = Worker.refresh_jwks(pid) + end + end + + describe inspect(&Worker.refresh_jwks_for_unknown_kid/2) do + test "works" do + pid = + start_supervised!( + {Worker, %{issuer: "https://accounts.google.com/", name: __MODULE__.GoogleProvider}} + ) + + assert :ok = Worker.refresh_jwks_for_unknown_kid(pid, "kid") + end + end +end diff --git a/test/oidcc/provider_configuration_test.exs b/test/oidcc/provider_configuration_test.exs new file mode 100644 index 0000000..199dad3 --- /dev/null +++ b/test/oidcc/provider_configuration_test.exs @@ -0,0 +1,14 @@ +defmodule Oidcc.ProviderConfigurationTest do + use ExUnit.Case + + alias Oidcc.ProviderConfiguration + + doctest ProviderConfiguration + + describe inspect(&ProviderConfiguration.load_configuration/2) do + test "works" do + assert {:ok, {%ProviderConfiguration{issuer: "https://accounts.google.com"}, _expiry}} = + ProviderConfiguration.load_configuration("https://accounts.google.com", %{}) + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()