Code.eval_something

In a function I would like to execute some elixircode that resides in json. How can I do that?
The from the map extracted string with elixircode is f.e.:

"if body[\"voornaam\"] != \"Stefan\" || body[\"achternaam\"] == \"Houtzager\", do: message = message <> \"First name must be Stefan, last name Houtzager<br>\"\nif body[\"age\"] < 56 && body[\"woonplaats\"] == \"Utrecht\", do: message = message <> \"Age should be higher than 55 for citizens of Utrecht<br>\""

body could be

%{"achternaam" => "Houtzager", "age" => 36, "emailadres" => "w.h@x.com",
  "geboortedatum" => "1981-01-03T23:00:00.000Z", "huisnummer" => "13",
  "postcode" => "1232ES", "straat" => "Brouwerstraat", "voornaam" => "Wim",
  "woonplaats" => "Utrecht"}
3 Likes

I believe you just need Code.eval_string(str, [body: body]). Do note that evaluating code from some external source is almost always a bad idea (it is very insecure to execute arbitrary code like this).

Example:

iex> str = "if body[\"voornaam\"] != \"Stefan\" || body[\"achternaam\"] == \"Houtzager\", do: message = message <> \"First name must be Stefan, last name Houtzager<br>\"\nif body[\"age\"] < 56 && body[\"woonplaats\"] == \"Utrecht\", do: message = message <> \"Age should be higher than 55 for citizens of Utrecht<br>\""
iex> body = %{"achternaam" => "Houtzager", "age" => 36, "emailadres" => "w.h@x.com",
  "geboortedatum" => "1981-01-03T23:00:00.000Z", "huisnummer" => "13",
  "postcode" => "1232ES", "straat" => "Brouwerstraat", "voornaam" => "Wim",
  "woonplaats" => "Utrecht"}
iex> your_message = "This variable is used by above snippet as well."
iex> Code.eval_string(str, [body: body, message: your_message])
warning: the variable "message" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

Unsafe variable found at:
  nofile:2

{"This variable is used by above snippet as wellFirst name must be Stefan, last name Houtzager<br>Age should be higher than 55 for citizens of Utrecht<br>",
 [body: %{"achternaam" => "Houtzager", "age" => 36, "emailadres" => "w.h@x.com",
    "geboortedatum" => "1981-01-03T23:00:00.000Z", "huisnummer" => "13",
    "postcode" => "1232ES", "straat" => "Brouwerstraat", "voornaam" => "Wim",
    "woonplaats" => "Utrecht"},
  message: "This variable is used by above snippet as wellFirst name must be Stefan, last name Houtzager<br>Age should be higher than 55 for citizens of Utrecht<br>"]}

Note that the snippet of code you have also generates some warnings. Storing code inside e.g. a database also makes it impossible to refactor it later, so I would strongly advise against it.

3 Likes

Great, thanks. I missed the second input parameter in my own tests. The code that is evaluated is edited in a formbuilder. For single field validations Iā€™m using javascript for the more complex validations. These are executed on the client, after submit they are executed on the server via GitHub - devinus/execjs: Run JavaScript code from Elixir also. F.e. (visible URL not reachable via internet btw):

For validations of combinations of fields Iā€™m using a textfield component (should become a special component later) wherein you can edit elixircode:


These are executed on the server only of course, after submit of the form.
Would you advise against the practise after this explanation?

1 Like

I would still advise against itā€¦ Any form of running externally submitted code ā€˜inā€™ the server itself (and not in a sandbox like LUA) will be a security issue at some point in my experience. Either make a DSEL (as Iā€™ve done) or use erlua or something, but I would never ever recommend executing external code directly on the BEAMā€¦

3 Likes

When you give users too much freedom in the language they can use on the front-end, you will end up with snippets of code that are tightly coupled with the old version of your application, making it very hard to update it without breaking existing code. Especially if you plan on distributing your application to companies, where each company might have their own validation snippets. Youā€™ll have created a situation that is impossible to test.

In this case, it seems like the code that users should be able to write should be restricted to validation logic. So indeed, it seems to me, just like @OvermindDL1 suggested, that you want to work with a small domain-specific language that is explicitly not Turing complete (See Surprisingly Turing Complete why this poses a problem).

3 Likes

As for a practical solution to your problem:

  • You could use Code.string_to_quoted to transform an arbitrary, user-entered string into a piece of Elixir AST.
  • You could then perform a Macro.postwalk to make sure that every piece of AST is part of a very limited whitelist of allowed Elixir functions and variable names.
  • Finally, if and only if the code only contains allowed functions, it is executed.
5 Likes

Thanks, also overmind. I will take a further look at your suggestions and links. Of course not every developer should be able to edit these js / elixir validations, authorization could help also.

1 Like

A nice thing about a DSEL is that you could generate validations both server-side and client-side from them, safely.

2 Likes

Any good reads about these Domain-Specific Embedded Languages (never heard of them)? I could also just use js for all validation logic, but that would make f.e. db lookups at least a complicated thing to think about.
Any problem with executing js validations on the server btw? I donā€™t have a webdev background, sorry. :wink:

1 Like

Heh, it is basically where you make up your own language to do something for a specific tasks. Like for doing validations Iā€™d probably just make it json so easy to parse on both client and server, purely data declarative, so maybe something like this from your first post:

[
  { "type":"equal", "values":[
    {"type":"params", "val":"voornaam"},
    {"type":"const", "val":"Stefan"}],
    "error_message":"First name must be Stefan"},
  { "type":"equal", "values":[
    {"type":"params", "val":"achternaam"},
    {"type":"const", "val":"Houtzager"}],
    "error_message":"Last name must be Houtzager"},
  { "type":"lessthan", "values":[
    {"type":"params", "val":"age"},
    {"type":"const", "val":"56"}],
    "error_message":"Age must be less than 56"},
  { "type":"equal", "values":[
    {"type":"params", "val":"woonplaats"},
    {"type":"const", "val":"Utrecht"}],
    "error_message":"Must be citizen of Utrecht"}
]

Or something like that. This example is verbose because it is JSON, but you do not need to build a parser, instead you just pass it around a few calls along with the data to work over and do the comparisons, almost stupid-easy to write in Elixir and Javascript both. :slight_smile:

Many, anything turing complete can do nasty things, even if it is not able to access other things.

2 Likes

I find the idea of using a DSL for validations very attractive. I will put it on my todo list. JSON is probably not the best fit: ā€œwhile some frameworks provide an XML or JSON [3] syntax for defining the validation rules, because of the limitations in semantics of these languages, they can be used only for simple casesā€ (https://www.cas.mcmaster.ca/~carette/publications/FedorenkoThesis.pdf , title of the thesis is ā€œVALIDATION DSL FOR CLIENT-SERVER APPLICATIONSā€. Iā€™ll create a separate thread for this one as the thesis contains interesting ideas).

3 Likes

To expand a little on this, by the way, since it was not immediately evident to me when I first heard about it:

When a language is Turing Complete, it can compute anything that can be computed by any other programming language (or other computer system). This means that:

  • Instead of writing code that runs validations, someone can write code that does the wildest, unintended things, like mining Bitcoins. This code is run on your server, so you pay for the computation, while the writer harvests the results.
  • Someone can, accidentally or purposefully, write code that loops indefinitely. It is impossible to create some kind of filter to prevent this from happening because of the Halting Problem. The only thing you could do is to put some upper-bound restriction on running time, but this will also reject proper programs that would finish.
2 Likes

I think a DSL in my case would be ideal. The form builder is part of my BPMS, workflows, business rules and forms should be maintainable by ā€œcitizen developersā€ mostly. Javascript and for sure elixir validations do not fit in this ideal. But it would cost me some time to find (no f.e. OMG standard for that exists I think) / develop a dsl and a translator to js / elixir. For the time being I keep the js/elixir validations, itā€™s a learning project for me. Maybe authorization (elixir devs only that can screw the application anyway) on the custom validation fields where you can edit js/elixir could provide a temporary solution. Of course authorization on the modeler as a whole is needed, but it could be more fine-grained. For simple validations like required, min/max values/lengths, regex expressions etc. on single fields the dsl is not needed.
There is a lot to say about validations, see the thesis I sent https://www.cas.mcmaster.ca/~carette/publications/FedorenkoThesis.pdf , in lots of applications validations seem to be poorly implemented.

2 Likes

For note, I only suggested JSON for storing validations in a format for both Elixir and the front-end to use, to actually define a set of validators Iā€™d make a better looking front-end for it, like I did with my permissions system (which also stores via JSON but can do very complex validations like you are wanting your validators to do ^.^).

2 Likes