Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question

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
    
Please give a concrete example of intended usage. –  Alf P. Steinbach May 4 '12 at 18:37
show 7 more comments

3 Answers

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.

share|improve this 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
add comment

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(); }
};
share|improve this answer
    
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
show 1 more comment

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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