Skip to content

Commit

Permalink
[federation] implement resolve endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
patatoid committed Nov 5, 2024
1 parent ba9f159 commit 8ab9746
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ defmodule BorutaFederation.FederationEntities do

@spec get_entity(id :: Ecto.UUID.t()) :: federation_entity :: FederationEntity.t() | nil
def get_entity(id) do
Repo.get(FederationEntity, id)
case Ecto.UUID.cast(id) do
{:ok, _} ->
Repo.get(FederationEntity, id)
_ ->
nil
end
end

@spec create_entity(
Expand Down
43 changes: 43 additions & 0 deletions apps/boruta_federation/lib/boruta_federation/openid_federation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule BorutaFederation.OpenidFederationApplication do
@callback resolve_success(context :: any, federation_entity_statement :: String.t()) :: any()
@callback resolve_failure(context :: any, error :: Boruta.Oauth.Error.t()) :: any()
end

defmodule BorutaFederation.OpenidFederation do
alias Boruta.Oauth.Error
alias BorutaFederation.FederationEntities
alias BorutaFederation.TrustChains

@type resolve_params :: %{
sub: String.t(),
anchor: String.t()
}

@spec resolve(context :: any(), resolve_params :: resolve_params(), module :: atom()) :: any()
def resolve(context, resolve_params, module) do
case FederationEntities.get_entity(resolve_params[:sub]) do
nil ->
error = %Error{
status: :not_found,
error: :not_found,
error_description: "Federation entity could not be found."
}
module.resolve_failure(context, error)

entity ->
case TrustChains.generate_statement(entity) do
{:ok, statement} ->
module.resolve_success(context, statement)

{:error, error} ->
error = %Error{
status: :bad_request,
error: :unknown_error,
error_description: "Could not generate federation entity statement #{error}."
}

module.resolve_failure(context, error)
end
end
end
end
6 changes: 3 additions & 3 deletions apps/boruta_federation/lib/boruta_federation/trust_chains.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ defmodule BorutaFederation.TrustChains do
now = :os.system_time(:second)

payload = %{
"iss" => issuer(),
"sub" => entity.id,
"iat" => now,
"exp" => now + entity.trust_chain_statement_ttl,
"iat" => now,
"iss" => issuer(),
"jwks" => jwks,
"metadata" => metadata,
"sub" => entity.id,
"trust_marks" => trust_marks
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule BorutaFederationWeb.ResolveController do
@behaviour BorutaFederation.OpenidFederationApplication

alias BorutaFederationWeb.ErrorView
use BorutaFederationWeb, :controller

alias BorutaFederation.OpenidFederation

def resolve(conn, params) do
resolve_params = %{
sub: params["sub"],
anchor: params["anchor"]
}

OpenidFederation.resolve(conn, resolve_params, __MODULE__)
end

@impl BorutaFederation.OpenidFederationApplication
def resolve_success(conn, federation_entity_statement) do
conn
|> put_resp_header("content-type", "application/resolve-response+jwt")
|> send_resp(200, federation_entity_statement)
end

@impl BorutaFederation.OpenidFederationApplication
def resolve_failure(conn, error) do
conn
|> put_status(error.status)
|> put_view(ErrorView)
|> render("error.json", error: error)
end
end
3 changes: 2 additions & 1 deletion apps/boruta_federation/lib/boruta_federation_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ defmodule BorutaFederationWeb.Router do
end

scope "/", BorutaFederationWeb do
pipe_through :browser
pipe_through :api

get "/", PageController, :index
get "/resolve", ResolveController, :resolve
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ defmodule BorutaFederationWeb.ErrorView do
def template_not_found(template, _assigns) do
Phoenix.Controller.status_message_from_template(template)
end

def render("error.json", %{error: error}) do
%{
error: error.error,
error_description: error.error_description
}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule BorutaFederation.TrustChainsTest do

import BorutaFederation.Factory

alias BorutaFederation.FederationEntities.LeafEntity.Token
alias BorutaFederation.TrustChains

describe "generate_statement/1" do
Expand All @@ -11,6 +12,29 @@ defmodule BorutaFederation.TrustChainsTest do

assert {:ok, statement} = TrustChains.generate_statement(entity)
assert statement

entity_id = entity.id

assert {:ok,
%{
"exp" => exp,
"iat" => iat,
"iss" => "http://localhost:4000",
"jwks" => [jwk],
"metadata" => %{"openid_provider" => %{"issuer" => "http://localhost:4000"}},
"sub" => ^entity_id,
"trust_marks" => [trust_mark]
}} = Joken.peek_claims(statement)

signer =
Joken.Signer.create(entity.trust_chain_statement_alg, %{
"pem" => JOSE.JWK.from_map(jwk) |> JOSE.JWK.to_pem() |> elem(1)
})

assert {:ok, _} = Token.verify_and_validate(statement, signer)
assert {:ok, _} = Token.verify_and_validate(trust_mark, signer)
assert iat
assert exp
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule BorutaFederationWeb.ResolveControllerTest do
use BorutaFederationWeb.ConnCase

import BorutaFederation.Factory

alias BorutaFederation.FederationEntities.LeafEntity.Token

describe "GET /resolve" do
test "retruns not found", %{conn: conn} do
conn = get(conn, Routes.resolve_path(conn, :resolve, %{sub: "sub", anchor: "anchor"}))
assert json_response(conn, 404) == %{
"error" => "not_found",
"error_description" => "Federation entity could not be found."
}
end

test "retruns a statement", %{conn: conn} do
entity = insert(:entity)

conn = get(conn, Routes.resolve_path(conn, :resolve, %{sub: entity.id, anchor: "anchor"}))
assert statement = response(conn, 200)

entity_id = entity.id

assert {:ok,
%{
"exp" => exp,
"iat" => iat,
"iss" => "http://localhost:4000",
"jwks" => [jwk],
"metadata" => %{"openid_provider" => %{"issuer" => "http://localhost:4000"}},
"sub" => ^entity_id,
"trust_marks" => [trust_mark]
}} = Joken.peek_claims(statement)

signer =
Joken.Signer.create(entity.trust_chain_statement_alg, %{
"pem" => JOSE.JWK.from_map(jwk) |> JOSE.JWK.to_pem() |> elem(1)
})

assert {:ok, _} = Token.verify_and_validate(statement, signer)
assert {:ok, _} = Token.verify_and_validate(trust_mark, signer)
assert iat
assert exp
end
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"argon2_elixir": {:hex, :argon2_elixir, "2.4.1", "edb27bdd326bc738f3e4614eddc2f73507be6fedc9533c6bcc6f15bbac9c85cc", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0e21f52a373739d00bdfd5fe6da2f04eea623cb4f66899f7526dd9db03903d9f"},
"asn1_compiler": {:hex, :asn1_compiler, "0.1.1", "64a4e52b59d1f225878445ace2c75cd2245b13a5a81182304fd9dc5acfc8994e", [:mix], [], "hexpm", "c250d24c22f1a3f305d88864400f9ac2df55c6886e1e3a030e2946efeb94695e"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"},
"boruta": {:git, "https://github.com/malach-it/boruta_auth", "b85da04f28412946295ac767b68652578b01f803", [branch: "openid-federation"]},
"boruta": {:git, "https://github.com/malach-it/boruta_auth", "919bf4536d42b26cc9a203ab3e6f760db2b1ef12", [branch: "openid-federation"]},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
"castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"},
Expand Down

0 comments on commit 8ab9746

Please sign in to comment.