Hi Christoph,
I know you're mostly asking about best-practice of process rather than
security, but I think it's nonetheless important to bring up the issues
that can arise when embedding primary keys of your entities client-side.
I'm still a T4 user, but when you say "a PageLink on a PersonSearch
page, which has the Person's primary id as context", I assume that's
analogous to the "parameters" attribute on a DirectLink in T4, in that
the parameter is embedded in the page. Even if that's not the case,
I'll continue anyway :P
Building on your example, let's say that the logged-in user should only
be able to search/edit people within his/her department. If we're only
passing the PersonID to the PersonEdit page, and that ID is embedded in
each rendered link on the PersonSearch page output, then the user could
hack the form/link from the PersonSearch page to pass an arbitrary
PersonID to PersonEdit, potentially giving the user the ability to
perform unauthorized edits.
I've started taking the approach of embedding not the ID, but a piece of
information that's unique within the security-context (by
"security-context", in this case I mean "department"). In this case,
assuming that a person's full name is unique within a department, we
could embed the person's full name into the PageLink, and the PersonEdit
page would search not on the primary key of the person, but instead on
the combination of the DepartmentID (retrieved from the session if we're
keeping a User object in the session) and the full name we passed in.
Since we're using the DepartmentID from the session, then the user can
hack the form/link all he/she wants, and the best they'll do is bring up
the editing form for someone that wasn't in the search results but is
still in their department, so it'd still be an authorized action.
This sort of approach is annoying, because we'd love to be cleanly using
solid efficient primary keys, but it probably pays to be paranoid.
Anyone have a better approach on this issue? Particularly with regard
to search pages?
Jim
Christoph Jäger wrote:
Hi,
Sorry for this long post. I spent quite some time now to try to figure
out how to use forms to edit/update objects the "right" way (you know,
simple, stable, elegant, easy to read, easy add things, ...). I use
Tapestry 5.0.11. I am almost satisfied with what I came up with now,
but some improvements need to be done. Maybe someone on this list can
help with some ideas.
As an example, lets have a PersonEdit page, with a PersonDao injected.
You can create new Person entries or edit existing ones. To edit an
existing person, there is a PageLink on a PersonSearch page, which has
the Person's primary id as context. To create a new Person, a PageLink
with 0 as context is used. To make this work, we have onActivate() and
onPassivate() methods in PersonEdit:
void onActivate(int id)
{
_id = id;
if (id == 0)
{
_person = new Person();
}
else
{
_person=personDao.get(id);
}
}
int onPassivate()
{
return _id;
}
This way we can avoid using @Persist on the Person property (because,
for instance, we want a user to be able to open two browser windows,
viewing two different Person entries side by side and edit and save
both of them. I think this would be problematic if we use @Persist,
but please correct me if I am wrong).
Now, editing an existing user works like this:
- click the "edit user XYZ" PageLink on the PersonSearch page
- in onActivate(), the personDao is used to query the Person from the
database
- an HTML form is rendered to let the user edit the values
Up to here everything looks perfect.
- the user edits the Person's data and hits the "save" button
- onActivate() is called, a fresh Person is loaded from the database
- for each field in the HTML form, validation is done (if defined),
and a property in the fresh Person instance is set if the validation
was successful
- onValidateForm() is called if existing to allow for cross-field
validation
- if all validations were successful, onSuccess() is called. Here we
call _person=personDao.save(_person). This save method returns a new
Person instance, exactly as it was written to the database (primary id
may be generated by the database, time stamps or version numbers
updated, ...). We use this new Person's id : _id=_person.getId() to
make sure we have the correct if for the next onPassivate()
- onPassivate() is called
- result sent to browser, redirect
- onActivate() loads Person again
- new form is rendered
This is good, but I think it could be improved.
1. The Person is loaded from the database twice (using
personDao.get()), and saved once. The save() method of personDao
already gives us a new, fresh instance of Person, it seems like a
waste to load it again after the redirect.
2. During validation, we check if there is already a Person with the
same userid (in onValidateForm(), or in onValidateFromUserId()) and
warn the user if this is the case. But what happens if a new Person
with this userId is added just after onValidateForm() is called, but
before onSuccess() is called, where we want to save? To make our
program solid, we have to take this into account. In case save() does
not work, we do not want to see some exception page, but the form, as
it was filled, with a hint what might have gone wrong, so the user can
try again. To make this possible, we have to move the save() to
onValidateForm(), because there we can still record form errors (I
think there is a JIRA for an improvement to this situation).
3. We want to give the user feedback of what happened. After clicking
the save button, we want to show a message like "Successfully saved
new person information" above the form (at the same place you would
see error messages like: "The first name is mandatory"). The only
thing that comes to my mind is to use @Persist to save the message
from the onSuccess() or onValidateForm() through the redirect to the
next page render. But now I have problems to make sure this
@Persist'ed message is cleared and is only presented to the user
directly after the save button was clicked.
I think to find a nice solution for these issues (you know, easy
things should be easy to do, and a form like this does not sound very
complex to me, so there should be an easy solution), a @Persist, which
persists values just through the redirect would be very helpful. The
problem is, I have no idea how this could work. But maybe an option
like this already exists (I am sure I am not the first to have
problems like this), and I just didn't find it.
Any ideas are welcome,
Thanks,
Christoph Jäger
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]