The recently addition of "required" attributes to the username and password fields (see #1174938: Natively support the HTML5 required and aria-required FAPI properties) has made it impossible to log in with OpenID, because the two fields are just hidden but not disabled.

This patch remove the #required attribute for the username and password fields and instead set the attribute via JavaScript. It also includes a fix for #897794: OpenID login link can hide the wrong fields and incorporates most things from the patch in that issue.

Comments

sun’s picture

+++ b/core/modules/openid/openid.module
@@ -134,12 +134,13 @@ function openid_form_user_login_alter(&$form, &$form_state) {
 function _openid_user_login_form_alter(&$form, &$form_state) {
...
+  $form['name']['#required'] = FALSE;
+  $form['pass']['#required'] = FALSE;

Hm. This means that the login form is always altered and required validation is disabled both on the server-side as well as on the client-side.

I'm concerned about unexpected security consequences of this.

I.e., what happens if you submit the login form with no values at all -- are we sure that all code is smart enough to handle that case? Likewise, what happens if you only submit a password but no username? [...]

Lastly, an OpenID module test should have prevented the HTML5 required patch from landing in the first place. ;)

attiks’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests
c960657’s picture

OpenID has module tests. The problem is that the Simpletest framework doesn't emulate a client-side check for the HTML5 required attribute (the server-side check is intentionally disabled in the existing code).

A possibly cleaner solution would be to turn the current login form into two different forms, the username/password form and the OpenID login form. I think it could be made in way so there would be no visible difference for users with JavaScript enabled. Users without JavaScript will get an additional submit button, but I think that makes the form easier to understand (right now it is not very obvious that you have to fill out either the OpenID identifier field or the username and password fields).

One thing that would make the implementation slightly easier and the UI cleaner (IMO) would be to move to two “Create new account” and “Request new password” links down below the submit button. I don't think they are part of the form, so I think they should be moved outside it. The button was above the links in D6, but it seems they changed place as an unintentional effect of #482816: Make a consistent wrapper around submit buttons, so I suggest that we move them back. What do you think of this?

c960657’s picture

Status: Needs review » Needs work
Issue tags: -JavaScript, -Needs manual testing, -html5
StatusFileSize
new18.35 KB

This patch implements two different forms as suggested in #3.

The patch introduces some user-visible changes. I intentionally didn't try to make it look 100%. I think the new approach is cleaner from a code-perspective and thus also improves consistency of the UI if we just fall back to the theme's default way of displaying things (this wasn't possible with the old code due to the somewhat hackish way that the OpenID controls were injected into the login form).

Now the JS switching logic is made declaratively using #states. This required adding a new trigger state based on location.hash. This may be useful for other purposes as well. It just matches the entire fragment part, e.g. #openid-login. Would it be better to treat the fragment as key-value pairs, i.e. #login=openid (like the Overlay module does it: #overlay=admin)?

UX-wise the user login block looks works like before, though the layout is slightly different (as mentioned above). Also, the indentation of the link that switches between the two states is now no longer indented. This is because the links were previously implemented as a &ul> list, but I don't really think they are a list, so now they are just implemented as basic links with the default spacing according to the theme. I think this is fine.

I split up the OpenID part of login form on user/login into user/login/login as a MENU_LOCAL_TASK. This is consistent with elsewhere in Drupal were such tab-like functionality is implemented via the menu system and not via custom JS code.

c960657’s picture

Status: Needs work » Needs review
Dave.Ingram’s picture

Status: Needs review » Reviewed & tested by the community

I came across this looking at some other things in OpenID and this patch fixed the problem. I've clicked through all of the OpenID functionality and think this patch takes care of it all.

I don't fully understand all the changes to states.js though, so someone may need to look over that before committing.

sun’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +JavaScript, +Needs manual testing, +html5

Sorry, this needs more reviews.

The changes to states.js look a bit spooky to me as well. Don't have time to review them in detail right now. Perhaps @nod_ or @xjm or someone else familiar with the states code can look into them.

Also, these are massive UI and CSS changes - needs manual testing.

Let's also look in the html5 folks to make 'em aware of this gotcha.

Niklas Fiekas’s picture

StatusFileSize
new694 bytes

When adding #required we were using the 'formnovalidate' attribute to work with #states. We could do the same thing here. That keeps all the server side validation and the required attribute itself (for screenreaders, etc.) ...

Edit: Reading more of the issue: Refactoring the whole thing could be a seperate issue. @c960657's patch would probably be a good start.

c960657’s picture

Status: Needs work » Needs review
Issue tags: +JavaScript, +Needs manual testing, +html5
StatusFileSize
new18.37 KB

Here is plain reroll of the refactoring patch.

I tried to find a way to implement the #login=openid instead of #openid-login, but I cannot find a good way to do it with making big changes to states.js (of course, this is not forbidded, but I'd like a second opinion on this first) and without adding a dependency on jquery.ba-bbq.js.

c960657’s picture

StatusFileSize
new18.52 KB

Reroll.

xjm’s picture

#10: openid-required-4.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +JavaScript, +Needs tests, +Needs manual testing, +html5

The last submitted patch, openid-required-4.patch, failed testing.

xjm’s picture

Issue tags: +Needs reroll

We'll reroll this and work on testing it during MWDS.

xjm’s picture

Issue tags: +MWDS2012

To test the #states behaviors:

  1. Download the #states exercise module to your 8.x installation's sites/all/modules directory and install it.
    http://drupal.org/project/1269964/git-instructions
  2. Note the existing bugs with the #states system:
    http://drupal.org/node/735528#comment-5473144
    http://drupal.org/node/735528#comment-5499236
  3. Visit the path states-exercise in your local D8 install. (A menu link should appear in the Navigation menu.)
  4. Test all the states in a given section without the patch applied. (Make sure to test all sequences/combinations).
  5. Apply the patch, and clear your site and browser caches.
  6. Re-test the states in your section and note any changes in the behavior.

We'll organize people to each test a certain section of the module during today's sprint.

attiks’s picture

@xjm, if you don't want to do it manually, consider extending the testswarm tests

c960657’s picture

Status: Needs work » Needs review
StatusFileSize
new18.61 KB

We'll organize people to each test a certain section of the module during today's sprint.

Cool!

Here is a reroll of the patch.

Status: Needs review » Needs work

The last submitted patch, openid-required-5.patch, failed testing.

c960657’s picture

Status: Needs work » Needs review
StatusFileSize
new18.63 KB

Reroll.

Status: Needs review » Needs work

The last submitted patch, openid-required-6.patch, failed testing.

c960657’s picture

StatusFileSize
new18.65 KB
c960657’s picture

Status: Needs work » Needs review
star-szr’s picture

Issue tags: -Needs reroll

Removing reroll tag.

Niklas Fiekas’s picture

Thanks for the patch and the rerolls, c960657!

I applied it locally and could confirm that the login block is fixed (in that you can login via OpenId after clicking the link to switch) and also that the seperate OpenId login page is working.

However I don't understand some parts in the code.

+++ b/core/misc/states.jsundefined
@@ -107,13 +107,25 @@ states.Dependent.prototype = {
+        var arg;
+        if (selector === 'window') {
+          arg = window;
+        }
+        else if (selector === 'document') {
+          arg = document;
+        }
+        else {
+          arg = selector;
+        }
+        var element = $(arg);

What is this about?

+++ b/core/modules/openid/openid.moduleundefined
@@ -16,6 +16,13 @@ function openid_menu() {
+  $items['user/login/openid'] = array(
+    'title' => 'Log in using OpenID',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('openid_login_form'),
+    'access callback' => 'user_is_anonymous',
+    'type' => MENU_LOCAL_TASK,
+  );

And much more high level, to see if were heading in the right direction: Having a seperate page might indeed be cleaner. Not sure about the pros and cons. Tagging for usability review.

+++ b/core/modules/openid/openid.moduleundefined
@@ -41,7 +48,7 @@ function openid_menu() {
-  if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && $path == 'openid/authenticate') {
+  if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && in_array($path, array('openid/authenticate', 'user/login/openid'))) {

Not sure why we need this one. Was the equivalent available in offline mode before?

c960657’s picture

What is this about?

It is used by this code:

+  $block['content']['openid_login_form']['#states'] = array(
+    'visible' => array(
+      'window' => array('hash' => '#openid-login'),
+    ),
+  );

Contrary to other types of events used in #states, the hashchange event is not triggered on an element inside the DOM but on the window object (i.e. an object that you cannot select using a regular jQuery selector). The patch adds support for the magic strings "window" and "document" (they don't match any HTML tag names, so they cannot be confused with jQuery selectors). The "document" type is not used by the patch but is only added for completeness.

Not sure why we need this one. Was the equivalent available in offline mode before?

Yes (see #363580: OpenID login fails when in maintenance mode). The login form itself was included on /user that is white-listed in
user_menu_site_status_alter().

Niklas Fiekas’s picture

StatusFileSize
new16.91 KB

Thanks for explaining everything, @c960657! On to a more nitpicky round ;)

+++ b/core/misc/states.jsundefined
@@ -107,13 +107,25 @@ states.Dependent.prototype = {
+        var arg;

Maybe add a short inline comment stating your explanation here.

+++ b/core/modules/openid/lib/Drupal/openid/Tests/OpenIDTestBase.phpundefined
@@ -19,6 +19,7 @@ abstract class OpenIDTestBase extends WebTestBase {
+    $modules[] = 'node';
     $modules[] = 'openid';
     parent::setUp($modules);
 
@@ -38,6 +39,9 @@ abstract class OpenIDTestBase extends WebTestBase {

@@ -38,6 +39,9 @@ abstract class OpenIDTestBase extends WebTestBase {
       ))
       ->execute();
 
+    // Use a different front page than login page.
+    config('system.site')->set('page.front', 'node')->save();
+
     // Create Basic page and Article node types.
     if ($this->profile != 'standard') {
       $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
@@ -51,7 +55,7 @@ abstract class OpenIDTestBase extends WebTestBase {

@@ -51,7 +55,7 @@ abstract class OpenIDTestBase extends WebTestBase {
   function submitLoginForm($identity) {
     // Fill out and submit the login form.
     $edit = array('openid_identifier' => $identity);
-    $this->drupalPost('', $edit, t('Log in'));
+    $this->drupalPost('', $edit, t('Log in'), array(), array(), 'openid-login-form');

Why are these changes nescessary? (To be 100% sure I understand your comment there correctly.)

+++ b/core/modules/openid/openid.moduleundefined
@@ -120,58 +127,77 @@ function openid_user_logout($account) {
+ * Form constructor for the OpenID login form.

We should have a @see reference to the submit handler.

+++ b/core/modules/user/user.moduleundefined
@@ -1036,33 +1036,22 @@ function user_account_form_validate($form, &$form_state) {
+function user_login_block() {

While were refactoring a large part of this, maybe add a paragraph of doxygen?

 

I continued testing manually, this time without JavaScript and block and form work fine. Yay!

 

The UI change is this:
1538462-openid-login-screenshot.png
(OpenID now on a seperate tab.) I think this looks pretty clean. Let's go with that, unless there are some terrible effects of this I am missing.

 

I'll try to ping @nod_ in IRC, because I am not too comfortable with the JavaScript parts. If no objections, then I'd say RTBC after the above changes.

nod_’s picture

Status: Needs review » Needs work

I'm not really happy to add window and document thing to #states.

the # link should be changed to #nogo at least, it's annoying to be sent to the top when toggling. Why can't we use a button to do that? Buttons can live outside of forms.

It's two different forms i'm sure we can find something a bit better than was we used to do in D7.

c960657’s picture

Why are these changes nescessary? (To be 100% sure I understand your comment there correctly.)

In the recent fix for #375397: Make Node module optional, the default front page changed from "node" to "user". For testing purposes we need to test both the login form on user and the login block some other page. To avoid any confusion for test writers, the patch makes sure that the "user" page is only accessible on /user, not on the front page.

I'm not really happy to add window and document thing to #states.

Could you please explain your concerns?

An alternative to the magic strings "window" and "document", we could use a colon prefix like the :input jquery selector. This syntax would allow for the key=value syntax mentioned in #9.

the # link should be changed to #nogo at least, it's annoying to be sent to the top when toggling. Why can't we use a button to do that? Buttons can live outside of forms.

I'll change the # link. Perhaps I can remove it completely using e.g. history.pushState().

I think a link is better UX than a button (not that I'm a UX expert), because pressing it does not execute anything but just takes you to another place (without reloading the page, but apart from that, the result is the same).

nod_’s picture

The states API is a convenience, JS has events, that's what we should be using for this. it makes sense to use #states inside of a form, not so much outside of it. Adding window and document opens things up to messy horrible things people will want to come up with. And #states is still bugged adding that would just add problems. People will expect that "document, input[type='text']" will match. It wont.

the link doesn't actually goes anywhere, and with JS off, it simply doesn't show up. Clear case for a button and not a link.

localStorage can be used for storing the value, we'll be able to do neat things with that, not so much with pushState. localStorage is already used in tabledrag :D

c960657’s picture

Status: Needs work » Needs review
StatusFileSize
new17.4 KB

@nod_: I understand you concerns about the states API, and I agree. I have reverted to some hybrid between the current code and the previous patch that also fixes #897794: OpenID login link can hide the wrong fields and #1663140: Clean up css in openid.

-    $this->drupalPost('', $edit, t('Log in'));
+    $this->drupalPost('', $edit, t('Log in'), array(), array(), 'openid-login-form');

Why are these changes nescessary? (To be 100% sure I understand your comment there correctly.)

With this change, there are two forms in the user login block (the username/password form and the OpenID form). We need to specify which of these should be submitted.

We should have a @see reference to the submit handler.

Done. I also moved the submit handler upwards in the file so that it appears right after the form builder.

While were refactoring a large part of this, maybe add a paragraph of doxygen?

Done:

/**
 * Generates the login form for use in the "login" block.
 *
 * @see user_login()
 */
localStorage can be used for storing the value, we'll be able to do neat things with that [...]

I agree. But this is a slightly bigger change, because the cookies are currently set server-side using user_cookie_save(). This is outside the scope of this task, so let's pursue the idea in a separate ticket.

the link doesn't actually goes anywhere, and with JS off, it simply doesn't show up. Clear case for a button and not a link.

I disagree. We use links for similar purposes several places in Drupal:

  1. Vertical tabs, e.g. on the node edit page.
  2. Links for revealing hidden text fields, e.g. “Machine name” on admin/structure/types/manage/page, or “Edit summary” on the node edit page.
  3. Collapsible fieldsets, e.g. the “Core” category on admin/modules.
  4. The “Show row weights” link on admin/structure/block.

I couldn't find anywhere in core where we use buttons for toggling visibility (or for anything else for that matter).
If you don't agree, let's hear a second opinion.

nod_’s picture

Check out #1719640: Use 'button' element instead of empty links.

agreed for local storage. there is the same thing for the toolbar state. easy enough to fix somewhere else.

c960657’s picture

Ah, I see. I suggest we deal with the buttons vs. links issue in #1719640: Use 'button' element instead of empty links. For now I think we should commit the patch based on links (like we have today). Then we can change the links to buttons in #1719640 in a consistent manner across core.

I think it's a bit dangerous to try to anticipate what other tickets may or may not be fixed in the near future (Drupal works in mysterious ways). If for some reason #1719640 is not committed but this ticket is, then we would have introduced an inconsistency, and that is IMO even worse than having one sub-optimal solution.

If #1719640 lands in trunk first, then we can easily update the patch for this ticket.

nod_’s picture

fair enough. I'll keep an eye on the different issues an follow up.

thanks.

c960657’s picture

Issue tags: +D8 stable release blocker
StatusFileSize
new18.08 KB
catch’s picture

Priority: Major » Critical
Issue tags: -D8 stable release blocker

Removing tag, bumping to critical.

Bojhan’s picture

Issue tags: -Needs usability review

Good to me.

sun’s picture

Status: Needs review » Needs work

I'd love to move forward here, and really want to say THANKS to @c960657 for keeping this alive!

This patch looks good to me. I only have two issues with it, which I hope can be addressed quickly:

+++ b/core/modules/user/user.module
@@ -733,33 +733,28 @@ function user_validate_current_pass(&$form, &$form_state) {
-function user_login_block($form) {
...
+function user_login_block() {

@@ -840,7 +835,7 @@ function user_block_view($delta = '') {
         $block['subject'] = t('User login');
-        $block['content'] = drupal_get_form('user_login_block');
+        $block['content'] = user_login_block();

I have to admit that I always wondered why the user login form and the user login block form are two separate forms, but I think this refactoring should be discussed in a separate issue -- this patch does not seem to depend on that, unless I overlooked something.

+++ b/core/modules/user/user.module
@@ -1196,6 +1191,12 @@ function user_menu() {
+  // Other authentication methods may add pages below user/login/.
+  $items['user/login/default'] = array(
+    'title' => 'Log in using username and password',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -1,
+  );

The weight of the default local task should be -10.

I'm not sure whether it makes sense to repeat "Log in" in the local task, since it is in the parent/root tab already... which would also shorten the other tab to just "OpenID".

c960657’s picture

Status: Needs work » Needs review
StatusFileSize
new16.99 KB

Thanks for the review.

This patch addresses the concerns raised in #36. I will file a separate bug about the user_login_block() refactoring.

podarok’s picture

#37 looks good for me
didn`t review css styles

nod_’s picture

StatusFileSize
new1.99 KB
new16.87 KB

Wasn't happy with the JS. Rerolled.

interdiff with #37

andypost’s picture

Status: Needs review » Reviewed & tested by the community

Manually tested this - it works!
I found a new tab for openid login a bit confusing but now it's possible to make it primary login form!

PS: About login_form refactoring see #1772584: Get rid of user_login_block() & user_login() in favour of user_login_form()

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Awesome. Thanks so much for the hard work on this, folks!

Committed and pushed to 8.x. Thanks.

Automatically closed -- issue fixed for 2 weeks with no activity.