Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensible AccessGrants and AccessTokens #81

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions lib/ex_oauth2_provider/access_grants/access_grant.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,33 @@ defmodule ExOauth2Provider.AccessGrants.AccessGrant do
]
end

@doc false
def access_grant_allowed_fields do
[:redirect_uri, :expires_in, :scopes]
end

@doc false
def access_grant_required_fields do
[:redirect_uri, :expires_in, :token, :resource_owner, :application]
end

@doc false
def access_grant_request_fields do
["redirect_uri", "scope"]
end

defmacro __using__(config) do
quote do
use ExOauth2Provider.Changeset
use ExOauth2Provider.Schema, unquote(config)

import unquote(__MODULE__), only: [access_grant_fields: 0]
import unquote(__MODULE__),
only: [
access_grant_fields: 0,
access_grant_allowed_fields: 0,
access_grant_required_fields: 0,
access_grant_request_fields: 0
]
end
end

Expand All @@ -61,18 +83,18 @@ defmodule ExOauth2Provider.AccessGrants.AccessGrant do
end

alias Ecto.Changeset
alias ExOauth2Provider.{Mixin.Scopes, Utils}
alias ExOauth2Provider.{Config, Mixin.Scopes, Utils}

@spec changeset(Ecto.Schema.t(), map(), keyword()) :: Changeset.t()
def changeset(grant, params, config) do
grant
|> Changeset.cast(params, [:redirect_uri, :expires_in, :scopes])
|> Changeset.cast(params, Config.access_grant(config).allowed_fields())
|> Changeset.assoc_constraint(:application)
|> Changeset.assoc_constraint(:resource_owner)
|> put_token()
|> Scopes.put_scopes(grant.application.scopes, config)
|> Scopes.validate_scopes(grant.application.scopes, config)
|> Changeset.validate_required([:redirect_uri, :expires_in, :token, :resource_owner, :application])
|> Changeset.validate_required(Config.access_grant(config).required_fields())
|> Changeset.unique_constraint(:token)
end

Expand Down
27 changes: 25 additions & 2 deletions lib/ex_oauth2_provider/access_tokens/access_token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,33 @@ defmodule ExOauth2Provider.AccessTokens.AccessToken do
]
end

@doc false
def access_token_allowed_fields do
[:expires_in, :scopes]
end

@doc false
def access_token_required_fields do
[:expires_in]
end

@doc false
def access_token_request_fields do
[]
end

defmacro __using__(config) do
quote do
use ExOauth2Provider.Changeset
use ExOauth2Provider.Schema, unquote(config)

import unquote(__MODULE__), only: [access_token_fields: 0]
import unquote(__MODULE__),
only: [
access_token_fields: 0,
access_token_allowed_fields: 0,
access_token_required_fields: 0,
access_token_request_fields: 0
]
end
end

Expand All @@ -71,13 +93,14 @@ defmodule ExOauth2Provider.AccessTokens.AccessToken do
server_scopes = server_scopes(token)

token
|> Changeset.cast(params, [:expires_in, :scopes])
|> Changeset.cast(params, Config.access_token(config).allowed_fields())
|> validate_application_or_resource_owner()
|> put_previous_refresh_token(params[:previous_refresh_token])
|> put_refresh_token(params[:use_refresh_token])
|> Scopes.put_scopes(server_scopes, config)
|> Scopes.validate_scopes(server_scopes, config)
|> put_token(config)
|> Changeset.validate_required(Config.access_token(config).required_fields())
end

defp server_scopes(%{application: %{scopes: scopes}}), do: scopes
Expand Down
14 changes: 14 additions & 0 deletions lib/ex_oauth2_provider/changeset.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule ExOauth2Provider.Changeset do
@moduledoc """
This module defines behaviour for oauth changesets
"""
@callback allowed_fields() :: nonempty_list(atom())
@callback required_fields() :: nonempty_list(atom())
@callback request_fields() :: nonempty_list(binary())

defmacro __using__(_) do
quote do
@behaviour ExOauth2Provider.Changeset
end
end
end
17 changes: 13 additions & 4 deletions lib/ex_oauth2_provider/oauth2/authorization/strategy/code.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ defmodule ExOauth2Provider.Authorization.Code do
end

defp reissue_grant({:error, params}, _config), do: {:error, params}
defp reissue_grant({:ok, %{access_token: _access_token} = params}, config), do: issue_grant({:ok, params}, config)
defp reissue_grant({:ok, %{access_token: access_token} = params}, config) do
params =
access_token
|> Map.take(Config.access_token(config).allowed_fields())
|> Map.merge(params)
issue_grant({:ok, params}, config)
end
defp reissue_grant({:ok, params}, _config), do: {:ok, params}

@doc """
Expand Down Expand Up @@ -142,18 +148,21 @@ defmodule ExOauth2Provider.Authorization.Code do
defp issue_grant({:ok, %{resource_owner: resource_owner, client: application, request: request} = params}, config) do
grant_params =
request
|> Map.take(["redirect_uri", "scope"])
|> Map.take(Config.access_grant(config).request_fields())
|> Map.merge(Map.take(params, Config.access_grant(config).allowed_fields()))
|> Map.new(fn {k, v} ->
case k do
"scope" -> {:scopes, v}
_ -> {String.to_atom(k), v}
"" <> _ -> {String.to_atom(k), v}
_ -> {k, v}
end
end)
|> Map.put(:expires_in, Config.authorization_code_expires_in(config))

case AccessGrants.create_grant(resource_owner, application, grant_params, config) do
{:ok, grant} -> {:ok, Map.put(params, :grant, grant)}
{:error, error} -> Error.add_error({:ok, params}, error)
{:error, {:error, _, _} = error} -> Error.add_error({:ok, params}, error)
{:error, _} -> Error.add_error({:ok, params}, Error.server_error())
end
end

Expand Down
38 changes: 29 additions & 9 deletions lib/ex_oauth2_provider/oauth2/token/strategy/authorization_code.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,23 @@ defmodule ExOauth2Provider.Token.AuthorizationCode do
end

defp issue_access_token_by_grant({:error, params}, _config), do: {:error, params}
defp issue_access_token_by_grant({:ok, %{access_grant: access_grant, request: _} = params}, config) do
token_params = %{use_refresh_token: Config.use_refresh_token?(config)}

result = Config.repo(config).transaction(fn ->
access_grant
|> revoke_grant(config)
|> maybe_create_access_token(token_params, config)
end)
defp issue_access_token_by_grant(
{:ok, %{access_grant: access_grant, request: request} = params},
config
) do
token_params =
request
|> Map.take(Config.access_token(config).request_fields())
|> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
|> Map.put(:use_refresh_token, Config.use_refresh_token?(config))

result =
Config.repo(config).transaction(fn ->
access_grant
|> revoke_grant(config)
|> maybe_create_access_token(token_params, config)
end)

case result do
{:ok, {:error, error}} -> Error.add_error({:ok, params}, error)
Expand All @@ -57,8 +66,19 @@ defmodule ExOauth2Provider.Token.AuthorizationCode do
do: AccessGrants.revoke(access_grant, config)

defp maybe_create_access_token({:error, _} = error, _token_params, _config), do: error
defp maybe_create_access_token({:ok, %{resource_owner: resource_owner, application: application, scopes: scopes}}, token_params, config) do
token_params = Map.merge(token_params, %{scopes: scopes, application: application})

defp maybe_create_access_token(
{:ok,
%{resource_owner: resource_owner, application: application, scopes: scopes} = grant},
token_params,
config
) do
token_params =
grant
|> Map.drop([:expires_in, :scopes])
|> Map.take(Config.access_token(config).allowed_fields())
|> Map.merge(token_params)
|> Map.merge(%{scopes: scopes, application: application})

resource_owner
|> AccessTokens.get_token_for(application, scopes, config)
Expand Down
38 changes: 27 additions & 11 deletions lib/ex_oauth2_provider/oauth2/token/strategy/refresh_token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,29 @@ defmodule ExOauth2Provider.Token.RefreshToken do
defp load_access_token_by_refresh_token(params, _config), do: Error.add_error(params, Error.invalid_request())

defp issue_access_token_by_refresh_token({:error, params}, _config), do: {:error, params}
defp issue_access_token_by_refresh_token({:ok, %{refresh_token: refresh_token, request: _} = params}, config) do
result = Config.repo(config).transaction(fn ->
token_params = token_params(refresh_token, config)

refresh_token
|> revoke_access_token(config)
|> case do
{:ok, %{resource_owner: resource_owner}} -> AccessTokens.create_token(resource_owner, token_params, config)
{:error, error} -> {:error, error}
end
end)
defp issue_access_token_by_refresh_token(
{:ok, %{refresh_token: refresh_token, request: request} = params},
config
) do
result =
Config.repo(config).transaction(fn ->
token_params =
request
|> Map.take(Config.access_token(config).request_fields())
|> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
|> Map.merge(token_params(refresh_token, config))

refresh_token
|> revoke_access_token(config)
|> case do
{:ok, %{resource_owner: resource_owner}} ->
AccessTokens.create_token(resource_owner, token_params, config)

{:error, error} ->
{:error, error}
end
end)

case result do
{:ok, {:error, error}} -> Error.add_error({:ok, params}, error)
Expand All @@ -69,7 +81,11 @@ defmodule ExOauth2Provider.Token.RefreshToken do
end

defp token_params(%{scopes: scopes, application: application} = refresh_token, config) do
params = %{scopes: scopes, application: application, use_refresh_token: true}
params =
refresh_token
|> Map.drop([:expires_in, :scopes])
|> Map.take(Config.access_token(config).allowed_fields())
|> Map.merge(%{scopes: scopes, application: application, use_refresh_token: true})

case Config.refresh_token_revoked_on_use?(config) do
true -> Map.put(params, :previous_refresh_token, refresh_token)
Expand Down
30 changes: 24 additions & 6 deletions lib/mix/ex_oauth2_provider/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,40 @@ defmodule Mix.ExOauth2Provider.Schema do
<%= if schema.binary_id do %>
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id<% end %>
schema <%= inspect schema.table %> do
<%= schema.macro_fields %>()
schema <%= inspect schema.table_name %> do
<%= schema.table %>_fields()

timestamps()
end<%= if schema.changeset do %>

@impl ExOauth2Provider.Changeset
def allowed_fields do
<%= schema.table %>_allowed_fields()
end

@impl ExOauth2Provider.Changeset
def required_fields do
<%= schema.table %>_required_fields()
end

@impl ExOauth2Provider.Changeset
def request_fields do
<%= schema.table %>_request_fields()
end<% end %>
end
"""

alias ExOauth2Provider.{AccessGrants.AccessGrant, AccessTokens.AccessToken, Applications.Application}

@schemas [{"application", Application}, {"access_grant", AccessGrant}, {"access_token", AccessToken}]
@schemas [
{"application", Application, false},
{"access_grant", AccessGrant, true},
{"access_token", AccessToken, true}
]

@spec create_schema_files(atom(), binary(), keyword()) :: any()
def create_schema_files(context_app, namespace, opts) do
for {table, schema} <- @schemas do
for {table, schema, changeset} <- @schemas do
app_base = Config.app_base(context_app)
table_name = "#{namespace}_#{table}s"
context = Macro.camelize(table_name)
Expand All @@ -34,8 +53,7 @@ defmodule Mix.ExOauth2Provider.Schema do
module = Module.concat([app_base, context, module])
binary_id = Keyword.get(opts, :binary_id, false)
macro = schema
macro_fields = "#{table}_fields"
content = EEx.eval_string(@template, schema: %{module: module, table: table_name, binary_id: binary_id, macro: macro, macro_fields: macro_fields}, otp_app: context_app)
content = EEx.eval_string(@template, schema: %{module: module, table: table, table_name: table_name, binary_id: binary_id, macro: macro, changeset: changeset}, otp_app: context_app)
dir = "lib/#{context_app}/#{Macro.underscore(context)}/"

File.mkdir_p!(dir)
Expand Down
15 changes: 15 additions & 0 deletions test/support/lib/dummy/oauth_access_grants/oauth_access_grant.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,19 @@ defmodule Dummy.OauthAccessGrants.OauthAccessGrant do
access_grant_fields()
timestamps()
end

@impl ExOauth2Provider.Changeset
def allowed_fields do
access_grant_allowed_fields()
end

@impl ExOauth2Provider.Changeset
def required_fields do
access_grant_required_fields()
end

@impl ExOauth2Provider.Changeset
def request_fields do
access_grant_request_fields()
end
end
15 changes: 15 additions & 0 deletions test/support/lib/dummy/oauth_access_tokens/oauth_access_token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,19 @@ defmodule Dummy.OauthAccessTokens.OauthAccessToken do
access_token_fields()
timestamps()
end

@impl ExOauth2Provider.Changeset
def allowed_fields do
access_token_allowed_fields()
end

@impl ExOauth2Provider.Changeset
def required_fields do
access_token_required_fields()
end

@impl ExOauth2Provider.Changeset
def request_fields do
access_token_request_fields()
end
end