<?xml version="1.0"?>
<record><wiki id="wiki?page_id=111">
	<page_name>Relationship_Permissions</page_name>
	<page_id>111</page_id>
	<page_title>Relationship Permissions</page_title>
	<content>[[toc]]

==Synopsis==

As relationships are a core feature of Xataface, it is helpful to understand how to handle permissions on related records.  Even if you apply permissions to every table individually, you need to take into account the relationships that you have defined between tables, because they may open access to actions that you did not intend.

For example, suppose we have two tables: &apos;&apos;people&apos;&apos; and &apos;&apos;publications&apos;&apos;, and we have a relationship from &apos;&apos;publications&apos;&apos; table to the &apos;&apos;people&apos;&apos; table called &apos;&apos;publication_authors&apos;&apos;.

Suppose you give a user write access to a record of the publications table, but no access to the people table.  If you are allowing the &apos;&apos;add new related record&apos;&apos; permission on the &apos;&apos;publications&apos;&apos; table record, then the user will still be able to add new people, via the &quot;Add related people record&quot; function of the database.  This may or may not be desirable.

This article discusses the issues that arise due to relationships and permissions, and how to deal with them.

==Relationship Permissions==

The Xataface [[permissions.ini file]] defines a handful of permissions that are related to the management of related records.  These include:

{| class=&quot;listing listing2&quot;
|-
! Name
! Description
! Included in Roles
|-
| [[add new related record]]
| Permission to add a new related record to a relationship.
| EDIT, DELETE, OWNER, ADMIN, MANAGER
|-
| [[add existing related record]]
| Permission to add an existing record to a relationship.
| EDIT, DELETE, OWNER, ADMIN, MANAGER
|-
| [[remove related record]]
| Permission to remove a record from a relationship.  (This only allows removing a record from the relationship - not deleting the record from the database, so this is only really relevant in a many-to-many relationship).
| EDIT, DELETE, OWNER, ADMIN, MANAGER
|-
| [[delete related record]]
| Permission to delete a related record.  This allows both removing the related record from the relationship, and deleting the record from the database.   This permission is not included in any default roles.  A combination of permission for [[remove related record]] in the source table and [[delete]] in the target table, are equivalent to access to this permission.  Use this permission only when you need to override the ability to delete records from the database based on membership in a relationship.
| -
|-
| [[view related records]]
| Permission to view the records of a relationship.
| READ ONLY, EDIT, DELETE, OWNER, ADMIN, MANAGER
|-
| [[related records feed]]
| Permission to access the RSS feed of a relationship.
| READ ONLY, EDIT, DELETE, OWNER, ADMIN, MANAGER
|}


==Fine-grained, Per-relationship Permissions==

You may often find that defining a flat set of permissions to all relationships on a record is insufficient for your purposes, because some relationships may demand different access levels than others.  You can override the permissions for any particular relationship by implementing the [[rel_relationshipname__permissions]] method in the table&apos;s delegate class, where &apos;&apos;relationshipname&apos;&apos; is the name of the relationship.

e.g.  Consider the relationship &apos;&apos;manufacturers&apos;&apos;:
&lt;code&gt;
function rel_manufacturers__permissions($record){
	// $record is a Dataface_Record object
	return array(
		&apos;view related records&apos; =&gt; 0
	);
}
&lt;/code&gt;
This will tell xataface that users should not be able to view related records on the &apos;&apos;manufacturers&apos;&apos; relationship.  This will override any permissions that were defined in the [[getPermissions]] method.


==More Complete Example==

In the following example, we design a products database.  We use 2 relationships on our products table:  One to keep track of the parts that are used in our product.  The other to keep track of the users that are allowed to edit our products.

We want to make it so that only the product owner can manage the editors for a product, but anyone in the product_editors relationship is allowed to edit the product or add/remove parts from the product.

We don&apos;t want to give any users access directly to the parts, product_parts, or product_editors tables.  We want all access to go through the relationships on the products table.

===Database/Relationship Design===

Consider a database with 4 tables:

# products (product_id, product_name, owner_username)
# parts (part_id, part_name)
# product_parts (part_id, product_id)
# product_editors (product_id, editor_username)
# users (username, password, role)

And we have the following relationships on the &apos;&apos;products&apos;&apos; table:

&lt;code&gt;
[parts]
    parts.part_id=product_parts.part_id
    product_parts.product_id=&quot;$product_id&quot;

[editors]
    product_editors.product_id=&quot;$product_id&quot;
&lt;/code&gt;


===Application Permissions : Very Restrictive===

Like a good boyscout, we define our default permissions in the [[Application Delegate Class]] to be very restrictive: Don&apos;t let anyone do anything.

&lt;code&gt;
class conf_ApplicationDelegate {
    function getPermissions($record){
        return Dataface_PermissionsTool::NO_ACCESS();
    }
}
&lt;/code&gt;


===Products Table Permissions: Less restrictive===

Now we open it up for our products table in the getPermissions() method of the products delegate class.

In tables/products/products.php:
&lt;code&gt;
class tables_products {
    function getPermissions($record){
        $user = Dataface_AuthenticationTool::getInstance()-&gt;getLoggedInUser();
        if ( $user and $record and $record-&gt;val(&apos;owner_username&apos;) == $user-&gt;val(&apos;username&apos;)){
        	// Give the record owner Edit permissions on the product
        	return Dataface_PermissionsTool::getRolePermissions(&apos;EDIT&apos;);
        }
        
        // Everybody else gets read only access to the products table.
        return Dataface_PermissionsTool::READ_ONLY();
    }
}

&lt;/code&gt;

===Checking if the current User is an Editor===

So far we have given the product owner edit permissions and everyone else read only permissions.  We still need to allow editors to edit the product.  In order to do this we need to be able to *efficiently* find out if the current user is an editor of a particular product.  There are a few different ways to do this, but some are better than others.  Some strategies include:

# Perform an SQL query inside the [[getPermissions]] method to see if the user is an editor for the product.  &apos;&apos;&apos;THIS IS VERY BAD!!!&apos;&apos;&apos; The [[getPermissions]] method should not include any IO or database queries because it is called a large number of times per request... making expensive calls in this method will slow down your app dramatically.
# Create a function to load and cache all of the current user&apos;s products so that this can be easily checked at will.  This is fine if the user is expected be able to edit only a few products.  If he could be an editor for thousands of products, this may not be practical as it will cause you to have to load thousands of records into memory on every page request.
# Use the [[__sql__]] method of the delegate class to create a grafted field on the &apos;&apos;products&apos;&apos; table indicating whether the current user is an editor for the product.  This results in a very quick and accessible indicator variable that can be used in the [[getPermissions]] method to check to see if the current user is an editor for the current product.  E.g.  In the tables/products/products.php file (delegate class):&lt;code&gt;
function __sql__(){
    return sprintf(&quot;select p.*, pe.editor_username from products p
                left join product_editors pe on p.product_id=pe.product_id
                where pe.editor_username=&apos;%s&apos;&quot;,
                addslashes(
                   Dataface_AuthenticationTool::getInstance()-&gt;getLoggedInUsername()
                )
            );
                
}&lt;/code&gt;

This will result in a situation where product records will have an additional field &apos;&apos;editor_username&apos;&apos; which will either be blank if the current user is not an editor for the product; or will contain the current user&apos;s username if they are an editor for the product.


===Table Permissions for Product Editors===

Now that we have a reliable way to tell, for any given product, whether the current user is, in fact, an editor, we can ammend the [[getPermissions]] method of the products table to include our editor permissions.

&lt;code&gt;
class tables_products {
    function getPermissions($record){
        $user = Dataface_AuthenticationTool::getInstance()-&gt;getLoggedInUser();
        if ( $user and $record and $record-&gt;val(&apos;owner_username&apos;) == $user-&gt;val(&apos;username&apos;)){
        	// Give the record owner Edit permissions on the product
        	return Dataface_PermissionsTool::getRolePermissions(&apos;EDIT&apos;);
        }
        
        if ( $user and $record and $record-&gt;val(&apos;editor_username&apos;) == $user-&gt;val(&apos;username&apos;) ){
            // If the user is an editor, we give them edit permissions
            // also
            return Dataface_PermissionsTool::getRolePermissions(&apos;EDIT&apos;);
        }
        
        
        if ( $user ){
        // Other logged in users have read only access
            $perms = Dataface_PermissionsTool::READ_ONLY();
            $perms[&apos;new&apos;] = 1; // We&apos;ll also let them add new products
            return $perms;
	}
	    
	// Regular users just get the default permissions as 
	// defined in the Application Delegate class
	return null;
    }
}

&lt;/code&gt;

===Removing Editor Access to the Editor Relationship===

You&apos;ll notice that at this point, the product editor has exactly the same permission as the product owner.  They both have permission to add and remove records from all relationships on the product.  However, we don&apos;t want them to be able to access the editors relationship at all.  We will use the [[rel_relationshipname__permissions]] method to override the permissions for the &apos;&apos;editors&apos;&apos; relationship.

In the tables/products/products.php delegate class:

&lt;code&gt;

function rel_editors__permissions($record){
    $user = Dataface_AuthenticationTool::getInstance()-&gt;getLoggedInUser();
	if ( $user and $record and $record-&gt;val(&apos;owner_username&apos;) == $user-&gt;val(&apos;username&apos;)){
		// Owners should just get their normal permissions
		return null;
	}
	
	if ( $user and $record and $record-&gt;val(&apos;editor_username&apos;) == $user-&gt;val(&apos;username&apos;) ){
		// If the user is an editor, we give them edit permissions
		// also
		return array(
		    &apos;view related records&apos; =&gt; 0,
		    &apos;add new related record&apos; =&gt; 0,
		    &apos;add existing related record&apos; =&gt; 0,
		    &apos;remove related record&apos; =&gt; 0,
		    &apos;delete related record&apos; =&gt; 0
		    );
	}
	
	// Other users just get their normal permissions
	return null;

}

&lt;/code&gt;


===Assigning product owner by default===

With the current permissions, something funny would happen.  Users have permission to add new records, but once the record is added they won&apos;t be able to edit it because they are neither an editor nor the owner of the product.  We&apos;ll fix this by assigning the current user as the product&apos;s owner using the [[beforeSave]] trigger in the products delegate class:

&lt;code&gt;
function beforeSave($record){
	$user = Dataface_AuthenticationTool::getInstance()-&gt;getLoggedInUser();
	if ( $user ){
    	$record-&gt;setValue(&apos;owner_username&apos;, $user-&gt;val(&apos;username&apos;));
    }
}
&lt;/code&gt;

===Testing Out Our Solution===

In your testing of the solution, you should find the following:

# Trying to access any table other than the &apos;&apos;products&apos;&apos; table should result in a &apos;&apos;permission denied&apos;&apos; error.
# If you access the &apos;&apos;products&apos;&apos; table, you should be able to see a list of existing products, and the &quot;Add New Record&quot; action.
# After you add a new product you should see that you are the product owner.
# As a product owner you should see both the &apos;&apos;parts&apos;&apos; and &apos;&apos;editors&apos;&apos; tabs in your product record.  You should be able to view and add new records to both of these relationships.
# Add another user as an editor to your product, then log in as that user.  You should be able to edit the product, but you shouldn&apos;t be able to see the &apos;&apos;editors&apos;&apos; tab for the product.

==See Also==

* [[permissions.ini file]] - Overview of the Xataface permissions.ini file
* [http://xataface.com/documentation/tutorial/getting_started/permissions Getting Started with Xataface Permissions]
* [[How to granulate permissions on each field]] - Brief tutorial on how to set permissions on a field by field basis.
* [[Delegate class methods]] - A list of all of the available delegate class methods that you can implement.  Many of them pertain to permissions and triggers.
* [[Application Delegate Class]] - Overview of the application delegate class.
* [[relationships.ini file]] - The relationships.ini file directives.
</content>
	<keywords>relationships, permissions, rel_relationshipname__permissions, getPermissions, permissions.ini</keywords>
	<language>en</language>
	<original_page></original_page>
</wiki></record>