[Jifty-commit] r3672 - in apps/CASPlus/trunk: etc lib lib/CASPlus lib/CASPlus/Model t

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Wed Jul 11 18:29:30 EDT 2007


Author: sterling
Date: Wed Jul 11 18:29:29 2007
New Revision: 3672

Modified:
   apps/CASPlus/trunk/   (props changed)
   apps/CASPlus/trunk/etc/config.yml
   apps/CASPlus/trunk/lib/CASPlus.pm
   apps/CASPlus/trunk/lib/CASPlus/CurrentUser.pm
   apps/CASPlus/trunk/lib/CASPlus/Model/ProfilePermission.pm
   apps/CASPlus/trunk/lib/CASPlus/Model/ProfileRelationship.pm
   apps/CASPlus/trunk/lib/CASPlus/Model/UniqueProfileIdentifier.pm
   apps/CASPlus/trunk/lib/CASPlus/ProfileBase.pm
   apps/CASPlus/trunk/t/40-is_mine.t

Log:
 r8032 at riddle:  andrew | 2007-07-11 17:29:10 -0500
  * Refactored is_mine in CASPlus::Model::ProfileBase to is_my in
    CASPlus::CurrentUser.
  * Added the parents_own_children and ownership_is_transitive properties to
    relationship definitions.
  * Fixed the unique IDs so that anyone can create and see them.
  * Added the parents_of and children_of helpers to relationship definitions.
  * I haven't completed the tests to this effect, but a profile objects other
    than the one directly related to the current user may now be owned.


Modified: apps/CASPlus/trunk/etc/config.yml
==============================================================================
--- apps/CASPlus/trunk/etc/config.yml	(original)
+++ apps/CASPlus/trunk/etc/config.yml	Wed Jul 11 18:29:29 2007
@@ -65,3 +65,5 @@
     TGC:
       Length: 64
       ExpirationTime: forever 
+  Relationships:
+    MaximumOwnershipDepth: 5

Modified: apps/CASPlus/trunk/lib/CASPlus.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus.pm	Wed Jul 11 18:29:29 2007
@@ -142,7 +142,7 @@
                 return 'allow';
             }
 
-            return undef;
+            return '';
         },
     );
 
@@ -154,7 +154,7 @@
                 return 'allow';
             }
 
-            return undef;
+            return '';
         },
     );
 }

Modified: apps/CASPlus/trunk/lib/CASPlus/CurrentUser.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/CurrentUser.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/CurrentUser.pm	Wed Jul 11 18:29:29 2007
@@ -64,7 +64,7 @@
 
 =head2 profile_permissions
 
-  my @permissions = $current_user->profile_ermissions;
+  my @permissions = $current_user->profile_permissions;
 
 Returns a list of all the L<CASPlus::Model::ProfilePermission> objects associated with the current user.
 
@@ -102,6 +102,121 @@
     return 0;
 }
 
+=head2 profile
+
+  my $profile = $current_user->profile;
+
+This is a shortcut for:
+
+  my $profile = $current_user->id ? $current_user->user_object->profile
+              :                     undef;
+
+It retrieves the profile object that is linked to the current user.
+
+=cut
+
+sub profile {
+    my $self = shift;
+
+    return ($self->id ? $self->user_object->profile : undef);
+}
+
+=head2 is_my
+
+  my $mine = $current_user->is_my($object);
+
+Returns true if the profile object C<$object> is "owned" by the current user. This determines whether or not "my own" permissions are applicable for that object or not.
+
+As of this writing, an object is owned by the current user if any of the following are true:
+
+=over
+
+=item *
+
+The C<$object> is the profile for the current user. That is,
+
+  $current_user->profile->unique_id == $object->unique_id
+
+=item *
+
+The C<$object> is the direct descendant of C<< $current_user->profile >> via a relationship with L<CASPlus::Model::ProfileRelationship/parents_own_children>.
+
+=item *
+
+The C<$object> is the direct descendant of another profile object that is owned by C<< $current_user >> via a relationship with L<CASPlus::Model::ProfileRelationship/ownership_is_transitive>.
+
+=back
+
+=cut
+
+sub is_my {
+    my $self   = shift;
+    my $object = shift;
+    my $object_id = $object->unique_id;
+
+    # Anonymous can't own anything, bummer.
+    return 0 if !$self->id;
+
+    # Get the profile belonging to the current user
+    my $profile = $self->profile;
+
+    # Check to see if the object is directly linked to the current user 
+    return 1 if $profile->unique_id == $object_id;
+
+    # Load the relationships for the current profile
+    my $relationships = CASPlus::Model::ProfileRelationshipCollection->new;
+    $relationships->limit(
+        column => 'relation_parent',
+        value  => $profile,
+    );
+    $relationships->limit(
+        column => 'parents_own_children',
+        value  => 1,
+    );
+
+    # Iterate through the relationships and fill the queue
+    my @profile_queue;
+    while (my $relationship = $relationships->next) {
+        push @profile_queue, 
+            map { [ $_, 1 ] } $relationship->children_of($object);
+    }
+
+    # Preload the maximum depth
+    my $maximum_depth 
+        = Jifty->config->app('Relationships')->{'MaximumOwnershipDepth'};
+
+    # Process the queue
+    while (my $current = shift @profile_queue) {
+        my ($current_object, $current_depth) = @$current; 
+
+        # Found it! It's mine!
+        return 1 if $current_object->unique_id = $object_id;
+
+        # At maximum depth, don't go deeper
+        next if $current_depth >= $maximum_depth;
+
+        # Find this profile's relationships
+        $relationships->unlimit;
+        $relationships->limit(
+            column => 'relation_parent',
+            value  => $current_object->profile,
+        );
+        $relationships->limit(
+            column => 'ownership_is_transitive',
+            value  => 1,
+        );
+
+        # Iterate through the relationships and fill the queue
+        while (my $relationship = $relationships->next) {
+            push @profile_queue, 
+                map { [ $_, $current_depth + 1 ] } 
+                    $relationship->children_of($current_object);
+        }
+    }
+
+    return 0;
+}
+
 =head1 AUTHOR
 
 Andrew Sterling Hanenkamp, C<<hanenkamp at cpan.org>>>

Modified: apps/CASPlus/trunk/lib/CASPlus/Model/ProfilePermission.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Model/ProfilePermission.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Model/ProfilePermission.pm	Wed Jul 11 18:29:29 2007
@@ -222,7 +222,7 @@
     my $may_delete = $self->may_delete;
 
     # if the object belongs to the current user, check the may my own flags
-    if ($obj->is_mine) {
+    if (Jifty->web->current_user->is_my($obj)) {
         $may_create ||= $self->may_create_my_own;
         $may_read   ||= $self->may_read_my_own;
         $may_write  ||= $self->may_write_my_own;

Modified: apps/CASPlus/trunk/lib/CASPlus/Model/ProfileRelationship.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Model/ProfileRelationship.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Model/ProfileRelationship.pm	Wed Jul 11 18:29:29 2007
@@ -58,6 +58,20 @@
 
 If this is set to true, any roles held by the parent object will also be passed to the children during role calculation.
 
+=head2 parents_own_children
+
+If this is set to a true value then this relationship confers ownership of the child to the parent object. This only makes sense when the parent profile is a has a C<profile_type> of "user". Only users can own things. And ownership of things other than self can only be granted by relationships with this set to true.
+
+This property doesn't imply any additional permissions unless the current user has applicable permissions through the C<may_*_my_own> properties.
+
+=head2 ownership_is_transitive
+
+If this is set to a true value then this relationship will make ownership transitive. That is, if the current user is considered the owner of X and X is the parent of Y via a relationship where C<ownership_is_transitive> is true, then the current user also owns Y.
+
+Be very careful about this property. Turning this on may result in an user account becoming "owner" without intending to do so. 
+
+This property doesn't imply any additional permissions unless the current user has applicable permissions through the C<may_*_my_own> properties.
+
 =head2 parent_column
 
 Calculated column. Do not modify.
@@ -140,20 +154,6 @@
         is immutable,
         default is 0;
 
-#    column count_parents =>
-#        type is 'text',
-#        label is 'From cardinality',
-#        valid_values are qw/ 0..1 1 0..* 1..* /,
-#        is mandatory,
-#        default is '0..*';
-#
-#    column count_children =>
-#        type is 'text',
-#        label is 'To cardinality',
-#        valid_values are qw/ 0..1 1 0..* 1..* /,
-#        is mandatory,
-#        default is '1';
-
     column dependent_relationship =>
         type is 'boolean',
         label is 'Dependent?',
@@ -167,6 +167,20 @@
         hints is 'If checked, roles held by parent profiles are passed to the children.',
         is mandatory,
         default is 0;
+
+    column parents_own_children =>
+        type is 'boolean',
+        label is 'Parents own children?',
+        hints is 'The parent object has ownership privileges for the child if this is checked.',
+        is mandatory,
+        default is 0;
+
+    column ownership_is_transitive =>
+        type is 'boolean',
+        label is 'Ownership is transitive?',
+        hints is 'If parents own children and ownership is transitive, then the owner of the parent owns the children too.',
+        is mandatory,
+        default is 0;
     
     column parent_column =>
         refers_to Jifty::Model::ModelClassColumn,
@@ -268,7 +282,7 @@
             name            => $child_name,
             refers_to_class => $link_table->qualified_class->collection_class,
             refers_to_by    => 'parent',
-            storage_type    => 'int',
+            virtual         => 1,
         );
 
         # Create the relationship to the parents via the link table
@@ -278,7 +292,7 @@
             name            => $parent_name,
             refers_to_class => $link_table->qualified_class->collection_class,
             refers_to_by    => 'child',
-            storage_type    => 'int',
+            virtual         => 1,
         );
 
         # Associate all these tables and columns with the new object
@@ -302,6 +316,7 @@
                 name            => $parent_name,
                 refers_to_class => $args->{relation_parent}->record_class->collection_class,
                 refers_to_by    => $child_name,
+                virtual         => 1,
             );
         }
 
@@ -311,6 +326,7 @@
                 model_class     => $args->{relation_child}->model_class,
                 name            => $parent_name,
                 refers_to_class => $args->{relation_parent}->record_class,
+                storage_type    => 'int',
             );
         }
 
@@ -324,6 +340,7 @@
                 name            => $child_name,
                 refers_to_class => $args->{relation_child}->record_class->collection_class,
                 refers_to_by    => $parent_name,
+                virtual         => 1,
             );
         }
 
@@ -333,6 +350,7 @@
                 model_class     => $args->{relation_parent}->model_class,
                 name            => $child_name,
                 refers_to_class => $args->{relation_child}->record_class,
+                storage_type    => 'int',
             );
         }
 
@@ -354,6 +372,7 @@
                 name            => $child_name,
                 refers_to_class => $args->{relation_child}->record_class,
                 refers_to_by    => $parent_name,
+                storage_type    => 'int',
             );
         }
 
@@ -457,6 +476,74 @@
     return undef;
 }
 
+=head2 children_of 
+
+  my @children = $relationship->children_of($object);
+
+This is a helper method for code that needs to iterate over and apply a relationship to fetch all the children of the C<$object> according to this relationship. This will only work if C<$relationship> has L</relation_parent> set to the profile that describes C<$object>.
+
+=cut
+
+sub children_of {
+    my $self   = shift;
+    my $object = shift;
+
+    my $method = $self->parent_column->name;
+    my @children;
+
+    # A many-to-many relationship, do the extra work of dereferencing links
+    if ($self->many_parents and $self->many_children) {
+        @children = map { $_->child } @{ $object->$method->items_array_ref };
+    }
+
+    # A one-to-many relationship
+    elsif ($self->many_children) {
+        @children = @{ $object->$method->items_array_ref };
+    }
+
+    # A one-to-one or many-to-one relationship
+    else {
+        my $child = $object->$method;
+        push @children, $child if $child->id;
+    }
+
+    return @children;
+}
+
+=item parents_of
+
+  my @parents = $relationship->parents_of($object)
+
+This is a helper that returns a list of all the parent of the C<$object> according to C<$relationship> without regard for the cardinality of either end of the relationship. For this to work, C<$relationship> must have the profile declaration for C<$object> as the L</relation_child>.
+
+=cut
+
+sub parents_of {
+    my $self   = shift;
+    my $object = shift;
+
+    my $method = $self->child_column->name;
+    my @parents;
+
+    # A many-to-many relationship, do the extra work of dereferencing links
+    if ($self->many_parents and $self->many_children) {
+        @parents = map { $_->parent } @{ $object->$method->items_array_ref };
+    }
+
+    # A many-to-one relationship
+    elsif ($self->many_parents) {
+        @parents = @{ $object->$method->items_array_ref };
+    }
+
+    # A one-to-one or one-to-many relationship
+    else {
+        my $parent = $object->$method;
+        push @parents, $parent if $parent->id;
+    }
+
+    return @parents;
+}
+
 =head1 AUTHOR
 
 Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>

Modified: apps/CASPlus/trunk/lib/CASPlus/Model/UniqueProfileIdentifier.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/Model/UniqueProfileIdentifier.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/Model/UniqueProfileIdentifier.pm	Wed Jul 11 18:29:29 2007
@@ -28,6 +28,25 @@
         is immutable;
 };
 
+=head1 METHODS
+
+=head2 current_user_can
+
+Allows all users to create and read unique IDs. Otherwise uses inherited permissions.
+
+=cut
+
+sub current_user_can {
+    my $self = shift;
+    my ($right) = @_;
+
+    if ($right eq 'read' or $right eq 'create') {
+        return 1;
+    }
+        
+    return $self->SUPER::current_user_can(@_);
+}
+
 =head2 WHY?
 
 Every profile object is assigned a unique identifier primarily to aid in role caching.

Modified: apps/CASPlus/trunk/lib/CASPlus/ProfileBase.pm
==============================================================================
--- apps/CASPlus/trunk/lib/CASPlus/ProfileBase.pm	(original)
+++ apps/CASPlus/trunk/lib/CASPlus/ProfileBase.pm	Wed Jul 11 18:29:29 2007
@@ -18,86 +18,6 @@
 
 =head1 METHODS
 
-=head2 is_mine
-
-  my $mine = $object->is_mine;
-
-Returns true if the object is "owned" by the current owner.
-
-As of this writing, an object is owned by current user if any of the following are true:
-
-=over
-
-=item *
-
-The C<$object> is the profile for the current user.
-
-=item *
-
-The C<$object> is a child or descendant of the profile for the current user, as defined by the relationships (L<CASPlus::Model::ProfileRelationship>) the current user may traverse.
-
-=back
-
-=cut
-
-# XXX TODO FIXME Get _is_a_descendant working
-sub _is_a_descendant {
-#    my $self        = shift;
-#    my $profile_obj = shift;
-#    my $profile     = shift;
-#
-#    # Find all the relationships toward children of this object
-#    my $relationships = CASPlus::Model::ProfileRelationshipCollection->new;
-#    $relationships->limit(
-#        column => 'relation_parent',
-#        value  => $profile,
-#    );
-#
-#    # For each, check to see if the object is a child or has a child
-#    while (my $relationship = $relationships->next) {
-#        if ($relationship->many_children) {
-#            my $method   = $relationship->child_column->name;
-#            my $children = $profile_object->$method;
-#
-#            while (my $child = $children->next) {
-#            }
-#        }
-#    }
-    return 0;
-}
-
-sub is_mine {
-    my $self = shift;
-    my $current_user = $self->current_user;
-
-    # Do we have a real user?
-    if ($current_user->id) {
-
-        # Find out what kind of object I am
-        my $profile = CASPlus::Model::Profile->new;
-        $profile->load_by_profile_object($self);
-
-        # Am I an object related to a user?
-        if ($profile->profile_type eq 'user') {
-
-            # I belong to the current user if I am directly related
-            return $self->user_object->id
-                && $self->user_object->id == $current_user->id;
-        }
-
-        # Otherwise, it might be something that's a child of my profile.
-        # Descend all relationships to see if it is.
-        else {
-            return $self->_is_a_descendant($current_user->profile, $profile);
-        }
-    }
-
-    # If I'm not a real user, it can't be mine
-    else {
-        return 0;
-    }
-}
-
 =head2 current_user_can
 
 Any user with superuser user or the manage profile objects permission assigned to at least one role may access this object.

Modified: apps/CASPlus/trunk/t/40-is_mine.t
==============================================================================
--- apps/CASPlus/trunk/t/40-is_mine.t	(original)
+++ apps/CASPlus/trunk/t/40-is_mine.t	Wed Jul 11 18:29:29 2007
@@ -33,7 +33,8 @@
 ok($employee->id, 'created an employee to test with');
 
 # Set the user object as current
-Jifty->web->current_user($user);
+my $current_user = CASPlus::CurrentUser->new( id => $user->id );
+Jifty->web->current_user($current_user);
 
 # Reload the employee 
 $employee = $employee_profile->record_class->new;
@@ -41,11 +42,11 @@
 ok($employee->id, 'reloaded the test employee');
 
 # It must be mine!
-ok($employee->is_mine, 'employee is_mine');
+ok($current_user->is_my($employee), 'is_my employee');
 
 # Next phase: Create a child object, it also MUST BE MINE!!! Muahahahahaha
 TODO: {
-    local $TODO = 'is_mine is not yet defined for relationships';
+    local $TODO = 'is_my is not yet defined for relationships';
 
     # Create a address profile
     my $address_profile = CASPlus::Model::Profile->new(current_user => $system_user);
@@ -65,6 +66,7 @@
         relation_child         => $address_profile,
         many_parents           => 0,
         many_children          => 0,
+        parents_own_children   => 1,
         dependent_relationship => 1,
     );
     ok($relationship->id, 'Created an employee-address relationship');
@@ -81,5 +83,5 @@
     $address = $address_profile->record_class->new;
     $address->load($address_id);
     ok($address->id, 'Reloaded the test address');
-    ok($address->is_mine, 'The address is_mine!');
+    ok($current_user->is_my($address), 'it is_my address!');
 };


More information about the Jifty-commit mailing list