7

Abstract (tl;dr)

Please read the full question, this is awfully simplified:
How can unix file permission style restrictions be applied to inter-type data/control flows, allowing fine-grained access to some class-members for some groups of classes?

Background information

If you think about unix file systems and permissions, there is a diverse way of encoding file-access privileges of users (especially if you also consider FACL). For example, if a directory contains 3 files, they could belong to several users, and different other users may have restricted permissions:

-rwxr-xr--  jean-luc  staff    engage.sh
-rw-r-----  william   crew     roster.txt
-rw-------  beverly   beverly  patients.txt

Core idea

As you can see, depending on the groups a particular user is in, different access levels are allowed. For example, crewmembers are allowed to read roster.txt, which belongs to william, but guests who presumably do not belong to crew cannot. More importantly, the group crew can contain many people.

So I was thinking that there is some similarity to access permissions inside object oriented languages like C++ if you think of types (classes) as users. Although a function can only be executed, but not read, the rwx flags represent meaningful descriptions for class members. A data member can be read (r) and written (w) to, perhaps via accessors, while member functions may be executed (x) or not.

However, in C++ and other object oriented languages (I know of), this is more or less an all or nothing thing, if we leave out inheritance for a second; If class William makes his member Txt roster; public, everybody will see it. If he makes it private, nobody except himself will see it. He may add one or more friends, friend JeanLuc; but then they will see all his private members (the equivalent of granting user:jean-luc:rwx to all his files, in FACL lingo).
This is entirely orthogonal to inheritance -- JeanLuc and William are not part of the same hierarchy, they are not related.

So the main idea would be to allow group-based access restrictions, as a generalisation of private/public. Allowing finer grained inter-class access to member functions and member data.

I believe this idiom could help maintainability/readability, as it adds additional facets to restrict interaction permissions. As with operating systems, where this adds an important layer of security to the system, the same familiar pattern could add safety to a C++ project.

Thoughts about representation in C++

However, I'm at a loss of thinking of a good way to represent this. You could decompose William objects into several objects of subtypes: William_Crew, William_William and so forth, representing the respective groups. This seems to be horribly ugly. Another idea could be dedicated types with forwarder functions, representing the individual groups, like this:

class Crew { // group class
  // in this group are:
  friend JeanLuc;
  friend Geordi;
  friend Beverly;
  // ...
  static Txt getRoster(William*);
};
class William {
  friend Crew; // Problem: Crew has full access (rwx)
  Txt roster;
};

But each group would have to be tailored to a particular class to be used with, which would seem to be massively redundant, if the group is used by several users/classes.

Question

The approaches I provided are not great (to put it mildly), and I'm sure they wouldn't work as intended. I'm not sure if this is a novel/stupid/well-known idea, but I wonder how you could implement this with the features provided by the C++ language. Are there objective arguments why this would or would not be useful/helpful?

migrated from stackoverflow.com May 28 '12 at 8:39

This question came from our site for professional and enthusiast programmers.

  • An application is run as a single user, why is this nescessary? – Matthew May 4 '12 at 18:20
  • Regarding the VTCs: I stressed the part, indicating the question's relationship to programming. – bitmask May 4 '12 at 18:21
  • @Matthew: I would like to ask you to reread the question. The users I mention are not system users, it's an analogy! – bitmask May 4 '12 at 18:22
  • Realize that while the language might be modified to provide this, you cannot really enforce it at runtime unless you put the actual data into a security context in which arbitrary (I'll borrow a word and say non-managed) code cannot be run - you need the cooperation of the operating system or a VM. This could for example be implemented with Binder ipc (most familiar for its present use on android), where the actual object to be protected could be in a remote process, but made to act as if it were local. Or you could consider it a code-quality mechanism without hard enforcement. – Chris Stratton May 4 '12 at 18:27
  • 1
    I'm talking about the limitation of language features such as typing - they may help you catch programmer mistakes in code written within that language, but not much more. They won't protect against bugs in the language implementation, or in other code linked in at buildtime, or runtime, or self-modifying code, or in any environment where you can manipulate a pointer to the actual implementing memory – Chris Stratton May 4 '12 at 18:54
2

Rephrasing the problem a bit:

  • I have an instance of MySpecialObject
  • an instance of JLP wants to call the methods of MySpecialObject
  • an instance of BEV wants to read the public data of MySpecialObject
  • the instance of JLP shouldn't be able to read public data and the BEV shouldn't be able to call member functions

This should be as type-safe as possible.

A solution I see involves wrappers :)

  • you write the code for MySpecialObject as you would normally, ignoring this extra requirement
  • you write a wrapper for each aspect of your class you wish to restrict. Say, MySpecialObject_Read and MySpecialObject_Execute.
  • these wrappers forward the requests (method calls, getter/setters) to an underlying shared_ptr of MySpecialObject
  • your MySpecialObject, MySpecialObject_Read and MySpecialObject_Execute classes each have (as needed) a "convert to ..." method. This method returns an appropriate wrapper over the underlying MySpecialObject

This solution provides a type-safe accessor with the desired limitations.

Each client class can choose what kind of access it needs. (And since you write these classes, you know what access you need.) If that is not acceptable, you could add factories that only create wrappers with certain limitations depending on a "token". That token might be the RTTI information of the calling class instance, although this could be abused.

Does this solve the original problem?

EDIT:

Remember that since you are the programmer you can instantiate an A whenever you want. By adding your class to a friend list or whatever...

What this solution provides is a clearer interface. Removing the explicit conversions probably makes it safer but less flexible. But, since they are explicit, you can easily look for them in the code and treat them as signs your architecture has a flaw somewhere.

Specifically, you can have code like this:

shared_ptr<A> * a = new A(parameters);

A_read aRead(a);
A_execute aExec(a);
A_write aWrite(aExec);

logger->Log(aRead);
view->SetUserData(aWrite);
controller->Execute(aExec);

Here there is an explicit conversion between an execute wrapper and a write wrapper, but you can decide on this based on your specific requirements.

But, with little effort (knowing which conversions are valid), just by looking at the call locations you can see that (with confidence!):

  • the logger will not change the state of your A
  • the view will not call methods on your A (other than setters)

This is true even if those particular method calls end up calling hundreds of other methods, more than you'd like to examine by hand.

At the cost of a few thin wrappers you gain the ability to see at a glance what a particular function call will do with the parameters you send. This may help a lot during debugging by helping you eliminate some branches from your investigation and, in general, would help people trying to understand the program.

I couldn't really find other reasons for using this ACL idea, at least ones where the costs don't outweigh the benefits. However, it does seem more intuitive than the visitor solution mentioned in the other answer.

  • Not sure I fully get this, because I could be confusing a previous idea I had with what you propose. So basically, to access a particular "feature" of a class A, your user instantiates a helper object that has a friend relationship to the A type, but promises to only access particular features. And you can only instantiate A if you are allowed to access these particular features. Is that what you're saying? How would you specify the wrappers? Manually? – bitmask May 4 '12 at 22:47
  • @bitmask: I've updated the answer a bit with an example that may benefit from this idea. – Andrei May 5 '12 at 5:13
  • shared_ptr<A> * a = new A(parameters); is using a raw/naked pointer to a smart pointer... this makes me shiver and should be avoided at all costs. – Joost Jan 10 '18 at 14:12
1

I don’t share your appraisal of the benefits, and I suspect that this is the reason why it isn’t done: it would make the object system vastly more complex, for very little benefits.

But in general, permissions (r, w, x) are made explicit through the use of methods: rather than having a publicly accessible member, you provide explicit read or write access through getters and setters.

Of course, this doesn’t allow modelling users or groups (other than making one object a friend). Other programming languages allow restricting access to other classes within the same package (Java, default) or within the same assembly (C#, friend). A similar restriction is achieved in C++ by separating the project into different compilation units and using the compiler firewall (aka PIMPL) to restrict access of some aspects of a class to that compilation unit.

So in a way you do have different access modes, as well as different “user” groups. They are just not available in a unified model with terminiology similar to FACL, but you get the same effect.

0

Ok, I think the obvious solution is to implement visitor pattern:

struct Crew 
{
    virtual void setRoster(Txt roster) = 0;
    virtual ~Crew(){}
};

class William 
{
public:
    void getRoster(Crew& crew) { crew.setRoster(roster); }
private:
    Txt roster;
}

Since somebody implement Crew, it "joins Crew group".

EDIT if you want some more general case, you can invent some security descriptors, but I think it overcomplication:

class  CrewDescriptorRead
{
protected:
    CrewDescriptorRead(){}
    friend class User;
};

class CrewDescriptorWrite
{
protected:
    CrewDescriptorWrite(){}
    friend class User;
};

class CrewDescriptorFullAccess: public CrewDescriptorRead, public CrewDescriptorWrite
{
public:
    CrewDescriptorFullAccess(const CrewDescriptorRead&, const CrewDescriptorWrite&){}
};



class William 
{
public:
    Txt getRoster(const CrewDescriptorRead& ) const {return roster;}
private:
    Txt roster;
};


struct User
{
    void f()
    {
        William w;
        w.getRoster(CrewDescriptorFullAccess(CrewDescriptorRead(), CrewDescriptorWrite()));
    }
};

struct UnAutorized
{
    void f()
    {
        William w;
        w.getRoster(CrewDescriptorFullAccess(CrewDescriptorRead(), CrewDescriptorWrite()));
        //OOps, I'm not friend
    }
};

Or, if you absolutely paranoid,

class  CrewDescriptorRead
{
private:
    CrewDescriptorRead(){}
    friend class User;
    friend class CrewDescriptorFullAccess;
};

class CrewDescriptorWrite
{
private:
    CrewDescriptorWrite(){}
    friend class User;
    friend class CrewDescriptorFullAccess;
};

class CrewDescriptorFullAccess
{
public:
    CrewDescriptorFullAccess(const CrewDescriptorRead&, const CrewDescriptorWrite&){}
    operator CrewDescriptorRead() { return CrewDescriptorRead(); }
    operator CrewDescriptorWrite() { return CrewDescriptorWrite(); }
};
  • It doesn't help (I'm assuming you meant getRoaster instead of setRoaster). Problem 1) Children of Crew would still have to implement the member function. Even if you didn't make it pure abstract, you have a bigger problem 2) How can you enforce that that Crewmembers access only the roaster, but no private data? This pattern doesn't work at all. – bitmask May 4 '12 at 19:04
  • @bitmask, of cause, they have to. They are going to use roster somehow, aren't they? Otherwise what the reason to request roster? How Crew memebers can access private data? They are not friends. – Lol4t0 May 4 '12 at 19:07
  • 1
    The way I read the problem, the underlying class decides who is part of which group. Your solution is like saying "Hi, I'm an Admin, delete this file!" – Andrei May 4 '12 at 19:07
  • @Andrei, you can always add your own class to friends list anyway. From the other hand, you can not to do Crew class part of public API. – Lol4t0 May 4 '12 at 19:09
  • I see where you're going with this, but this is much better than the version in the question, you still have to create a specific version of Crew that is specifically designed to work with William. Also, reversing the data-flow (telling William to set a roster variable, instead of simply reading it) doesn't help making everything clearer. What if your concrete crewman needs access to roasters from several William instances? (okay, this is where the example stops making intuitive sense, because there is only one William T. Riker) – bitmask May 4 '12 at 19:13
0

Any solution you think, will have syntactic complications. It's everything about knowing the sender. Something like:

void A::f() { b.method(); }

B requires to know that A is the caller, which implies to transform all of your B:: methods to templates, and add an extra parameter. The only think I can think of is using some kind of "witness" which carries the sender, and some kind of RAII object to save the "sender" during a while:

class target : public requires_permissions<read_permissions<A, B>,
                                           write_permissions<B>,
                                           execute_permissions<>>
{
public:
    // Must call has_read_permissions() as first line (for example)
    int f() const; 

    // Must call has_write_permissions() (for example).
    void g();
};

The sender class would be something like:

// Curiour recursive pattern, to know things about `sender`.
// See later.
class sender : private want_permissions<sender>
{
public:
   void caller();
};

void sender::caller()
{
    // give_me... is a `want_permissions` method.
    scoped_perms sc(give_me_an_access_key_for(my_target)); 

    my_target.f();

    // The give_me method requires my_target inherits from
    // `requires_permissions`, checked at compile time.
    // The key given to `sc` is also sent to `my_target`, in order
    // `requires_permissions` knows which object is asking for
    // using the class, like when calling `f`. my_target's guards 
    // (read/write_permissions()) will take care of the rest (using
    //  the key to know the sender), thowing an exception in case
    // of permission mismatch.

    // `scoped_perms` must allow permissions only for an object
    // at once, to make checking faster and to avoid unintended
    // actions, but must be also recursive: what if this->f() calls
    // this->g() and both requieres permissions? A recursive
    // permission checker!!

    // Of course `scoped_perms` frees the key when is destructed.
}

Considerations:

  • I think the only way to do it safe is want_permissions is inherited privately to avoid other objects use your permissions, and all want_permissions methods must be protected to avoid other objects creates objects of class want_permissions without inheritage. For same reasons, want_permissions must check if sender inherits from want_permissions or not before delivering a key.

  • The key must be designed (how do identify the sender, just saving a pointer? why not?)

  • If you want it supports multithreading, things get harder, because there could be different authorized objects at once. Keys must be saved in a container, and check methods must search on that container. The problem is, again, you don't know which of them is the sender in a specific moment if you save more than one at once. Perhaps with thread_local variables and the pImpl idiom you can do something. Or just restrict a same target cannot be used from different threads. Or just using mutexes to block until a block_perm instance is destructed, to allow the next object to go on.

  • Since both permissions-related base classes are not thought to be used as pointer to base classes or anything like that, the destructor don't need to be virtual, are there are not other virtual member, so, all polimorfism overhead is avoided (no vtables for these classes).

  • Not every method requieres permissions. Just add has_read_permission(), has_write_permission() or has_execution_permission for those methods you want to protect.

Read/write/execute permissions are just tags for transport a type container:

template<class... allowed>
struct read_permissions;

template<class... allowed>
struct write_permissions;

Requires permissions just takes two types, and with a specializations we extract the parameter pack:

template<class readers_type, class writers_type>
struct requires_permissions;

template<class... readers, class... writers>
struct requires_permissions<read_permissions<readers...>,
                            write_permissions<writers...> >
{
protected:
   void has_read_permissions() const;
   void has_write_permissions() const;

   // other must-be-well-designed stuff

private:
   template<class T*>
   T const* get_key() const;
} 

Suppose key is just the sender pointer, and get_key is a magic method (to be designed as well) to get the current key.

template<class... readers, class... writers>
void requires_permissions<read_permissions<readers...>,
                          write_permission<writers...> >::
has_read_permissions() const
{
   auto* key_ptr = get_key();
   // magic, I said that. It's even possible that it can't be done
   // (with type erasure sure you can).
   // Of course, when creating perm blocks, there should be
   // a way of passing the sender type to this class, in order to
   // get_key() is instantiated in compiler time and 
   // `has_read_permissios` can be a compiler time checker as well,
   // but it requieres a peacefully time to think.

   if (!key_ptr) throw something();

   if (!is_in_pack<decltype(auto), readers...>()) throw something();
}

// no matter where is this function implemented,
// if external or internal:

// They are not specializations each other (partial specializations
// are not allowed for functions). They are two different templates,
// one with at least two template parameters, and other with only one.
template<class guilty, class type, class... suspected>
constexpr bool is_in_pack()
{
    return std::is_same<guilty, type>::value or
      is_in_pack<guilty, suspected...>();
}

template<class guilty> constexpr bool is_in_pack()
{ return false; }

That's everything I can do now for you.

0

You say you don't want something unbreakable, just something that if a good developer just follow the estalished rules. I have a simpler approach to what have been proposed until now :

Seperate execution or reading/write

Well an easy way to do this is to have class split between datas and logic. Similar to POJO and Service for Javaist. I will call them William and WilliamExecutor.

Separate read/write

Just use the const keyword or the const from boost for read-only. Not that this doesn't handle writing only object, but is this really needed ?

So you would just have :

class William{
    private: int a;
}
class WilliamExecutor{
    public: void t(William william){}
}

class A{
    // read only
    const Williaw& value;
}

class B{
    //read/write
    William &value;
}
//full
class C{
    William &value;
    WilliamExecutor &executor;

}

This is the simplest version, now what if i want read access and exec ?

At the moment, it's impossible as WilliamExecutor won't be able to take a const reference to William, this is because currently WilliamExecutor is stateless, and so we can use it as a singleton. However if we remove that possibility we can do it like this :

class WilliamExecutor{
     private: William &value;
     public : void test(){}
}

class D{
   private: 
        const William &value;
        WilliamExecutor &exec;
}

//be carefull however, it will be the responsability of the developer who instantiate D to properly have the exec pointing to the same instance of William

new D(williamRef, new WilliamExecutor(williamRef));

Now if you want a write-only (without read) object you could just have a 3rd stateless class WilliamWriter that is just writtent like this :

public class WilliamWriter{
    William &value;
    public void setFoo(String newValue){
        value.setFoo(newValue);
    }
}

And pass it to the class that only need to write to the class William.

I have the same opinion than @KonradRudolph on this matter, however i wanted to provide a simplier solution involving less number of classes, less complexity of usage which could fit your needs.

Maybe it's possible to adapts this with a some generics but i let this to people that master c++ better than me.

Note : moving method to another class can be consider as breaking OOP. While it might be true, i just don't think you will be able to have something usable, meaning doable AND that people want to use, if you don't.

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.