Re: Restricting functionality on objects: "remote access proxy" (pattern)



On 17 Aug., 17:44, "H. S. Lahman" <h.lah...@xxxxxxxxxxx> wrote:
Responding to Wutzke...

<snip example>

How do you restrict access for a real chat room on the network by
using the current (or another user's) level/permissions (there are
admins, mods, owners, ops, normal users)?

1. The easiest way is probably not to expose functionality via the
GUI, so users with admin level will see a popup with more entries than
a regular chatter. However this is no real security concept. Accessing
the model objects can still produce an unwanted command (e.g. by some
artificial instance not having a GUI at all).

I don't buy this argument, at least directly. The subsystem or layer
where this object exists will have an interface for communicating with
the outside world that will be shared by all "users", whether they be a
person at a GUI or another chunk of software. That interface will have a
DbC precondition to ensure that the external context for invoking that
interface element is valid. IOW, it is not this subsystem's
responsibility to check if its clients are doing their jobs correctly.

OTOH, prudence suggests that software should be able to detect when it
is broken and cannot continue processing properly. So it is valid for
the subsystem to have correctness assertions that can detect if the
client has lost its mind and signal an exception if it has. So...



2. The list of commands generated via some CommandFactory (the objects
that would do something when a button gets pressed) is limited by the
user's (or some other) level. A normal user can't instantiate a
"BanCommand" for example. The command objects act as messenger
objects, but nevertheless those objects would still call the public
unfiltered methods on that interface.

You could use the Command pattern to check privileges, but that is a
pretty complicated way to do a simple task.

I wonder what you had in mind by saying complicated. The client knows
about the current user's access rights in a chat. This "access state"
is dynamic, that is it can change while chatting, e.g. when an
operator up- or degrades your level (admin, superop, owner, op,
tempop, normal). The state of the current users chat (one of many
active chats) can be set with something like setLevel(...). When that
is done, the chat will appear do be a little different, either some
functionality was added or some was removed.

What I had in mind then was to provide Commmand objects depending on
the user's level in the chat. These commands would be called from menu
items or buttons. If the level of a user doesn't allow a command, the
respective GUI component wouldn't be created (either Command object
creation throws an exception or null is returned).

This is not complicated IMHO, I just wonder why I'd need a Proxy then.
Maybe only a "remote proxy", but not "remote access/protection proxy"
since the access restriction was/is already handled by the command
factory (or such).



3. Somehow provide different implementations of that interface by
subclassing (or else) and throwing some exception for those operations/
method calls that are not allowed with the current user's level. A
normal user calling myChat.ban(otherUser) would get such an exception.
Disadvantage of subclassing here would be, that I'd need one subclass
per user level (admin, moderator, owner, operator, normal). In case
the user's level gets upgraded, the instance/s of such chats would
have to be replaced. Looks like bad design to me.

This is close but a better idea is to provide different interfaces to
the object and only expose the less privileged clients to the interface
elements they have a right to access. That, though, becomes difficult
when the client is external to the subsystem, like a GUI. One would need
to provide the right subsystem interface to the client, but which one is
right will depend on who is logged in.


I have two problems with providing different interfaces to clients:

1. How does the client know which Proxy to expect/use? Isn't the
purpose of the Proxy to provide a placeholder so that clients *don't*
need to know which interface to expect? Furthermore, which variable
would I assign a concrete Proxy to after it got instantiated (via some
Factory maybe)?

Proxy classes are: AdminChatContext, SuperOpChatContext,
OwnerChatContext, OpChatContext, TempOpCHatContext, NormalChatContext.
Each class going from NormalChatContext up to AdminChatContext would
add 1-n new methods.

In the client of the proxy, what local variable would I assign the
concrete proxies to to make use of their extended interfaces?
Assigning them to the base

Chat cht = SomeFactory.createOpChatFactory();

seems right at first, but I can't access the concrete class' methods
without downcasting.


2. Isn't adding/withdrawing functionality dynamically rather a
decorator, providing different subclasses and methods? Even when using
decorators, clients are still left with problem 1.

My own summary would (currently) be:

Using subclassing for user rights not being able to use a common
interface to all seems not to be the right approach. Maybe I missed
something, so please correct me if I'm wrong.

The simplest way is to allow each user to identify themselves and
provide a privilege bitmap for each user from a separate <secure>
configuration source. There are many ways to do that, so the following
is only one possibility:

* creates R1 1
[UserSurrogate] ---------------- [UserFactory]
+ privilegeBitmap
| *
| accessed by
|
| R2
|
| *
[Chat]
+ getName()
+ setName()
+ getOwner()
+ setOwnder().

When a UserSurrogate is instantiated by the factory object (presumably
when someone logs into a chat session), the factory object goes to a DB
and looks up the privilegeBitmap for that particular user identifier to
initialize it. For accessors like getName and getOwner the privilege
doesn't matter so they don't do anything special. However, the setName
and setOwner accessors will access the privilegeBitmap and check if
their bit in it is set of not. If not, they generate and exception.


Right.

So what's wrong with this picture? The R2 relationship is *:*, so how
does a Chat know which UserSurrogate to access to get the bitmap? The
obvious and simplest way is for UserSurrogate to pass its bitmap value
in the message that it sends to Chat (e.g., setName(name, bitmap)). Note
that passing the bitmap is fine because that is under the control of the
subsystem in hand and it forces the mapping of privileges to Just Work
because it controls the initialization of privilegeBitmap.

[Caveat: passing the bitmap value is not the most robust solution from a
maintenance viewpoint, but I wanted to keep the example simple.]


I understood that, ok. I must pass the user's level/permissions s/he
has for the chat (the State pattern variable) to the surrogate/proxy.



Instead of subclassing one could also implement this via several
Strategies, one per method. I believe the "Bridge" pattern could be
applied as well, for dynamic method implementations.

In any way, I started creating a concrete subclass of a Chat interface
called "ChatProxy" which is supposed to
handle the communication with the actual chat server (no
implementation yet). Maybe that is supposed to be the class where the
restrictions belong. Such a proxy might act as an "access proxy" but
also as a "remote proxy".

Note that UserSurrogate is essentially a proxy for the external user. It
is just less complicated than the GoF pattern because there is no exotic
dynamic substitution required here.

Hmm I don't understand that really. Can you explain a little more what
you mean why there's no dynamic substitution here? The only thing that
changes (and must change) is the user's level *per chat*. So...

When a message comes from GUI (or
whatever) is will contain an identifier for the actual user. The
subsystem interface will then redirect that message to the right
UserSurrogate object who will then talk to other objects as needed.


I wonder how it's possible to use concrete interfaces from the
client... Conceptually and practically I'm reverting to implementing a
*common* interface, that is, have n subclasses for the n possible user
levels/states. Methods not allowed for the chat's user level will
(probably) throw some exception. Like this a common interface can be
accessed and depending on the instance's class will do something
meaningful or report "invalid". When the user's chat level is changed,
the proxy instance provided will be different and consequently changes
what the user can do/access. The GUI will reflect this of course.

The command pattern also seems appropriate to use since I've written a
small framework which supports Command objects. I can also create
commands that are made up of several smaller commands. An example
would be a user to "Kick & ban" another user, effectively this could
be a KickCommmand + BanCommand.

Final note:

It took me quite a while to understand and learn more about the
problem/s at hand. I will probably do fine if I can get the few last
things straight in my head. I'd very much appreciate if you (or
others) could comment on my questions, even if it refers to only a
small part in the above.

TIA
Karsten

.


Quantcast