Data wrapper

Asked by Prageeth Silva

I have been researching about using custom annotations recently and I've come up with a way of implementing the data wrapper. I'll try to explain it in short:

Custom Annotation: @UserLevel(Role) can be assigned to all fields (and methods if required) of the model class.

However, this means that we no longer need to use separate classes called Info classes, we will be directly giving out the model classes.

For example, if a client requests for details on a User, and he has Developer rights, we would:
1. simply retrieve the object from the datastore
2. create a clone and set a internal flag saying it's a clone and should not be stored back by any chance
3. strip out unwanted information off the clone (set them to be null depending on the current login's Role)
4. return the clone object

This method ensures that we do not accidentally make mistakes and give out wrong bits of information to the client.
Striping out of information will automatically happen depending on the @UserLevel annotation, so all we need to do is make sure the correct Role is assigned for each field.

However, this also allows us to throw UserPrivilegeException in the get and set methods, if we want; we will need to add the annotation to each method as well though.

The major drawback to this will be the overhead in checking the user role in order to throw exceptions everytime a method is called.

*Note:
Some may say that setting the internal flag if necessary should not be sent out to the client as this may create a security vulnerability. In that case, we can have the base classes to be the shared class (e.g. User) and the datastore class to derive from the base class (e.g. UserData). This way, the UserData can be only server side (hidden from the client) and we can have the internal flag on it. This way clients will only have access to the User class; and when saving the object on the server side, all we need to do is check if the object is of type UserData before storing).

Any suggestions?

Question information

Language:
English Edit question
Status:
Solved
For:
MUGLE Edit question
Assignee:
No assignee Edit question
Solved by:
Prageeth Silva
Solved:
Last query:
Last reply:
Revision history for this message
Matt Giuca (mgiuca) said :
#1

Sounds like a great plan. I would be impressed if you can pull it off.

I'm still not sure how you actually *use* annotations. I am used to Python, where an annotation runs some code to "wrap" a method. In Java, they just look like they contain static metadata. Do you just use reflection at runtime to determine what the annotation is for each piece of data? That's fine ... just wondering how you plan to do it.

As for the get/set methods: you say you will need to add the annotation to each method as well. Why would this be? Couldn't each method be programmed to check the annotation on the field? Hopefully there would be some generic code that does this checking. Again, coming from a Python standpoint, the way you would do this would be to have each get/set automatically call some common code which checks the privilege level against the annotation on the field it is supposed to return.

As for subclassing for the database, what is the security vulnerability scenario exactly? Is it just that someone accidentally sends a data object out without first creating the limited clone of it?

Revision history for this message
Prageeth Silva (prageethsilva) said :
#2

Yes, the only way to look at the annotations at runtime is to use reflections (i personally think using reflections has an overhead, but most programmers take it for granted these days).

About the get/set methods, i think I've confused you. Let me put it this way, we have two options:

1. Don't worry about the client having access to the get/set methods, just return null if the server already stripped out the information and the related field for the get/set method contains null.

2. Everytime the get/set method is called, do a check (a generic method that's there in the abstracted super class) if the current user has privilege to use the get/set method and then throw an exception accordingly. This allows us to let the client read the value in field, but not set it. *

* You might suggest to use the protected modifier on the set method, but this means we can use these classes in any other package in the server side.

Revision history for this message
Matt Giuca (mgiuca) said :
#3

Don't worry about the overhead of reflection. Yes, it is quite high, but we shouldn't prematurely concern ourselves with performance.

I would go with option 1. It is simpler, and results in an exception (NullPointerException) anyway. Not quite as nice, but much easier to implement. Similarly for set; it would be nice to raise an exception, but it doesn't really matter if the client writes to these fields; it's just that it won't work.

Is there ever a need to write to these fields, on the client side, and have them written back to the database? I would think that is wrong, and difficult to do (receive one of these things back, and pull in the updated information). It would be best if there is a separate static method for writing information to the database (from the client). Then we have full control over which things can be written to.

Revision history for this message
Prageeth Silva (prageethsilva) said :
#4

Yes i too would like to go ahead with option one (I've started renaming classes and packages, and it takes a while).

About letting the clients write to the fields using set methods was for a scenario like this:

If an admin wanted to update information on a user, he'd first request for the object, update the required fields and send it back for updates. On the server side, we check each field again to see if update for a particular field is possible given the current login role (using the annotation).

However, if we have static methods to update the fields, we would have to have a generic method of updating a particular field at a time.

Any work around you can think of would be helpful. Thanks!

Revision history for this message
Matt Giuca (mgiuca) said :
#5

OK but I can see problems with that. For example, how do you write null back to the database? We would have to *ignore* any field that is null (since it might have been one of those fields which were nullified when we got it out). Therefore, if you explicitly set a field to null, it would be ignored. So how can you communicate to the server "set this thing to null"?

There's also quite a bit more infrastructure involved in the writing back.

So, are you aware if Google's db.Model class actually works in the client side at all? If so, what are the ramifications for writing to setters?

Revision history for this message
Prageeth Silva (prageethsilva) said :
#6

I was thinking of using the same mechanism to check the fields. For example:

@UserLevel(Role.ADMIN)
Long id;

@UserLevel(Role.DEVELOPER)
String name;

If the current user has a role of DEVELOPER, he will only be able to see the name technically. In order to get this implemented, I'd have a method to go through every field and in this case would return:

id = null;
name = "some string from db";

Same applies for updating:

id = whatever_value set by client -> will be SKIPPED, because the developer has no access to that.
name = whatever_value set by client -> will be UPDATED.

However, I think we should make sure that the primary key will never be updated on client updates.

I didn't really get the question "So, are you aware if Google's db.Model class actually works in the client side at all? If so, what are the ramifications for writing to setters?".

Revision history for this message
Matt Giuca (mgiuca) said :
#7

OK I suppose that will work.

By the last question, I mean ... doesn't db.Model do something special when you write to a setter, like pushing the data back to the database? In which case, will that special code interfere with what we want to do on the client, with setters, which is just to treat it like normal data and write it into the class (nothing to do with the database). In other words, how does a db.Model class work on the client side, if at all?

Revision history for this message
Prageeth Silva (prageethsilva) said :
#8

Oh, I was under the impression that we don't directly update the object whenever a value is changed using a setter.

What I've implemented so far is, when an object is retrieved from the datastore, it's instantly detached from the persistence manager. This means that, there will be no implicit updates on the datastore. If we need to update some object, then we would have to explicitly call the update method.

I've already got them working, but no I'm unsure if there is a better way to do this.

Or am I wrong? Is there a better way to handle this?

Revision history for this message
Matt Giuca (mgiuca) said :
#9

No, we don't. But I don't know how db.Model behaves. Perhaps the setters in db.Model are nothing special, and it will wait until the put() method is called before it does anything. Therefore, I guess it's OK.

If you've already got it working, then that's good... I'm just asking whether it's possible to get it working.

Revision history for this message
Prageeth Silva (prageethsilva) said :
#10

Yes it is possible, but I'll double check on that as soon as this is done. It was working before I broke the repo the other day, and since there were some changes after that, I'll need to check on that again.

Revision history for this message
Prageeth Silva (prageethsilva) said :
#11

After being on the computer since 6pm (for approximately 8 hours), I've finally implemented the Data Wrapper completely.
A summary:

All data store classes will be addressed as *data model classes* and the stripped down version counter parts as *client model classes*. Data model classes need to be hidden from the clients, hence need to be placed inside server/(model) and client model classes need to be in shared/(model).

Data model classes:
* MUST implement ModelDataClass<S, T, U> generic interface

Client model classes:
* MUST implement ModelClass<S,T,U> generic interface
* use the annotation @MugleDataWrapper for the "class"
* use @MuglePrimaryKey for the primarykey value
* use @UserLevel(privateView=Role, publicView=Role)

<S, T, U> where,
S - the primary key type
T - the client model class type
U - the data model class type

Other notes:

* Please note that, if the annotation UserLevel is not used in a particular field, it will be just left alone (which means that the data will be passed from server to client.

* MugleWrapper (static class) has been added, that has useful methods such as converting back and forth of model classes, checking for user levels, etc.

Example of User implementation:
Client Model Class -
____

@MugleDataWrapper
public User implments ModelClass<Long, User, UserData> {

    @MuglePrimaryKey
    @UserLevel (privateView=Role.ADMIN, publicView=Role.USER)
    Long id;

    ....

}
__

Data Model Class -
____

public UserData implments ModelDataClass<Long, User, UserData> {

    ....

}
___

One last thing to mention, though the implementation compiles without any problems and everything seems to work technically, I have NOT tested the code. Hence, I have not merged the branch back to the trunk. I would design a Test in a couple of days and would fix any bugs that I'd come across.

Cheers!

Revision history for this message
Prageeth Silva (prageethsilva) said :
#12

I made a tiny mistake above, the data model class *MUST also extend from the corresponding client model*.

Client Model Class -
____

@MugleDataWrapper
public User implments ModelClass<Long, User, UserData> {

    @MuglePrimaryKey
    @UserLevel (privateView=Role.ADMIN, publicView=Role.USER)
    Long id;

    ....

}
__

Data Model Class -
____

public UserData extends User implments ModelDataClass<Long, User, UserData> {

    ....

}
___

Revision history for this message
Matt Giuca (mgiuca) said :
#13

Very nice work!

In IVLE, when we spent a ridiculous amount of time on something which seemed easy but just wasn't, we would put into the commit log, "You have no idea how tricky this was!" I permit you to do it now!