Hi Jim,
I like to use the primary key, because it is short and by definition
unique, and usually the quickest way to retrieve an object from the
database.
For the problem you describe, users are only allowed to view people in
their own department, three different solutions come to my mind:
1. in onActivate(), where the personDao is queried for the Person with
the given id, check if the logged in user may view the requested
Person information
2. in the PersonDao itself check for permissions
3. as Jonathan Barker wrote in an email in this thread, use domain-
object level security
1 is the quick and dirty way, easy to leave some holes open, but it
should work in any environment. 2 only works if personDao (wherever it
is executed, might be a remote call on some different server than your
web-app) knows about the logged in user, and for 3 you probably have
to use some framework like Acegi or some container managed
authentication and authorization.
But there is a different scenario, where we have to find a different
solution: imagine some web-frontend to a database of companies.
Everyone can search for companies, but you want to limit the search to
show only the top 10 results, so no one can harvest your whole
database. Using the primary key here proves to be easily exploitable,
just try the result page with ids from 1 to 10000 (it is quite easy to
use wget and some lines of perl code to extract the whole database
this way). You can try to limit the rate of requests from a single IP
address and stuff like this, but this only slows down things. And you
can not use security settings here, because everyone is meant to be
able to view each entry (only not all of them). I did not yet have to
solve a problem like this (only discussed it). You could probably use
some alternate keys, created as hash values from your primary key and
some "secret" string, or use UUIDs like Cesar Lesc mentioned in his
post on this thread.
Best Regards,
Christoph
On Apr 23, 2008, at 04:08 , Jim wrote:
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]
Christoph Jäger
[EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]