[Jifty-commit] r4109 - in apps/CASPlus: . trunk/etc trunk/lib/CASPlus trunk/lib/CASPlus/Action trunk/lib/CASPlus/Model trunk/lib/CASPlus/View trunk/lib/CASPlus/View/Admin/User trunk/share/web/static/css trunk/share/web/templates/_elements trunk/share/web/templates/user trunk/t

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Thu Sep 13 15:54:47 EDT 2007


Author: sterling
Date: Thu Sep 13 15:54:46 2007
New Revision: 4109

Added:
   apps/CASPlus/trunk/lib/CASPlus/Action/UpdateUserPassword.pm
   apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Limits.pm
   apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Permissions.pm
   apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Properties.pm
   apps/CASPlus/trunk/lib/CASPlus/View/User.pm
   apps/CASPlus/trunk/t/20-action-UpdateUserPassword.t
Removed:
   apps/CASPlus/trunk/share/web/templates/_elements/
   apps/CASPlus/trunk/share/web/templates/user/
Modified:
   apps/CASPlus/   (props changed)
   apps/CASPlus/trunk/   (props changed)
   apps/CASPlus/trunk/etc/config.yml
   apps/CASPlus/trunk/lib/CASPlus/Action/ProxyValidate.pm
   apps/CASPlus/trunk/lib/CASPlus/Action/Validate.pm
   apps/CASPlus/trunk/lib/CASPlus/Dispatcher.pm
   apps/CASPlus/trunk/lib/CASPlus/Model/SSOSession.pm
   apps/CASPlus/trunk/lib/CASPlus/Model/ServiceSession.pm
   apps/CASPlus/trunk/lib/CASPlus/Model/User.pm
   apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipBase.pm
   apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipMixin.pm
   apps/CASPlus/trunk/lib/CASPlus/View.pm
   apps/CASPlus/trunk/share/web/static/css/app.css
   apps/CASPlus/trunk/t/50-me.t

Log:
 r12094 at riddle:  andrew | 2007-09-13 14:53:07 -0500
 Merge down from Boomer SSO vendor branch.
 ----------------------------------------------------------------------
 r11982:  andrew | 2007-09-11 17:44:23 -0500
 
  r11970 at dynpc145 (orig r1988):  andrew | 2007-09-11 17:54:44 -0500
   r11969 at dynpc145:  andrew | 2007-09-11 17:41:13 -0500
   Fixing a typo in the record_class variable.
  
 
 ----------------------------------------------------------------------
 r12074:  andrew | 2007-09-13 14:46:18 -0500
 
  r12010 at riddle (orig r1989):  andrew | 2007-09-12 10:06:47 -0500
   r12009 at riddle:  andrew | 2007-09-12 09:51:22 -0500
   Adding relationships to the administrative menu.
  
 
 ----------------------------------------------------------------------
 r12075:  andrew | 2007-09-13 14:46:18 -0500
 
  r12012 at riddle (orig r1990):  andrew | 2007-09-12 10:08:11 -0500
   r12011 at riddle:  andrew | 2007-09-12 09:54:34 -0500
   Fix the name of the relationships menu.
  
 
 ----------------------------------------------------------------------
 r12076:  andrew | 2007-09-13 14:46:19 -0500
 
  r12014 at riddle (orig r1991):  andrew | 2007-09-12 10:33:47 -0500
   r12013 at riddle:  andrew | 2007-09-12 10:20:10 -0500
   Adding rule to auto-generate CRUD views for relationships.
  
 
 ----------------------------------------------------------------------
 r12077:  andrew | 2007-09-13 14:46:19 -0500
 
  r12016 at riddle (orig r1992):  andrew | 2007-09-12 10:36:00 -0500
   r12015 at riddle:  andrew | 2007-09-12 10:22:20 -0500
   Fixing a missing parameter to _register_crud().
  
 
 ----------------------------------------------------------------------
 r12078:  andrew | 2007-09-13 14:46:20 -0500
 
  r12018 at riddle (orig r1993):  andrew | 2007-09-12 10:38:32 -0500
   r12017 at riddle:  andrew | 2007-09-12 10:24:58 -0500
   Making relationship CRUD use relationship tables rather than ModelClass.
  
 
 ----------------------------------------------------------------------
 r12079:  andrew | 2007-09-13 14:46:20 -0500
 
  r12020 at riddle (orig r1994):  andrew | 2007-09-12 10:42:43 -0500
   r12019 at riddle:  andrew | 2007-09-12 10:29:08 -0500
   Making the relationship admin screens show the link table rather than ModelClass.
  
 
 ----------------------------------------------------------------------
 r12080:  andrew | 2007-09-13 14:46:21 -0500
 
  r12024 at riddle (orig r1995):  andrew | 2007-09-12 11:29:00 -0500
   r12023 at riddle:  andrew | 2007-09-12 11:15:19 -0500
   Adding admin screens for permissions. Adding better allow/deny handling of actions.
  
 
 ----------------------------------------------------------------------
 r12081:  andrew | 2007-09-13 14:46:21 -0500
 
  r12025 at riddle (orig r1996):  andrew | 2007-09-12 11:32:04 -0500
   r6117 at viper:  root | 2007-09-12 11:18:04 -0500
   Fixed an accidentally inserted colon.
  
 
 ----------------------------------------------------------------------
 r12082:  andrew | 2007-09-13 14:46:22 -0500
 
  r12026 at riddle (orig r1997):  andrew | 2007-09-12 11:43:01 -0500
   r6119 at viper:  root | 2007-09-12 11:29:41 -0500
   Fixed a mispelt call to may_manage_roles().
  
 
 ----------------------------------------------------------------------
 r12083:  andrew | 2007-09-13 14:46:22 -0500
 
  r12027 at riddle (orig r1998):  andrew | 2007-09-12 12:19:53 -0500
   r6121 at viper:  andrew | 2007-09-12 12:05:48 -0500
   Make sure the relationship trigger always passes objects to the role calculation utilities.
  
 
 ----------------------------------------------------------------------
 r12084:  andrew | 2007-09-13 14:46:22 -0500
 
  r12028 at riddle (orig r1999):  andrew | 2007-09-12 12:20:03 -0500
   r6122 at viper:  root | 2007-09-12 12:06:29 -0500
   Added missing current_user_can() method for relationships.
  
 
 ----------------------------------------------------------------------
 r12085:  andrew | 2007-09-13 14:46:23 -0500
 
  r12029 at riddle (orig r2000):  andrew | 2007-09-12 13:17:59 -0500
   r6125 at viper:  andrew | 2007-09-12 13:00:38 -0500
   Added a check to the password test to confirm that the user actually has a password.
  
 
 ----------------------------------------------------------------------
 r12086:  andrew | 2007-09-13 14:46:23 -0500
 
  r12030 at riddle (orig r2001):  andrew | 2007-09-12 13:18:09 -0500
   r6126 at viper:  andrew | 2007-09-12 13:04:36 -0500
   Correct the log message on a missing password error.
  
 
 ----------------------------------------------------------------------
 r12087:  andrew | 2007-09-13 14:46:24 -0500
 
  r12033 at riddle (orig r2002):  andrew | 2007-09-12 13:51:22 -0500
   r12032 at riddle:  andrew | 2007-09-12 13:37:22 -0500
   Removing the remaining Mason templates and stripped half-baked RDF stuff.
  
 
 ----------------------------------------------------------------------
 r12088:  andrew | 2007-09-13 14:46:24 -0500
 
  r12039 at riddle (orig r2003):  andrew | 2007-09-12 14:07:38 -0500
   r12038 at riddle:  andrew | 2007-09-12 13:53:37 -0500
   Adding the user's profile information to their profile view page.
  
 
 ----------------------------------------------------------------------
 r12089:  andrew | 2007-09-13 14:46:25 -0500
 
  r12044 at riddle (orig r2004):  andrew | 2007-09-12 16:42:43 -0500
   r12042 at riddle:  andrew | 2007-09-12 16:25:17 -0500
   Added password updates to the my profile page.
  
 
 ----------------------------------------------------------------------
 r12090:  andrew | 2007-09-13 14:46:25 -0500
 
  r12045 at riddle (orig r2005):  andrew | 2007-09-12 16:42:53 -0500
   r12043 at riddle:  andrew | 2007-09-12 16:28:58 -0500
   Fixed navigation setup so that it happens after actions are run. Added a logout link to the nav.
  
 
 ----------------------------------------------------------------------
 r12091:  andrew | 2007-09-13 14:46:26 -0500
 
  r12062 at riddle (orig r2006):  andrew | 2007-09-13 10:10:54 -0500
   r6129 at viper:  andrew | 2007-09-13 09:57:21 -0500
   Fixing various permission issues regarding service/proxy validation.
  
 
 ----------------------------------------------------------------------
 r12092:  andrew | 2007-09-13 14:46:26 -0500
 
  r12063 at riddle (orig r2007):  andrew | 2007-09-13 10:54:59 -0500
   r12061 at dynpc145:  andrew | 2007-09-13 10:40:57 -0500
   Making the password update form smarter to allow administrators to change passwords nicely.
  
 
 ----------------------------------------------------------------------
 r12093:  andrew | 2007-09-13 14:46:26 -0500
 
  r12065 at riddle (orig r2008):  andrew | 2007-09-13 10:57:48 -0500
   r12064 at dynpc145:  andrew | 2007-09-13 10:44:10 -0500
   Fixing the help message for user administrators on the change password screen.
  
 
 ----------------------------------------------------------------------
 


Modified: apps/CASPlus/trunk/etc/config.yml
==============================================================================
--- apps/CASPlus/trunk/etc/config.yml	(original)
+++ apps/CASPlus/trunk/etc/config.yml	Thu Sep 13 15:54:46 2007
@@ -8,12 +8,12 @@
   Database: 
     CheckSchema: 1
     Database: casplus
-    Driver: SQLite
+    Driver: mysql
     Host: localhost
     Password: ''
     RecordBaseClass: Jifty::DBI::Record::Cachable
-    RecordUUIDs: active
     User: ''
+    RecordUUIDs: active
     Version: 0.0.1
   DevelMode: 1
   L10N: 

Modified: apps/CASPlus/trunk/lib/CASPlus/Action/ProxyValidate.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Action/ProxyValidate.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Action/ProxyValidate.pm	Thu Sep 13 15:54:46 2007
@@ -95,7 +95,10 @@
         # Proxy ticket renewal doesn't make sense, renew is ignored.
         
         # Load the proxy ticket
-        my $proxy_ticket = CASPlus::Model::ProxySession->new;
+        my $superuser = CASPlus::CurrentUser->superuser;
+        my $proxy_ticket = CASPlus::Model::ProxySession->new(
+            current_user => $superuser,
+        );
         $proxy_ticket->load_by_cols(
             service_identifier => $service,
             proxy_ticket       => $ticket,

Added: apps/CASPlus/trunk/lib/CASPlus/Action/UpdateUserPassword.pm
==============================================================================
--- (empty file)
+++ apps/CASPlus/trunk/lib/CASPlus/Action/UpdateUserPassword.pm	Thu Sep 13 15:54:46 2007
@@ -0,0 +1,150 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+CASPlus::Action::UpdateUserPassword
+
+=cut
+
+package CASPlus::Action::UpdateUserPassword;
+use base qw/CASPlus::Action Jifty::Action/;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+    param id => 
+        is mandatory,
+        render as 'Hidden',
+        ;
+
+    param old_password =>
+        label is 'Old password',
+        render as 'Password',
+        ajax validates,
+        is focus,
+        ;
+
+    param new_password =>
+        label is 'New password',
+        render as 'Password',
+        ajax validates,
+        ;
+
+    param confirm_password =>
+        label is 'Re-type password',
+        render as 'Password',
+        hints is 'Please enter the same password here as in the previous box to confirm your entry.',
+        ajax validates,
+        ;
+};
+
+=head2 take_action
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    
+    my $new_password = $self->argument_value('new_password');
+    $self->user->set_password($new_password);
+    
+    $self->report_success if not $self->result->failure;
+    
+    return 1;
+}
+
+=head2 report_success
+
+=cut
+
+sub report_success {
+    my $self = shift;
+
+    if ($self->current_user->id == $self->user->id) {
+        $self->result->message('Your password has been updated.');
+    }
+    else {
+        $self->result->message(
+            _('The password for <em>%1</em> has been updated.', $self->user->username)
+        );
+    }
+}
+
+__PACKAGE__->mk_accessors(qw/ _user_record /);
+
+sub user {
+    my $self = shift;
+    unless ($self->_user_record) {
+        my $user = CASPlus::Model::User->new;
+        $user->load($self->argument_value('id'));
+        $self->_user_record($user);
+    }
+    return $self->_user_record;
+}
+
+sub validate_old_password {
+    my $self = shift;
+    my $value = shift;
+
+    # Administrators don't need to enter the old_password
+    if ($self->current_user->id 
+            && $self->current_user->may_manage_users 
+            && $self->current_user->id != $self->argument_value('id')) {
+
+        return $self->validation_ok('old_password');
+    }
+
+    unless (length $value) {
+        return $self->validation_error(
+            old_password => 'Sorry, you must enter your old password to confirm your identity.');
+    }
+
+    unless ($self->user->password_is($value)) {
+        return $self->validation_error(
+            old_password => 'This password is incorrect.');
+    }
+
+    return $self->validation_ok('old_password');
+}
+
+sub validate_new_password {
+    my $self = shift;
+    my $value = shift;
+
+    unless (length $value) {
+        return $self->validation_error(
+            new_password => 'You must specify a new password for your account.');
+    }
+
+    if ($self->user->password_is($value)) {
+        return $self->validation_error(
+            new_password => 'This password is the same as the old password.');
+    }
+
+    if (length $value < 6) {
+        return $self->validation_error(
+            new_password => 'Your new password must be at least 6 letters long.');
+    }
+
+    return $self->validation_ok('new_password');
+}
+
+sub validate_confirm_password {
+    my $self = shift;
+    my $value = shift;
+
+    unless (length $value) {
+        return $self->validation_error(
+            confirm_password => 'You must re-type your new password to continue.');
+    }
+
+    unless ($value eq $self->argument_value('new_password')) {
+        return $self->validation_error(
+            confirm_password => 'This password does not match the previous entry.');
+    }
+
+    return $self->validation_ok('confirm_password');
+}
+
+1;
+

Modified: apps/CASPlus/trunk/lib/CASPlus/Action/Validate.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Action/Validate.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Action/Validate.pm	Thu Sep 13 15:54:46 2007
@@ -221,7 +221,10 @@
     my $pgt_url = $self->argument_value('pgtUrl');
 
     # Load the service ticket
-    my $service_ticket = CASPlus::Model::ServiceSession->new;
+    my $superuser = CASPlus::CurrentUser->superuser;
+    my $service_ticket = CASPlus::Model::ServiceSession->new( 
+        current_user => $superuser
+    );
     $service_ticket->load_by_cols(
         service_url    => $service,
         service_ticket => $ticket,
@@ -288,7 +291,7 @@
 
             $self->log->info('SERVICE TICKET FAILED '
                 ."Service mismatch for ticket $ticket and "
-                ."service $service");
+                ."service '$service' (not the same as '@{[$service_ticket->service_url]}')");
 
         }
 

Modified: apps/CASPlus/trunk/lib/CASPlus/Dispatcher.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Dispatcher.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Dispatcher.pm	Thu Sep 13 15:54:46 2007
@@ -9,6 +9,8 @@
 use CASPlus::Action::Logout;
 use CASPlus::Action::Validate;
 
+use Scalar::Util qw/ looks_like_number /;
+
 =head1 NAME
 
 CASPlus::Dispatcher - Dispatcher for CAS+
@@ -44,6 +46,44 @@
         }
     }
 
+    # Make sure the admin actions are available to administrators
+    {
+        my $current_user = Jifty->web->current_user;
+        my $cud = qr/^CASPlus::Action::(?:Create|Update|Delete)/;
+
+        if ($current_user->may_manage_profiles) {
+            Jifty->api->allow('Jifty::Action::UpdateModelClass');
+            Jifty->api->allow('Jifty::Action::DeleteModelClass');
+            Jifty->api->allow('Jifty::Action::UpdateModelClassColumn');
+            Jifty->api->allow('Jifty::Action::DeleteModelClassColumn');
+        }
+
+        # If not a profile manager, block them
+        else {
+            Jifty->api->deny(qr/${cud}Profile$/);
+            Jifty->api->deny(qr/${cud}ProfileProperty$/);
+            Jifty->api->deny(qr/${cud}ProfileRelationship$/);
+        }
+
+        # If not a user manager, block them
+        if (!$current_user->may_manage_users) {
+            Jifty->api->deny(qr/${cud}User$/);
+        }
+
+        # If not a role manager, block them
+        if (!$current_user->may_manage_roles) {
+            Jifty->api->deny(qr/${cud}Role$/);
+            Jifty->api->deny(qr/${cud}ProfilePermission$/);
+            Jifty->api->deny(qr/${cud}ProfilePermissionLimit$/);
+            Jifty->api->deny(qr/${cud}ProfilePermissionProperty$/);
+        }
+
+        # TODO Come up with a schema for blocking the profile object and
+        # relationship actions. This isn't easy because we have to take the
+        # various permissions into account.
+    }
+    
+    # TODO Move part of this to the before /admin rule
     # Setup the administration menus
     {
         my $menu = CASPlus->admin_menu;
@@ -104,11 +144,26 @@
                 url         => '/admin/user/roles',
                 description => _('Manage the roles that may be granted to user accounts to grant special privileges within the CAS server and in client services.'),
             );
+            $users->child( ProfilePermission =>
+                label       => _('Profile Permissions'),
+                url         => '/admin/user/permissions',
+                description => _('Manage the permissions assigned to roles.'),
+            );
+            $users->child( ProfilePermissionLimit =>
+                label       => _('Permission Limits'),
+                url         => '/admin/user/limits',
+                description => _('Manage the limits on permissions.'),
+            );
+            $users->child( ProfilePermissionProperty =>
+                lable       => _('Permission Properties'),
+                url         => '/admin/user/properties',
+                description => _('Determine which properties are affected by a permission.'),
+            );
         }
 
         # Sessions menu
         my $sessions =
-            $menu->child( Sessions => label => 'Sessions', side => 'right' );
+            $menu->child( Sessions => label => 'Sessions', side => 'left' );
 
         $sessions->child( AccessLog =>
             label       => _('Access Log'),
@@ -142,6 +197,44 @@
             }
         }
 
+        if ($current_user->may_manage_profile_objects) {
+            my $relationships_menu =
+                $menu->child( Relationships => label => 'Relationships', side => 'right' );
+
+            my $relationships = CASPlus::Model::ProfileRelationshipCollection->new;
+            $relationships->limit(
+                column => 'many_parents',
+                value  => 1,
+            );
+            $relationships->limit(
+                column => 'many_children',
+                value  => 1,
+            );
+
+            while (my $relationship = $relationships->next) {
+                $relationships_menu->child( $relationship->link_table->name,
+                    label => $relationship->name,
+                    url   => '/admin/relationship/' . $relationship->link_table->name,
+                    description => _('Relationship between %1 and %2.',
+                        $relationship->relation_parent->name, 
+                        $relationship->relation_child->name),
+                );
+            }
+        }
+    }
+};
+
+=head2 /**
+
+Sets up all the main menu items.
+
+=cut
+
+on '**' => run {
+
+    # Setup the main menu for administrator
+    {
+
         # Add the Administer item to main navigation
         my $nav = Jifty->web->navigation;
         $nav->child( Administer =>
@@ -150,6 +243,24 @@
             sort_order => 50,
         );
     }
+
+    # Add the profile link for logged users
+    {
+        if (Jifty->web->current_user->id) {
+            my $nav = Jifty->web->navigation;
+            $nav->child( MyProfile =>
+                label      => _('My Profile'),
+                url        => '/user/me',
+                sort_order => 20,
+            );
+
+            $nav->child( Logout =>
+                label      => _('Logout'),
+                url        => '/logout',
+                sort_order => 999,
+            );
+        }
+    }
 };
 
 =head2 ROOT
@@ -442,19 +553,45 @@
 
 =cut
 
+sub _load_user {
+    my $user_or_id = shift;
+
+    my $user;
+    if (ref $user_or_id) {
+        $user = $user_or_id;
+    }
+    else {
+        $user = CASPlus::Model::User->new;
+
+        if (looks_like_number $user_or_id) {
+            $user->load($user_or_id);
+        }
+        else {
+            $user->load_by_cols( username => $user_or_id );
+        }
+    }
+
+    return '' unless $user->id;
+
+    set id     => $user->id;
+    set user   => $user;
+    set action => $user->as_update_action;
+
+    my $profile = $user->profile;
+    if (defined $profile && $profile->id) {
+        set profile        => $profile;
+        set profile_action => $profile->as_update_action;
+    }
+
+    return 1;
+}
+
 under 'user' => [
 
     on 'me' => run {
-        if (Jifty->web->current_user->id) {
-            my $user   = Jifty->web->current_user->user_object;
-            my $action = Jifty->web->new_action(
-                class  => 'UpdateUser',
-                record => $user,
-            );
+        if (Jifty->web->current_user->id 
+                && _load_user( Jifty->web->current_user->user_object )) {
 
-            set rdf    => $user->as_rdf;
-            set user   => $user;
-            set action => $action;
             show '/user/view';
         }
 
@@ -463,6 +600,47 @@
         }
     },
 
+    on 'display' => run {
+        my $id = get 'id';
+        _load_user($id);
+        show '/user/display';
+    },
+
+    on 'password' => run {
+        my $id = get 'id';
+        my $user = CASPlus::Model::User->new;
+        $user->load($id);
+
+        my $action = Jifty->web->new_action(
+            class     => 'UpdateUserPassword',
+            arguments => { id => $user->id },
+        );
+
+        set user   => $user;
+        set action => $action;
+        show '/user/password';
+    },
+
+    under '=' => [
+        on '#' => run {
+            if (_load_user($1)) {
+                show '/user/view';
+            }
+
+            else {
+                Jifty->log->error("No user with ID ($1) found.");
+            }
+        },
+
+        on '*' => run {
+            if (_load_user($1)) {
+                show '/user/view';
+            }
+            else {
+                Jifty->log->error("No user with name '$1' found.");
+            }
+        },
+    ],
 ];
 
 =head2 /admin/setup
@@ -507,18 +685,54 @@
 
     # Load the associated profile
     my $profile = CASPlus::Model::Profile->new;
-    $profile->load_by_cols( name => $1 );
+    $profile->load_by_cols( name => $name );
 
     # Stop now unless the profile exists
     next_rule unless $profile->id;
 
+    my $path = '/admin/object/'.$name;
+
+    # Only create the CRUD view if it doesn't exist yet
+    unless (Template::Declare->resolve_template($path)) {
+        CASPlus::View::_register_crud($path, $name, $profile->record_class);
+    }
+
+    # Show that CRUD view!
+    show $path;
+};
+
+=head2 /admin/relationship/*
+
+Automatically creates CRUD views for relationships.
+
+=cut
+
+on 'admin/relationship/*' => run {
+    my $name = $1;
+
+    # Load the link table
+    my $link_table = Jifty::Model::ModelClass->new;
+    $link_table->load_by_cols( name => $name );
+
+    next_rule unless $link_table->id;
+
+    # Load the associated profile
+    my $relationship = CASPlus::Model::ProfileRelationship->new;
+    $relationship->load_by_cols( link_table => $link_table );
+
+    # Stop now unless the profile exists
+    next_rule unless $relationship->id;
+
+    my $path = '/admin/relationship/'.$name;
+
     # Only create the CRUD view if it doesn't exist yet
-    unless (Template::Declare->resolve_template('/admin/object/'.$name)) {
-        CASPlus::View::_register_profile_object($name, $profile->record_class);
+    unless (Template::Declare->resolve_template($path)) {
+        CASPlus::View::_register_crud($path, $name, 
+            $relationship->record_class);
     }
 
     # Show that CRUD view!
-    show '/admin/object/'.$name;
+    show $path;
 };
 
 =head2 /error, /error/**

Modified: apps/CASPlus/trunk/lib/CASPlus/Model/SSOSession.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Model/SSOSession.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Model/SSOSession.pm	Thu Sep 13 15:54:46 2007
@@ -138,10 +138,11 @@
     my $self = shift;
 
     my $now = DateTime->now;
+    my $exp = $self->expiration_time;
 
     return $self->id
-        && $self->current_valid_ticket 
-        && (!defined $self->expiration_time || $now < $self->expiration_time);
+        && $self->current_valid_ticket
+        && (!defined $exp || $now < $exp);
 }
 
 =head2 fetch_cookie
@@ -211,7 +212,7 @@
 
 sub current_user_can {
     my ($self, $right, %args) = @_;
-    
+
     if ($self->current_user->id) {
 
         if ($right eq 'create') {

Modified: apps/CASPlus/trunk/lib/CASPlus/Model/ServiceSession.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Model/ServiceSession.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Model/ServiceSession.pm	Thu Sep 13 15:54:46 2007
@@ -127,11 +127,12 @@
     my $self = shift;
 
     my $now = DateTime->now;
+    my $exp = $self->expiration_time;
 
-    return $self->id 
+    return $self->id
         && $self->sso_session->is_valid
-        && $self->current_valid_ticket 
-        && (!defined $self->expiration_time || $now < $self->expiration_time);
+        && $self->current_valid_ticket
+        && (!defined $exp || $now < $exp);
 }
 
 =head2 authenticated_user

Modified: apps/CASPlus/trunk/lib/CASPlus/Model/User.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Model/User.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Model/User.pm	Thu Sep 13 15:54:46 2007
@@ -88,6 +88,12 @@
     my ($self, $value) = @_;
 
     my $password = $self->__value('password');
+    
+    unless (ref $password eq 'ARRAY') {
+        Jifty->log->warn("NO PASSWORD for user @{[$self->username]}");
+        return '';
+    }
+
     my ($hash, $salt) = @$password;
 
     return $hash eq md5_hex($value . $salt);

Modified: apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipBase.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipBase.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipBase.pm	Thu Sep 13 15:54:46 2007
@@ -18,12 +18,42 @@
 
 Any user with superuser user or the manage profile objects permission assigned to at least one role may access this object.
 
-Otherwise, a user will only be able to create, read, write, or delete this object if it is granted by a profile permission associated with the user's roles. This association must be with both ends of a link. If the user is able to create
+Otherwise, a user will only be able to create, read, write, or delete this object if it is granted by a profile permission associated with the user's roles. This association must be with both ends of a link. 
 
 =cut
 
-# XXX FIXME TODO Add triggers for the parent and child columns to handle
-# add/delete of relationships in many-to-many.
+sub current_user_can {
+    my $self = shift;
+    my ($right, %args) = @_;
+
+    # Has access if an administrator
+    return 1 if $self->current_user->may_manage_profile_objects;
+
+    # These will store the left and right permissions
+    my ($a, $b);
+
+    # handle create
+    if ($right eq 'create') {
+        $a = $self->current_user->can_access_profile(
+            $args{parent}, $right, %args);
+        $b = $self>current_user->can_access_profile(
+            $args{child}, $right, %args);
+    }
+
+    # handle read, write, and delete
+    else {
+        $a = $self->current_user->can_access_profile(
+            $self->parent, $right, %args);
+        $b = $self->current_user->can_access_profile(
+            $self->child, $right, %args);
+    }
+
+    # does the use have access to both ends?
+    return 1 if $a and $b;
+
+    # fallback on the built-ins otherwise
+    return $self->SUPER::current_user_can($right, %args);
+}
 
 =head2 profile_relationship_definition
 

Modified: apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipMixin.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipMixin.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/ProfileRelationshipMixin.pm	Thu Sep 13 15:54:46 2007
@@ -89,6 +89,20 @@
     my $args = delete $self->{__create_relationship};
     $args->{relationship} = $self->profile_relationship_definition;
 
+    # Convert parent into object
+    unless (ref $args->{parent}) {
+        my $id = $args->{parent};
+        $args->{parent} = $args->{relationship}->relation_parent->record_class->new;
+        $args->{parent}->load($id);
+    }
+
+    # Convert child into object
+    unless (ref $args->{child}) {
+        my $id = $args->{child};
+        $args->{child} = $args->{relationship}->relation_child->record_class->new;
+        $args->{child}->load($id);
+    }
+
     CASPlus::Util::Relationship->add_relationship(%$args) if $result;
 
     return 1;

Modified: apps/CASPlus/trunk/lib/CASPlus/View.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/View.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/View.pm	Thu Sep 13 15:54:46 2007
@@ -14,6 +14,9 @@
 
     'User::Users'            => 'User',
     'User::Roles'            => 'Role',
+    'User::Permissions'      => 'ProfilePermission',
+    'User::Limits'           => 'ProfilePermissionLimit',
+    'User::Properties'       => 'ProfilePermissionProperty',
 );
 
 Readonly our @ADMIN_OTHER_VIEWS => qw/
@@ -256,6 +259,9 @@
     };
 };
 
+use CASPlus::View::User;
+alias CASPlus::View::User under '/user';
+
 use CASPlus::View::Error;
 alias CASPlus::View::Error under '/error';
 
@@ -281,12 +287,13 @@
     alias $view_class under $path;
 }
 
-sub _register_profile_object {
+sub _register_crud {
+    my $path = shift;
     my $name = shift;
     my $record_class = shift;
 
     # Install a CRUD view for the profile object
-    alias Jifty::View::Declare::CRUD under '/admin/object/'.$name, {
+    alias Jifty::View::Declare::CRUD under $path, {
         object_type  => $name,
         record_class => $record_class,
     };

Added: apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Limits.pm
==============================================================================
--- (empty file)
+++ apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Limits.pm	Thu Sep 13 15:54:46 2007
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+
+package CASPlus::View::Admin::User::Limits;
+use Jifty::View::Declare -base;
+use base qw/ Jifty::View::Declare::CRUD /;
+
+1;

Added: apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Permissions.pm
==============================================================================
--- (empty file)
+++ apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Permissions.pm	Thu Sep 13 15:54:46 2007
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+
+package CASPlus::View::Admin::User::Permissions;
+use Jifty::View::Declare -base;
+use base qw/ Jifty::View::Declare::CRUD /;
+
+1;

Added: apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Properties.pm
==============================================================================
--- (empty file)
+++ apps/CASPlus/trunk/lib/CASPlus/View/Admin/User/Properties.pm	Thu Sep 13 15:54:46 2007
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+
+package CASPlus::View::Admin::User::Properties;
+use Jifty::View::Declare -base;
+use base qw/ Jifty::View::Declare::CRUD /;
+
+1;

Added: apps/CASPlus/trunk/lib/CASPlus/View/User.pm
==============================================================================
--- (empty file)
+++ apps/CASPlus/trunk/lib/CASPlus/View/User.pm	Thu Sep 13 15:54:46 2007
@@ -0,0 +1,70 @@
+use strict;
+use warnings;
+
+package CASPlus::View::User;
+use Jifty::View::Declare -base;
+
+use Jifty::Plugin::TabView::View;
+use Scalar::Defer qw/ defer /;
+
+template 'view' => page {
+    my $self = shift;
+    my $user = get 'user';
+
+    { title is _(q{%1's Profile}, $user->username) }
+
+    form {
+        $self->render_tabs('profile', [qw(id)], 
+            { label => 'View',     path => '/user/display',  defer => 1 },
+            { label => 'Password', path => '/user/password', defer => 1 },
+        );
+    };
+};
+
+template 'display' => sub {
+    my $action  = get 'action';
+    my $profile = get 'profile';
+    my $paction = get 'profile_action';
+
+    die unless $action;
+
+    render_action $action, [ qw/ username / ], { render_mode => 'read' };
+
+    if ($profile) {
+        render_action $paction, 
+            [ grep { $_ ne 'user_object' } 
+              map  { $_->name } $profile->columns ], 
+            { render_mode => 'read' };
+    }
+};
+
+template 'password' => sub {
+    my $user   = get 'user';
+    my $action = get 'action';
+
+    my $current_user = Jifty->web->current_user;
+    if ($current_user->may_manage_users && $current_user->id != $user->id) {
+        p { outs_raw _(q{To change <em>%1's</em> password, please fill out the following form and click <strong>Change Password</strong>.}, $user->username); };
+
+        # XXX TODO FIXME The defer thing is a cheap trick to focus new_password
+        my $count = 0;
+        render_action $action, [ qw( id new_password confirm_password ) ], {
+            focus => defer { $count++ == 1 ? 1 : 0 },
+        };
+    }
+    else {
+        p { outs_raw _('If you would like to update your password, please fill in the following form and click <strong>Change Password</strong>.'); };
+
+        render_action $action;
+    }
+
+    form_submit 
+        label   => 'Change Password',
+        onclick => { 
+            submit       => $action,
+            refresh_self => 1,
+        },
+        ;
+};
+
+1;

Modified: apps/CASPlus/trunk/share/web/static/css/app.css
==============================================================================
--- apps/CASPlus/trunk/share/web/static/css/app.css	(original)
+++ apps/CASPlus/trunk/share/web/static/css/app.css	Thu Sep 13 15:54:46 2007
@@ -36,3 +36,139 @@
     font-size: 1.2em;
     margin-bottom: 0.2em;
 }
+
+/* default space between tabs */
+.yui-navset .yui-nav li {
+    margin-right:0.5em; /* horizontal tabs */
+}
+.yui-navset-left .yui-nav li, .yui-navset-right .yui-nav li {
+    margin:0 0 0.5em; /* vertical tabs */
+}
+
+/* default width for side tabs */
+.yui-navset-left .yui-nav, .yui-navset-right .yui-nav { width:6em; }
+.yui-navset-left { padding-left:6em; } /* map to nav width */
+.yui-navset-right { padding-right:6em; } /* ditto */
+
+/* core */
+
+.yui-nav, .yui-nav li {
+    margin:0;
+    padding:0;
+    list-style:none;
+}
+.yui-navset li em { font-style:normal; }
+
+.yui-navset {
+    position:relative; /* contain absolute positioned tabs (left/right) */
+    zoom:1;
+}
+
+.yui-navset .yui-content { zoom:1; }
+
+.yui-navset .yui-nav li {
+    display:inline-block;
+    display:-moz-inline-stack;
+    *display:inline; /* IE */
+    vertical-align:bottom; /* safari: for overlap */
+    cursor:pointer; /* gecko: due to -moz-inline-stack on anchor */
+    zoom:1; /* IE: kill space between horizontal tabs */
+}
+
+.yui-navset-left .yui-nav li, .yui-navset-right .yui-nav li {
+    display:block;
+}
+
+.yui-navset .yui-nav a {
+    outline:0; /* gecko: keep from shifting */
+}
+
+.yui-navset .yui-nav a { position:relative; } /* IE: to allow overlap */
+
+.yui-navset .yui-nav li a {
+    display:block;
+    display:inline-block;
+    vertical-align:bottom; /* safari: for overlap */
+    zoom:1;
+}
+
+.yui-navset-left .yui-nav li a, .yui-navset-right .yui-nav li a {
+    display:block;
+}
+
+.yui-navset-bottom .yui-nav li a {
+    vertical-align:text-top; /* for inline overlap (reverse for Op border bug) */
+}
+
+.yui-navset .yui-nav li a em { display:block; }
+
+/* position left and right oriented tabs */
+.yui-navset-left .yui-nav, .yui-navset-right .yui-nav { position:absolute; z-index:1; }
+.yui-navset-left .yui-nav { left:0; }
+.yui-navset-right .yui-nav { right:0; }
+
+.yui-navset .yui-nav li a, .yui-navset .yui-content {
+    border:1px solid #000;  /* label and content borders */
+}
+
+.yui-navset .yui-nav .selected a, .yui-navset .yui-nav a:hover, .yui-navset .yui-content {
+    background-color:#f6f7ee; /* active tab, tab hover, and content bgcolor */
+}
+
+.yui-navset .yui-nav li em { padding:.5em; } /* tab padding */
+
+/* defaults to orientation "top" */
+.yui-navset .yui-nav .selected a {
+    border-bottom-width:0; /* no bottom border for active tab */
+    padding-bottom:1px; /* to match height of other tabs */
+}
+
+.yui-navset .yui-content {
+    margin-top:-1px; /* for active tab overlap */
+}
+
+/* overrides for other orientations */
+
+.yui-navset-bottom .yui-nav .selected a {
+    border-width:0 1px 1px; /* no top border for active tab */
+    padding:1px 0 0; /* to match height of other tabs */
+}
+
+.yui-navset-bottom .yui-content {
+    margin:0 0 -1px; /* for active tab overlap */
+}
+
+.yui-navset-left .yui-nav li.selected a {
+    border-width:1px 0 1px 1px; /* no right border for active tab */
+    padding:0 1px 0 0; /* to match width of other tabs */
+}
+
+.yui-navset-left .yui-content {
+    margin:0 0 0 -1px; /* for active tab overlap */
+}
+
+.yui-navset-right .yui-nav li.selected a {
+    border-width:1px 1px 1px 0; /* no left border for active tab */
+    padding:0 0 0 1px; /* to match width of other tabs */
+}
+
+.yui-navset-right .yui-content {
+    margin:0 -1px 0 0; /* for active tab overlap */
+}
+
+ul.yui-nav {
+    padding: 0 1em;
+}
+
+.yui-navset .yui-nav .selected a {
+    padding: 0.3em;
+}
+
+ul.yui-nav li a {
+    padding: 0.3em;
+    vertical-align: inherit;
+}
+
+div.yui-content {
+    padding: 1em;
+}

Added: apps/CASPlus/trunk/t/20-action-UpdateUserPassword.t
==============================================================================
--- (empty file)
+++ apps/CASPlus/trunk/t/20-action-UpdateUserPassword.t	Thu Sep 13 15:54:46 2007
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A (very) basic test harness for the UpdateUserPassword action.
+
+=cut
+
+use Jifty::Test tests => 1;
+
+# Make sure we can load the action
+use_ok('CASPlus::Action::UpdateUserPassword');
+

Modified: apps/CASPlus/trunk/t/50-me.t
==============================================================================
--- apps/CASPlus/trunk/t/50-me.t	(original)
+++ apps/CASPlus/trunk/t/50-me.t	Thu Sep 13 15:54:46 2007
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use Jifty::Test tests => 27;
+use Jifty::Test tests => 17;
 use Jifty::Test::WWW::Mechanize;
 
 my $system_user = CASPlus::CurrentUser->superuser;
@@ -51,46 +51,6 @@
 $mech->title_is('Login Status', 'attempted login');
 $mech->content_contains('test-me', 'login success');
 
-# XXX Use instead of follow_link_ok because Jifty::Test::WWW::Mechanize applies
-# HTML::Lint, which pukes on the RDF.
-my $response = $mech->follow_link(text => 'test-me');
-ok($response->is_success, 'go to me');
+$mech->follow_link_ok(text => 'test-me', 'go to me');
 
 $mech->title_is(q{test-me's Profile}, 'test-me profile page');
-
-SKIP: {
-    eval "use XML::LibXML";
-    skip "XML::LibXML is installed and these tests will fail when using that library.", 10 unless $@;
-    eval "use Test::XML::XPath";
-    skip "Test::XML::XPath is not available.", 10 if $@;
-
-    my $xml = $mech->content;
-    my $xp  = XML::XPath->new($xml);
-
-    like_xpath($xml, '//rdf:RDF', 'has RDF element');
-    like_xpath($xml, '//rdf:RDF/casplus:user', 'has user element');
-
-    my $rdf_ns = $xp->findnodes('//rdf:RDF')->shift
-        ->getNamespace('rdf')->getExpanded;
-    my $casplus_ns = $xp->findnodes('//rdf:RDF/casplus:user')->shift
-        ->getNamespace('casplus')->getExpanded;
-
-    is($rdf_ns, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf namespace');
-    is($casplus_ns, 'http://sterling.hanenkamp.com/rdf/CASPlus#', 
-        'casplus namespace');
-
-    my $user_id = $user->id;
-    like_xpath($xml, "//rdf:RDF/casplus:user[\@rdf:about='$url/user/$user_id']",
-        'rdf:about is set correctly');
-    like_xpath($xml, '//rdf:RDF/casplus:user/casplus:username', 
-        'username namespace');
-    is_xpath($xml, '//rdf:RDF/casplus:user/casplus:username', 'test-me',
-        'username is set correctly');
-
-    like_xpath($xml, '//rdf:RDF/casplus:user/casplus:profile',
-        'user has a profile');
-    like_xpath($xml, '//rdf:RDF/casplus:user/casplus:profile/casplus:full_name',
-        'user has a full name');
-    is_xpath($xml, '//rdf:RDF/casplus:user/casplus:profile/casplus:full_name',
-        'Unit Test', 'full name is set correctly');
-};


More information about the Jifty-commit mailing list