Download & Extend

Please do not randomize my password on every login

Project:CAS
Version:master
Component:Code
Category:bug report
Priority:normal
Assigned:Unassigned
Status:closed (fixed)

Issue Summary

I set up a CAS setup with a Drupal site being the source of the credentials. The idea being that users can sign up on one site, and be automatically logged in on other independently-developed Drupal sites (I was sketchy on sharing databases, especially between versions) and other web applications.

In "User account settings", I checked the box labeled "Is Drupal also the CAS user repository?" When I logged in the first time, it worked. After that, I couldn't log in again, even not using CAS. Then I found this magic piece of code in cas.module (line numbers shown):

<?php
195
-      // update the roles and reset the password
196-      $roles = $user->roles;
197-      foreach($cas_roles as $role) {
198-       $roles[$role] = $role;
199-      }
200-      $user_up = array(
201-       "pass" => user_password(),
202-       "roles" => $roles
203
-      );
204-      $user = user_save($user, $user_up);
?>

If I comment out "pass" => user_password(), , everything seems to work (at least, after I reset my passwords via MySQL!). I understand why assigning a random password on login seems like a good idea when using an independent CAS database (if we are never using the password, it might as well be very difficult for an intruder to guess or brute-force). That said, it's a horrible idea when Drupal is the CAS repository, because then the user doesn't know their password for the next login.

I suspect the code should really look like this:

<?php
     
// update the roles and reset the password
     
if (!$cas_authmap) { // Update roles and password only if Drupal isn't used as the authentication database
       
$roles = $user->roles;
        foreach(
$cas_roles as $role) {
         
$roles[$role] = $role;
        }
       
$user_up = array(
         
"pass" => user_password(),
         
"roles" => $roles
       
);
       
$user = user_save($user, $user_up);
      }
?>

It's possible I'm just misunderstanding the intention of "Is Drupal also the CAS user repository?", and if that's the case feel free to let me know. But if anyone else is trying to do what I'm doing, this is a pretty irritating bug to run into. I suppose there may be an interest in assigning the roles if some people log in via Drupal and some log in via CAS, but I'm not sure.

Comments

#1

I'm not sure if this should be happening or not. I didn't notice this when I did the code review when this feature (default roles) was added. In your case, shouldn't an account of a given name be "either a CAS account" or a drupal generated account? Not sure why you'd be logging into accounts with the same name that are both cas and drupal login enabled.

Drupal is the user repository is really meant for when you want all your drupal user's to be cas. When drupal is not the user repository for cas you expect cas to create an "external user" mapping for the drupal login. It's not well documented, I admit, but if cas user repository is NOT CHECKED, then the user external_load is called to match the cas user against the drupal "External user id" instead of the user name..

Normally with CAS, drupal passwords don't matter. You don't (Normally) athenticate via the cas module with passwords that are stored in the drupal database, so I'm not quite sure how resettting the passwords is an issue unless you are using drupal logins and cas logins for an account with the same name. Either way this will be problematic.

I need to understand a little more about what you're trying to achieve here. So that I can balance the security with the feature set you're looking for. A single account generally shouldn't have to ways of authentication, and that's kind of what the password randomization achieves here.

#2

I understand what you are saying. Let me give you a quick use case, so you can see what I'm trying to do.

We have three sites, which we'll call siteA.com, siteB.com, and siteC.com. Content is divided between the three based on some business decisions -- basically siteA.com is a free community-oriented blog/forum site, siteB.com sells products (physical and online access), and siteC.com sells services. I don't want people to have to sign up on all three sites -- I expect every user of siteB to be a member of siteA, and the users of siteC will probably also be active on siteA.

My primary responsibility is siteA (though I am also the main technical person, so I'll be involved in the others). I would like to use Drupal 6.2 to run that site, because I don't require many modules and I like the new features. Now siteB is expected to also be Drupal, but it will have to run either Ubercart or eCommerce, neither of which has a 6.x version on the horizon. So I think I might run that on Drupal 5.7. That site will also have some Java applications running in JBoss, which I'd like to be able to confirm that users are legitimate as well. We haven't thought of how to do siteC yet, but we want to keep our options open.

I could say that everything will run Drupal 5.7, and the Java applications will either have to peek into Drupal's user/session databases or will just have to be set up in such a way that it is unlikely that a user without permissions would be able to run them (I'm sure there are plenty of ways to do this which are "good enough"). But then I have to share tables and/or use a single-sign-on module (probably both). And, of course, everything has to run from the same Drupal install (if nothing else, I wouldn't want different Drupal versions sharing the same database tables).

What I have set up right now is as follows. I install Drupal 6.2 on siteA. I install CAS server in JBoss. I use the JDBC authentication handler to read from the siteA users table, and compare the MD5'd password there with the MD5 of the password given. This means that users on siteA should be able to sign in to the CAS server. On siteA, I install the CAS module and set the "Is Drupal also the CAS user repository?" setting. On siteB, I install Drupal 5.7 (with the 5.x version of the CAS module). I point it to the same CAS server, but tell it that it is not the user repository, and that it should create any new users. The idea is that I can set up any number of other sites this way, and when someone creates an account on siteA and logs in there (via CAS), then at most they need to click the "CAS Login" link on any other site and after a few redirects they will be logged in there. I figure people will only want profiles on siteA (the community site), so I don't have to worry about copying any user information between sites.

Maybe someone with more CAS and/or Drupal experience can see some flaws in this. It's already occurred to me that nobody should be able to change their username, or else all hell will break loose. I need to make some links on all the secondary sites to ensure that if someone wants to change their password, they get sent to siteA. I also would like to do some referral/userpoints stuff between the sites, but I can figure that out later. Admittedly, I would REALLY like to have each user have the same uid on every Drupal site, so I might have to code something in to cause that (otherwise, if I try to write any cross-site code, I'll have to make sure to translate from siteA_uid->username->siteB_uid or something like that).

The point is that under my use case, Drupal *IS* the authentication database, so I obviously don't want it to reset the password when I log in, or else every user can only log in once.

Maybe that isn't a supported use of the CAS module -- I might just be reading what I want to see when I look at "Is Drupal also the CAS user repository?". I mean, I know CAS is mostly used for institutions, not to provide SSO between a few public websites. Or maybe you have some better ideas on how I could accomplish this (in which case, I'm all ears).

#3

Thank you, that does get to the hart of the matter. A clever plan for sure, and yes that's not quite what the original implementer meant by Drupal is the cas user repository... The most common use case for cas is to provide common auth for an external user database so that's why other's haven't discovered this... at least yet.

Based on that, here's some options we can consider.

A. I think the easiest patch would be to actually add a specific setting to the cas module to enable/disable this function, and explain why. I'm worried about existing clients depending ont this feature for security, so I'd like to be clear about turning it off. I'd commit a patch to this module if you submitted it.

B. I've often had to answer support questions for people expecting the cas module to provide a complete authentication server and client solution within drupal. I think that's the ideal (but obvoisly a ton of work) way to do what you are asking for. That way you wouldn't need a JBOSS server to do what you are trying to do. Basically the cas spec is a simple XML REST Web service and could be reimplemented in PHP if you've the the nerve and time. This would let you tie into drupals existing registration/password management mechanisms. You'd basically enable the cas server module on the primary site, but the cas client module on the ancillaries. You'd need an ssl drupal site to ensure this works with clients. The protocol isn't that complex and I'd be willing to assist if you indeed are willing to contribute.

C. Change your CAS server code to copy the password into a new/different table and do the auth against that table. New password is verified the first time, but then stored in the CAS server data store thereafter.

D. I love CAS, and think it's great, but you might look at the OpenID modules which (I believe ) are designed to provide a complete distributed authentication mechanism for dupal. I can't vouch for it, but there's a lot of drupal developer support for this direction.

Do any of these options seem pallatable?

Dave

#4

Dave,

I think a number of those options could work. Also option "E", which would be "I'm doing something so strange I should just have my own patch for this module". :)

I'm trying to get this up and running pretty quickly, so I think I'll have to pass on option B for now (though if I had the time, I'd be glad to write such a module). I'll have to have some sort of J2EE environment anyway, so it doesn't save me much. I think option D (OpenID) isn't quite enough for me. While that would allow people to use the same username/password combination on each site, they'd at least have to type in their OpenID URL into each site. They'd also have to sign up for accounts separately on each site, if I understand correctly, and then associate their OpenID with each account. And I'd have trouble associating accounts between sites since the "common link" would be the OpenID associated with the accounts instead of a username or UID (and since you can have multiple OpenIDs associated with one account, things get a little odd).

Option A clearly seems the easiest. That said, option C might be "better". Let me see if I understand what you are suggesting for "C". The CAS server gets a request to authenticate someone. That person enters their username and password, and first the CAS server searches it's database. If it finds the user, it validates them there. If it doesn't find the user, it looks in the Drupal users table, and if the user can be validated against that source, it then creates an entry for them in the CAS server's database. I would have to add support for changing the user's password in the CAS database, but other than that it seems like a good solution. I also need to figure out if what I really want is to use the external_user table. Let me think about it and get back to you.

On a side note, I think I found a "real" bug. I wondered why clicking on "CAS Logout" seemed to always be a dead end. I realized that I was getting logout URLs like this:

    https://login.myserver.com/cas/logout?service=http%3a%2f%2fmyserver.com%2fdrupal-6.2

instead of:

    https://login.myserver.com/cas/logout?url=http%3a%2f%2fmyserver.com%2fdrupal-6.2

which is what is requested in the spec (section 2.3.1 @ http://www.ja-sig.org/products/cas/overview/protocol/index.html )

If I use "url" instead of "service", it gives me a link to the URL provided so I don't have to hit the back button to get back to the site. Looks like line 598 of cas.module just uses the wrong URL. (You can tell I considered option B, or else I would've never skimmed the spec to notice that ;))

#5

Yeah, it seems there's some discrepancy on other cas implementations on this. Some say service works, some say url works. I'm unsure as to what to do here. Could you review the patch on http://drupal.org/node/193460, and see if the destination options works?

Dave

#6

I'm wondering why the password has to be reset to begin with. It's either set by the user in the scenario that korvus describes, exists previously when the module hijacks users or is generated "randomly" when the user is registered by the module. If brute-force attacks were successful enough to exploit these stale passwords, we'd need hardcore password expiration policies across the board. Am I missing anything?

Resetting it causes some unexpected behaviour with sessions. The user_save function destroys sessions if the password has changed, this can cause havoc if multiple sessions per user is expected.

Great module overall, thanks Dave.

Mitch

#7

The code got put in there because if you create a new user, and there is no password set, then you will leave a security vulnerability. No password means that you don't need a password to login.

Continue to be open to patches that preserve this protection.

#8

Isn't there a way to share tables between different databases. In other words, can't you share the user table between the three sites if they share the same MYSQL server? Since you want all of your users to be 'in sync' this seems like a good solution. I think the CAS solution is meant to handle authentication between different CMS.

Dave

#9

Sorry I haven't been back here for a while -- I put the CAS solution aside while working on some other things. To answer dwees, there's probably a degree to which that can be done, but I don't think it would work in my case. The main issues are that 1) I don't know what changes happened in the database between 5.x and 6.x (though I could probably find that out), and 2) if one site sets a cookie for a login session, other sites can't see it. I don't want the person to have to log in three times, I want them to log in once. With CAS, they may have to click on "CAS Login" on the second and third sites, but it is smart enough to realize they are already logged in and just brings them right back to the site without prompting for a password.

I'm still not sure CAS makes sense here, but it was the most graceful solution I found. Also, if we did add non-Drupal sites (quite possible, as I have a JBoss proponent working on one of the sites), CAS could work between all of them.

#10

Aah yes the cookies will not match because your sites are on completely different domains. CAS is the way to go then.

#11

Glad to hear you back. I've been mulling over this problem as I've been in the code in your absence. I think what we want to accomplish here is that the user password only gets reset when creating new accounts and not on updates. I believe that will satisfy your issue, since on the first site where you grant CAS accounts you'd be creating them manually, so no problem there. On the other sites, where you're creating them, well those passwords don't matter do they? So it's ok to reset them on the non master auth site?

Does that sound like it would work?

Dave

#12

Oh and we probably do want to implement a configuration option so that users who want to randomize passwords to make sure they don't get brute forced via a back door may desire this behavior.

#13

Yeah, I think that would work fine for me. I'm fine with randomizing passwords, if they haven't been explicitly set through some other process. So if we are automatically creating an account, I'm glad it has a random password. If we are just logging someone in to an existing account, randomizing the password doesn't make sense (in my application). Leaving the current behavior as an option would be a good idea.

#14

I have a module I built that limits the normal login to only the uid1 account. It probably wouldn't be hard to add that as an option if it would be helpful.

#15

Code was smaller than I thought, but here it is

<?php
function uid1_lock_user($op, &$edit, &$account, $category = NULL) {
    global
$user;

    if(
$op == 'login'){
      if(
$user->uid != 1 AND $_SERVER["REQUEST_URI"] == '/user/login'){
       
//drupal_set_message('You are not allowed to login.');
        // Destroy the current session:
       
session_destroy();
       
module_invoke_all('user', 'logout', NULL, $user);
     
       
// We have to use $GLOBALS to unset a global variable:
       
$user = user_load(array('uid' => 0));
      }
    }
  }
?>

#16

Another use case supporting the resolution discussed in #11-13 is when there really are two methods of authentication necessary.

I use CAS for general access on a site. A handful of content contributors use Windows Live Writer (WLW) to contribute to the blog. They use CAS to authenticate on the site, but need to use local drupal authentication for Windows Live Writer. Randomizing the password with each CAS login means they can't sign in using WLW after logging in with CAS.

Maybe there is some way to authenticate in WLW via CAS, but I haven't figured out how.

#17

The problem described by Mitch in #6 has also some impact on the new session handling from phpCAS 1.0.0. phpCAS renames sessions so that the session name is based on the ticket name ("ST..."). Because user_save destroys this session again, Drupal does not use the naming convention from phpCAS. Maybe this affects single-sign-out?

The discussed solution to randomize passwords only when creating the user should solve this issue, too.

#18

Version:6.x-1.0» master
Status:active» fixed

I've had a lot of time to reflect on this and I've sort of realized that givent that there is no stock password aging functionality in drupal and that you can always prevent all drupal logins using the cas redirect function, that my stance wasnt' really justified. I've removed the password setting functionality here. I think it creeped without me noticing it to begin with anyway.

Users who are interested creating this functionality should submit an appropriate settings controlled patch.

AttachmentSizeStatusTest resultOperations
cas-258909.patch906 bytesIgnored: Check issue status.NoneNone

#19

I'm unclear on what the behavior will be in the next version of CAS. Will the password be set to a random value when:

  1. A user account is created via CAS;
  2. A user account is taken over by CAS; or,
  3. Every time a user logs in via CAS?

#20

#21

Status:fixed» closed (fixed)

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

#22

The password will be radomized only when a new account is created.

Dave