BartOtten

BartOtten

How to get name of 'calling' module and/or function?

Currently I am writing a small macro which adds authorization functionality to a function. Rewrite ‘def’ to ‘defprot’ and add the rules to an Authorization module. Too easy it is.

defmodule One do
defprot bar(), do: :result
end

defmodule Two do
def foo(), do: One.bar()
end

In order to write nice debug information and add the possibility to create enforced boundaries between (context) modules, I need the names of the ‘calling’ Function and Module in a macro. Saving them as an environment variable and retrieving them in the macro does work while developing but A: ain’t nice and B: would be useless in case of concurrency.

The output should become:
iex > Two.foo()
[debug] "Two.foo() requested authorization to access One.bar()"

Is there a good way to retrieve the information of the calling Module (Two) and calling Function (foo) while in One.bar()?

Marked As Solved

BartOtten

BartOtten

I cleaned up the code quite a bit, might even try to use it to protect my latest project just to find all the flaws. Here are some details:

def() is overridden so it sends and receives information about the current Module, Fuction and Arity (m/f/a) and wraps the ‘real body’ in a Task so it has it’s own process with inbox. Each function called in the wrapped function will receive a message with the m/f/a that called the function.

Knowing the current m/f/a and the calling m/f/a, a map is created and passed to the authorization function which you write yourself. As it’s a map, it’s easy to do pattern matching! The examples don’t show it, but you are able to protect context (Module) borders, restrict calls to "update_" <> _ for users (for every function of the Module) or giving admins a wildcard with def authorize(%{args: %{context: %{role: :admin}}}), do: :ok

Removed: If a function calls itself (see: get_post/2) this is detected and authorization will be skipped.
Added: Sounds like a rule to me! def authorize(%{mod: mod, func: func, arity: arity, calling_mod: mod, calling_func: func, calling_arity: arity}), do: :ok does the job as expected. Removed the loop detection.

Authorization map:

 auth_map = %{
          calling_mod: calling_mod,
          calling_func: calling_func,
          calling_arity: calling_arity,
          mod: current_mod,
          func: current_func,
          arity: current_arity,
          args: current_args
        }

The rules:

defmodule Main.Authorizer do

  def authorize(auth_map \\ %{})

  # update post rules
  def authorize(%{func: "update_post", args: %{context: %{role: :admin}}}), do: :ok

  def authorize(%{func: "update_post", args: args}) do
    case Main.get_post(args[:id], args[:context]) do
      :unauthorized -> :unauthorized
      post -> post[:author_id] == args[:context][:user_id]  && {:ok, post} || :unauthorized
    end
  end

  # get post rules
  def authorize(%{func: "get_post", args: %{context: %{role: :admin}}}), do: :ok

  def authorize(%{func: "get_post", args: args}) do
    post = Main.get_post(args[:id], args[:context])
    post[:author_id] == args[:context][:user_id]  && {:ok, post} || :unauthorized
  end

  # sink
  def authorize(_), do: :unauthorized
end

The result (User 124 is author of Post 2):

iex(auth@127.0.0.1)1> Main.update_post(2, %{content: "Updated content"}, %{user_id: 124, role: :user})
[info] Main.get_post/2 requested authorization to access Main.get_post/2. [SKIPPED AUTH]
[info] Main.update_post/3 requested authorization to access Main.get_post/2. [ACCESS GRANTED]
[info] An unknown function requested authorization to access Main.update_post/3. Are you using IEX?. [ACCESS GRANTED]
{:ok, %{content: "Updated content"}}
iex(auth@127.0.0.1)2> Main.update_post(2, %{content: "Updated content"}, %{user_id: 123, role: :user})
[info] Main.get_post/2 requested authorization to access Main.get_post/2. [SKIPPED AUTH]
[info] Main.update_post/3 requested authorization to access Main.get_post/2. [ACCESS DENIED]
[info] An unknown function requested authorization to access Main.update_post/3. Are you using IEX?. [ACCESS DENIED]
:unauthorized
iex(auth@127.0.0.1)3> Main.update_post(2, %{content: "Updated content"}, %{user_id: 123, role: :admin})
[info] An unknown function requested authorization to access Main.update_post/3. Are you using IEX?. [ACCESS GRANTED]
{:ok, %{content: "Updated content"}}
iex(auth@127.0.0.1)4>

Also Liked

michalmuskala

michalmuskala

You can use Process.info(self(), :current_stacktrace) to get the current stacktrace (and the calling function should be there), but this is generally considered a debugging utility, not something to be used in production.

Additionally, be aware that tail calls don’t produce stack entries, so in a code like this:

def foo(), do: bar()
def bar(), do: Process.info(self(), :current_stacktrace)

The stacktrace information won’t include foo().

NobbZ

NobbZ

A function should not care from where it is called. It should return the same regardless the caller.

If though you really have to, you could do it similar how logger injects metadata.

Use a macro which takes the arguments defined by your API, injects code to collect metadata and then delegates to a function that takes the actual arguments AND the collected metadata.

BartOtten

BartOtten

__CALLER__ returns the current calling environment as a Macro.Env struct; a struct that holds compile time environment information. That’s why you can’t use it in a quote block.

However, I need run time information. When function Two.foo/1 is calling function One.bar/1 as in the example, the caller should be Two.foo/1 (info might be split in callingMod en callingFunc)

Where Next?

Popular in Questions Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

Other popular topics Top

skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52673 488
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54120 245
New

We're in Beta

About us Mission Statement