Project:CAS Attributes
Version:7.x-1.x-dev
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:closed (fixed)

Issue Summary

We've configured our CAS server to include attributes of user objects in the authentication response using the suggestions posted to the cas-user mailing list here http://www.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-resp... . We'd like to include these attributes in the Drupal database and update them on each authentication in the case of changes. We've extended phpCAS to include two additional functions, getAttribute and getAttributes for the purpose of returning attribute values included in the auth response to applications. These changes to phpCAS are available here: http://github.com/adamfranco/phpcas/zipball/AttributeSupport . Also, the latest update of phpCAS includes a getAttributes function to return attributes from SAML and we plan to submit a patch to that project to extend that function to work with CAS responses as well. We have additional documentation on our changes available here: https://mediawiki.middlebury.edu/wiki/LIS/CAS

The attached patch extends the administration form to include four additional fields: one to turn on attribute support and three for each of the attributes to map to the uid, name, and mail fields in the Drupal database. After enabling attribute support, you enter the name of the attribute field from the CAS response into each of these fields. You can also assign multiple attributes to a field by entering them as a comma separated list which is translated to a space separated (by default) value in the database field (e.g. FirstName,LastName would be entered as Ian McBride in my case). None of these fields appear unless the phpCAS::getAttributes method exists, nor should any other operations of the module be affected in that case.

We're running this against CAS 2.0 with a rather recent version of phpCAS and haven't been able to test against previous versions.

(Also, this is my first code submission to a contrib module, so let me know if I messed anything up).

AttachmentSizeStatusTest resultOperations
cas-00.patch5.96 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch cas-00_0.patch.View details

Comments

#1

This looks great. We just moved to CAS3 and our starting to use release attributes and I just began testing phpcas 1.1 which has a getAttributes method. So in theory this should work with phpCAS 1.1. I'm going to try and look at this very soon and hopefully have a good review.

#2

Let's start out with a re-roll to allow for some changes.

AttachmentSizeStatusTest resultOperations
attrib-503414-1.patch6.08 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch attrib-503414-1.patch.View details

#3

How is name supposed to be used? I don't think in a default installation of drupal do we have a place for a full name of the user.

#4

Status:needs review» needs work

You can use the drupal name field if you don't have drupal be the user repository, and automatically change the name of the user. The load third party stuff should find the user in a way that's sane. So you could use $user->name for the full name. That would be nice since $user->name is displayed as the field. The patch should definitely check to make sure that the CAS settings are right before mucking with $user->name .

I'm with redndhead, that I don't think we should just be concatenating attributes onto the end of cas_name. That seems like a mistake. I'd like to see these attributes loaded into the user object in some kind of standard way so that they could be used by the
theming layer.

One way to accomplish this would be to store the attributes in a session variable that got loaded all the time? However, It would be nice to see the ability to update matching profile fields with the profile module enabled, so that these values persist at login time.

Do I understand this correctly that this patch will work against Relase 1.1 of phpCAS and no longer requires a hacked version of that library?

#5

Either CAS 3 or phpcas loads the attributes in a session variable called attributes.

#6

I'm working on this today so metzlerd, i.mcbride if you are on IRC ping me. I currently have the admin area enumerating all profile module fields if the profile module is enabled, so we can populate profile fields as users sign on. We can add support for other modules as they come up.

I think drupal supports numerous ways of changing the display name. I think we should support those modules and not implement our own for now at least.

i.mcbride: I'm going to remove the concatenation for now. I would love to hear your feedback on how your implementation works for you and how removing concatenation will affect your sites.

#7

Sounds great redndahead. We may want to break up the profile administration to a separate tab. The admin settings screen is getting kind of cluttered. Feel free to start the tab creation, and I'll work on reorganizing the administration at a later date.

Do you have some examples of modules that alter display names that I can browse?

Rednhead, are you thinking of role syncronization with this? I'd love to eventually drop the LDAP integeration support from CAS and this might be a great step in that direction. Sent you a message via your contact form about how to get ahold of me. But will be monitoring this queue as well.

Thanks for the work!

Dave

#8

Status:needs work» needs review

Here is a newer version with these changes
* Name and Concat pieces removed. We can add that back in easily if necessary. This may need more thought.
* Added support for the profile module. You can map attributes from the CAS to the profile modules fields.
* Added support for SAML_VERSION_1_1. For some reason I don't receive any attributes without using this. It may be just on my end, but I went ahead and added that option.

That should be it.

@metzlerd, Auto-assigning roles based on attribute could be done, but the role would already have to exist. This would need more thought as it's not necessarily a 1 to 1 relationship as profile fields are.

AttachmentSizeStatusTest resultOperations
attrib-503414-2.patch7.28 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch attrib-503414-2.patch.View details

#9

Status:needs review» needs work

There is a bug in this one. I think I needed to keep cas_attributes_name and not cas_attributes_uid. The problem I'm having is it's not setting the email address correctly. Any insight as to why that may be would be helpful.

#10

the following code looks wrong to me:

<?php
    $cas_uid
= "name";
    if (
$cas_attributes && $cas_attributes_uid) {
      if (
$cas_authmap) {
       
$cas_uid = "init";
      }
      else {
       
$cas_uid = "uid";
      }
    }
?>

What the devil is "init" ? That's not a valid loadable username attribute. I think the dynamic attribute naming is confusing. I'd rather just see an if then. It's not like there are a large number of ways that you can execute user_load. It needs either a name or a uid, end of story. I'd like to see this snippet of code changed as well:

<?php
$user
= user_load(array($cas_uid => $cas_name));
?>

#11

I've killed the first block of code in my latest version. I'm still rummaging through this, but I'll send something up once I get email working.

#12

Status:needs work» needs review

Alright here is another one. My email problem was because I wasn't actually getting the attribute. CAS 3.3 it's a little bit of a pain to configure.

AttachmentSizeStatusTest resultOperations
attrib-503414-3.patch6.36 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch attrib-503414-3.patch.View details

#13

There is a bug with saving profile fields that don't have attributes. user_save() erases them unless you update them in the table. I don't quite have time to write a patch at the moment, but here is the actual code change.

  // We need to re-add profile values after save. Fixes issue where
  // user_save() erases profile values.
  if (module_exists('profile')) {
    $profile_fields = db_query('SELECT `fid`, `name` FROM {profile_fields} ORDER BY `weight` ASC');
      if ($profile_fields != FALSE) {
        while ($row = db_fetch_array($profile_fields)) {
          $profile_values[$row['fid']] = $user->$row['name'];
        }
      }
    }
     
    $user = user_save($user, $user_up);
     
    // Add our profile module values.
    if (module_exists('profile')) {
      foreach ($profile_values AS $fid => $value) {
        db_query("UPDATE {profile_values} SET value = '%s' WHERE fid = %d AND uid = %d", $value, $fid, $user->uid);
      }
       
      $user = user_load($user->uid);
    }

#14

Any news on this issue, guys? Pretty much interesting feature to use!

#15

Also interested to know if this has been rolled in. I'm evaluating SSO options and this is a feature requirement.

#16

I must confess I have not given this issue it's due attention. I've not rolled this into head. There are some changes I'd like to make to get it to be more configurable. I also need to spend the time to make sure I've got a suitable test environment for this one. With a cas server that works for this.

It is not rolled into head, but I'm committed to add this support eventually. I'll try and get to testing with cas 1.1 as soon as I can.

Having help in reviewing and commenting on the pathes would greatly help here.

Dave

#17

Here is an updated patch. Rolls in #13. I need to be able to set roles using cas attributes so I'll be working on that now.

AttachmentSizeStatusTest resultOperations
503414-cas_attributes-1.patch7.63 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 503414-cas_attributes-1.patch.View details

#18

Here is with role support and a small fix for an error when there are not profile fields, but profile module is enabled.

From what I can see what is left is support for content profile module.

AttachmentSizeStatusTest resultOperations
503414-cas_attributes-2.patch8.41 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 503414-cas_attributes-2.patch.View details

#19

Status:needs review» needs work

I'm asking for a change in direction here to make the way we deal with saving profile/email information to be consistent with the way that the LDAP module will want to do the same thing. Basically I want to implement some alter hooks that would let modules alter user information from the cas object prior to being saved.

Look at #533762: Provide LDAP attributes/groups integration for more information, partcularly at comment #47. I've created a 6.x-3 branch to start this work. Full on attribute support should then happen in the 6.x-3 version of the cas module.

#20

Version:master» 6.x-3.x-dev
Status:needs work» needs review

ok let's try this.

This adds a new hook and just passes the attributes to it for processing.

hook_process_attributes_cas($user, $attributes) {

}
AttachmentSizeStatusTest resultOperations
503414-cas_attributes-3.patch981 bytesIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 503414-cas_attributes-3.patch.View details

#21

Assigned to:Anonymous» metzlerd
Status:needs review» needs work

This isn't quite what I had in mind, as it fires after the user save event. I'm going to spend some time refactoring the cas module so that it handles this case a bit better. I'll need to pull some bits of code from several different issues, since many folks have been trying to tackle the same problem in different ways. I'll post some revised patches here soon.

#22

We are tentatively using the attached patch which is based on cas_attributes-3.patch above. We needed to be able to assign roles based on SAML attributes. Additionally, for some of our clients, we need to modify $user_default immediately before calling user_save(); I've added a hook to accomplish this.

function user_properties_cas(&$user_default, $properties, $cas_name) { }

AttachmentSizeStatusTest resultOperations
cas_attributes-4.patch1.22 KBIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch cas_attributes-4.patch.View details

#23

I work on the same team as Ian McBride (the original issue submitter) and recently reworked our CAS integration to update our cas.module and phpCAS to the latest versions. In doing so, I found we had been over-complicating things quite a bit and that it is much easier to just add a custom module that implements hook_user() and handles attribute mapping when user_module_invoke('login', $edit, $user); is called.

Below (and attached) is an example module that maps names and emails via CAS/SAML attributes accessed through phpCAS:

<?php

/**
* Implementation of hook_user()
*
* Renames the user account using CAS attributes if authenticated via CAS.
*/
function cas_midd_user ($op, &$edit, &$account, $category = NULL) {
  // For cas logins, set the user properties based on the CAS attributes.
  if ($op == 'login' && class_exists('phpCAS') && phpCAS::isSessionAuthenticated()) {
    $attributes = phpCAS::getAttributes();
    $changes = array(
      'name' => $attributes['FirstName'].' '.$attributes['LastName'].' ('.$attributes['EMail'].')',
      'mail' => $attributes['EMail'],
    );
    $account = user_save($account, $changes);
  }
}

While user_save() has already been called once by cas_login_check(), calling it again seems to work fine for us. This would also be a good place to put code that added roles based on attributes. One big advantage of a custom hook_user() implementation is that it provides a huge amount of flexibility beyond what could reasonably be added to the CAS module proper. Implementers will all have different attributes, some of which might be multi-valued -- all in all, a lot of variation to handle.

So, from our perspective this issue is resolved -- but as is discussed in the other comments someone might have a case where user_module_invoke('login', $edit, $user); is too late in the process to do the work they need.

#24

The primary use case that isn't covered by a login hook is when we want the attributes to control whether or not the drupal accounts get created in the first place. Also, I would like to have some custom attribute support available complete with roll mapping. If I can every get our payroll implementation finished I'll get back to this issue with more serious attention.

Dave

#25

subscribing

#26

Subscribing -- note that I'm not waiting for the feature, I'm going to take a crack at #23, but just want to keep in the loop.

#27

People interested in implementing custom modules associated with this issue should read:

#1059942: Refactor Hooks

There's a lot of interesting dicussion about best approaches, and the cas module is needing to undergo some fairly heavy refactoring due to the deprecation of the drupal authmap system. Be warned that the 6.x-3 and 7.x-1 development snapshots are undergoing some significant api changes.

#28

So I think it'd be fantastic to provide a helper module to allow for (easy) setting of Drupal user attributes from information returned by phpCAS::getAttributes().

I've mocked up a simple module "CAS Attributes" in a CAS Helpers sandbox for D7. It'd be great to convert it to use token type replacements, but that's for later.

#29

I'm happy to roll the CAS Helpers code into this module, but I'd like to write some test routines before doing so. Does anybody have a link to the CAS attributes protocol? Essentially I need to modify cas_test.module (which acts as a fake CAS server) to return attributes, but I don't know how phpCAS expects them to be sent.

#30

#31

Hi Bradley,

SAML authentication is the officially supported way of passing attributes to client applications, however that protocol doesn't support proxy authentication. While there is no official standard format for attributes in a CAS 2.0 protocol response, there are several formats in somewhat wide use that phpCAS supports:

* Jasig style:

    <cas:attributes>
        <cas:attraStyle>Jasig</cas:attraStyle>
        <cas:surname>Smith</cas:surname>
        <cas:givenName>John</cas:givenName>
        <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
        <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
    </cas:attributes>

* RubyCAS style

    <cas:attraStyle>RubyCAS</cas:attraStyle>
    <cas:surname>Smith</cas:surname>
    <cas:givenName>John</cas:givenName>
    <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
    <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>

* Name-Value style

    <cas:attribute name='attraStyle' value='Name-Value' />
    <cas:attribute name='surname' value='Smith' />
    <cas:attribute name='givenName' value='John' />
    <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
    <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />

The support for these formats is documented in the unit tests in phpCAS that were introduced in phpCAS 1.2.0:
https://source.jasig.org/cas-clients/phpcas/trunk/test/tests/Cas20Attrib...

Hope this helps,
Adam

P.S. I'm not sure if it would be helpful for the Drupal unit tests, but as of 1.2.0, phpCAS includes a pluggable HTTP request system that can be swapped out with a dummy system for unit testing. See the test file linked above for an example of its usage.

#32

The attached one-liner patch to latest 6.x and 7.x dev versions adds attributes to the $cas_user array. This allows both hook_cas_user_alter and hook_cas_user_presave to use the attributes to allow/deny access, to assign roles, to change the username, etc.

AttachmentSizeStatusTest resultOperations
503414-cas_attributes-5.patch399 bytesIdlePASSED: [[SimpleTest]]: [MySQL] 405 pass(es).View details

#33

Status:needs work» needs review

#34

Status:needs review» needs work

+++ cas.module 2011-04-15 09:59:20.802587100 -0400
@@ -88,6 +88,7 @@
       'register' => variable_get('cas_user_register', TRUE),
+      'attributes' => method_exists('phpCAS', 'getAttributes') ? phpCAS::getAttributes() : array()

Looks great, however we'll also need to document this in cas.api.php.

Powered by Dreditor.

#35

The attached patch to cas.api.php documents the 'attributes' element of the $cas_user array.

AttachmentSizeStatusTest resultOperations
503414-cas_attributes_api.patch665 bytesIdleFAILED: [[SimpleTest]]: [MySQL] Unable to apply patch 503414-cas_attributes_api.patch.View details

#36

Status:needs work» active

Wonderful. daniel_j, I've committed your patches (#32 and #35) to 6.x-3.x and 7.x-1.x [and attributed you as the author].

I'm resetting the issue status to "Active" as there was some additional discussion about adding a UI for automatically setting the CAS attributes which has not been resolved.

#37

Title:CAS Attribute Support» CAS Attribute Token Support
Version:6.x-3.x-dev» 7.x-1.x-dev
Status:active» needs review

Here's the beginning of some more code.. token support for CAS username and attributes in D7.

The tokens do work -- for example:

<?php
token_replace
('[cas:name]', array('cas' => cas_current_user()));
token_replace('[cas:attribute:surname]', array('cas' => cas_current_user()));
token_replace('[cas:attribute:memberOf]', array('cas' => cas_current_user()));
?>

I envision a UI which would allow one to enter in tokens for different profile fields, username, and e-mail fields. CAS attribute tokens would be provided by the CAS module, and LDAP-related tokens would be provided by a cas_ldap module (possibly included here). (These tokens would be of the form '[cas:ldap:displayName]', for example.)

AttachmentSizeStatusTest resultOperations
503414-Implement-tokens-for-the-CAS-username-and-attrib.patch8.16 KBIdlePASSED: [[SimpleTest]]: [MySQL] 411 pass(es).View details

#38

Project:CAS» CAS Attributes
Version:7.x-1.x-dev» 7.x-1.x-dev
Assigned to:metzlerd» Anonymous
Status:needs review» active

Moving this to the NEW CAS Attributes module.

#39

Status:active» fixed

Please try out this module (CAS Attributes), which should be functional for both 6.x-3.x and 7.x-1.x.

#40

Status:fixed» closed (fixed)

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