Current section

Files

Jump to
charon_oauth2 lib charon_oauth2 internal gen_mod grants.ex
Raw

lib/charon_oauth2/internal/gen_mod/grants.ex

defmodule CharonOauth2.Internal.GenMod.Grants do
@moduledoc false
def generate(schemas_and_contexts, repo) do
quote location: :keep,
generated: true,
bind_quoted: [schemas_and_contexts: schemas_and_contexts, repo: repo] do
@moduledoc """
Context to manage grants
"""
require Logger
alias CharonOauth2.Internal
alias Ecto.Changeset
import Ecto.Query, only: [from: 2, where: 3, limit: 2, offset: 2, order_by: 2]
import Internal
@grant_schema schemas_and_contexts.grant
@repo repo
@doc """
Get a single grant by one or more clauses, optionally with preloads.
Returns nil if Grant cannot be found.
## Doctests
iex> grant = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> %Grant{} = Grants.get_by(id: grant.id)
iex> nil = Grants.get_by(id: grant.id + 1)
# preloads things
iex> grant = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> %{authorization: %{client: %{id: _}}} = Grants.get_by([id: grant.id], Grant.supported_preloads)
# a grant can be retrieved by its code (actually by the HMAC of its code)
iex> %{id: id, code: code} = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> ^id = Grants.get_by(code: code).id
"""
@spec get_by(keyword | map, [@grant_schema.resolvable]) :: @grant_schema.t() | nil
def get_by(clauses, preloads \\ []) do
preloads |> @grant_schema.preload() |> @repo.get_by(clauses)
end
@doc """
Get a list of all oauth2 grants.
## Doctests
iex> insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> [%Grant{}] = Grants.all()
# can be filtered
iex> grant = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> [%Grant{}] = Grants.all(%{authorization_id: grant.authorization_id})
iex> [%Grant{}] = Grants.all(%{code: grant.code})
iex> [] = Grants.all(%{authorization_id: grant.authorization_id + 1})
"""
@spec all(%{required(atom) => any}, [@grant_schema.resolvable]) :: [@grant_schema.t()]
def all(filters \\ %{}, preloads \\ []) do
base_query = @grant_schema.preload(preloads)
filters
|> Enum.reduce(base_query, fn
{:id, v}, q -> where(q, [g], g.id == ^v)
{:code, v}, q -> where(q, [g], g.code == ^v)
{:redirect_uri, v}, q -> where(q, [g], g.redirect_uri == ^v)
{:type, v}, q -> where(q, [g], g.type == ^v)
{:authorization_id, v}, q -> where(q, [g], g.authorization_id == ^v)
{:limit, v}, q -> limit(q, ^v)
{:offset, v}, q -> offset(q, ^v)
{:order_by, v}, q -> order_by(q, ^v)
{k, _v}, _ -> raise "can't filter grant query by #{k}"
end)
|> @repo.all()
end
@doc """
Insert a new grant
## Examples / doctests
# succesfully creates a grant
iex> {:ok, _} = insert_test_grant(resource_owner_id: insert_test_user().id)
iex> Grants.insert(%{}) |> errors_on()
%{authorization_id: ["can't be blank"], type: ["can't be blank"], resource_owner_id: ["can't be blank"]}
# authorization must exist
iex> insert_test_grant(resource_owner_id: insert_test_user().id, authorization_id: -1) |> errors_on()
%{authorization: ["does not exist"]}
# resource owner must exist and must match the authorization's owner
iex> user1 = insert_test_user().id
iex> user2 = insert_test_user().id
iex> insert_test_grant(resource_owner_id: user1, authorization_id: insert_test_authorization!(resource_owner_id: user2).id) |> errors_on()
%{authorization_id: ["belongs to other resource owner"]}
# type must be one of client grant_type's
iex> client = insert_test_client!(owner_id: insert_test_user().id, grant_types: ~w(refresh_token))
iex> authorization = insert_test_authorization!(resource_owner_id: insert_test_user().id, client_id: client.id)
iex> insert_test_grant(authorization_id: authorization.id) |> errors_on()
%{type: ["not supported by client"]}
# redirect_uri must be one of client redirect_uri's
iex> insert_test_grant(resource_owner_id: insert_test_user().id, redirect_uri: "https://boom") |> errors_on()
%{redirect_uri: ["does not match client"]}
# redirect_uri is required if client has multiple uris set
iex> client = insert_test_client!(owner_id: insert_test_user().id, redirect_uris: ~w(https://a https://b))
iex> authorization = insert_test_authorization!(resource_owner_id: insert_test_user().id, client_id: client.id)
iex> insert_test_grant(authorization_id: authorization.id, redirect_uri: nil) |> errors_on()
%{redirect_uri: ["can't be blank"]}
iex> insert_test_grant(authorization_id: authorization.id, redirect_uri: "https://c") |> errors_on()
%{redirect_uri: ["does not match client"]}
"""
@spec insert(map) :: {:ok, @grant_schema.t()} | {:error, Changeset.t()}
def insert(params), do: params |> @grant_schema.insert_only_changeset() |> @repo.insert()
@doc """
Delete a grant.
## Examples / doctests
# grant must exist
iex> {:error, :not_found} = Grants.delete(id: -1)
# succesfully deletes a grant
iex> grant = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> {:ok, _} = Grants.delete([id: grant.id])
iex> {:error, :not_found} = Grants.delete([id: grant.id])
"""
@spec delete(@grant_schema.t() | keyword | map) ::
{:ok, @grant_schema.t()} | {:error, :not_found}
def delete(grant = %@grant_schema{}), do: @repo.delete(grant)
def delete(clauses) do
get_and_do(fn -> get_by(clauses) end, &delete/1, @repo)
end
@doc """
Delete all grants older than the configured `grant_ttl`.
## Examples / doctests
iex> valid = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> expired = insert_test_grant!(resource_owner_id: insert_test_user().id)
iex> past = DateTime.utc_now() |> DateTime.add(-10)
iex> from(t in Grant, where: t.id == ^expired.id) |> Repo.update_all(set: [expires_at: past])
iex> Grants.delete_expired()
iex> valid_id = valid.id
iex> [%{id: ^valid_id}] = Grants.all()
"""
@spec delete_expired() :: {integer, nil}
def delete_expired() do
from(g in @grant_schema, where: g.expires_at < ago(0, "second"))
|> @repo.delete_all()
|> tap(fn {n, _} -> Logger.info("Deleted #{n} expired oauth2_grants") end)
end
end
end
end