1

There's a design pattern of sorts that I've thought about but haven't seen implemented where every function / method passes around a permissions object. I'm wondering whether using it in a project is a good idea or not. The key ideas here are that 1) capabilities are only ever taken away, not added (unless you're in some kind of entry point) and 2) don't inspect the contents of the permissions object except to assert that you have whatever specific capability is needed before doing something.

Most mainstream languages besides Haskell have nothing resembling an effect system, which is really what I'm trying to emulate.

The goal is that the specific fields in the permissions type will be meaningful for the application. It seems likely that the permissions that are meaningful to track will change more slowly than the rest of the application.

For example,

struct permission {
    bool can_write_db;
    bool can_create_tempfile;
    bool can_delete_tempfile;
};
typedef struct permission permission;

And then inside a function that writes to the database you might do the following

void commit_transaction(permission p, X x, Y y) 
{
    assert(p.can_write_db);
    ...
    do_something_else(p, 47);
    return;
}

with do_something_else being another function that gets handed the permissions object.

As for (2), the following example would violate the rule against inspecting the contents of the permissions object.

// BAD
void possibly_commit_transaction(permission p)
{
    if (p.can_write_db) {
        // commit the transaction
    } else {
        // write a description of the transaction to a log file
    }
}

The permissions object isn't intended to be used like the other parameters, being handed the wrong thing indicates that the program has a bug in it.

Would it be worth going to the trouble of using something like this? Would this thing be likely to carry its own weight?

P.S.

The goal of this is expressly not security or hardening the application.

  • I would argue not for the simple fact that each method written like this has to deal with every possible scenario of permissions, leading to bulky awkward methods. It makes more sense to associate each action to a list of permissions which must be true to use it, and when the action is called, check all permissions involved and allow or deny. – Neil May 23 '18 at 9:41
  • You mention that the goal isn't security or hardening, but what exactly is the goal? – Sean Burton May 23 '18 at 16:31
  • @SeanBurton The goal is to make it easier to think about which paths through the application have access to which resources and to catch bugs earlier, particularly in large projects that are being modified by developers without an understanding of the whole thing. In languages like Haskell you know from the type of (the return value of) a function whether it's pure or potentially has side effects. That's a very coarse-grained distinction, but the idea is similar. – Gregory Nisbet May 23 '18 at 16:43
2

The problem here is that the permissions are checked 100% at runtime, and need to be checked manually. It is easy to forget these checks. If you omit them, your code will still work.

Usually, the problem your permissions address is solved by objects. That is: the function is unable to perform its actions unless an object or callback providing those actions is given to it (as a parameter).

struct DbWriter;
void DbWriter_write(DbWriter*, Value);

struct Tempfile;
typedef Tempfile* (CreateTempfile)(const char* name);
typedef void (RemoveTempfile)(Tempfile*);

void commit_transaction(
        DbWriter* writer,
        CreateTempfile* mktemp,
        RemoveTempfile* rmtemp,
        X x,
        Y y)
{
    assert(writer);  // must be non-null
    assert(mktemp);
    assert(rmtemp);
    Tempfile* t = mktemp("foo.txt");
    DbWriter_write(writer, foo(x));
    DbWriter_write(writer, bar(y));
    do_something_else(rmtemp, t, 47);
}

The problem here is that you will get many parameters for directly or indirectly needed permissions, so it can still make sense to bundle them into a parameter object – but now the need to assert that they must be non-null is less obvious.

Let me be clear: while the question is technically language-agnostic, C is a very frustrating language for this kind of design. Even if you only move to C++, you get two major features that simplify this: first-class support for object syntax, and references which must be non-null, so the asserts become unnecessary.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.