Location: PHPKode > projects > iF.SVNAdmin > svnadmin/classes/providers/ldap/LdapUserViewProvider.class.php
<?php
/**
 * iF.SVNAdmin
 * Copyright (c) 2010 by Manuel Freiholz
 * http://www.insanefactory.com/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.
 */
namespace svnadmin\providers\ldap
{
class LdapUserViewProvider extends \IF_AbstractLdapConnector
                            implements \svnadmin\core\interfaces\IUserViewProvider,
                                      \svnadmin\core\interfaces\IGroupViewProvider
{
  // Authentication data, which is used to connect to the ldap server.
  public $bind_dn;
  public $bind_password;
  
  // Connection information.
  public $host_address;
  public $host_port;
  public $host_protocol_version;

  // Settings to find the users.
  public $users_base_dn;              // The base path of users.
  public $users_search_filter;        // The base filter to identify a entry as user. Example: "objectClass=person"
  public $users_attributes;           // The attributes of a user, which should be returned on all requests. (The name of the attribute which SVN(apache) uses to authenticate the user)
  
  // Settings to find groups.
  public $groups_base_dn;             // The base path of groups.
  public $groups_search_filter;       // The base filter to identify a entry as group. Example: "objectClass=group"
  public $groups_attributes;          // The attributes of a group, which should be returned on all requests. (The name of the group, which will be shown in the application view)
  public $groups_to_users_attribute;  // The attribute name of a group, which identifies the member association. Example: "member"
  public $groups_to_users_attribute_value;
  
  /**
   * Holds the singelton instance of this class.
   * @var LdapUserViewProvider
   */
  private static $m_instance = NULL;
  
  // IGroupViewProvider: Holds another group view provider which is used as cache.
  public $groupViewCachingProvider;
  
  // TODO: this var should not be needed!!!!
  private $is_uptodate = false;

  /**
   * Constructor.
   */     
  public function __construct()
  {
    $this->groupViewCachingProvider = NULL;
    $this->host_port = 0;
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // Singelton implementation.
  /////////////////////////////////////////////////////////////////////////////  
  
  public static function getInstance()
  {
    if( self::$m_instance == NULL )
    {
      self::$m_instance = new LdapUserViewProvider;

      global $appEngine;
      $cfg = $appEngine->getConfig();
      self::$m_instance->host_address = $cfg->getValue("Ldap", "HostAddress");
      self::$m_instance->host_protocol_version = $cfg->getValue("Ldap", "ProtocolVersion");
      self::$m_instance->bind_dn = $cfg->getValue("Ldap", "BindDN");
      self::$m_instance->bind_password = $cfg->getValue("Ldap", "BindPassword");

      self::$m_instance->users_base_dn = $cfg->getValue("Users:ldap", "BaseDN");
      self::$m_instance->users_search_filter = $cfg->getValue("Users:ldap", "SearchFilter");
      self::$m_instance->users_attributes = $cfg->getValue("Users:ldap", "Attributes");
      self::$m_instance->users_attributes = explode(",", self::$m_instance->users_attributes);


      self::$m_instance->groups_base_dn = $cfg->getValue("Groups:ldap", "BaseDN");
      self::$m_instance->groups_search_filter = $cfg->getValue("Groups:ldap", "SearchFilter");
      self::$m_instance->groups_attributes = $cfg->getValue("Groups:ldap", "Attributes");
      self::$m_instance->groups_attributes = explode(",", self::$m_instance->groups_attributes);
      self::$m_instance->groups_to_users_attribute = $cfg->getValue("Groups:ldap", "GroupsToUserAttribute");
      self::$m_instance->groups_to_users_attribute_value = $cfg->getValue("Groups:ldap", "GroupsToUserAttributeValue");
    }
    return self::$m_instance;
  }
  
    public function isUpdateable()
    {
      return true;
    }
    
    public function update()
    {
      // Update the SVNAuthFile with data from LDAP server.
      return self::p_update();
    }
  
  /////////////////////////////////////////////////////////////////////////////
  // IViewProvider overloads
  /////////////////////////////////////////////////////////////////////////////
  
  private $m_init_done = false;
  public function init()
  {
    if (!$this->m_init_done)
    {
      $this->m_init_done = true;
      $this->ldapVersion = $this->host_protocol_version;

      if (parent::connect($this->host_address, $this->host_port) === FALSE){
        return false;
      }

      if (parent::bind($this->bind_dn, $this->bind_password) === FALSE){
        return false;
      }
      
      if ($this->groupViewCachingProvider != NULL)
        $this->groupViewCachingProvider->init();
    }
    return true;
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // IUserViewProvider overloads
  /////////////////////////////////////////////////////////////////////////////
  
  public function getUsers($withStarUser=true)
  {
    $propUserName = $this->users_attributes[0];
    $propUserName = strtolower( $propUserName );
  
    $ret = array();
    $ldapUsers = self::p_getUsers($this->users_attributes);
    $ldapUsersLen = count($ldapUsers);
    for( $i=0; $i<$ldapUsersLen; $i++ )
    {
      $u = new \svnadmin\core\entities\User;
      $u->id = $ldapUsers[$i]->dn;
      $u->name = $ldapUsers[$i]->$propUserName;
      array_push( $ret, $u );
    }
    
    // Staticly get the '*' user.
    if ($withStarUser)
    {
      $oUAll = new \svnadmin\core\entities\User;
      $oUAll->id = '*';
      $oUAll->name = '*';
      array_push( $ret, $oUAll );
    }
    
    return $ret;
  }
  
  public function userExists( $objUser )
  {
    // Create filter. Example: sAMAccountName=ifmanuel
    $user_name_filter = $this->users_attributes[0] . '=' . $objUser->name;
    $final_filter = '(&('.$user_name_filter.')'.$this->users_search_filter.')';
    
    // Search for a user, where the 'users_attributes' equals the $objUser->name.
    $found = parent::objectSearch( $this->connection, $this->users_base_dn, $final_filter, $this->users_attributes, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return false;
    }
    
    return true;
  }
  
  public function authenticate( $objUser, $password )
  {
    // Create filter. Example: sAMAccountName=ifmanuel
    $user_name_filter = $this->users_attributes[0] . '=' . $objUser->name;
    $final_filter = '(&('.$user_name_filter.')'.$this->users_search_filter.')';
    
    // Search for a user, where the 'users_attributes' equals the $objUser->name.
    $found = parent::objectSearch( $this->connection, $this->users_base_dn, $final_filter, $this->users_attributes, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return false;
    }
    
    // The user has been found.
    // Get the dn of the user and authenticate him/her now.
    return \if_ldap_authenticate( $this->host_address, $this->host_port, $found[0]->dn, $password, $this->host_protocol_version );
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // IGroupViewProvider overloads
  /////////////////////////////////////////////////////////////////////////////
  
  public function getGroups()
  {
    if( $this->groupViewCachingProvider != NULL )
    {
      return $this->groupViewCachingProvider->getGroups();
    }
    else
    {
      return self::ldap_getGroups();
    }
  }
  
  public function groupExists( $objGroup )
  {
    if( $this->groupViewCachingProvider != NULL )
    {
      return $this->groupViewCachingProvider->groupExists( $objGroup );
    }
    else
    {
      return self::ldap_groupExists( $objGroup );
    }
  }
  
  public function getGroupsOfUser( $objUser )
  {
    if( $this->groupViewCachingProvider != NULL )
    {
      return $this->groupViewCachingProvider->getGroupsOfUser( $objUser );
    }
    else
    {
      return self::ldap_getGroupsOfUser( $objUser );
    }
  }
  
  public function getUsersOfGroup( $objGroup )
  {
    if( $this->groupViewCachingProvider != NULL )
    {
      return $this->groupViewCachingProvider->getUsersOfGroup( $objGroup );
    }
    else
    {
      return self::ldap_getUsersOfGroup( $objGroup );
    }
  }
  
  public function isUserInGroup( $objUser, $objGroup )
  {
    if( $this->groupViewCachingProvider != NULL )
    {
      return $this->groupViewCachingProvider->isUserInGroup( $objUser, $objGroup );
    }
    else
    {
      return self::ldap_isUserInGroup( $objUser, $objGroup );
    }
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // IGroupViewProvider LDAP overloads
  // These functions gets informations directly from ldap.
  /////////////////////////////////////////////////////////////////////////////
  
  public function ldap_getGroups()
  {
    $propGroupName = $this->groups_attributes[0];
    $propGroupName = strtolower( $propGroupName );
  
    $ret = array();
    $ldapGroups = self::p_getGroups($this->groups_attributes);
    $ldapGroupsLen = count($ldapGroups);
    for( $i=0; $i<$ldapGroupsLen; $i++ )
    {
      $u = new \svnadmin\core\entities\Group;
      $u->id = $ldapGroups[$i]->dn;
      $u->name = $ldapGroups[$i]->$propGroupName;
      array_push( $ret, $u );
    }
    return $ret;
  }
  
  public function ldap_groupExists( $objGroup )
  {
    // Create filter. Example: sAMAccountName=_ISVNADMIN
    $group_name_filter = $this->groups_attributes[0] . '=' . $objGroup->name;
    $final_filter = '(&('.$group_name_filter.')'.$this->groups_search_filter.')';
    
    // Search for a group, where the 'groups_attributes' equals the $objGroup->name.
    $found = parent::objectSearch( $this->connection, $this->groups_base_dn, $final_filter, $this->groups_attributes, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return false;
    }
    
    return true;
  }
  
  public function ldap_getGroupsOfUser( $objUser )
  {
    $ret = array();
  
    // First, we have to find the user entry in the LDAP.
    $userEntry = self::p_findUserEntry( $objUser );
    
    $propUserId = strtolower( $this->groups_to_users_attribute_value );
    
    // Create filter to find all group CNs which contains the usersEntry DN as 'member'.
    $filter = $this->groups_to_users_attribute.'='.$userEntry->$propUserId;
    $filter = '(&('.$filter.')'.$this->groups_search_filter.')';
    
    // Execute search.
    $found = parent::objectSearch( $this->connection, $this->groups_base_dn, $filter, $this->groups_attributes, 0 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return $ret;
    }
    
    $propGroupName = $this->groups_attributes[0];
    $propGroupName = strtolower( $propGroupName );
    
    foreach( $found as $stdObj )
    {
      $o = new \svnadmin\core\entities\Group;
      $o->name = $stdObj->$propGroupName;
      array_push( $ret, $o );
    }
    return $ret;
  }
  
  public function ldap_getUsersOfGroup( $objGroup )
  {
    $ret = array();
    
    // Create filter to find all members of the given group.
    $filter = $this->groups_attributes[0].'='.$objGroup->name;
    $filter = '(&('.$filter.')'.$this->groups_search_filter.')';

    // We need the 'groups_to_users_attribute'.
    $att = array();
    array_push( $att, $this->groups_to_users_attribute );
    
    // Execute search.
    $found = parent::objectSearch( $this->connection, $this->groups_base_dn, $filter, $att, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return $ret;
    }
    
    // Variables:
    // $found[0]->dn
    // $found[0]->member(array?)
    //
    // Now we have to match the value which is saved in member to the user entry.
    
    $propName = strtolower( $this->groups_to_users_attribute );
    $propName2 = strtolower( $this->users_attributes[0] );
    if( !property_exists( $found[0], $propName ) )
    { // The group has no members.
      return $ret;
    }
    if( is_array( $found[0]->$propName ) )
    {
      foreach( $found[0]->$propName as $value )
      {
        // Find the user entry.
        $userEntry = self::p_resolveGroupMemberId($value);
        if( $userEntry != NULL )
        {
          // Create User-object from the entry.
          $u = new \svnadmin\core\entities\User;
          $u->name = $userEntry->$propName2;
          array_push( $ret, $u );
        }
      }
    }
    elseif( $found[0]->$propName != NULL )
    { // Its a single member.
      // Find the user entry.
      $userEntry = self::p_resolveGroupMemberId($found[0]->$propName);
      if( $userEntry != NULL )
      {
        // Create User-object from the entry.
        $u = new \svnadmin\core\entities\User;
        $u->name = $userEntry->$propName2;
        array_push( $ret, $u );
      }
    }
    
    return $ret;
  }
  
  public function ldap_isUserInGroup( $objUser, $objGroup )
  {
    // Get the user and group entry.
    $userEntry = self::p_findUserEntry( $objUser );
    $groupEntry = self::p_findGroupEntry( $objGroup );
    
    $propGroupName = $this->groups_attributes[0];
    $propGroupName = strtolower( $propGroupName );
    
    // Create filter to find the user as attribute inside the group.
    $filter_user = $this->groups_to_users_attribute.'='.$userEntry->dn;
    $filter_group = $this->groups_attributes[0].'='.$groupEntry->$propGroupName;
    $filter = '(&('.$filter_user.')('.$filter_group.')'.$this->groups_search_filter.')';
    
    // Execute search.
    $found = parent::objectSearch( $this->connection, $this->groups_base_dn, $filter, $this->groups_attributes, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return false;
    }
    return true;
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // Protected helper functions
  /////////////////////////////////////////////////////////////////////////////
  
  /**
   * Searches the LDAP entry of the given user.
   *
   * @param User
   * @param array Attributes if NULL, then the default user attributes will be used.
   *
   * @return stdClass or NULL
   */
  protected function p_findUserEntry( $objUser, $attributes = NULL )
  {
    // Create filter. Example: sAMAccountName=ifmanuel
    $user_name_filter = $this->users_attributes[0] . '=' . $objUser->name;
    $final_filter = '(&('.$user_name_filter.')'.$this->users_search_filter.')';
    
    // The attributes.
    $att = NULL;
    if( $attributes != NULL )
    {
      $att = $attributes;
    }
    else
    {
      $att = $this->users_attributes;
    }
    
    // Search for a user, where the 'users_attributes' equals the $objUser->name.
    $found = parent::objectSearch( $this->connection, $this->users_base_dn, $final_filter, $att, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return NULL;
    }
    
    return $found[0];
  }
  
  /**
   * Searches the LDAP entry of the given group.
   *
   * @param Group
   *
   * @return stdClass or NULL
   */
  protected function p_findGroupEntry( $objGroup, $attributes = NULL )
  {
    // Create filter. Example: sAMAccountName=_ISVNADMIN
    $filter = $this->groups_attributes[0] .'=' . $objGroup->name;
    $filter = '(&('.$filter.')'.$this->groups_search_filter.')';
    
    // The attributes.
    $att = NULL;
    if( $attributes != NULL )
    {
      $att = $attributes;
    }
    else
    {
      $att = $this->groups_attributes;
    }
    
    // Execute search.
    $found = parent::objectSearch( $this->connection, $this->groups_base_dn, $filter, $att, 1 );
    
    if( !is_array( $found ) || count( $found ) <= 0 )
    {
      return NULL;
    }
    
    return $found[0];
  }
  
  /**
   * Searches for a user-entry based on the member-id from the group.
   *
   * @param string The member id which is associated to a group.
   *
   * @return stdClass User-entry or NULL
   */
  protected function p_resolveGroupMemberId( $memberId )
  {
    // Create filter.
    $filter = $this->groups_to_users_attribute_value.'='.$memberId;
    $filter = '(&('.$filter.')'.$this->users_search_filter.')';
    
    // Execute search.
    $found = parent::objectSearch( $this->connection, $this->users_base_dn, $filter, $this->users_attributes, 1 );
    if( !is_array($found) || count($found) <= 0 )
    {
      return NULL;
    }
    
    return $found[0];
  }
  
  /////////////////////////////////////////////////////////////////////////////
  // Private helper functions
  /////////////////////////////////////////////////////////////////////////////
  
  /**
   * Gets all users in the defined $users_base_dn filtered by $users_search_filter.
   * 
   * @param int $limit The maximum number of users which should be fetched via LDAP.
   *
   * @return array<stdClass>()
   */            
  public function p_getUsers($attributes, $limit=0)
  {
    return parent::objectSearch($this->connection, $this->users_base_dn, $this->users_search_filter, $attributes, $limit);
  }
  
  /**
   * Gets all groups in the defined $groups_base_dn filtered by $groups_search_filter.
   *
   * @param int $limit The maximum number of groups which should be fetched via LDAP.
   *
   * @return array<stdClass>()
   */
  public function p_getGroups($attributes, $limit=0)
  {
    return parent::objectSearch($this->connection, $this->groups_base_dn, $this->groups_search_filter, $attributes, $limit);
  }
  
  /**
   * getUserAttributes
   *
   * @param string $user_dn
   * @param array<string> $attributes
   *
   * @return stdClass
   */
  private function p_getUserAttributes($user_dn, $attributes)
  {
    return parent::objectRead($this->connection, $user_dn, $this->users_search_filter, $attributes);
  }
  
  /**
   * getUsersOfGroup
   *
   * @param string $group_dn
   * @param int $limit(0)
   *
   * @return array<stdClass>()
   */
  /*private function p_getUsersOfGroup($group_dn, $limit=0)
  {
    // Build filter to find only users who are in the given group.
    $f = "(&".$this->users_search_filter."(".$this->users_to_groups_attribute."=".$group_dn."))";
    return parent::objectSearch($this->connection, $this->users_base_dn, $f, $this->users_attributes, $limit);
  }*/
  
  /**
   * getGroupsOfUser
   *
   * @param string $user_dn
   * @param int $limit(0)
   *
   * @return array<stdClass>()
   */
  private function p_getGroupsOfUser($user_dn, $limit=0)
  {
    // Built filter to find only groups which has the $user_dn associated.
    $f = "(&(".$this->groups_search_filter.")(".$this->groups_to_users_attribute."=".$user_dn."))";
    return parent::objectSearch($this->connection, $this->groups_base_dn, $f, $this->groups_attributes, $limit);
  }
  
  /**
   * Writes the LDAP user group assignemnts into the SVNAuthFile.
   *
   * Note: This is a temporary work arround until subversion provides LDAP integration...
   * return bool
   */
  private function p_update()
  {
    // WORKARROUND:
    global $appEngine;
    $cfg = $appEngine->getConfig();

    if( $this->is_uptodate || $cfg->getValue("Engine:Providers", "GroupViewProviderType") != "ldap" ){
      return true;
    }
    else{
      $this->is_uptodate = true;
    }

                  //
                  // We get all users and all groups first.
                  // On this way we have only two request to the LDAP server
                  // and the searching for user-group associations are much faster
                  // on this way.
                  //

                  $uprov = $this;
                  $gprov = $this;
                  
                  // User entries.
                  $userEntries = NULL;
                  $userAttributes = array();
                  foreach( $uprov->users_attributes as $name )
                  {
                    array_push($userAttributes, $name);
                  }
                  array_push($userAttributes, $uprov->groups_to_users_attribute_value);
                  $userEntries = $uprov->p_getUsers($userAttributes);


                  // Group entries.
                  $groupEntries = NULL;
                  $groupAttributes = array();
                  foreach( $gprov->groups_attributes as $name )
                  {
                    array_push($groupAttributes, $name);
                  }
                  array_push($groupAttributes, $gprov->groups_to_users_attribute);
                  $groupEntries = $gprov->p_getGroups($groupAttributes);


                  //
                  // Now we iterate all groups and search
                  // the userEntries array for members of the group.
                  //


                  // The property names of a group and user object, which are needed.
                  $pgName = strtolower($gprov->groups_attributes[0]);
                  $pgMemberId = strtolower($gprov->groups_to_users_attribute);

                  $puName = strtolower($uprov->users_attributes[0]);
                  $puId = strtolower($gprov->groups_to_users_attribute_value);

                  // This is the group section, fill it with
                  // "groupname"=>"member1,member2,member3".
                  $section = array();

                  foreach($groupEntries as $eGroup)
                  {
                    $members = "";

                    // Iterate the member of a group.
                    if( !property_exists($eGroup,$pgMemberId) )
                    { // No members
                    }
                    elseif( is_array($eGroup->$pgMemberId) )
                    { // Multiple members.
                      $doComma = false;
                      foreach($eGroup->$pgMemberId as $mid)
                      {
                        foreach($userEntries as $eUser)
                        {
                          if( $eUser->$puId == $mid )
                          {
                            if( $doComma )
                            {
                              $members.= ",";
                            }
                            $doComma = true;
                            $members.= $eUser->$puName;
                          }
                        }
                      }
                    }
                    else
                    { // One member.
                      foreach($userEntries as $eUser)
                      {
                        if( $eUser->$puId == $eGroup->$pgMemberId )
                        {
                          $members.=$eUser->$puName;
                          break;
                        }
                      }
                    }
                    
                    $section[$eGroup->$pgName] = $members;
                  }
                  ksort($section);


                  //
                  // Open the subversion authorization file.
                  // Remove the old [groups] block and replace it
                  // with the new created one from LDAP.
                  //


                  // Open the file.
                  $svnFile = new \IF_SVNAuthFileC;
                  $svnAuthFile = $cfg->getValue("Subversion", "SVNAuthFile");
                  if (!$svnFile->open($svnAuthFile))
                  {
                    echo 'Can not open or read SVNAuthFile: ' . $svnAuthFile . "\n";
                    echo 'Did you configured your SVNAuthFile path? (+rw)';
                    exit(1);
                  }

                  // Override the [groups] section from SVNAuthFile.
                  $svnFile->m_data[$svnFile->GROUP_SECTION] = $section;
                  if( $svnFile->save() )
                  {
                    return true;
                  }
                  else
                  {
                    return false;
                  }
    
  } //func-end
}
}
?>
Return current item: iF.SVNAdmin