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)

OvermindDL1

OvermindDL1

I’m exceptionally curious, “what” are you trying to accomplish? o.O

BartOtten

BartOtten

An insane experiment to build a non plug-based authorization code which can easily applied on existing modules, functions, con(n/text) and even arguments. In Phoenix applications authorization is quite easy as Phoenix uses conventions and does normalize a lot to (conn, object map, current_user). Custom applications don’t have such thing. I doubt I will even succeed, but it’s worth to spend a week of my free time on as I learn a lot :slight_smile:

Had a working POC which needed environment variables to work; which would make it useless. If I can take those out, it might actually be a nice solution to secure an otherwise insecure codebase without much rewrites.

  @doc """
  Replace a function definition, adding a call to the authorization function.
  Example:
      defprot function(arg1, arg2), do: IO.inspect({arg1,arg2})
      function(1,2)

  will call authorize with this parameters

      authorizate(__MODULE__, :function, [arg1: 1, arg2: 2], nil)

  """

If there is a solid way to track which Module and Function did the request, it will also be possible to pattern match at those. Allowing (for example) to mimic defp (just because we can…) or reject all calls to functions which write to the database unless the authorization rules allow them to.

Edit: If I ever find a good solution, I will try to have it added to https://github.com/arjan/decorator, as that lib is doing 90% of what I wrote already. Now only those few last bits…

Edit2: Gotta get some sleep…sigh

Where Next?

Popular in Questions Top

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
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
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
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
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
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
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

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement