Open adamlundrigan opened 9 years ago
Zend/Form already has intelligence to handle passwords so this concern is resolve by PR #563, possibly eliminating the need to make yet another BC change.
@claytondaley good point, the new_password
stuff isn't really necessary then. Document updated
I also really like the idea of implementing password hashing through a Hydrator Strategy. In fact, it would be great if the Hydrator was implemented to be easily extensible.
It'd really be better if ZfcUserAdmin used (extended) a core hydrator... and I could extend it further. Unfortunately, I'm still on the learning curve for Hydrator Strategies so I'd benefit from a recommended pattern for this. My go-to was the event system, but my research on performance suggests this may not be the best long-term strategy.
I assume a hydrator strategy also eliminates any hard-coding since you could replace the default right there in the hydrator.
I also really like the idea of implementing password hashing through a Hydrator Strategy. In fact, it would be great if the Hydrator was implemented to be easily extensible.
I've been battling with that myself, as it's not the purpose of the hydrator. The hydrator is meant to have one responsibility: converting between an object and it's array representation. The proper solution would be to do this on the form post-validation but pre-object-populate, so I'm thinking the best course of action would be to create a filter which hashes the value of the password field.
For example, I have PR that converts ZfcUserAdmin's EditForm to use a hydrator. Now I need to extend it to properly Hydrate DateTimes.
I had worked on something similar last summer but never got the chance to complete it and PR it (I did recently get as far as opening a ticket for it: https://github.com/Danielss89/ZfcUserAdmin/issues/65). A lot of what ZfcUserAdmin does should be part of ZfcUser, and that module should just be the UI and integration with ZfcAdmin; the logic of CRUDing users is something ZfcUser should handle itself...and that's the main goal of my recent PR blitz here, which when complete I had planned to turn my attention to ZfcUserAdmin next.
You're right. This is really what InputFilters do. The Zend\Form Quick Start gives examples like:
'filters' => array(
array('name' => 'Zend\Filter\StringTrim'),
array('name' => 'Zend\Filter\StringToLower'),
),
If an InputFilter can trim and change case, why not hash? If the stored hash uses a different strategy, you can always getRawValues()
and rehash.
... and I finally figured out the clean way to handle hydrator extension.
init()
(called by FormElementManager, see below)
$form->getHydrator()
to access the hydrator and add the related strategy (if needed).For the ZfcUser forms, I'd also like to recommend a dynamic validation group implementation comparable to Rob Allen's recommendation. I care more about the requirement than the strategy -- add/remove methods for the validation group would be just as good (and perhaps better).
This is particularly important on "edit" forms where you may want to present readonly
data that should not be committed back to the database. The current method for achieving this is to create a validation group. Unfortunately, the default validation group implementation depends on explicit knowledge of all of the elements on the form, which is antithetical to easy extension.
I believe the readonly
attribute should also exclude by default, with the option to override by setting exclude => false
. IMHO the core Zend/Form should be upgraded in this way, but this is as good a place to start as any.
A final enhancement would be a universal form renderer placed in a parent project (ZfcMvc?) that is inherited by ZfcUser and ZfcAdmin. ZfcUserAdmin has the right idea importing _form.phtml
into numerous pages. I believe I've successfully implemented a fully generic example that would be a drop-in replacement for existing ZfcUser forms. Several notes:
$form->form()
.RE: validation groups this is something I've brought up in discussion about ZF3's forms, as the current implementation of validation groups makes it hard to work with complex forms. IMO there should be a convenience wrapper around that so you can selectively include and exclude fields without having to specify the entire validation group. In one of my projects last year I wrote an atrociously dirty hack to work around the issue, and it's on my someday agenda to extract and clean up that code so it can be released.
RE: form rendering, I think that's best left up to the consuming application. We'll bundle a minimum viable set of view scripts (like we do now) but if those don't fit your use case you'll need to override them. The approach ZfcUser currently uses (looping over every element in the form) is likely the behavior that will remain, though more than likely anyone adding custom fields would also be overriding the view scripts to control how they are displayed. ZfcUserAdmin's handling of this annoys me a bit as it include
s the common form template rather than sending it through the view renderer, making it unnecessarily difficult to override.
RE: redirects, I'm not sure about this one. IMO there are better ways to handle the post-login redirect than sticking the target in a hidden form element. That's prone to abuse if not done properly, as we saw last year with the open redirect security issue. It's something I plan to give some thought to once the current round of PRs I've opened are wrapped up.
Obviously, there's no code to react to so the current state is the available point of reference
setData()
is called and be automatically returned with an invalid form. There seems no advantage to the current approach.There are several points in your response on forms. Trying to separate them out:
register.phtml
loop would work perfectly for login.phtml
as well. Thus, ZfcUser 2.x has two blocks of code when one would suffice. The ZfcUserAdmin strategy is DRY by sharing that code between both pages.add()
a submit button in __construct
. Besides that, the forms are as expected -- materially indistinguishable.Ran into another quirk worth adding to the punchlist. Custom Form Elements only work when you use two specific strategies:
FormElementManager
(e.g. $sl->get('FormElementManager')->get('\Application\Form\MyForm');
)init()
rather than __construct()
.To address an issue, I also (re)discovered:
init()
is called (see zendframework/zf2#7305 for related discussion)
I've combined proposed changes to the forms and service layer into one as the latter depends heavily on the former. This one actually is an RFC as I haven't implemented a prototype yet.
Service Layer
We can simplify the service layer by pushing concerns such as the object being hydrated/extracted and password hashing on save -- both of which the service currently handles -- out of the service layer.
A class implementing this interface would require three dependencies
The login form and input filter would remain largely unchanged except for some updates now that we're away from PHP 5.3.x. The registration form and input filter will evolve to work for both create and update operations:
UserForm (formerly BaseForm)
In terms of elements it will be unchanged.
In terms of elements it will be unchanged.
The
RecordExists
andNoRecordExists
validators should be updated to use the context argument so that they work for both create and update operations.