Skip to content

Commit

Permalink
preauthorized code flow - token request
Browse files Browse the repository at this point in the history
  • Loading branch information
patatoid committed Dec 17, 2023
1 parent c5f6f66 commit df2fe09
Show file tree
Hide file tree
Showing 10 changed files with 1,498 additions and 1,314 deletions.
8 changes: 7 additions & 1 deletion lib/boruta/adapters/ecto/codes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Boruta.Ecto.Codes do
@behaviour Boruta.Oauth.Codes

import Boruta.Config, only: [repo: 0]
import Ecto.Query
import Boruta.Ecto.OauthMapper, only: [to_oauth_schema: 1]

alias Boruta.Ecto.Errors
Expand All @@ -18,7 +19,12 @@ defmodule Boruta.Ecto.Codes do
else
{:error, "Not cached."} ->
with %Token{} = token <-
repo().get_by(Token, type: "code", value: value, redirect_uri: redirect_uri),
repo().one(
from t in Token,
where:
t.type in ["code", "preauthorized_code"] and t.value == ^value and
t.redirect_uri == ^redirect_uri
),
{:ok, token} <-
token
|> to_oauth_schema()
Expand Down
83 changes: 83 additions & 0 deletions lib/boruta/oauth/authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,89 @@ defimpl Boruta.Oauth.Authorization, for: Boruta.Oauth.AuthorizationCodeRequest d
end
end

defimpl Boruta.Oauth.Authorization, for: Boruta.Oauth.PreauthorizationCodeRequest do
alias Boruta.AccessTokensAdapter
alias Boruta.CodesAdapter
alias Boruta.Oauth.Authorization
alias Boruta.Oauth.PreauthorizationCodeRequest
alias Boruta.Oauth.AuthorizationSuccess
alias Boruta.Oauth.IdToken
alias Boruta.Oauth.ResourceOwner
alias Boruta.Oauth.Scope
alias Boruta.Oauth.Token

def preauthorize(%PreauthorizationCodeRequest{
client_id: client_id,
client_authentication: client_source,
preauthorized_code: preauthorized_code,
redirect_uri: redirect_uri,
code_verifier: code_verifier
}) do
with {:ok, client} <-
Authorization.Client.authorize(
id: client_id,
source: client_source,
redirect_uri: redirect_uri,
grant_type: "preauthorization_code",
code_verifier: code_verifier
),
{:ok, code} <-
Authorization.Code.authorize(%{
value: preauthorized_code,
redirect_uri: redirect_uri,
client: client,
code_verifier: code_verifier
}),
{:ok, %ResourceOwner{sub: sub}} <-
Authorization.ResourceOwner.authorize(resource_owner: code.resource_owner) do
{:ok,
%AuthorizationSuccess{
client: client,
code: code,
redirect_uri: redirect_uri,
sub: sub,
scope: code.scope,
nonce: code.nonce
}}
end
end

def token(request) do
with {:ok,
%AuthorizationSuccess{
client: client,
code: code,
redirect_uri: redirect_uri,
sub: sub,
scope: scope,
nonce: nonce
}} <-
preauthorize(request),
{:ok, access_token} <-
AccessTokensAdapter.create(
%{
client: client,
redirect_uri: redirect_uri,
previous_code: code.value,
sub: sub,
scope: scope
},
refresh_token: true
),
{:ok, _code} <- CodesAdapter.revoke(code) do
case String.match?(scope, ~r/#{Scope.openid().name}/) do
true ->
id_token = IdToken.generate(%{token: access_token}, nonce)

{:ok, %{token: access_token, id_token: id_token}}

false ->
{:ok, %{token: access_token}}
end
end
end
end

defimpl Boruta.Oauth.Authorization, for: Boruta.Oauth.TokenRequest do
alias Boruta.AccessTokensAdapter
alias Boruta.Oauth.Authorization
Expand Down
26 changes: 26 additions & 0 deletions lib/boruta/oauth/json/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,32 @@ defmodule Boruta.Oauth.Json.Schema do
|> Schema.resolve()
end

def preauthorization_code do
%{
"type" => "object",
"properties" => %{
"grant_type" => %{"type" => "string", "pattern" => "urn:ietf:params:oauth:grant-type:pre-authorized_code"},
"client_id" => %{
"type" => "string",
"pattern" => @uuid_pattern
},
"client_authentication" => %{
"type" => "object",
"properties" => %{
"type" => %{"type" => "string", "pattern" => "^(basic|post|jwt)$"},
"value" => %{"type" => ["string", "null"]}
},
"required" => ["type", "value"]
},
"pre-authorized_code" => %{"type" => "string"},
"redirect_uri" => %{"type" => "string"},
"code_verifier" => %{"type" => "string"}
},
"required" => ["grant_type", "pre-authorized_code", "client_id", "redirect_uri"]
}
|> Schema.resolve()
end

def token do
%{
"type" => "object",
Expand Down
12 changes: 12 additions & 0 deletions lib/boruta/oauth/request/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Boruta.Oauth.Request.Base do
alias Boruta.Oauth.HybridRequest
alias Boruta.Oauth.IntrospectRequest
alias Boruta.Oauth.PasswordRequest
alias Boruta.Oauth.PreauthorizationCodeRequest
alias Boruta.Oauth.PreauthorizedCodeRequest
alias Boruta.Oauth.RefreshTokenRequest
alias Boruta.Oauth.RevokeRequest
Expand Down Expand Up @@ -54,6 +55,17 @@ defmodule Boruta.Oauth.Request.Base do
}}
end

def build_request(%{"grant_type" => "urn:ietf:params:oauth:grant-type:pre-authorized_code"} = params) do
{:ok,
%PreauthorizationCodeRequest{
client_id: params["client_id"],
client_authentication: client_authentication_from_params(params),
preauthorized_code: params["pre-authorized_code"],
redirect_uri: params["redirect_uri"],
code_verifier: params["code_verifier"]
}}
end

def build_request(%{"response_type" => "urn:ietf:params:oauth:response-type:pre-authorized_code"} = params) do
{:ok,
%PreauthorizedCodeRequest{
Expand Down
29 changes: 29 additions & 0 deletions lib/boruta/oauth/requests/preauthorization_code_request.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Boruta.Oauth.PreauthorizationCodeRequest do
@moduledoc """
Preauthorization code request
"""

@typedoc """
Type representing an authorization code request following the pre-authorized code flow as stated in [OpenID for Verifiable Credential Issuance - draft 12](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html).
"""
@type t :: %__MODULE__{
client_id: String.t(),
client_authentication: %{
type: String.t(),
value: String.t()
},
redirect_uri: String.t(),
preauthorized_code: String.t(),
grant_type: String.t(),
code_verifier: String.t()
}
@enforce_keys [:client_id, :redirect_uri, :preauthorized_code]
defstruct client_id: nil,
client_authentication: nil,
redirect_uri: nil,
preauthorized_code: nil,
grant_type: "authorization_code",
code_verifier: ""
end
6 changes: 5 additions & 1 deletion lib/boruta/oauth/responses/credential_offer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ defmodule Boruta.Openid.CredentialOfferResponse do
}, %PreauthorizedCodeRequest{
resource_owner: resource_owner
}) do
credentials = Enum.flat_map(resource_owner.authorization_details, fn detail ->
detail["credential_definition"]["type"]
end)
|> Enum.uniq()
%__MODULE__{
credential_issuer: Config.issuer(),
credentials: resource_owner.available_credentials,
credentials: credentials,
grants: %{
"urn:ietf:params:oauth:grant-type:pre-authorized_code" => %{
"pre-authorized_code" => preauthorized_code.value
Expand Down
4 changes: 4 additions & 0 deletions lib/boruta/oauth/schemas/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ defmodule Boruta.Oauth.Client do
Enum.member?(supported_grant_types, "authorization_code")
end

def grant_type_supported?(%__MODULE__{supported_grant_types: supported_grant_types}, "preauthorization_code") do
Enum.member?(supported_grant_types, "preauthorized_code")
end

def grant_type_supported?(%__MODULE__{supported_grant_types: supported_grant_types}, grant_type) do
Enum.member?(supported_grant_types, grant_type)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/boruta/oauth/schemas/resource_owner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ defmodule Boruta.Oauth.ResourceOwner do
"""

@enforce_keys [:sub]
defstruct sub: nil, username: nil, last_login_at: nil, extra_claims: %{}, available_credentials: []
defstruct sub: nil, username: nil, last_login_at: nil, extra_claims: %{}, authorization_details: []

@type t :: %__MODULE__{
sub: String.t(),
username: String.t() | nil,
last_login_at: DateTime.t() | nil,
extra_claims: Boruta.Oauth.IdToken.claims(),
available_credentials: list(String.t())
authorization_details: list(map())
}
end
14 changes: 14 additions & 0 deletions lib/boruta/oauth/validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ defmodule Boruta.Oauth.Validator do
end
end

def validate(:token, %{"grant_type" => "urn:ietf:params:oauth:grant-type:pre-authorized_code"} = params) do
case ExJsonSchema.Validator.validate(
apply(Schema, :preauthorization_code, []),
params,
error_formatter: BorutaFormatter
) do
:ok ->
{:ok, params}

{:error, errors} ->
{:error, "Request body validation failed. " <> Enum.join(errors, " ")}
end
end

def validate(:token, %{"grant_type" => _} = params) do
case ExJsonSchema.Validator.validate(
Schema.grant_type(),
Expand Down
Loading

0 comments on commit df2fe09

Please sign in to comment.