Skip to content

Commit

Permalink
FAPI 2.0 Compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
maennchen committed Apr 30, 2024
1 parent 2275f7f commit eaf5c2b
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 163 deletions.
78 changes: 57 additions & 21 deletions lib/oidcc/plug/authorization_callback.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ defmodule Oidcc.Plug.AuthorizationCallback do

@behaviour Plug

alias Oidcc.ClientContext
alias Oidcc.Plug.Authorize
alias Oidcc.ProviderConfiguration
alias Oidcc.Token
alias Oidcc.Userinfo

import Plug.Conn,
only: [get_session: 2, delete_session: 2, put_private: 3, get_peer_data: 1, get_req_header: 2]
Expand All @@ -95,6 +99,8 @@ defmodule Oidcc.Plug.AuthorizationCallback do
* `provider` - name of the `Oidcc.ProviderConfiguration.Worker`
* `client_id` - OAuth Client ID to use for the introspection
* `client_secret` - OAuth Client Secret to use for the introspection
* `client_context_opts` - Options for Client Context Initialization
* `client_profile_opts` - Options for Client Context Profiles
* `redirect_uri` - Where to redirect for callback
* `check_useragent` - check if useragent is the same as before the
authorization request
Expand All @@ -108,6 +114,8 @@ defmodule Oidcc.Plug.AuthorizationCallback do
provider: GenServer.name(),
client_id: String.t() | (-> String.t()),
client_secret: String.t() | (-> String.t()),
client_context_opts: :oidcc_client_context.opts() | (-> :oidcc_client_context.opts()),
client_profile_opts: :oidcc_profile.opts(),
redirect_uri: String.t() | (-> String.t()),
check_useragent: boolean(),
check_peer_ip: boolean(),
Expand All @@ -131,6 +139,8 @@ defmodule Oidcc.Plug.AuthorizationCallback do
:provider,
:client_id,
:client_secret,
:client_context_opts,
:client_profile_opts,
:redirect_uri,
:preferred_auth_methods,
check_useragent: true,
Expand All @@ -145,6 +155,8 @@ defmodule Oidcc.Plug.AuthorizationCallback do
client_id = opts |> Keyword.fetch!(:client_id) |> evaluate_config()
client_secret = opts |> Keyword.fetch!(:client_secret) |> evaluate_config()
redirect_uri = opts |> Keyword.fetch!(:redirect_uri) |> evaluate_config()
client_context_opts = opts |> Keyword.get(:client_context_opts, %{}) |> evaluate_config()
client_profile_opts = opts |> Keyword.get(:client_profile_opts, %{profiles: []})

params = Map.merge(params, body_params)

Expand All @@ -159,8 +171,18 @@ defmodule Oidcc.Plug.AuthorizationCallback do
retrieve_userinfo? = Keyword.fetch!(opts, :retrieve_userinfo)

result =
with :ok <- check_peer_ip(conn, peer_ip, check_peer_ip?),
with {:ok, client_context} <-
ClientContext.from_configuration_worker(
provider,
client_id,
client_secret,
client_context_opts
),
{:ok, client_context, profile_opts} <-
apply_profile(client_context, client_profile_opts),
:ok <- check_peer_ip(conn, peer_ip, check_peer_ip?),
:ok <- check_useragent(conn, useragent, check_useragent?),
:ok <- check_issuer_request_param(params, client_context),
{:ok, code} <- fetch_request_param(params, "code"),
scope = Map.get(params, "scope", "openid"),
scopes = :oidcc_scope.parse(scope),
Expand All @@ -177,14 +199,12 @@ defmodule Oidcc.Plug.AuthorizationCallback do
{:ok, token} <-
retrieve_token(
code,
provider,
client_id,
client_secret,
client_context,
retrieve_userinfo?,
token_opts
Map.merge(profile_opts, token_opts)
),
{:ok, userinfo} <-
retrieve_userinfo(token, provider, client_id, client_secret, retrieve_userinfo?) do
retrieve_userinfo(token, client_context, retrieve_userinfo?) do
{:ok, {token, userinfo}}
end

Expand Down Expand Up @@ -234,16 +254,33 @@ defmodule Oidcc.Plug.AuthorizationCallback do
end
end

defp check_issuer_request_param(params, client_context)

defp check_issuer_request_param(params, %ClientContext{
provider_configuration: %ProviderConfiguration{
issuer: issuer,
authorization_response_iss_parameter_supported: true
}
}) do
with {:ok, given_issuer} <- fetch_request_param(params, "iss") do
if issuer == given_issuer do
:ok
else
{:error, {:invalid_issuer, given_issuer}}
end
end
end

defp check_issuer_request_param(_params, _client_context), do: :ok

@spec retrieve_token(
code :: String.t(),
provider :: GenServer.name(),
client_id :: String.t(),
client_secret :: String.t(),
client_context :: ClientContext.t(),
retrieve_userinfo? :: boolean(),
token_opts :: :oidcc_token.retrieve_opts()
) :: {:ok, Oidcc.Token.t()} | {:error, error()}
defp retrieve_token(code, provider, client_id, client_secret, retrieve_userinfo?, token_opts) do
case Oidcc.retrieve_token(code, provider, client_id, client_secret, token_opts) do
defp retrieve_token(code, client_context, retrieve_userinfo?, token_opts) do
case Token.retrieve(code, client_context, token_opts) do
{:ok, token} -> {:ok, token}
{:error, {:none_alg_used, token}} when retrieve_userinfo? -> {:ok, token}
{:error, reason} -> {:error, reason}
Expand All @@ -252,21 +289,20 @@ defmodule Oidcc.Plug.AuthorizationCallback do

@spec retrieve_userinfo(
token :: Oidcc.Token.t(),
provider :: GenServer.name(),
client_id :: String.t(),
client_secret :: String.t(),
client_context :: ClientContext.t(),
retrieve_userinfo? :: true
) :: {:ok, :oidcc_jwt_util.claims()} | {:error, error()}
@spec retrieve_userinfo(
token :: Oidcc.Token.t(),
provider :: GenServer.name(),
client_id :: String.t(),
client_secret :: String.t(),
client_context :: ClientContext.t(),
retrieve_userinfo? :: false
) :: {:ok, nil} | {:error, error()}
defp retrieve_userinfo(token, provider, client_id, client_secret, retrieve_userinfo?)
defp retrieve_userinfo(_token, _provider, _client_id, _client_secret, false), do: {:ok, nil}
defp retrieve_userinfo(token, client_context, retrieve_userinfo?)
defp retrieve_userinfo(_token, _client_context, false), do: {:ok, nil}

defp retrieve_userinfo(token, client_context, true),
do: Userinfo.retrieve(token, client_context, %{})

defp retrieve_userinfo(token, provider, client_id, client_secret, true),
do: Oidcc.retrieve_userinfo(token, provider, client_id, client_secret, %{})
defp apply_profile(client_context, profile_opts),
do: ClientContext.apply_profiles(client_context, profile_opts)
end
54 changes: 40 additions & 14 deletions lib/oidcc/plug/authorize.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ defmodule Oidcc.Plug.Authorize do
## Query Params
* `state` - STate to relay to OpenID Provider. Commonly used for target redirect
* `state` - State to relay to OpenID Provider. Commonly used for target redirect
URL after authorization.
"""
@moduledoc since: "0.1.0"

@behaviour Plug

alias Oidcc.Authorization
alias Oidcc.ClientContext

import Plug.Conn,
only: [send_resp: 3, put_resp_header: 3, put_session: 3, get_peer_data: 1, get_req_header: 2]

Expand Down Expand Up @@ -57,6 +60,8 @@ defmodule Oidcc.Plug.Authorize do
* `provider` - name of the `Oidcc.ProviderConfiguration.Worker`
* `client_id` - OAuth Client ID to use for the introspection
* `client_secret` - OAuth Client Secret to use for the introspection
* `client_context_opts` - Options for Client Context Initialization
* `client_profile_opts` - Options for Client Context Profiles
"""
@typedoc since: "0.1.0"
@type opts :: [
Expand All @@ -65,7 +70,9 @@ defmodule Oidcc.Plug.Authorize do
url_extension: :oidcc_http_util.query_params(),
provider: GenServer.name(),
client_id: String.t() | (-> String.t()),
client_secret: String.t() | (-> String.t())
client_secret: String.t() | (-> String.t()),
client_context_opts: :oidcc_client_context.opts() | (-> :oidcc_client_context.opts()),
client_profile_opts: :oidcc_profile.opts()
]

@impl Plug
Expand All @@ -76,6 +83,8 @@ defmodule Oidcc.Plug.Authorize do
:client_id,
:client_secret,
:redirect_uri,
:client_context_opts,
:client_profile_opts,
url_extension: [],
scopes: ["openid"]
])
Expand All @@ -86,6 +95,8 @@ defmodule Oidcc.Plug.Authorize do
client_id = opts |> Keyword.fetch!(:client_id) |> evaluate_config()
client_secret = opts |> Keyword.fetch!(:client_secret) |> evaluate_config()
redirect_uri = opts |> Keyword.fetch!(:redirect_uri) |> evaluate_config()
client_context_opts = opts |> Keyword.get(:client_context_opts, %{}) |> evaluate_config()
client_profile_opts = opts |> Keyword.get(:client_profile_opts, %{profiles: []})

state = Map.get(params, "state", :undefined)
nonce = 31 |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
Expand All @@ -106,23 +117,38 @@ defmodule Oidcc.Plug.Authorize do
)
|> Map.new()

case Oidcc.create_redirect_url(provider, client_id, client_secret, authorization_opts) do
{:ok, redirect_uri} ->
conn
|> put_session(get_session_name(), %{
nonce: nonce,
peer_ip: peer_ip,
useragent: useragent,
pkce_verifier: pkce_verifier
})
|> put_resp_header("location", IO.iodata_to_binary(redirect_uri))
|> send_resp(302, "")

with {:ok, client_context} <-
ClientContext.from_configuration_worker(
provider,
client_id,
client_secret,
client_context_opts
),
{:ok, client_context, profile_opts} <-
apply_profile(client_context, client_profile_opts),
{:ok, redirect_uri} <-
Authorization.create_redirect_url(
client_context,
Map.merge(profile_opts, authorization_opts)
) do
conn
|> put_session(get_session_name(), %{
nonce: nonce,
peer_ip: peer_ip,
useragent: useragent,
pkce_verifier: pkce_verifier
})
|> put_resp_header("location", IO.iodata_to_binary(redirect_uri))
|> send_resp(302, "")
else
{:error, reason} ->
raise Error, reason: reason
end
end

defp apply_profile(client_context, profile_opts),
do: ClientContext.apply_profiles(client_context, profile_opts)

@doc false
@spec get_session_name :: String.t()
def get_session_name, do: inspect(__MODULE__)
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ defmodule Oidcc.Plug.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:oidcc, "~> 3.0"},
{:oidcc, "~> 3.2.0-beta"},
{:plug, "~> 1.14"},
{:ex_doc, "~> 0.29", only: :dev, runtime: false},
{:excoveralls, "~> 0.18.1", only: :test, runtime: false},
Expand Down
Loading

0 comments on commit eaf5c2b

Please sign in to comment.