diff --git a/htdocs/always.php b/htdocs/always.php index f3a816bb..2f41cfe7 100644 --- a/htdocs/always.php +++ b/htdocs/always.php @@ -37,7 +37,8 @@ $c->collections_always_exist = false; $c->allow_get_email_visibility = false; $c->permission_scan_depth = 2; $c->expand_pdo_parameters = true; -$c->home_calendar_name = 'home'; +$c->home_calendar_name = 'calendar'; +$c->home_addressbook_name = 'addresses'; $c->enable_row_linking = true; $c->enable_scheduling = false; $c->http_auth_mode = 'Basic'; @@ -202,92 +203,7 @@ if ( $qry->Exec('always',__LINE__,__FILE__) && $row = $qry->Fetch() ) { if ( isset($_SERVER['HTTP_X_DAVICAL_TESTCASE']) ) $qry->QDo('SET TIMEZONE TO \'Pacific/Auckland\''); } - -$_known_users_name = array(); -$_known_users_id = array(); -$_known_users_pid = array(); -$_known_users_email = array(); - -function _davical_get_principal_query_cached( $where, $parameter ) { - global $c, $session, $_known_users_name, $_known_users_id, $_known_users_pid; - - $sql = 'SELECT *, to_char(updated at time zone \'GMT\',\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS modified, principal.*, '; - if ( isset($session->principal_id) ) { - $sql .= 'pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) AS privileges '; - $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); - } - else { - $sql .= '0::BIT(24) AS privileges '; - $params = array( ); - } - $sql .= 'FROM usr LEFT JOIN principal USING(user_no) WHERE '. $where; - $params[':param'] = $parameter; - - $qry = new AwlQuery( $sql, $params ); - if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows() == 1 && $row = $qry->Fetch() ) { - if ( isset($session->principal_id) ) { - $_known_users_name[$row->username] = $row; - $_known_users_id[$row->user_no] = $row; - $_known_users_pid[$row->principal_id] = $row; - $_known_users_email[$row->email] = $row; - } - return $row; - } - - return false; -} - -/** -* Return a user record identified by a username, caching it for any subsequent lookup -* @param string $username The username of the record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getUserByName( $username, $use_cache = true ) { - global $_known_users_name; - - if ( $use_cache && isset( $_known_users_name[$username] ) ) return $_known_users_name[$username]; - return _davical_get_principal_query_cached( 'lower(username) = lower(:param)', $username ); -} - - -/** -* Return a user record identified by e-mail address, caching it for any subsequent lookup -* @param string $email The email address of the user record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getUserByEMail( $email, $use_cache = true ) { - global $_known_users_name; - - if ( $use_cache && isset( $_known_users_email[$email] ) ) return $_known_users_email[$email]; - return _davical_get_principal_query_cached( 'lower(email) = lower(:param)', $email ); -} - - -/** -* Return a user record identified by a user_no, caching it for any subsequent lookup -* @param int $user_no The ID of the record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getUserByID( $user_no, $use_cache = true ) { - global $c, $session, $_known_users_id; - - if ( $use_cache && isset( $_known_users_id[$user_no] ) ) return $_known_users_id[$user_no]; - return _davical_get_principal_query_cached( 'user_no = :param', $user_no ); -} - - -/** -* Return a user record identified by a user_no, caching it for any subsequent lookup -* @param int $user_no The ID of the record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getPrincipalByID( $principal_id, $use_cache = true ) { - global $c, $session, $_known_users_pid; - - if ( $use_cache && isset( $_known_users_pid[$principal_id] ) ) return $_known_users_pid[$principal_id]; - return _davical_get_principal_query_cached( 'principal_id = :param', $principal_id ); -} - +require_once('Principal.php'); /** * Return the HTTP status code description for a given code. Hopefully diff --git a/htdocs/freebusy.php b/htdocs/freebusy.php index e876d6bb..8be903e2 100644 --- a/htdocs/freebusy.php +++ b/htdocs/freebusy.php @@ -45,12 +45,12 @@ require_once("CalDAVRequest.php"); $request = new CalDAVRequest(array("allow_by_email" => 1)); $path_match = '^'.$request->path; if ( preg_match( '{^/(\S+@[a-z0-9][a-z0-9-]*[.][a-z0-9.-]+)/?$}i', $request->path, $matches ) ) { - $u = getUserByEMail($matches[1]); - $path_match = '^/'.$u->username.'/'; + $principal = new Principal('email',$matches[1]); + $path_match = '^'.$principal->dav_name(); } if ( isset($fb_format) && $fb_format != 'text/calendar' ) { - $request->DoResponse( 406, 'This server only supports the text/calendar format for freebusy URLs' ); + $request->DoResponse( 406, translate('This server only supports the text/calendar format for freebusy URLs') ); } if ( ! $request->HavePrivilegeTo('read-free-busy') ) $request->DoResponse( 404 ); diff --git a/htdocs/tools.php b/htdocs/tools.php index 39335696..3aaa6307 100644 --- a/htdocs/tools.php +++ b/htdocs/tools.php @@ -132,14 +132,15 @@ class Tools { if ( $ics != '' ) { include_once('check_UTF8.php'); if ( check_string($ics) ) { - $path = "/".substr($file,0,-4).$path_ics; + $username = substr($file,0,-4); + $path = "/".$username.$path_ics; dbg_error_log( "importFromDirectory", "importing to $path"); $c->readonly_webdav_collections = false; // Override this setting so we can create collections/events on import. require_once("caldav-PUT-functions.php"); - if ( $user = getUserByName(substr($file,0,-4),'importFromDirectory',__LINE__,__FILE__)) { - $user_no = $user->user_no; + if ( $principal = new Principal('username',$username) ) { + $user_no = $principal->user_no(); } - if(controlRequestContainer(substr($file,0,-4),$user_no, $path,false) === -1) + if ( controlRequestContainer($username, $user_no, $path, false) === -1) continue; import_collection($ics,$user_no,$path,1); $c->messages[] = sprintf(translate('all events of user %s were deleted and replaced by those from file %s'),substr($file,0,-4),$dir.'/'.$file); diff --git a/inc/CalDAVRequest.php b/inc/CalDAVRequest.php index 84adcb89..22aae37b 100644 --- a/inc/CalDAVRequest.php +++ b/inc/CalDAVRequest.php @@ -16,7 +16,7 @@ require_once("AwlCache.php"); require_once("XMLDocument.php"); -require_once("CalDAVPrincipal.php"); +require_once("DAVPrincipal.php"); include("DAVTicket.php"); define('DEPTH_INFINITY', 9999); @@ -48,7 +48,7 @@ class CalDAVRequest /** * The 'principal' (user/resource/...) which this request seeks to access - * @var CalDAVPrincipal + * @var DAVPrincipal */ var $principal; @@ -137,7 +137,6 @@ class CalDAVRequest $this->options = $options; if ( !isset($this->options['allow_by_email']) ) $this->options['allow_by_email'] = false; - $this->principal = (object) array( 'username' => $session->username, 'user_no' => $session->user_no ); $this->raw_post = file_get_contents ( 'php://input'); @@ -281,14 +280,7 @@ class CalDAVRequest } if ( strstr($this->path,'//') ) $this->path = preg_replace( '#//+#', '/', $this->path); - $this->user_no = $session->user_no; - $this->username = $session->username; - if ( $session->user_no > 0 ) { - $this->current_user_principal_url = new XMLElement('href', ConstructURL('/'.$session->username.'/') ); - } - else { - $this->current_user_principal_url = new XMLElement('unauthenticated' ); - } + $this->principal = new Principal('path',$this->path); /** * RFC2518, 5.2: URL pointing to a collection SHOULD end in '/', and if it does not then @@ -402,11 +394,11 @@ EOSQL; /** * Extract the user whom we are accessing */ - $this->principal = new CalDAVPrincipal( array( "path" => $this->path, "options" => $this->options ) ); - if ( isset($this->principal->user_no) ) $this->user_no = $this->principal->user_no; - if ( isset($this->principal->username)) $this->username = $this->principal->username; - if ( isset($this->principal->by_email) && $this->principal->by_email) $this->by_email = true; - if ( isset($this->principal->principal_id)) $this->principal_id = $this->principal->principal_id; + $this->principal = new DAVPrincipal( array( "path" => $this->path, "options" => $this->options ) ); + $this->user_no = $this->principal->user_no(); + $this->username = $this->principal->username(); + $this->by_email = $this->principal->byEmail(); + $this->principal_id = $this->principal->principal_id(); if ( $this->collection_type == 'principal' || $this->collection_type == 'email' || $this->collection_type == 'proxy' ) { $this->collection = $this->principal->AsCollection(); @@ -562,45 +554,6 @@ EOSQL; } - /** - * Work out the user whose calendar we are accessing, based on elements of the path. - */ - function UserFromPath() { - global $session; - - $this->user_no = $session->user_no; - $this->username = $session->username; - $this->principal_id = $session->principal_id; - - @dbg_error_log( "WARN", "Call to deprecated CalDAVRequest::UserFromPath()" ); - - if ( $this->path == '/' || $this->path == '' ) { - dbg_error_log( "caldav", "No useful path split possible" ); - return false; - } - - $path_split = explode('/', $this->path ); - $this->username = $path_split[1]; - if ( $this->username == 'principals' ) $this->username = $path_split[3]; - @dbg_error_log( "caldav", "Path split into at least /// %s /// %s /// %s", $path_split[1], $path_split[2], $path_split[3] ); - if ( isset($this->options['allow_by_email']) && preg_match( '#/(\S+@\S+[.]\S+)/?$#', $this->path, $matches) ) { - $this->by_email = $matches[1]; - $qry = new AwlQuery("SELECT user_no, principal_id, username FROM usr JOIN principal USING (user_no) WHERE email = :email", - array(':email' => $this->by_email ) ); - if ( $qry->Exec('caldav',__LINE__,__FILE__) && $user = $qry->Fetch() ) { - $this->user_no = $user->user_no; - $this->username = $user->username; - $this->principal_id = $user->principal_id; - } - } - elseif( $user = getUserByName($this->username,'caldav',__LINE__,__FILE__)) { - $this->principal = $user; - $this->user_no = $user->user_no; - $this->principal_id = $user->principal_id; - } - } - - /** * Permissions are controlled as follows: * 1. if the path is '/', the request has read privileges @@ -620,9 +573,9 @@ EOSQL; $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl')); dbg_error_log( "caldav", "Full read permissions for user accessing /" ); } - else if ( $session->AllowedTo("Admin") || $session->user_no == $this->user_no ) { + else if ( $session->AllowedTo("Admin") || $session->principal->user_no() == $this->user_no ) { $this->privileges = privilege_to_bits('all'); - dbg_error_log( "caldav", "Full permissions for %s", ( $session->user_no == $this->user_no ? "user accessing their own hierarchy" : "a systems administrator") ); + dbg_error_log( "caldav", "Full permissions for %s", ( $session->principal->user_no() == $this->user_no ? "user accessing their own hierarchy" : "a systems administrator") ); } else { $this->privileges = 0; @@ -638,7 +591,7 @@ EOSQL; /** * In other cases we need to query the database for permissions */ - $params = array( ':session_principal_id' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); + $params = array( ':session_principal_id' => $session->principal->principal_id(), ':scan_depth' => $c->permission_scan_depth ); if ( isset($this->by_email) && $this->by_email ) { $sql ='SELECT pprivs( :session_principal_id::int8, :request_principal_id::int8, :scan_depth::int ) AS perm'; $params[':request_principal_id'] = $this->principal_id; @@ -1006,7 +959,7 @@ EOSQL; */ function AllowedTo( $activity ) { global $session; - dbg_error_log('caldav', 'Checking whether "%s" is allowed to "%s"', $session->username, $activity); + dbg_error_log('caldav', 'Checking whether "%s" is allowed to "%s"', $session->principal->username(), $activity); if ( isset($this->permissions['all']) ) return true; switch( $activity ) { case 'all': @@ -1209,7 +1162,7 @@ EOSQL; $message = substr( preg_replace("#\s+#m", ' ', $message ), 0, 100) . (strlen($message) > 100 ? "..." : ""); } - dbg_error_log("caldav", "Status: %d, Message: %s, User: %d, Path: %s", $status, $message, $session->user_no, $this->path); + dbg_error_log("caldav", "Status: %d, Message: %s, User: %d, Path: %s", $status, $message, $session->principal->user_no(), $this->path); } if ( isset($c->dbg['statistics']) && $c->dbg['statistics'] ) { $script_time = microtime(true) - $c->script_start_time; diff --git a/inc/CalDAVPrincipal.php b/inc/DAVPrincipal.php similarity index 67% rename from inc/CalDAVPrincipal.php rename to inc/DAVPrincipal.php index 19184546..6cbcc93c 100644 --- a/inc/CalDAVPrincipal.php +++ b/inc/DAVPrincipal.php @@ -9,30 +9,14 @@ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ -/** -* @var $_CalDAVPrincipalCache -* A global variable holding a cache of any DAV Principals which are -* read from the DB. -*/ -$_CalDAVPrincipalCache = (object) array( 'p' => array(), 'u' => array() ); - /** * A class for things to do with a DAV Principal * * @package davical */ -class CalDAVPrincipal +class DAVPrincipal extends Principal { - /** - * @var The home URL of the principal - */ - private $url; - - /** - * @var Identifies whether this principal exists in the DB yet - */ - private $exists; /** * @var RFC4791: Identifies the URL(s) of any WebDAV collections that contain @@ -51,23 +35,6 @@ class CalDAVPrincipal */ private $calendar_free_busy_set; - /** - * @var draft-desruisseaux-caldav-sched-03: Identify the URL of the scheduling - * Inbox collection owned by the associated principal resource. - */ - var $schedule_inbox_url; - - /** - * @var draft-desruisseaux-caldav-sched-03: Identify the URL of the scheduling - * Outbox collection owned by the associated principal resource. - */ - var $schedule_outbox_url; - - /** - * @var Whether or not we are using an e-mail address based URL. - */ - var $by_email; - /** * @var RFC3744: The principals that are direct members of this group. */ @@ -76,47 +43,43 @@ class CalDAVPrincipal /** * @var RFC3744: The principals that are direct members of this group. */ - protected $group_member_set; + private $group_member_set; /** * @var RFC3744: The groups in which the principal is directly a member. */ - protected $group_membership; + private $group_membership; /** * @var caldav-cu-proxy-02: The principals which this one has read permissions on. */ - protected $read_proxy_for; + private $read_proxy_for; /** * @var caldav-cu-proxy-02: The principals which this one has read-write prmissions for. */ - protected $write_proxy_for; + private $write_proxy_for; /** * @var caldav-cu-proxy-02: The principals which have read permissions on this one. */ - protected $read_proxy_group; + private $read_proxy_group; /** * @var caldav-cu-proxy-02: The principals which have write permissions on this one. */ - protected $write_proxy_group; + private $write_proxy_group; /** * @var CardDAV: The URL to an addressbook entry for this principal */ - protected $principal_address; + private $principal_address; /** - * @var The username for this principal + * A unique tag which will change if this principal changes + * @var string */ - protected $username; - - /** - * @var The dav_name for this principal - a partial path - */ - protected $dav_name; + private $unique_tag; /** * Constructor @@ -132,60 +95,44 @@ class CalDAVPrincipal global $session, $c; $this->exists = null; - $this->url = null; - if ( $parameters == null ) return false; - $this->by_email = false; + if ( $parameters == null ) return; + if ( is_object($parameters) ) { dbg_error_log( 'principal', 'Principal: record for %s', $parameters->username ); - $usr = $parameters; + parent::__construct('username',$parameters->username); } else if ( is_int($parameters) ) { dbg_error_log( 'principal', 'Principal: %d', $parameters ); - $usr = getUserByID($parameters); - $this->user_no = $parameters['user_no']; + parent::__construct('principal_id',$parameters); } else if ( is_array($parameters) ) { if ( ! isset($parameters['options']['allow_by_email']) ) $parameters['options']['allow_by_email'] = false; if ( isset($parameters['username']) ) { - $usr = getUserByName($parameters['username']); - $this->username = $parameters['username']; + parent::__construct('username',$parameters['username']); } else if ( isset($parameters['user_no']) ) { - $usr = getUserByID($parameters['user_no']); - $this->user_no = $parameters['user_no']; + parent::__construct('user_no',$parameters['user_no']); } - else if ( isset($parameters['email']) && $parameters['options']['allow_by_email'] ) { - if ( $username = $this->UsernameFromEMail($parameters['email']) ) { - $usr = getUserByName($username); - $this->username = $username; - } + else if ( isset($parameters['principal_id']) ) { + parent::__construct('principal_id',$parameters['principal_id']); + } + else if ( isset($parameters['email']) ) { + parent::__construct('email',$parameters['email']); } else if ( isset($parameters['path']) ) { - dbg_error_log( 'principal', 'Finding Principal from path: "%s", options.allow_by_email: "%s"', $parameters['path'], $parameters['options']['allow_by_email'] ); - if ( $username = $this->UsernameFromPath($parameters['path'], $parameters['options']) ) { - $usr = getUserByName($username); - $this->username = $username; - } + parent::__construct('path',$parameters['path']); } else if ( isset($parameters['principal-property-search']) ) { - $usr = $this->PropertySearch($parameters['principal-property-search']); + $username = $this->PropertySearch($parameters['principal-property-search']); + parent::__construct('username',$username); } } - if ( !isset($usr) || !is_object($usr) ) { - $this->exists = false; - return false; - } - $this->exists = true; - $this->InitialiseRecord($usr); + if ( ! $this->exists ) return; + + $this->InitialiseRecord(); - if ( is_array($parameters) && !isset($parameters['username']) && !isset($parameters['user_no']) - && isset($parameters['path']) && preg_match('{^/(~|principals/)}', $parameters['path']) ) { - // Force it to match - $this->url = $parameters['path']; - $this->dav_name = $parameters['path']; - } } @@ -193,33 +140,20 @@ class CalDAVPrincipal * Initialise the Principal object from a $usr record from the DB. * @param object $usr The usr record from the DB. */ - function InitialiseRecord($usr) { + function InitialiseRecord() { global $c; - foreach( $usr AS $k => $v ) { - $this->{$k} = $v; - } - if ( !isset($this->modified) ) $this->modified = $this->updated; - if ( !isset($this->created) ) $this->created = $this->joined; - $this->dav_etag = md5($this->username . $this->updated); + $this->unique_tag = '"'.md5($this->username . $this->modified).'"'; + $this->_is_group = (isset($this->type_id) && $this->type_id == 3); - $this->_is_group = (isset($usr->type_id) && $usr->type_id == 3); - - $this->principal_url = ConstructURL( '/'.$this->username.'/', true ); - $this->url = $this->principal_url; - - $this->principal_address = $this->principal_url . 'principal.vcf'; + $this->principal_address = $this->url . 'principal.vcf'; $this->user_address_set = array( 'mailto:'.$this->email, - ConstructURL( '/'.$this->username.'/', true ), + $this->url, // ConstructURL( '/~'.$this->username.'/', true ), // ConstructURL( '/__uuids__/'.$this->username.'/', true ), ); - $this->schedule_inbox_url = sprintf( '%s.in/', $this->url); - $this->schedule_outbox_url = sprintf( '%s.out/', $this->url); - $this->dropbox_url = sprintf( '%s.drop/', $this->url); - $this->notifications_url = sprintf( '%s.notify/', $this->url); if ( isset ( $c->notifications_server ) ) { $this->xmpp_uri = 'xmpp:pubsub.'.$c->notifications_server['host'].'?pubsub;node=/davical-'.$this->principal_id; @@ -229,7 +163,7 @@ class CalDAVPrincipal if ( $this->_is_group ) { $this->group_member_set = array(); $qry = new AwlQuery('SELECT usr.username FROM group_member JOIN principal ON (principal_id=member_id) JOIN usr USING(user_no) WHERE group_id = :group_id ORDER BY principal.principal_id ', array( ':group_id' => $this->principal_id) ); - if ( $qry->Exec('CalDAVPrincipal') && $qry->rows() > 0 ) { + if ( $qry->Exec('DAVPrincipal') && $qry->rows() > 0 ) { while( $member = $qry->Fetch() ) { $this->group_member_set[] = ConstructURL( '/'. $member->username . '/', true); } @@ -238,7 +172,7 @@ class CalDAVPrincipal $this->group_membership = array(); $qry = new AwlQuery('SELECT usr.username FROM group_member JOIN principal ON (principal_id=group_id) JOIN usr USING(user_no) WHERE member_id = :member_id UNION SELECT usr.username FROM group_member LEFT JOIN grants ON (to_principal=group_id) JOIN principal ON (principal_id=by_principal) JOIN usr USING(user_no) WHERE member_id = :member_id and by_principal != member_id ORDER BY 1', array( ':member_id' => $this->principal_id ) ); - if ( $qry->Exec('CalDAVPrincipal') && $qry->rows() > 0 ) { + if ( $qry->Exec('DAVPrincipal') && $qry->rows() > 0 ) { while( $group = $qry->Fetch() ) { $this->group_membership[] = ConstructURL( '/'. $group->username . '/', true); } @@ -249,7 +183,7 @@ class CalDAVPrincipal $this->write_proxy_for = null; $this->read_proxy_for = null; - dbg_error_log( 'principal', ' User: %s (%d) URL: %s, Home: %s, By Email: %d', $this->username, $this->user_no, $this->url, $this->principal_url, $this->by_email ); + dbg_error_log( 'principal', ' User: %s (%d) URL: %s, By Email: %d', $this->username, $this->user_no, $this->url, $this->by_email ); } @@ -272,7 +206,7 @@ class CalDAVPrincipal $sql = 'SELECT principal_id, username, pprivs(:request_principal::int8,principal_id,:scan_depth::int) FROM principal JOIN usr USING(user_no) WHERE principal_id IN (SELECT * from p_has_proxy_access_to(:request_principal,:scan_depth))'; $params = array( ':request_principal' => $this->principal_id, ':scan_depth' => $c->permission_scan_depth ); $qry = new AwlQuery($sql, $params); - if ( $qry->Exec('CalDAVPrincipal') && $qry->rows() > 0 ) { + if ( $qry->Exec('DAVPrincipal') && $qry->rows() > 0 ) { while( $relationship = $qry->Fetch() ) { if ( (bindec($relationship->pprivs) & $write_priv) != 0 ) { $this->write_proxy_for[] = ConstructURL( '/'. $relationship->username . '/', true); @@ -287,7 +221,7 @@ class CalDAVPrincipal $sql = 'SELECT principal_id, username, pprivs(:request_principal::int8,principal_id,:scan_depth::int) FROM principal JOIN usr USING(user_no) WHERE principal_id IN (SELECT * from grants_proxy_access_from_p(:request_principal,:scan_depth))'; $qry = new AwlQuery($sql, $params ); // reuse $params assigned for earlier query - if ( $qry->Exec('CalDAVPrincipal') && $qry->rows() > 0 ) { + if ( $qry->Exec('DAVPrincipal') && $qry->rows() > 0 ) { while( $relationship = $qry->Fetch() ) { if ( bindec($relationship->pprivs) & $write_priv ) { $this->write_proxy_group[] = ConstructURL( '/'. $relationship->username . '/', true); @@ -352,58 +286,6 @@ class CalDAVPrincipal } - /** - * Work out the username, based on elements of the path. - * @param string $path The path to be used. - * @param array $options The request options, controlling whether e-mail paths are allowed. - */ - function UsernameFromPath( $path, $options = null ) { - global $session, $c; - - if ( $path == '/' || $path == '' ) { - dbg_error_log( 'principal', 'No useful path split possible' ); - return $session->username; - } - - $path_split = explode('/', $path ); - @dbg_error_log( 'principal', 'Path split into at least /// %s /// %s /// %s', $path_split[1], $path_split[2], $path_split[3] ); - - $username = $path_split[1]; - if ( $path_split[1] == 'principals' && isset($path_split[3]) ) $username = $path_split[3]; - if ( substr($username,0,1) == '~' ) $username = substr($username,1); - - if ( isset($options['allow_by_email']) && $options['allow_by_email'] && preg_match( '#^(\S+@\S+[.]\S+)$#', $username) ) { - $username = $this->UsernameFromEMail($username); - } - return $username; - } - - - /** - * Work out the username, based on the given e-mail - * @param string $email The email address to be used. - */ - function UsernameFromEMail( $email ) { - @dbg_error_log( 'principal', 'Retrieving username from e-mail address "%s" ', $email ); - $qry = new AwlQuery('SELECT username FROM usr WHERE email = :email', array( ':email' => $email ) ); - if ( $qry->Exec('principal') && $user = $qry->Fetch() ) { - $username = $user->username; - $this->by_email = true; - return $username; - } - return null; - } - - - /** - * Does this principal exist? - * @return boolean Whether or not it exists. - */ - function Exists() { - return $this->exists; - } - - /** * Is this a group principal? * @return boolean Whether this is a group principal @@ -413,28 +295,6 @@ class CalDAVPrincipal } - /** - * Return the username - * @return string The username - */ - function username() { - return (isset($this->username)?$this->username:'username not set'); - } - - - /** - * Return the partial path representing this principal - * @return string The dav_name - */ - function dav_name() { - if ( !isset($this->dav_name) ) { - if ( !isset($this->username) ) $this->dav_name = ''; - else $this->dav_name = '/'.$this->username.'/'; - } - return $this->dav_name; - } - - /** * Return an arbitrary property * @return string The name of the arbitrary property @@ -457,13 +317,15 @@ class CalDAVPrincipal return null; } - /** - * Return the URL for this principal - * @return string The principal-URL, or null if they don't exist + * Returns the unique_tag (ETag or getctag) for this resource */ - function url() { - return ($this->exists ? $this->url : null ); + public function unique_tag() { + if ( isset($this->unique_tag) ) return $this->unique_tag; + + if ( $this->exists !== true ) $this->unique_tag = '"-1"'; + + return $this->unique_tag; } @@ -473,17 +335,18 @@ class CalDAVPrincipal function calendar_home_set() { if ( !isset($this->calendar_home_set) ) { $this->calendar_home_set = array(); -/* $qry = new AwlQuery('SELECT DISTINCT parent_container FROM collection WHERE is_calendar AND user_no = :user_no', array( ':user_no' => $this->user_no)); + $qry = new AwlQuery('SELECT DISTINCT parent_container FROM collection WHERE is_calendar AND dav_name ~ :dav_name_start', + array( ':dav_name_start' => '^'.$this->dav_name)); if ( $qry->Exec('principal',__LINE__,__FILE__) ) { if ( $qry->rows() > 0 ) { while( $calendar = $qry->Fetch() ) { $this->calendar_home_set[] = ConstructURL($calendar->parent_container, true); } } - else {*/ - $this->calendar_home_set[] = $this->principal_url; -// } -// } + else { + $this->calendar_home_set[] = $this->url; + } + } } return $this->calendar_home_set; } @@ -494,18 +357,19 @@ class CalDAVPrincipal */ function addressbook_home_set() { if ( !isset($this->addressbook_home_set) ) { - $this->addressbook_home_set = array(); -/* $qry = new AwlQuery('SELECT DISTINCT parent_container FROM collection WHERE is_addressbook AND user_no = :user_no', array( ':user_no' => $this->user_no)); + $this->addressbook_home_set = array(); + $qry = new AwlQuery('SELECT DISTINCT parent_container FROM collection WHERE is_addressbook AND dav_name ~ :dav_name_start', + array( ':dav_name_start' => '^'.$this->dav_name)); if ( $qry->Exec('principal',__LINE__,__FILE__) ) { if ( $qry->rows() > 0 ) { while( $addressbook = $qry->Fetch() ) { $this->addressbook_home_set[] = ConstructURL($addressbook->parent_container, true); } } - else {*/ - $this->addressbook_home_set[] = $this->principal_url; -// } -// } + else { + $this->addressbook_home_set[] = $this->url; + } + } } return $this->addressbook_home_set; } @@ -518,11 +382,11 @@ class CalDAVPrincipal if ( !isset($this->calendar_free_busy_set) ) { /** * calendar-free-busy-set has been dropped from draft 5 of the scheduling extensions for CalDAV - * in favour of + * in favour of ??? */ $this->calendar_free_busy_set = array(); - $qry = new AwlQuery('SELECT dav_name FROM collection WHERE user_no = :user_no AND is_calendar AND (schedule_transp = \'opaque\' OR schedule_transp IS NULL) ORDER BY user_no, collection_id', - array( ':user_no' => $this->user_no) ); + $qry = new AwlQuery('SELECT dav_name FROM collection WHERE is_calendar AND (schedule_transp = \'opaque\' OR schedule_transp IS NULL) AND dav_name ~ :dav_name_start ORDER BY user_no, collection_id', + array( ':dav_name_start' => '^'.$this->dav_name)); if ( $qry->Exec('principal',__LINE__,__FILE__) ) { while( $calendar = $qry->Fetch() ) { $this->calendar_free_busy_set[] = ConstructURL($calendar->dav_name, true); @@ -532,6 +396,7 @@ class CalDAVPrincipal return $this->calendar_free_busy_set; } + /** * Return the privileges bits for the current session user to this resource */ @@ -539,8 +404,10 @@ class CalDAVPrincipal global $session; if ( !isset($this->privileges) ) $this->privileges = 0; if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges ); - if ( $this->_is_group && in_array(ConstructURL('/'.$session->username.'/'), $this->GroupMemberSet()) ) { - $this->privileges |= privilege_to_bits( array('DAV::read', 'DAV::read-current-user-privilege-set') ); + if ( $this->_is_group ) { + if ( in_array($session->principal->url(), $this->GroupMemberSet()) ) { + $this->privileges |= privilege_to_bits( array('DAV::read', 'DAV::read-current-user-privilege-set') ); + } } return $this->privileges; } @@ -550,26 +417,33 @@ class CalDAVPrincipal * Returns a representation of the principal as a collection */ function AsCollection() { + $dav_name = (isset($this->original_request_url) ? DeconstructURL($this->original_request_url) : $this->dav_name()); $collection = (object) array( - 'collection_id' => (isset($this->principal_id) ? $this->principal_id : 0), + 'collection_id' => ($this->principal_id() ? $this->principal_id() : 0), 'is_calendar' => false, 'is_addressbook' => false, 'is_principal' => true, - 'type' => 'principal' . (substr($this->dav_name(), 0, 12) == '/principals/'?'_link':''), - 'user_no' => (isset($this->user_no) ? $this->user_no : 0), + 'type' => 'principal' . (isset($this->original_request_url) ? '_link' : ''), + 'user_no' => ($this->user_no() ? $this->user_no() : 0), 'username' => $this->username(), - 'dav_name' => $this->dav_name, + 'dav_name' => $dav_name, 'parent_container' => '/', - 'email' => (isset($this->email) ? $this->email : ''), - 'created' => (isset($this->created) ? $this->created : date('Ymd\THis')), - 'updated' => (isset($this->updated) ? $this->updated : date('Ymd\THis')) + 'email' => ($this->email()? $this->email() : ''), + 'created' => $this->created, + 'updated' => $this->modified, + 'dav_etag' => substr($this->unique_tag(),1,-1), + 'resourcetypes' => $this->resourcetypes ); - $collection->dav_etag = (isset($this->dav_etag) ? $this->dav_etag : md5($collection->username . $collection->updated)); $collection->dav_displayname = (isset($this->dav_displayname) ? $this->dav_displayname : (isset($this->fullname) ? $this->fullname : $collection->username)); return $collection; } + + function PropertySearch( $parameters ) { + throw new Exception("Unimplemented!"); + } + /** * Returns properties which are specific to this principal */ @@ -590,7 +464,7 @@ class CalDAVPrincipal break; case 'DAV::principal-URL': - $prop->NewElement('principal-URL', $reply->href($this->principal_url) ); + $prop->NewElement('principal-URL', $reply->href($this->url()) ); break; case 'DAV::getlastmodified': @@ -618,15 +492,15 @@ class CalDAVPrincipal break; case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL': - $reply->CalDAVElement($prop, 'schedule-inbox-URL', $reply->href($this->schedule_inbox_url) ); + $reply->CalDAVElement($prop, 'schedule-inbox-URL', $reply->href($this->url('schedule_inbox')) ); break; case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL': - $reply->CalDAVElement($prop, 'schedule-outbox-URL', $reply->href($this->schedule_outbox_url) ); + $reply->CalDAVElement($prop, 'schedule-outbox-URL', $reply->href($this->url('schedule_outbox')) ); break; case 'http://calendarserver.org/ns/:dropbox-home-URL': - $reply->CalendarserverElement($prop, 'dropbox-home-URL', $reply->href($this->dropbox_url) ); + $reply->CalendarserverElement($prop, 'dropbox-home-URL', $reply->href($this->url('dropbox')) ); break; case 'http://calendarserver.org/ns/:xmpp-server': @@ -657,7 +531,7 @@ class CalDAVPrincipal case 'DAV::owner': // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner - $reply->DAVElement( $prop, 'owner', $reply->href( $this->principal_url ) ); + $reply->DAVElement( $prop, 'owner', $reply->href( $this->url ) ); break; // Empty tag responses. diff --git a/inc/DAVResource.php b/inc/DAVResource.php index e116b606..3addb3cd 100644 --- a/inc/DAVResource.php +++ b/inc/DAVResource.php @@ -527,20 +527,19 @@ EOSQL; */ function FetchPrincipal() { if ( isset($this->principal) ) return; - $this->principal = new CalDAVPrincipal( array( "path" => $this->bound_from() ) ); + $this->principal = new DAVPrincipal( array( "path" => $this->bound_from() ) ); if ( $this->_is_principal ) { $this->exists = $this->principal->Exists(); - $this->collection->dav_name = $this->dav_name; + $this->collection->dav_name = $this->dav_name(); $this->collection->type = 'principal'; if ( $this->exists ) { + $this->collection = $this->principal->AsCollection(); $this->displayname = $this->principal->GetProperty('displayname'); - $this->unique_tag = '"'.$this->principal->dav_etag.'"'; + $this->user_no = $this->principal->user_no(); + $this->resource_id = $this->principal->principal_id(); $this->created = $this->principal->created; $this->modified = $this->principal->modified; - $this->resourcetypes = ''; - $this->resource_id = $this->principal->principal_id; - $this->collection = $this->principal->AsCollection(); - $this->user_no = $this->principal->user_no; + $this->resourcetypes = $this->principal->resourcetypes; } } } @@ -618,20 +617,20 @@ EOQRY; if ( $this->dav_name == '/' || $this->dav_name == '' ) { $this->privileges = (1 | 16 | 32); // read + read-acl + read-current-user-privilege-set - dbg_error_log( 'DAVResource', 'Read permissions for user accessing /' ); + dbg_error_log( 'DAVResource', ':FetchPrivileges: Read permissions for user accessing /' ); return; } if ( $session->AllowedTo('Admin') ) { $this->privileges = privilege_to_bits('all'); - dbg_error_log( 'DAVResource', 'Full permissions for an administrator.' ); + dbg_error_log( 'DAVResource', ':FetchPrivileges: Full permissions for an administrator.' ); return; } if ( $this->IsPrincipal() ) { if ( !isset($this->principal) ) $this->FetchPrincipal(); $this->privileges = $this->principal->Privileges(); - dbg_error_log( 'DAVResource', 'Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username() ); + dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username() ); return; } @@ -648,12 +647,12 @@ EOQRY; $this->privileges = $this->collection->path_privs; if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges ); - dbg_error_log( 'DAVResource', 'Privileges of "%s" for user "%s" accessing "%s"', + dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user "%s" accessing "%s"', decbin($this->privileges), $session->username, $this->dav_name() ); if ( isset($request->ticket) && $request->ticket->MatchesPath($this->bound_from()) ) { $this->privileges |= $request->ticket->privileges(); - dbg_error_log( 'DAVResource', 'Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) ); + dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) ); } if ( isset($this->tickets) ) { @@ -661,7 +660,7 @@ EOQRY; foreach( $this->tickets AS $k => $ticket ) { if ( $ticket->MatchesResource($this->resource_id()) || $ticket->MatchesPath($this->bound_from()) ) { $this->privileges |= $ticket->privileges(); - dbg_error_log( 'DAVResource', 'Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) ); + dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) ); } } } @@ -1145,7 +1144,10 @@ EOQRY; */ function unique_tag() { if ( isset($this->unique_tag) ) return $this->unique_tag; - if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal(); + if ( $this->IsPrincipal() && !isset($this->principal) ) { + $this->FetchPrincipal(); + $this->unique_tag = $this->principal->unique_tag(); + } else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource(); if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = ''; @@ -1245,7 +1247,8 @@ EOQRY; $acl[] = $this->BuildACE($xmldoc, pow(2,25) - 1, new XMLElement('property', new XMLElement('owner')) ); $qry = new AwlQuery('SELECT dav_principal.dav_name, grants.* FROM grants JOIN dav_principal ON (to_principal=principal_id) WHERE by_collection = :collection_id OR by_principal = :principal_id ORDER BY by_collection', - array( ':collection_id' => $this->collection->collection_id, ':principal_id' => $this->principal->principal_id ) ); + array( ':collection_id' => $this->collection->collection_id, + ':principal_id' => $this->principal->principal_id() ) ); if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) { $by_collection = null; while( $grant = $qry->Fetch() ) { @@ -1276,7 +1279,12 @@ EOQRY; return $this->collection->collection_id; break; - case 'resourcetype': + case 'principal_id': + if ( !isset($this->principal) ) $this->FetchPrincipal(); + return $this->principal->principal_id(); + break; + + case 'resourcetype': if ( isset($this->resourcetypes) ) { $this->resourcetypes = preg_replace('{^\s*<(.*)/>\s*$}', '$1', $this->resourcetypes); $type_list = preg_split('{(/>\s*<|\n)}', $this->resourcetypes); @@ -1554,7 +1562,7 @@ EOQRY; break; case 'DAV::current-user-principal': - $prop->NewElement('current-user-principal', $reply->href( $request->principal->principal_url ) ); + $prop->NewElement('current-user-principal', $reply->href( $request->principal->url() ) ); break; case 'SOME-DENIED-PROPERTY': /** @todo indicating the style for future expansion */ diff --git a/inc/HTTPAuthSession.php b/inc/HTTPAuthSession.php index 52100659..5350c71e 100644 --- a/inc/HTTPAuthSession.php +++ b/inc/HTTPAuthSession.php @@ -128,23 +128,21 @@ class HTTPAuthSession { * Fall through to the normal PHP authentication variables. */ if ( isset($_SERVER['PHP_AUTH_USER']) ) { - if ( $u = $this->CheckPassword( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { + if ( $p = $this->CheckPassword( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { /** * Maybe some external authentication didn't return false for an inactive * user, so we'll be pedantic here. */ - if ( $u->active ) { - $this->AssignSessionDetails($u); + if ( $p->user_active ) { + $this->AssignSessionDetails($p); return; } } } if ( isset($c->allow_unauthenticated) && $c->allow_unauthenticated ) { - $this->user_no = -1; - $this->username = 'guest'; - $this->fullname = 'Unauthenticated User'; - $this->email = 'invalid'; + $this->AssignSessionDetails('unauthenticated'); + $this->logged_in = false; return; } @@ -247,10 +245,10 @@ class HTTPAuthSession { } } - if ( $usr = getUserByName($username) ) { - dbg_error_log( "BasicAuth", ":CheckPassword: Name:%s, Pass:%s, File:%s, Active:%s", $username, $password, $usr->password, ($usr->active?'Yes':'No') ); - if ( $usr->active && session_validate_password( $password, $usr->password ) ) { - return $usr; + if ( $principal = new Principal('username', $username) ) { + dbg_error_log( "BasicAuth", ":CheckPassword: Name:%s, Pass:%s, File:%s, Active:%s", $username, $password, $principal->password, ($principal->user_active?'Yes':'No') ); + if ( $principal->user_active && session_validate_password( $password, $principal->password ) ) { + return $principal; } } return false; @@ -288,20 +286,26 @@ class HTTPAuthSession { * Internal function used to assign the session details to a user's new session. * @param object $u The user+session object we (probably) read from the database. */ - function AssignSessionDetails( $u ) { - if ( !isset($u->principal_id) ) { - // If they don't have a principal_id set then we should re-read from our local database - $qry = new AwlQuery('SELECT * FROM dav_principal WHERE username = :username', array(':username' => $u->username) ); - if ( $qry->Exec() && $qry->rows() == 1 ) { - $u = $qry->Fetch(); - } + function AssignSessionDetails( $principal ) { + if ( is_string($principal) ) $principal = new Principal('username',$principal); + if ( get_class($principal) != 'Principal' ) { + $principal = new Principal('username',$principal->username); } // Assign each field in the selected record to the object - foreach( $u AS $k => $v ) { + foreach( $principal AS $k => $v ) { $this->{$k} = $v; } - + if ( !get_class($principal) == 'Principal' ) { + throw new Exception('HTTPAuthSession::AssignSessionDetails could not find a Principal object'); + } + $this->username = $principal->username(); + $this->user_no = $principal->user_no(); + $this->principal_id = $principal->principal_id(); + $this->email = $principal->email(); + $this->dav_name = $principal->dav_name(); + $this->principal = $principal; + $this->GetRoles(); $this->logged_in = true; if ( function_exists("awl_set_locale") && isset($this->locale) && $this->locale != "" ) { diff --git a/inc/Principal.php b/inc/Principal.php new file mode 100644 index 00000000..36888a55 --- /dev/null +++ b/inc/Principal.php @@ -0,0 +1,465 @@ + +* @copyright Morphoss Ltd +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later +*/ + +require_once('AwlCache.php'); + +/** +* A class for things to do with a Principal +* +* @package davical +*/ +class Principal { + + /** + * Some control over our DB + * @var unknown_type + */ + private static $db_tablename = 'dav_principal'; + private static $db_mandatory_fields = array( + 'username', + ); + public static function updateableFields() { + return array( + 'username', 'email', 'user_active', 'modified', 'password', 'fullname', + 'email_ok', 'date_format_type', 'locale', 'type_id', 'displayname', 'default_privileges' + ); + } + + /** + * We cache these so if we try and access a row by principal_id/user_no/e_mail that we've + * already read we don't read it again. + * @var unknown_type + */ + private static $byUserno = array(); + private static $byId = array(); + private static $byEmail = array(); + + /** + * Columns from the database + */ + protected $username; + protected $user_no; + protected $principal_id; + protected $email; + protected $dav_name; + public $user_active; + public $created; + public $modified; + public $password; + public $fullname; + public $email_ok; + public $date_format_type; + public $locale; + public $type_id; + public $displayname; + public $default_privileges; + public $is_principal; + public $is_calendar; + public $collection_id; + public $is_addressbook; + public $resourcetypes; + public $privileges; + + /** + * Whether this Principal actually exists in the database yet. + * @var boolean + */ + protected $exists; + + /** + * @var The home URL of the principal + */ + protected $url; + + /** + * @var The actual requested URL for this principal, when the request was for /principals/... or such + */ + protected $original_request_url; + + /** + * Whether this was retrieved using an e-mail address + * @var boolean + */ + protected $by_email; + + /** + * If we're using memcached these is the namespace we'll put stuff in + * @var unknown_type + */ + private $cacheNs; + private $cacheKey; + + function __construct( $type, $value, $use_cache=true ) { + global $c, $session; + + $this->exists = false; + $this->by_email = false; + $this->original_request_url = null; + switch( $type ) { + case 'path': + $type = 'username'; + $value = $this->usernameFromPath($value); + break; + case 'dav_name': + $type = 'username'; + $value = substr($value, 1, -1); + break; + } + + $cache = new AwlCache(); + if ( $use_cache && isset($session->principal_id) ) { + switch ( $type ) { + case 'user_no': + $this->user_no = $value; + if ( isset(self::$byUserno[$value]) ) { + $type = 'username'; + $value = self::$byUserno[$value]; + } + break; + case 'principal_id': + $this->principal_id = $value; + if ( isset(self::$byId[$value]) ) { + $type = 'username'; + $value = self::$byId[$value]; + } + break; + case 'email': + $this->by_email = true; + $this->email = $value; + if ( isset(self::$byEmail[$value]) ) { + $type = 'username'; + $value = self::$byEmail[$value]; + } + break; + case 'username': + break; + default: + throw new Exception('Can only retrieve a Principal by user_no,principal_id,username or email address'); + } + + if ( $type == 'username' ) { + $this->username = $value; + $this->dav_name = '/'.$value.'/'; + $this->url = ConstructURL( $this->dav_name, true ); + $this->cacheNs = 'principal-/'.$value.'/'; + $this->cacheKey = 'p-'.$session->principal_id; + $row = $cache->get('principal-/'.$value.'/', 'p-'.$session->principal_id ); + if ( $row !== false ) { + self::$byId[$row->principal_id] = $row->username; + self::$byUserno[$row->user_no] = $row->username; + self::$byEmail[$row->email] = $row->username; + $this->assignRowValues($row); + return $this; + } + } + } + + $sql = 'SELECT *, '; + if ( isset($session->principal_id) ) { + $sql .= 'pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS privileges '; + $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); + } + else { + $sql .= '0::BIT(24) AS privileges '; + $params = array( ); + } + $sql .= 'FROM dav_principal WHERE '; + switch ( $type ) { + case 'username': + $sql .= 'lower(username)=lower(:param)'; + break; + case 'user_no': + $sql .= 'user_no=:param'; + break; + case 'principal_id': + $sql .= 'principal_id=:param'; + break; + case 'email': + $this->by_email = true; + $sql .= 'lower(email)=lower(:param)'; + break; + } + $params[':param'] = $value; + + $qry = new AwlQuery( $sql, $params ); + if ( $qry->Exec('Principal',__LINE__,__FILE__) && $qry->rows() == 1 && $row = $qry->Fetch() ) { + $this->exists = true; + if ( isset($session->principal_id) ) { + self::$byId[$row->principal_id] = $row->username; + self::$byUserno[$row->user_no] = $row->username; + self::$byEmail[$row->email] = $row->username; + if ( !isset($this->cacheNs) ) { + $this->cacheNs = 'principal-'.$row->dav_name; + $this->cacheKey = 'p-'.$session->principal_id; + } + } + $this->assignRowValues($row); + $this->url = ConstructURL( $this->dav_name, true ); + $row = $cache->set($this->cacheNs, $this->cacheKey, $row, 864000 ); + return $this; + } + + if ( $type == 'username' && $value == 'unauthenticated' ) { + $this->assignGuestValues(); + } + } + + private function assignGuestValues() { + $this->user_no = -1; + $this->exists = false; + $this->username = translate('unauthenticated'); + $this->fullname = $this->displayname = translate('Unauthenticated User'); + $this->email = false; + $this->is_principal = true; + $this->is_calendar = false; + $this->principal_id = -1; + $this->dav_name = '/'; + $this->privileges = $this->default_privileges = 0; + } + + private function assignRowValues( $db_row ) { + foreach( $db_row AS $k => $v ) { + $this->{$k} = $v; + } + } + + public function Exists() { + return $this->exists; + } + + + public function byEmail() { + return $this->by_email; + } + + + /** + * Work out the username, based on elements of the path. + * @param string $path The path to be used. + * @param array $options The request options, controlling whether e-mail paths are allowed. + */ + private function usernameFromPath( $path ) { + global $session, $c; + + if ( $path == '/' || $path == '' ) { + dbg_error_log( 'Principal', 'No useful path split possible' ); + return $session->username; + } + + $path_split = explode('/', $path ); + @dbg_error_log( 'Principal', 'Path split into at least /// %s /// %s /// %s', $path_split[1], $path_split[2], $path_split[3] ); + + $username = $path_split[1]; + if ( $path_split[1] == 'principals' && isset($path_split[3]) ) { + $username = $path_split[3]; + $this->original_request_url = $path; + } + if ( substr($username,0,1) == '~' ) { + $username = substr($username,1); + $this->original_request_url = $path; + } + + if ( isset($c->allow_by_email) && $c->allow_by_email && preg_match( '#^(\S+@\S+[.]\S+)$#', $username) ) { + // This might seem inefficient, but we cache the result, so the second time will not read from the DB + $p = new Principal('email',$username); + $username = $p->username; + $this->by_email = true; + } + return $username; + } + + + /** + * Return the username + * @return string The username + */ + function username() { + return (isset($this->username)?$this->username:false); + } + + + /** + * Set the username - but only if the record does not yet exist! + * @return string The username + */ + function setUsername($new_username) { + if ( $this->exists ) return false; + $this->username = $new_username; + return $this->username; + } + + + /** + * Return the user_no + * @return int The user_no + */ + function user_no() { + return (isset($this->user_no)?$this->user_no:false); + } + + + /** + * Return the principal_id + * @return string The principal_id + */ + function principal_id() { + return (isset($this->principal_id)?$this->principal_id:false); + } + + + /** + * Return the email + * @return string The email + */ + function email() { + return (isset($this->email)?$this->email:false); + } + + + /** + * Return the partial path representing this principal + * @return string The dav_name + */ + function dav_name() { + if ( !isset($this->dav_name) ) { + if ( !isset($this->username) ) { + throw new Exception('Can\'t calculate dav_name for unknown username'); + } + $this->dav_name = '/'.$this->username.'/'; + } + return $this->dav_name; + } + + + /** + * Return the URL for this principal + * @param string $type The type of URL we want (the principal, by default) + * @param boolean $internal Whether an internal reference is requested + * @return string The principal-URL + */ + public function url($type = 'principal', $internal=false ) { + if ( $internal ) + $result = $this->dav_name(); + else { + if ( isset($this->original_request_url) && $type == 'principal' ) + $result = $this->original_request_url; + else + $result = $this->url; + } + + switch( $type ) { + case 'schedule_inbox': $result .= '.in/'; break; + case 'schedule_outbox': $result .= '.out/'; break; + case 'dropbox': $result .= '.drop/'; break; + case 'notifications': $result .= '.notify/'; break; + } + return $result; + } + + + public function internal_url($type = 'principal' ) { + return $this->url($type,true); + } + + + public function unCache() { + if ( !isset($this->cacheNs) ) return; + $cache = new AwlCache(); + $cache->delete($this->cacheNs, null ); + } + + + private function Write( $field_values, $inserting=true ) { + if ( is_array($field_values) ) $field_values = (object) $field_values; + + if ( !isset($field_values->{'user_active'}) && isset($field_values->{'active'}) ) + $field_values->{'user_active'} = $field_values->{'active'}; + if ( !isset($field_values->{'modified'}) && isset($field_values->{'updated'}) ) + $field_values->{'modified'} = $field_values->{'updated'}; + + + $sql = ''; + if ( $inserting ) { + $insert_fields = array(); + $param_names = array(); + } + else { + $update_list = array(); + } + $sql_params = array(); + foreach( self::updateableFields() AS $k ) { + if ( !isset($field_values->{$k}) && !isset($this->{$k}) ) continue; + if ( $inserting ) { + $insert_fields[] = $k; + $param_names[] = ':'.$k; + } + else { + $update_list[] = $k.'=:'.$k; + } + $sql_params[':'.$k] = $field_values->{$k}; + } + + if ( $inserting ) { + foreach( $this->db_mandatory_fields AS $k ) { + if ( !isset($sql_params[':'.$k]) ) { + throw new Exception( get_class($this).'::Create: Mandatory field "'.$k.'" is not set.'); + } + } + $sql = 'INSERT INTO '.$this->db_tablename.' ('.implode(',',$insert_fields).') VALUES('.implode(',',$param_names).')'; + } + else { + $sql = 'UPDATE '.$this->db_tablename.' SET '.implode(',',$update_list); + $sql .= ' WHERE principal_id=:principal_id'; + $sql_params[':principal_id'] = $this->principal_id; + } + + $qry = new AwlQuery($sql, $sql_params); + if ( $qry->Exec('Principal',__FILE__,__LINE__) ) { + $this->unCache(); + $new_principal = new Principal('username', $sql_params[':username']); + foreach( $new_principal AS $k => $v ) { + $this->{$k} = $v; + } + } + } + + + public function Create( $field_values ) { + $this->Write($field_values, true); + } + + public function Update( $field_values ) { + if ( !$this->Exists() ) { + throw new Exception( get_class($this).'::Create: Attempting to update non-existent record.'); + } + $this->Write($field_values, false); + } + + static public function cacheFlush( $where, $whereparams=array() ) { + $cache = new AwlCache(); + if ( !$cache->isActive() ) return; + $qry = new AwlQuery('SELECT dav_name FROM dav_principal WHERE '.$where, $whereparams ); + if ( $qry->Exec('Principal',__FILE__,__LINE__) ) { + while( $row = $qry->Fetch() ) { + $cache->delete('principal-'.$row->dav_name, null); + } + } + } + + static public function cacheDelete( $type, $value ) { + $cache = new AwlCache(); + if ( !$cache->isActive() ) return; + if ( $type == 'username' ) { + $value = '/'.$value.'/'; + } + $cache->delete('principal-'.$value, null); + } +} diff --git a/inc/PublicSession.php b/inc/PublicSession.php index 36bf6302..560ad9aa 100644 --- a/inc/PublicSession.php +++ b/inc/PublicSession.php @@ -56,11 +56,25 @@ class PublicSession { function PublicSession() { global $c; - $this->user_no = -1; - $this->principal_id = -1; - $this->email = null; - $this->username = 'guest'; - $this->fullname = 'Anonymous'; + $principal = new Principal('username','unauthenticated'); + + // Assign each field in the selected record to the object + foreach( $principal AS $k => $v ) { + $this->{$k} = $v; + } + + $this->username = $principal->username(); + $this->user_no = $principal->user_no(); + $this->principal_id = $principal->principal_id(); + $this->email = $principal->email(); + $this->dav_name = $principal->dav_name(); + $this->principal = $principal; + + if ( function_exists("awl_set_locale") && isset($this->locale) && $this->locale != "" ) { + awl_set_locale($this->locale); + } + + $this->groups = ( isset($c->public_groups) ? $c->public_groups : array() ); $this->roles = array( 'Public' => true ); $this->logged_in = false; diff --git a/inc/always.php.in b/inc/always.php.in index bb0c9d73..2f41cfe7 100644 --- a/inc/always.php.in +++ b/inc/always.php.in @@ -37,7 +37,8 @@ $c->collections_always_exist = false; $c->allow_get_email_visibility = false; $c->permission_scan_depth = 2; $c->expand_pdo_parameters = true; -$c->home_calendar_name = 'home'; +$c->home_calendar_name = 'calendar'; +$c->home_addressbook_name = 'addresses'; $c->enable_row_linking = true; $c->enable_scheduling = false; $c->http_auth_mode = 'Basic'; @@ -168,8 +169,8 @@ init_gettext( 'davical', $c->locale_path ); * */ $c->code_version = 0; -$c->want_awl_version = 0.46; // The actual version # is replaced into that during the build /release process -$c->version_string = '0.9.8.3'; // The actual version # is replaced into that during the build /release process +$c->want_awl_version = 0.46; +$c->version_string = '0.9.9.4'; // The actual version # is replaced into that during the build /release process if ( isset($c->version_string) && preg_match( '/(\d+)\.(\d+)\.(\d+)(.*)/', $c->version_string, $matches) ) { $c->code_major = $matches[1]; $c->code_minor = $matches[2]; @@ -186,7 +187,7 @@ $_SERVER['SERVER_NAME'] = $c->domain_name; require_once('AwlQuery.php'); -$c->want_dbversion = array(1,2,8); +$c->want_dbversion = array(1,2,9); $c->schema_version = 0; $qry = new AwlQuery( 'SELECT schema_major, schema_minor, schema_patch FROM awl_db_revision ORDER BY schema_id DESC LIMIT 1;' ); if ( $qry->Exec('always',__LINE__,__FILE__) && $row = $qry->Fetch() ) { @@ -202,92 +203,7 @@ if ( $qry->Exec('always',__LINE__,__FILE__) && $row = $qry->Fetch() ) { if ( isset($_SERVER['HTTP_X_DAVICAL_TESTCASE']) ) $qry->QDo('SET TIMEZONE TO \'Pacific/Auckland\''); } - -$_known_users_name = array(); -$_known_users_id = array(); -$_known_users_pid = array(); -$_known_users_email = array(); - -function _davical_get_principal_query_cached( $where, $parameter ) { - global $c, $session, $_known_users_name, $_known_users_id, $_known_users_pid; - - $sql = 'SELECT *, to_char(updated at time zone \'GMT\',\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS modified, principal.*, '; - if ( isset($session->principal_id) ) { - $sql .= 'pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) AS privileges '; - $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); - } - else { - $sql .= '0::BIT(24) AS privileges '; - $params = array( ); - } - $sql .= 'FROM usr LEFT JOIN principal USING(user_no) WHERE '. $where; - $params[':param'] = $parameter; - - $qry = new AwlQuery( $sql, $params ); - if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows() == 1 && $row = $qry->Fetch() ) { - if ( isset($session->principal_id) ) { - $_known_users_name[$row->username] = $row; - $_known_users_id[$row->user_no] = $row; - $_known_users_pid[$row->principal_id] = $row; - $_known_users_email[$row->email] = $row; - } - return $row; - } - - return false; -} - -/** -* Return a user record identified by a username, caching it for any subsequent lookup -* @param string $username The username of the record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getUserByName( $username, $use_cache = true ) { - global $_known_users_name; - - if ( $use_cache && isset( $_known_users_name[$username] ) ) return $_known_users_name[$username]; - return _davical_get_principal_query_cached( 'lower(username) = lower(:param)', $username ); -} - - -/** -* Return a user record identified by e-mail address, caching it for any subsequent lookup -* @param string $email The email address of the user record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getUserByEMail( $email, $use_cache = true ) { - global $_known_users_name; - - if ( $use_cache && isset( $_known_users_email[$email] ) ) return $_known_users_email[$email]; - return _davical_get_principal_query_cached( 'lower(email) = lower(:param)', $email ); -} - - -/** -* Return a user record identified by a user_no, caching it for any subsequent lookup -* @param int $user_no The ID of the record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getUserByID( $user_no, $use_cache = true ) { - global $c, $session, $_known_users_id; - - if ( $use_cache && isset( $_known_users_id[$user_no] ) ) return $_known_users_id[$user_no]; - return _davical_get_principal_query_cached( 'user_no = :param', $user_no ); -} - - -/** -* Return a user record identified by a user_no, caching it for any subsequent lookup -* @param int $user_no The ID of the record to retrieve -* @param boolean $use_cache Whether or not to use the cache (default: yes) -*/ -function getPrincipalByID( $principal_id, $use_cache = true ) { - global $c, $session, $_known_users_pid; - - if ( $use_cache && isset( $_known_users_pid[$principal_id] ) ) return $_known_users_pid[$principal_id]; - return _davical_get_principal_query_cached( 'principal_id = :param', $principal_id ); -} - +require_once('Principal.php'); /** * Return the HTTP status code description for a given code. Hopefully diff --git a/inc/auth-functions.php b/inc/auth-functions.php index cd090051..4715e58d 100644 --- a/inc/auth-functions.php +++ b/inc/auth-functions.php @@ -27,59 +27,125 @@ require_once("DataUpdate.php"); +if ( !function_exists('auth_functions_deprecated') ) { + function auth_functions_deprecated( $method, $message = null ) { + global $c; + if ( isset($c->dbg['ALL']) || isset($c->dbg['deprecated']) ) { + $stack = debug_backtrace(); + array_shift($stack); + if ( preg_match( '{/inc/auth-functions.php$}', $stack[0]['file'] ) && $stack[0]['line'] > __LINE__ ) return; + dbg_error_log("LOG", " auth-functions: Call to deprecated routine '%s'%s", $method, (isset($message)?': '.$message:'') ); + foreach( $stack AS $k => $v ) { + dbg_error_log( 'LOG', ' auth-functions: Deprecated call from line %4d of %s', $v['line'], $v['file']); + } + } + } +} + + +function getUserByName( $username, $use_cache=true ) { + auth_functions_deprecated('getUserByName','replaced by Principal class'); + return new Principal('username', $username, $use_cache); +} + +function getUserByEMail( $email, $use_cache = true ) { + auth_functions_deprecated('getUserByEMail','replaced by Principal class'); + return new Principal('email', $email, $use_cache); +} + +function getUserByID( $user_no, $use_cache = true ) { + auth_functions_deprecated('getUserByID','replaced by Principal class'); + return new Principal('user_no', $user_no, $use_cache); +} + +function getPrincipalByID( $principal_id, $use_cache = true ) { + auth_functions_deprecated('getPrincipalByID','replaced by Principal class'); + return new Principal('principal_id', $principal_id, $use_cache); +} + /** -* Create a default home calendar for the user. +* Creates some default home collections for the user. * @param string $username The username of the user we are creating relationships for. */ -function CreateHomeCalendar( $username ) { +function CreateHomeCollections( $username ) { global $session, $c; if ( ! isset($c->home_calendar_name) || strlen($c->home_calendar_name) == 0 ) return true; - $usr = getUserByName( $username ); - $parent_path = "/".$username."/"; - $calendar_path = $parent_path . $c->home_calendar_name."/"; - $dav_etag = md5($usr->user_no . $calendar_path); - $qry = new AwlQuery( 'SELECT 1 FROM collection WHERE dav_name = :dav_name', array( ':dav_name' => $calendar_path) ); - if ( $qry->Exec() ) { - if ( $qry->rows() > 0 ) { - $c->messages[] = i18n("Home calendar already exists."); - return true; + $principal = new Principal('username',$username); + $params = array( ':collection_path' => $principal->dav_name().$c->home_calendar_name.'/' ); + $qry = new AwlQuery( 'SELECT 1 FROM collection WHERE dav_name = :collection_path', $params ); + if ( !$qry->Exec() ) { + $c->messages[] = i18n("There was an error reading from the database."); + return false; + } + if ( $qry->rows() > 0 ) { + $c->messages[] = i18n("Home calendar already exists."); + return true; + } + else { + $sql = 'INSERT INTO collection (user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, created, modified, resourcetypes) '; + $sql .= 'VALUES( :user_no, :parent_container, :collection_path, :dav_etag, :displayname, true, current_timestamp, current_timestamp, :resourcetypes );'; + $params = array( + ':user_no' => $principal->user_no(), + ':parent_container' => $principal->dav_name(), + ':collection_path' => $principal->dav_name().$c->home_calendar_name.'/', + ':dav_etag' => '-1', + ':displayname' => $principal->fullname, + ':resourcetypes' => '' + ); + $qry = new AwlQuery( $sql, $params ); + if ( $qry->Exec() ) { + $c->messages[] = i18n("Home calendar added."); + dbg_error_log("User",":Write: Created user's home calendar at '%s'", $params[':collection_path'] ); + } + else { + $c->messages[] = i18n("There was an error writing to the database."); + return false; } } - else { - $c->messages[] = i18n("There was an error writing to the database."); - return false; - } - $sql = 'INSERT INTO collection (user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, created, modified, resourcetypes) '; - $sql .= 'VALUES( :user_no, :parent_container, :calendar_path, :dav_etag, :displayname, true, current_timestamp, current_timestamp, :resourcetypes );'; - $params = array( - ':user_no' => $usr->user_no, - ':parent_container' => $parent_path, - ':calendar_path' => $calendar_path, - ':dav_etag' => $dav_etag, - ':displayname' => $usr->fullname, - ':resourcetypes' => '' - ); - $qry = new AwlQuery( $sql, $params ); - if ( $qry->Exec() ) { - $c->messages[] = i18n("Home calendar added."); - dbg_error_log("User",":Write: Created user's home calendar at '%s'", $calendar_path ); - } - else { - $c->messages[] = i18n("There was an error writing to the database."); - return false; + if ( !isset($c->home_addressbook_name) ) { + $qry = new AwlQuery( 'SELECT 1 FROM collection WHERE dav_name = :dav_name', array( ':dav_name' => $principal->dav_name().$c->home_addressbook_name.'/') ); + if ( !$qry->Exec() ) { + $c->messages[] = i18n("There was an error reading from the database."); + return false; + } + if ( $qry->rows() > 0 ) { + $c->messages[] = i18n("Home addressbook already exists."); + return true; + } + else { + $params[':collection_path'] = $principal->dav_name().$c->home_addressbook_name.'/'; + $qry = new AwlQuery( $sql, $params ); + if ( $qry->Exec() ) { + $c->messages[] = i18n("Home addressbook added."); + dbg_error_log("User",":Write: Created user's home addressbook at '%s'", $params[':collection_path'] ); + } + else { + $c->messages[] = i18n("There was an error writing to the database."); + return false; + } + } } return true; } +/** + * Backward compatibility + * @param unknown_type $username + */ +function CreateHomeCalendar($username) { + auth_functions_deprecated('CreateHomeCalendar','renamed to CreateHomeCollections'); + return CreateHomeCollections($username); +} /** * Defunct function for creating default relationships. * @param string $username The username of the user we are creating relationships for. */ function CreateDefaultRelationships( $username ) { + auth_functions_deprecated('CreateDefaultRelationships','No longer applicable.'); return true; } @@ -90,6 +156,8 @@ function CreateDefaultRelationships( $username ) { */ function UpdateUserFromExternal( &$usr ) { global $c; + + auth_functions_deprecated('UpdateUserFromExternal','refactor to use the "Principal" class'); /** * When we're doing the create we will usually need to generate a user number */ @@ -201,19 +269,22 @@ EOERRMSG; if ( $qry->Exec('Login',__LINE__,__FILE__) && $qry->rows() == 1 ) { $usr = $qry->Fetch(); if ( session_validate_password( $password, $usr->password ) ) { - UpdateUserFromExternal($usr); + $principal = new Principal($username); + if ( $principal->Exists() ) { + if ( $principal->modified <= $usr->updated ) + $principal->Update($usr); + } + else { + $principal->Create($usr); + CreateHomeCollections($username); + } /** * We disallow login by inactive users _after_ we have updated the local copy */ if ( isset($usr->active) && $usr->active == 'f' ) return false; - $qry = new AwlQuery('SELECT * FROM dav_principal WHERE username = :username', array(':username' => $usr->username) ); - if ( $qry->Exec() && $qry->rows() == 1 ) { - $principal = $qry->Fetch(); - return $principal; - } - return $usr; // Somewhat optimistically + return $principal; } } diff --git a/inc/caldav-ACL.php b/inc/caldav-ACL.php index 06d78a92..b57a0fd9 100644 --- a/inc/caldav-ACL.php +++ b/inc/caldav-ACL.php @@ -94,20 +94,23 @@ $aces = $xmltree->GetPath("/DAV::acl/*"); $grantor = new DAVResource($request->path); if ( ! $grantor->Exists() ) $request->DoResponse( 404 ); -$by_principal = null; -$by_collection = null; -if ( $grantor->IsPrincipal() ) $by_principal = $grantor->GetProperty('principal_id'); -else if ( $grantor->IsCollection() ) $by_collection = $grantor->GetProperty('collection_id'); -else $request->PreconditionFailed(403,'not-supported-privilege','ACLs may only be applied to Principals or Collections'); +if ( ! $grantor->IsCollection() ) + $request->PreconditionFailed(403,'not-supported-privilege','ACLs are only supported on Principals or Collections'); +$grantor->NeedPrivilege('write-acl'); + +$cache_delete_list = array(); + $qry = new AwlQuery('BEGIN'); $qry->Exec('ACL',__LINE__,__FILE__); -foreach( $aces AS $k => $ace ) { +function process_ace( $grantor, $by_principal, $by_collection, $ace ) { + global $cache_delete_list; + $elements = $ace->GetContent(); - $principal = $elements[0]; + $principal_node = $elements[0]; $grant = $elements[1]; - if ( $principal->GetTag() != 'DAV::principal' ) $request->MalformedRequest('ACL request must contain a principal, not '.$principal->GetTag()); + if ( $principal_node->GetTag() != 'DAV::principal' ) $request->MalformedRequest('ACL request must contain a principal, not '.$principal->GetTag()); $grant_tag = $grant->GetTag(); if ( $grant_tag == 'DAV::deny' ) $request->PreconditionFailed(403,'grant-only'); if ( $grant_tag == 'DAV::invert' ) $request->PreconditionFailed(403,'no-invert'); @@ -120,7 +123,7 @@ foreach( $aces AS $k => $ace ) { } $privileges = privilege_to_bits($privilege_names); - $principal_content = $principal->GetContent(); + $principal_content = $principal_node->GetContent(); if ( count($principal_content) != 1 ) $request->MalformedRequest('ACL request must contain exactly one principal per ACE'); $principal_content = $principal_content[0]; switch( $principal_content->GetTag() ) { @@ -139,10 +142,11 @@ foreach( $aces AS $k => $ace ) { case 'DAV::href': $principal_type = 'href'; - $principal = new DAVResource( DeconstructURL($principal_content->GetContent()) ); - if ( ! $principal->Exists() || !$principal->IsPrincipal() ) + $grantee = new DAVResource( DeconstructURL($principal_content->GetContent()) ); + $grantee_id = $grantee->getProperty('principal_id'); + if ( !$grantee->Exists() || !$grantee->IsPrincipal() ) $request->PreconditionFailed(403,'recognized-principal', 'Principal "' + $principal_content->GetContent() + '" not found.'); - $sqlparms = array( ':to_principal' => $principal->GetProperty('principal_id') ); + $sqlparms = array( ':to_principal' => $grantee_id); $where = 'WHERE to_principal=:to_principal AND '; if ( isset($by_principal) ) { $sqlparms[':by_principal'] = $by_principal; @@ -163,7 +167,10 @@ foreach( $aces AS $k => $ace ) { } $sqlparms[':privileges'] = $privileges; $qry = new AwlQuery($sql, $sqlparms); - $qry->Exec('ACL',__LINE__,__FILE__); + if ( $qry->Exec('ACL',__LINE__,__FILE__) ) { + Principal::cacheDelete('dav_name',$grantee->dav_name()); + Principal::cacheFlush('principal_id IN (SELECT member_id FROM group_member WHERE group_id = '.$grantee_id); + } break; case 'DAV::authenticated': @@ -179,7 +186,12 @@ foreach( $aces AS $k => $ace ) { $sqlparms[':by_principal'] = $by_principal; } $qry = new AwlQuery($sql, $sqlparms); - $qry->Exec('ACL',__LINE__,__FILE__); + if ( $qry->Exec('ACL',__LINE__,__FILE__) ) { + /** + * Basically this has changed everyone's permissions now, so... + */ + Principal::cacheFlush('TRUE'); + } break; case 'DAV::all': @@ -194,6 +206,13 @@ foreach( $aces AS $k => $ace ) { } +$by_principal = ($grantor->IsPrincipal() ? $grantor->GetProperty('principal_id') : null); +$by_collection = ($grantor->IsPrincipal() ? null : $grantor->GetProperty('collection_id')); + +foreach( $aces AS $k => $ace ) { + process_ace($grantor, $by_principal, $by_collection, $ace); +} + $qry = new AwlQuery('COMMIT'); $qry->Exec('ACL',__LINE__,__FILE__); diff --git a/inc/caldav-OPTIONS.php b/inc/caldav-OPTIONS.php index 10f8da45..1cd3daaf 100644 --- a/inc/caldav-OPTIONS.php +++ b/inc/caldav-OPTIONS.php @@ -13,7 +13,12 @@ dbg_error_log("OPTIONS", "method handler"); include_once('DAVResource.php'); $resource = new DAVResource($request->path); -$resource->NeedPrivilege( 'DAV::read', true ); +/** + * The spec calls for this to be controlled by 'read' access, but we expand + * that a little to also allow read-current-user-privilege-set since we grant that + * more generally and Mozilla attempts this and gets upset... + */ +$resource->NeedPrivilege( array('DAV::read','DAV::read-current-user-privilege-set'), true ); if ( !$resource->Exists() ) { $request->DoResponse( 404, translate("No collection found at that location.") ); diff --git a/inc/caldav-PUT-functions.php b/inc/caldav-PUT-functions.php index 70eba6d5..732cc2fc 100644 --- a/inc/caldav-PUT-functions.php +++ b/inc/caldav-PUT-functions.php @@ -184,7 +184,7 @@ function handle_schedule_request( $ical ) { foreach( $attendees AS $k => $attendee ) { $attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() ); - if ( $attendee_email == $request->principal->email ) { + if ( $attendee_email == $request->principal->email() ) { dbg_error_log( "POST", "not delivering to owner" ); continue; } @@ -195,18 +195,18 @@ function handle_schedule_request( $ical ) { dbg_error_log( "POST", "Delivering to %s", $attendee_email ); - $attendee_principal = new CalDAVPrincipal ( array ('email'=>$attendee_email, 'options'=> array ( 'allow_by_email' => true ) ) ); + $attendee_principal = new DAVPrincipal ( array ('email'=>$attendee_email, 'options'=> array ( 'allow_by_email' => true ) ) ); if ( $attendee_principal == false ){ $attendee->SetParameterValue ('SCHEDULE-STATUS','3.7;Invalid Calendar User'); continue; } - $deliver_path = preg_replace ( '/^.*caldav.php/','', $attendee_principal->schedule_inbox_url ); + $deliver_path = $attendee_principal->internal_url('schedule_inbox'); $ar = new DAVResource($deliver_path); $priv = $ar->HavePrivilegeTo('schedule-deliver-invite' ); if ( ! $ar->HavePrivilegeTo('schedule-deliver-invite' ) ){ $reply = new XMLDocument( array('DAV:' => '') ); - $privnodes = array( $reply->href(ConstructURL($attendee_principal->schedule_inbox_url)), new XMLElement( 'privilege' ) ); + $privnodes = array( $reply->href($attendee_principal->url('schedule_inbox')), new XMLElement( 'privilege' ) ); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement( $privnodes[1], 'schedule-deliver-invite' ); $xml = new XMLElement( 'need-privileges', new XMLElement( 'resource', $privnodes) ); @@ -222,10 +222,10 @@ function handle_schedule_request( $ical ) { $ncal->AddComponent ( array_merge ( $ical->GetComponents('VEVENT',false) , array ($ic) )); $content = $ncal->Render(); $cid = $ar->GetProperty('collection_id'); - dbg_error_log('DELIVER', 'to user: %s, to path: %s, collection: %s, from user: %s, caldata %s', $attendee_principal->user_no, $deliver_path, $cid, $request->user_no, $content ); - write_resource( $attendee_principal->user_no, $deliver_path . $etag . '.ics' , - $content , $ar->GetProperty('collection_id'), $request->user_no, - md5($content), $ncal, $put_action_type='INSERT', $caldav_context=true, $log_action=true, $etag ); + dbg_error_log('DELIVER', 'to user: %s, to path: %s, collection: %s, from user: %s, caldata %s', $attendee_principal->user_no(), $deliver_path, $cid, $request->user_no, $content ); + write_resource( $attendee_principal->user_no(), $deliver_path . $etag . '.ics' , + $content , $ar->GetProperty('collection_id'), $request->user_no, + md5($content), $ncal, $put_action_type='INSERT', $caldav_context=true, $log_action=true, $etag ); $attendee->SetParameterValue ('SCHEDULE-STATUS','1.2;Scheduling message has been delivered'); } // don't write an entry in the out box, ical doesn't delete it or ever read it again @@ -234,7 +234,7 @@ function handle_schedule_request( $ical ) { $ncal->AddProperty ( 'METHOD', 'REQUEST' ); $ncal->AddComponent ( array_merge ( $ical->GetComponents('VEVENT',false) , array ($ic) )); $content = $ncal->Render(); - $deliver_path = preg_replace ( '/^.*caldav.php/','', $request->principal->schedule_inbox_url ); + $deliver_path = $request->principal->internal_url('schedule_inbox'); $ar = new DAVResource($deliver_path); write_resource( $request->user_no, $deliver_path . $etag . '.ics' , $content , $ar->GetProperty('collection_id'), $request->user_no, @@ -272,8 +272,8 @@ function handle_schedule_reply ( $ical ) { foreach( $attendees AS $k => $attendee ) { $attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() ); dbg_error_log( "POST", "Delivering to %s", $attendee_email ); - $attendee_principal = new CalDAVPrincipal ( array ('email'=>$attendee_email, 'options'=> array ( 'allow_by_email' => true ) ) ); - $deliver_path = preg_replace ( '/^.*caldav.php/','', $attendee_principal->schedule_inbox_url ); + $attendee_principal = new DAVPrincipal ( array ('email'=>$attendee_email, 'options'=> array ( 'allow_by_email' => true ) ) ); + $deliver_path = $attendee_principal->internal_url('schedule_inbox'); $attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() ); if ( $attendee_email == $request->principal->email ) { dbg_error_log( "POST", "not delivering to owner" ); @@ -282,7 +282,7 @@ function handle_schedule_reply ( $ical ) { $ar = new DAVResource($deliver_path); if ( ! $ar->HavePrivilegeTo('schedule-deliver-reply' ) ){ $reply = new XMLDocument( array('DAV:' => '') ); - $privnodes = array( $reply->href(ConstructURL($attendee_principal->schedule_inbox_url)), new XMLElement( 'privilege' ) ); + $privnodes = array( $reply->href($attendee_principal->url('schedule_inbox')), new XMLElement( 'privilege' ) ); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement( $privnodes[1], 'schedule-deliver-reply' ); $xml = new XMLElement( 'need-privileges', new XMLElement( 'resource', $privnodes) ); @@ -296,7 +296,7 @@ function handle_schedule_reply ( $ical ) { $ncal->AddProperty ( 'METHOD', 'REPLY' ); $ncal->AddComponent ( array_merge ( $ical->GetComponents('VEVENT',false) , array ($ic) )); $content = $ncal->Render(); - write_resource( $attendee_principal->user_no, $deliver_path . $etag . '.ics' , + write_resource( $attendee_principal->user_no(), $deliver_path . $etag . '.ics' , $content , $ar->GetProperty('collection_id'), $request->user_no, md5($content), $ncal, $put_action_type='INSERT', $caldav_context=true, $log_action=true, $etag ); } @@ -314,9 +314,9 @@ function handle_schedule_reply ( $ical ) { */ function write_scheduling_request( &$resource, $attendee_value, $create_resource ) { $email = preg_replace( '/^mailto:/', '', $attendee_value ); - $schedule_target = getUserByEmail($email); - if ( isset($schedule_target) && is_object($schedule_target) ) { - $attendee_inbox = new WritableCollection(array('path' => $schedule_target->dav_name.'.in/')); + $schedule_target = new Principal('email',$email); + if ( $schedule_target->Exists() ) { + $attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox'))); if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) { $response = '3.8;'.translate('No authority to deliver invitations to user.'); } diff --git a/inc/drivers_imap_pam.php b/inc/drivers_imap_pam.php index 7c6daec8..c8b46dae 100644 --- a/inc/drivers_imap_pam.php +++ b/inc/drivers_imap_pam.php @@ -5,9 +5,10 @@ * @package davical * @category Technical * @subpackage ldap -* @author Oliver Schulze +* @author Oliver Schulze , +* Andrew McMillan * @copyright Based on Eric Seigne script drivers_squid_pam.php -* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ require_once("auth-functions.php"); @@ -53,54 +54,54 @@ class imapPamDrivers function IMAP_PAM_check($username, $password ){ global $c; - /** - * @todo Think of the children! This is a horribly insecure use of unvalidated user input! Probably it should be done with a popen or something, and it seems remarkably dodgy to expect that naively quoted strings will work in any way reliably. - * Meanwhile, I've quickly hacked something basic in place to improve the situation. No quotes/backslashes in passwords for YOU! - */ + $imap_username = $username; + if ( function_exists('mb_convert_encoding') ) { + $imap_username = mb_convert_encoding($imap_username, "UTF7-IMAP",mb_detect_encoding($imap_username)); + } + else { + $imap_username = imap_utf7_encode($imap_username); + } - $username_ori = $username; - $username = escapeshellcmd($username); - //$password = escapeshellcmd($password); - - //$imap_url = '{localhost:143/imap/notls}'; - //$imap_url = '{localhost:993/imap/ssl/novalidate-cert}'; - $imap_url = $c->authenticate_hook['config']['imap_url']; - $auth_result = "ERR"; - - $imap_stream = @imap_open($imap_url, $username, $password, OP_HALFOPEN); - //print_r(imap_errors()); - if ( $imap_stream ) { - // disconnect - imap_close($imap_stream); - // login ok - $auth_result = "OK"; - } + //$imap_url = '{localhost:143/imap/notls}'; + //$imap_url = '{localhost:993/imap/ssl/novalidate-cert}'; + $imap_url = $c->authenticate_hook['config']['imap_url']; + $auth_result = "ERR"; + + $imap_stream = @imap_open($imap_url, $imap_username, $password, OP_HALFOPEN); + //print_r(imap_errors()); + if ( $imap_stream ) { + // disconnect + imap_close($imap_stream); + // login ok + $auth_result = "OK"; + } if ( $auth_result == "OK") { - if ( $usr = getUserByName($username) ) { - return $usr; - } - else { - dbg_error_log( "PAM", "user %s doesn't exist in local DB, we need to create it",$username ); + $principal = new Principal('username',$username); + if ( ! $principal->Exists() ) { + dbg_error_log( "PAM", "Principal '%s' doesn't exist in local DB, we need to create it",$username ); $cmd = "getent passwd '$username'"; $getent_res = exec($cmd); - $getent_arr = explode(":", $getent_res); - $fullname = $getent_arr[4]; - if(empty($fullname)) { - $fullname = $username; - } - $usr = (object) array( - 'user_no' => 0, - 'username' => $username, - 'active' => 't', - 'email' => $username . "@" . $c->authenticate_hook['config']['email_base'], - 'updated' => date(), - 'fullname' => $fullname - ); + $getent_arr = explode(":", $getent_res); + $fullname = $getent_arr[4]; + if(empty($fullname)) { + $fullname = $username; + } - UpdateUserFromExternal( $usr ); - return $usr; + $principal->Create( array( + 'username' => $username, + 'user_active' => true, + 'email' => $username . "@" . $c->authenticate_hook['config']['email_base'], + 'modified' => date(), + 'fullname' => $fullname + )); + if ( ! $principal->Exists() ) { + dbg_error_log( "PAM", "Unable to create local principal for '%s'", $username ); + return false; + } + CreateHomeCalendar($username); } + return $principal; } else { dbg_error_log( "PAM", "User %s is not a valid username (or password was wrong)", $username ); diff --git a/inc/drivers_ldap.php b/inc/drivers_ldap.php index 30518175..2f5a6519 100644 --- a/inc/drivers_ldap.php +++ b/inc/drivers_ldap.php @@ -5,7 +5,8 @@ * @package davical * @category Technical * @subpackage ldap -* @author Maxime Delorme +* @author Maxime Delorme , +* Andrew McMillan * @copyright Maxime Delorme * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ @@ -248,7 +249,7 @@ function getStaticLdap() { // If the instance is not there, create one if(!isset($instance)) { - $ldapDrivers =& new ldapDrivers($c->authenticate_hook['config']); + $ldapDrivers = new ldapDrivers($c->authenticate_hook['config']); } return $ldapDrivers; } @@ -256,32 +257,36 @@ function getStaticLdap() { /** * Synchronise a cached user with one from LDAP -* @param object $usr A user record to be updated (or created) +* @param object $principal A Principal object to be updated (or created) */ -function sync_user_from_LDAP( &$usr, $mapping, $ldap_values ) { +function sync_user_from_LDAP( Principal &$principal, $mapping, $ldap_values ) { global $c; dbg_error_log( "LDAP", "Going to sync the user from LDAP" ); - $validUserFields = get_fields('usr'); + $fields_to_set = array(); + $updateable_fields = Principal::updateableFields(); + $updateable_fields[] = 'active'; // Backward compatibility: now 'user_exists' + $updateable_fields[] = 'updated'; // Backward compatibility: now 'modified' if ( isset($c->authenticate_hook['config']['default_value']) && is_array($c->authenticate_hook['config']['default_value']) ) { - foreach ( $c->authenticate_hook['config']['default_value'] as $field => $value ) { - if ( isset($validUserFields[$field]) ) { - $usr->{$field} = $value; - dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $value ); + foreach( $updateable_fields AS $field ) { + if ( isset($ldap_values[$mapping[$field]]) ) { + $fields_to_set[$field] = $ldap_values[$mapping[$field]]; + dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$mapping[$field]], $mapping[$field] ); + } + else if ( isset($c->authenticate_hook['config']['default_value'][$field] ) ) { + $fields_to_set[$field] = $c->authenticate_hook['config']['default_value'][$field]; + dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $c->authenticate_hook['config']['default_value'][$field] ); } } } - - foreach ( $mapping as $field => $value ) { - dbg_error_log( "LDAP", "Considering copying %s", $field ); - if ( isset($validUserFields[$field]) ) { - $usr->{$field} = $ldap_values[$value]; - dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$value], $value ); - } + if ( $principal->Exists ) { + $principal->Update($fields_to_set); + } + else { + $principal->Create($fields_to_set); + CreateHomeCalendar($principal->username()); } - - UpdateUserFromExternal( $usr ); } @@ -332,24 +337,25 @@ function LDAP_check($username, $password ){ $ldap_timestamp = "$Y"."$m"."$d"."$H"."$M"."$S"; $valid[$mapping["updated"]] = "$Y-$m-$d $H:$M:$S"; - if ( $usr = getUserByName($username) ) { + $principal = new Principal('username',$username); + if ( $principal->Exists() ) { // should we update it ? - $db_timestamp = $usr->updated; + $db_timestamp = $principal->modified; $db_timestamp = substr(strtr($db_timestamp, array(':' => '',' '=>'','-'=>'')),0,14); - if($ldap_timestamp <= $db_timestamp) { - return $usr; // no need to update + if( $ldap_timestamp <= $db_timestamp ) { + return $principal; // no need to update } // we will need to update the user record } else { dbg_error_log( "LDAP", "user %s doesn't exist in local DB, we need to create it",$username ); - $usr = (object) array( 'user_no' => 0 ); + $principal->setUsername($username ); } // The local cached user doesn't exist, or is older, so we create/update their details - sync_user_from_LDAP($usr, $mapping, $valid ); - - return $usr; + sync_user_from_LDAP( $principal, $mapping, $valid ); + + return $principal; } @@ -359,117 +365,132 @@ function LDAP_check($username, $password ){ function sync_LDAP_groups(){ global $c; $ldapDriver = getStaticLdap(); - if($ldapDriver->valid){ - $mapping = $c->authenticate_hook['config']['group_mapping_field']; - //$attributes = array('cn','modifyTimestamp','memberUid'); - $attributes = array_values($mapping); - $ldap_groups_tmp = $ldapDriver->getAllGroups($attributes); + if ( $ldapDriver->valid ) return; - if ( sizeof($ldap_groups_tmp) == 0 ) - return; + $mapping = $c->authenticate_hook['config']['group_mapping_field']; + //$attributes = array('cn','modifyTimestamp','memberUid'); + $attributes = array_values($mapping); + $ldap_groups_tmp = $ldapDriver->getAllGroups($attributes); - foreach($ldap_groups_tmp as $key => $ldap_group){ - $ldap_groups_info[$ldap_group[$mapping['username']]] = $ldap_group; - if (is_array($ldap_groups_info[$ldap_group[$mapping['username']]][$mapping['members']])) { - unset ( $ldap_groups_info[$ldap_group[$mapping['username']]][$mapping['members']]['count'] ); + if ( sizeof($ldap_groups_tmp) == 0 ) return; + + $member_field = $mapping['members']; + + foreach($ldap_groups_tmp as $key => $ldap_group){ + $group_mapping = $ldap_group[$mapping['username']]; + $ldap_groups_info[$group_mapping] = $ldap_group; + if ( is_array($ldap_groups_info[$group_mapping][$member_field]) ) { + unset( $ldap_groups_info[$group_mapping][$member_field]['count'] ); + } + else { + $ldap_groups_info[$group_mapping][$member_field] = array($ldap_groups_info[$group_mapping][$member_field]); + } + unset($ldap_groups_tmp[$key]); + } + $db_groups = array(); + $db_group_members = array(); + $qry = new AwlQuery( "SELECT g.username AS group_name, member.username AS member_name FROM dav_principal g LEFT JOIN group_member ON (g.principal_id=group_member.group_id) LEFT JOIN dav_principal member ON (member.principal_id=group_member.member_id) WHERE g.type_id = 3"); + $qry->Exec('sync_LDAP',__LINE__,__FILE__); + while($db_group = $qry->Fetch()) { + $db_groups[$db_group->group_name] = $db_group->group_name; + $db_group_members[$db_group->group_name][] = $db_group->member_name; + } + + $ldap_groups = array_keys($ldap_groups_info); + // users only in ldap + $groups_to_create = array_diff($ldap_groups,$db_groups); + // users only in db + $groups_to_deactivate = array_diff($db_groups,$ldap_groups); + // users present in ldap and in the db + $groups_to_update = array_intersect($db_groups,$ldap_groups); + + if ( sizeof ( $groups_to_create ) ){ + $c->messages[] = sprintf(i18n('- creating groups : %s'),join(', ',$groups_to_create)); + $validUserFields = get_fields('usr'); + foreach ( $groups_to_create as $k => $group ){ + $user = (object) array(); + + if ( isset($c->authenticate_hook['config']['default_value']) && is_array($c->authenticate_hook['config']['default_value']) ) { + foreach ( $c->authenticate_hook['config']['default_value'] as $field => $value ) { + if ( isset($validUserFields[$field]) ) { + $user->{$field} = $value; + dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $value ); + } + } + } + $user->user_no = 0; + $ldap_values = $ldap_groups_info[$group]; + foreach ( $mapping as $field => $value ) { + dbg_error_log( "LDAP", "Considering copying %s", $field ); + if ( isset($validUserFields[$field]) ) { + $user->{$field} = $ldap_values[$value]; + dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$value], $value ); + } + } + if ($user->fullname=="") { + $user->fullname = $group; + } + if ($user->displayname=="") { + $user->displayname = $group; + } + $user->username = $group; + $user->updated = "now"; /** @todo Use the 'updated' timestamp from LDAP for groups too */ + + $principal = new Principal('username',$group); + if ( $principal->Exists() ) { + $principal->Update($user); } else { - $ldap_groups_info[$ldap_group[$mapping['username']]][$mapping['members']] = array($ldap_groups_info[$ldap_group[$mapping['username']]][$mapping['members']]); + $principal->Create($user); } - unset($ldap_groups_tmp[$key]); - } - $db_groups = array (); - $db_group_members = array (); - $qry = new AwlQuery( "SELECT g.username AS group_name, member.username AS member_name FROM dav_principal g LEFT JOIN group_member ON (g.principal_id=group_member.group_id) LEFT JOIN dav_principal member ON (member.principal_id=group_member.member_id) WHERE g.type_id = 3"); - $qry->Exec('sync_LDAP',__LINE__,__FILE__); - while($db_group = $qry->Fetch()) { - $db_groups[$db_group->group_name] = $db_group->group_name; - $db_group_members[$db_group->group_name][] = $db_group->member_name; - } - $ldap_groups = array_keys($ldap_groups_info); - // users only in ldap - $groups_to_create = array_diff($ldap_groups,$db_groups); - // users only in db - $groups_to_deactivate = array_diff($db_groups,$ldap_groups); - // users present in ldap and in the db - $groups_to_update = array_intersect($db_groups,$ldap_groups); - - if ( sizeof ( $groups_to_create ) ){ - $c->messages[] = sprintf(i18n('- creating groups : %s'),join(', ',$groups_to_create)); - $validUserFields = get_fields('usr'); - foreach ( $groups_to_create as $k => $group ){ - $user = (object) array( 'user_no' => 0, 'username' => '' ); - - if ( isset($c->authenticate_hook['config']['default_value']) && is_array($c->authenticate_hook['config']['default_value']) ) { - foreach ( $c->authenticate_hook['config']['default_value'] as $field => $value ) { - if ( isset($validUserFields[$field]) ) { - $usr->{$field} = $value; - dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $value ); - } - } - } - $user->user_no = 0; - $ldap_values = $ldap_groups_info[$group]; - foreach ( $mapping as $field => $value ) { - dbg_error_log( "LDAP", "Considering copying %s", $field ); - if ( isset($validUserFields[$field]) ) { - $user->{$field} = $ldap_values[$value]; - dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$value], $value ); - } - } - if ($user->fullname=="") { - $user->fullname = $group; - } - if ($user->displayname=="") { - $user->displayname = $group; - } - $user->username = $group; - $user->updated = "now"; /** @todo Use the 'updated' timestamp from LDAP for groups too */ - - UpdateUserFromExternal( $user ); - $qry = new AwlQuery( "UPDATE dav_principal set type_id = 3 WHERE username=:group ",array(':group'=>$group) ); - $qry->Exec('sync_LDAP',__LINE__,__FILE__); - $c->messages[] = sprintf(i18n('- adding users %s to group : %s'),join(',',$ldap_groups_info[$group][$mapping['members']]),$group); - foreach ( $ldap_groups_info[$group][$mapping['members']] as $member ){ - $qry = new AwlQuery( "INSERT INTO group_member SELECT g.principal_id AS group_id,u.principal_id AS member_id FROM dav_principal g, dav_principal u WHERE g.username=:group AND u.username=:member;",array (':group'=>$group,':member'=>$member) ); - $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__); - } - } - } - - if ( sizeof ( $groups_to_update ) ){ - $c->messages[] = sprintf(i18n('- updating groups : %s'),join(', ',$groups_to_update)); - foreach ( $groups_to_update as $group ){ - $db_members = array_values ( $db_group_members[$group] ); - $ldap_members = array_values ( $ldap_groups_info[$group][$mapping['members']] ); - $add_users = array_diff ( $ldap_members, $db_members ); - if ( sizeof ( $add_users ) ){ - $c->messages[] = sprintf(i18n('- adding %s to group : %s'),join(', ', $add_users ), $group); - foreach ( $add_users as $member ){ - $qry = new AwlQuery( "INSERT INTO group_member SELECT g.principal_id AS group_id,u.principal_id AS member_id FROM dav_principal g, dav_principal u WHERE g.username=:group AND u.username=:member",array (':group'=>$group,':member'=>$member) ); - $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__); - } - } - $remove_users = array_diff ( $db_members, $ldap_members ); - if ( sizeof ( $remove_users ) ){ - $c->messages[] = sprintf(i18n('- removing %s from group : %s'),join(', ', $remove_users ), $group); - foreach ( $remove_users as $member ){ - $qry = new AwlQuery( "DELETE FROM group_member USING dav_principal g,dav_principal m WHERE group_id=g.principal_id AND member_id=m.principal_id AND g.username=:group AND m.username=:member",array (':group'=>$group,':member'=>$member) ); - $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__); - } - } - } - } - - if ( sizeof ( $groups_to_deactivate ) ){ - $c->messages[] = sprintf(i18n('- deactivate groups : %s'),join(', ',$groups_to_deactivate)); - foreach ( $groups_to_deactivate as $group ){ - $qry = new AwlQuery( "UPDATE dav_principal set active='f'::bool WHERE username=:group AND type_id = 3",array(':group'=>$group) ); - $qry->Exec('sync_LDAP',__LINE__,__FILE__); + $qry = new AwlQuery( "UPDATE dav_principal set type_id = 3 WHERE username=:group ",array(':group'=>$group) ); + $qry->Exec('sync_LDAP',__LINE__,__FILE__); + Principal::cacheDelete('username', $group); + $c->messages[] = sprintf(i18n('- adding users %s to group : %s'),join(',',$ldap_groups_info[$group][$mapping['members']]),$group); + foreach ( $ldap_groups_info[$group][$mapping['members']] as $member ){ + $qry = new AwlQuery( "INSERT INTO group_member SELECT g.principal_id AS group_id,u.principal_id AS member_id FROM dav_principal g, dav_principal u WHERE g.username=:group AND u.username=:member;",array (':group'=>$group,':member'=>$member) ); + $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__); + Principal::cacheDelete('username', $member); } } } + + if ( sizeof ( $groups_to_update ) ){ + $c->messages[] = sprintf(i18n('- updating groups : %s'),join(', ',$groups_to_update)); + foreach ( $groups_to_update as $group ){ + $db_members = array_values ( $db_group_members[$group] ); + $ldap_members = array_values ( $ldap_groups_info[$group][$member_field] ); + $add_users = array_diff ( $ldap_members, $db_members ); + if ( sizeof ( $add_users ) ){ + $c->messages[] = sprintf(i18n('- adding %s to group : %s'),join(', ', $add_users ), $group); + foreach ( $add_users as $member ){ + $qry = new AwlQuery( "INSERT INTO group_member SELECT g.principal_id AS group_id,u.principal_id AS member_id FROM dav_principal g, dav_principal u WHERE g.username=:group AND u.username=:member",array (':group'=>$group,':member'=>$member) ); + $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__); + Principal::cacheDelete('username', $member); + } + } + $remove_users = array_diff ( $db_members, $ldap_members ); + if ( sizeof ( $remove_users ) ){ + $c->messages[] = sprintf(i18n('- removing %s from group : %s'),join(', ', $remove_users ), $group); + foreach ( $remove_users as $member ){ + $qry = new AwlQuery( "DELETE FROM group_member USING dav_principal g,dav_principal m WHERE group_id=g.principal_id AND member_id=m.principal_id AND g.username=:group AND m.username=:member",array (':group'=>$group,':member'=>$member) ); + $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__); + Principal::cacheDelete('username', $member); + } + } + } + } + + if ( sizeof ( $groups_to_deactivate ) ){ + $c->messages[] = sprintf(i18n('- deactivate groups : %s'),join(', ',$groups_to_deactivate)); + foreach ( $groups_to_deactivate as $group ){ + $qry = new AwlQuery( 'UPDATE dav_principal set active=FALSE WHERE username=:group AND type_id = 3',array(':group'=>$group) ); + $qry->Exec('sync_LDAP',__LINE__,__FILE__); + Principal::cacheFlush('username=:group AND type_id = 3', array(':group'=>$group) ); + } + } + } /** @@ -478,107 +499,111 @@ function sync_LDAP_groups(){ function sync_LDAP(){ global $c; $ldapDriver = getStaticLdap(); - if($ldapDriver->valid){ - $mapping = $c->authenticate_hook['config']['mapping_field']; - $attributes = array_values($mapping); - $ldap_users_tmp = $ldapDriver->getAllUsers($attributes); + if ( ! $ldapDriver->valid ) return; - if ( sizeof($ldap_users_tmp) == 0 ) - return; + $mapping = $c->authenticate_hook['config']['mapping_field']; + $attributes = array_values($mapping); + $ldap_users_tmp = $ldapDriver->getAllUsers($attributes); - foreach($ldap_users_tmp as $key => $ldap_user){ - $ldap_users_info[$ldap_user[$mapping["username"]]] = $ldap_user; - unset($ldap_users_tmp[$key]); + if ( sizeof($ldap_users_tmp) == 0 ) return; + + foreach($ldap_users_tmp as $key => $ldap_user){ + $ldap_users_info[$ldap_user[$mapping["username"]]] = $ldap_user; + unset($ldap_users_tmp[$key]); + } + $qry = new AwlQuery( "SELECT username, user_no, modified as updated FROM dav_principal where type_id=1"); + $qry->Exec('sync_LDAP',__LINE__,__FILE__); + while($db_user = $qry->Fetch()) { + $db_users[] = $db_user->username; + $db_users_info[$db_user->username] = array('user_no' => $db_user->user_no, 'updated' => $db_user->updated); + } + + // all users from ldap + $ldap_users = array_keys($ldap_users_info); + // users only in ldap + $users_to_create = array_diff($ldap_users,$db_users); + // users only in db + $users_to_deactivate = array_diff($db_users,$ldap_users); + // users present in ldap and in the db + $users_to_update = array_intersect($db_users,$ldap_users); + + // creation of all users; + if ( sizeof($users_to_create) ) { + $c->messages[] = sprintf(i18n('- creating record for users : %s'),join(', ',$users_to_create)); + + foreach( $users_to_create as $username ) { + $principal = new Principal( 'username', $username ); + $valid = $ldap_users_info[$username]; + $ldap_timestamp = $valid[$mapping['updated']]; + + /** + * This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S + */ + foreach($c->authenticate_hook['config']['format_updated'] as $k => $v) + $$k = substr($ldap_timestamp,$v[0],$v[1]); + $ldap_timestamp = $Y.$m.$d.$H.$M.$S; + $valid[$mapping["updated"]] = "$Y-$m-$d $H:$M:$S"; + + sync_user_from_LDAP( $principal, $mapping, $valid ); } - $qry = new AwlQuery( "SELECT username, user_no, modified as updated FROM dav_principal where type_id=1"); + } + + // deactivating all users + $params = array(); + $i = 0; + foreach( $users_to_deactivate AS $v ) { + if ( isset($c->do_not_sync_from_ldap) && isset($c->do_not_sync_from_ldap[$v]) ) continue; + $params[':u'.$i++] = strtolower($v); + } + if ( count($params) > 0 ) { + $c->messages[] = sprintf(i18n('- deactivating users : %s'),join(', ',$users_to_deactivate)); + $qry = new AwlQuery( 'UPDATE usr SET active = FALSE WHERE lower(username) IN ('.implode(',',array_keys($params)).')', $params); $qry->Exec('sync_LDAP',__LINE__,__FILE__); - while($db_user = $qry->Fetch()) { - $db_users[] = $db_user->username; - $db_users_info[$db_user->username] = array('user_no' => $db_user->user_no, 'updated' => $db_user->updated); - } - $ldap_users = array_keys($ldap_users_info); - // users only in ldap - $users_to_create = array_diff($ldap_users,$db_users); - // users only in db - $users_to_deactivate = array_diff($db_users,$ldap_users); - // users present in ldap and in the db - $users_to_update = array_intersect($db_users,$ldap_users); + Principal::cacheFlush('lower(username) IN ('.implode(',',array_keys($params)).')', $params); + } - // creation of all users; - if ( sizeof($users_to_create) ) { - $c->messages[] = sprintf(i18n('- creating record for users : %s'),join(', ',$users_to_create)); + // updating all users + if ( sizeof($users_to_update) ) { + foreach ( $users_to_update as $key=> $username ) { + $principal = new Principal( 'username', $username ); + $valid=$ldap_users_info[$username]; + $ldap_timestamp = $valid[$mapping['updated']]; - foreach( $users_to_create as $username ) { - $user = (object) array( 'user_no' => 0, 'username' => $username ); - $valid = $ldap_users_info[$username]; - $ldap_timestamp = $valid[$mapping["updated"]]; + $valid['user_no'] = $db_users_info[$username]['user_no']; + $mapping['user_no'] = 'user_no'; - /** - * This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S - */ - foreach($c->authenticate_hook['config']['format_updated'] as $k => $v) - $$k = substr($ldap_timestamp,$v[0],$v[1]); - $ldap_timestamp = "$Y"."$m"."$d"."$H"."$M"."$S"; - $valid[$mapping["updated"]] = "$Y-$m-$d $H:$M:$S"; + /** + * This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S + */ + foreach($c->authenticate_hook['config']['format_updated'] as $k => $v) { + $$k = substr($ldap_timestamp,$v[0],$v[1]); + } + $ldap_timestamp = $Y.$m.$d.$H.$M.$S; + $valid[$mapping['updated']] = "$Y-$m-$d $H:$M:$S"; - sync_user_from_LDAP( $user, $mapping, $valid ); + $db_timestamp = substr(strtr($db_users_info[$username]['updated'], array(':' => '',' '=>'','-'=>'')),0,14); + if ( $ldap_timestamp > $db_timestamp ) { + sync_user_from_LDAP($principal, $mapping, $valid ); + } + else { + unset($users_to_update[$key]); + $users_nothing_done[] = $username; } } + if ( sizeof($users_to_update) ) + $c->messages[] = sprintf(i18n('- updating user records : %s'),join(', ',$users_to_update)); + if ( sizeof($users_nothing_done) ) + $c->messages[] = sprintf(i18n('- nothing done on : %s'),join(', ', $users_nothing_done)); + } - // deactivating all users - $params = array(); - $i = 0; - foreach( $users_to_deactivate AS $v ) { - if ( isset($c->do_not_sync_from_ldap) && isset($c->do_not_sync_from_ldap[$v]) ) continue; - $params[':u'.$i++] = strtolower($v); - } - if ( count($params) > 0 ) { - $c->messages[] = sprintf(i18n('- deactivating users : %s'),join(', ',$users_to_deactivate)); - $qry = new AwlQuery( 'UPDATE usr SET active = FALSE WHERE lower(username) IN ('.implode(',',array_keys($params)).')', $params); - $qry->Exec('sync_LDAP',__LINE__,__FILE__); - } - - // updating all users - if ( sizeof($users_to_update) ) { - foreach ( $users_to_update as $key=> $username ) { - $valid=$ldap_users_info[$username]; - $ldap_timestamp = $valid[$mapping["updated"]]; - - $valid["user_no"] = $db_users_info[$username]["user_no"]; - $mapping["user_no"] = "user_no"; - - /** - * This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S - */ - foreach($c->authenticate_hook['config']['format_updated'] as $k => $v) - $$k = substr($ldap_timestamp,$v[0],$v[1]); - $ldap_timestamp = "$Y"."$m"."$d"."$H"."$M"."$S"; - $valid[$mapping["updated"]] = "$Y-$m-$d $H:$M:$S"; - - $db_timestamp = substr(strtr($db_users_info[$username]['updated'], array(':' => '',' '=>'','-'=>'')),0,14); - if ( $ldap_timestamp > $db_timestamp ) { - sync_user_from_LDAP($usr, $mapping, $valid ); - } - else { - unset($users_to_update[$key]); - $users_nothing_done[] = $username; - } - } - if ( sizeof($users_to_update) ) - $c->messages[] = sprintf(i18n('- updating user records : %s'),join(', ',$users_to_update)); - if ( sizeof($users_nothing_done) ) - $c->messages[] = sprintf(i18n('- nothing done on : %s'),join(', ', $users_nothing_done)); - } - - $admins = 0; - $qry = new AwlQuery( "select count(*) as admins from usr join role_member using ( user_no ) join roles using (role_no) where usr.active = true and role_name='Admin'"); - $qry->Exec('sync_LDAP',__LINE__,__FILE__); - while($db_user = $qry->Fetch()) { - $admins = $db_user->admins; - } - if ( $admins == 0 ) { - $c->messages[] = sprintf(i18n('Warning: there are no active admin users, you should fix this before logging out.')); - } + $admins = 0; + $qry = new AwlQuery( "SELECT count(*) AS admins FROM usr JOIN role_member USING ( user_no ) JOIN roles USING (role_no) WHERE usr.active=TRUE AND role_name='Admin'"); + $qry->Exec('sync_LDAP',__LINE__,__FILE__); + while ( $db_user = $qry->Fetch() ) { + $admins = $db_user->admins; + } + if ( $admins == 0 ) { + $c->messages[] = sprintf(i18n('Warning: there are no active admin users! You should fix this before logging out. Consider using the $c->do_not_sync_from_ldap configuration setting.')); } } diff --git a/inc/drivers_pwauth_pam.php b/inc/drivers_pwauth_pam.php index d01fcc15..8564a800 100644 --- a/inc/drivers_pwauth_pam.php +++ b/inc/drivers_pwauth_pam.php @@ -6,9 +6,10 @@ * @category Technical * @subpackage pwauth * @author Eric Seigne , - * Michael B. Trausch + * Michael B. Trausch , + * Andrew McMillan * @copyright Eric Seigne - * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 + * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later * * Based on drivers_squid_pam.php */ @@ -42,8 +43,7 @@ class pwauthPamDrivers { global $c; if(!file_exists($config)) { - $c->messages[] = - sprintf(i18n('drivers_pwauth_pam : Unable to find %s file'), $config); + $c->messages[] = sprintf(i18n('drivers_pwauth_pam : Unable to find %s file'), $config); $this->valid=false; return ; } @@ -62,102 +62,101 @@ function PWAUTH_PAM_check($username, $password) { $pipe = popen(escapeshellarg($program), 'w'); $authinfo = sprintf("%s\n%s\n", $username, $password); $written = fwrite($pipe, $authinfo); - dbg_error_log('pwauth', 'Bytes written: %d of %d', $written, - strlen($authinfo)); + dbg_error_log('pwauth', 'Bytes written: %d of %d', $written, strlen($authinfo)); $return_status = pclose($pipe); switch($return_status) { - case 0: - // STATUS_OK: Authentication succeeded. - dbg_error_log('pwauth', 'User %s successfully authenticated', $username); - if($user = getUserByName($username)) { - return($user); - } else { - dbg_error_log('pwauth', 'User %s does not exist in local db, creating', - $username); - $fullname = exec(sprintf('getent passwd %s', escapeshellarg($username))); - $fullname = preg_replace('{^[^:]+:[^:]+:\d+:\d+:([^:,]+)(,[^:]*):.*$}', - '$1', $fullname); - $user = (object) array('user_no' => 0, - 'username' => $username, - 'active' => 't', - 'email' => sprintf('%s@%s', $username, - $email_base), - 'updated' => date('%r'), - 'fullname' => $fullname); - - UpdateUserFromExternal($user); - return($user); - } - break; + case 0: + // STATUS_OK: Authentication succeeded. + dbg_error_log('pwauth', 'User %s successfully authenticated', $username); + $principal = new Principal('username',$username); + if ( !$principal->Exists() ) { + dbg_error_log('pwauth', 'User %s does not exist in local db, creating', $username); + $pwent = posix_getpwnam($username); + $gecos = explode(',',$pwent['gecos']); + $fullname = $gecos[0]; + $principal->Create( array( + 'username' => $username, + 'user_active' => 't', + 'email' => sprintf('%s@%s', $username, $email_base), + 'fullname' => $fullname + )); + if ( ! $principal->Exists() ) { + dbg_error_log( "PAM", "Unable to create local principal for '%s'", $username ); + return false; + } + CreateHomeCalendar($username); + } + return $principal; + break; /* * Note that for system configurations using PAM instead of * reading the password database directly, if PAM is unable to * read the password database, pwauth will return status 1. */ - case 1: - case 2: - // (1) STATUS_UNKNOWN: Invalid username or password. - // (2) STATUS_INVALID: Invalid password. - dbg_error_log('pwauth', 'Invalid username or password (username: %s)', - $username); - break; + case 1: + case 2: + // (1) STATUS_UNKNOWN: Invalid username or password. + // (2) STATUS_INVALID: Invalid password. + dbg_error_log('pwauth', 'Invalid username or password (username: %s)', $username); + break; - case 3: - // STATUS_BLOCKED: UID for username is < pwauth's MIN_UNIX_UID - dbg_error_log('pwauth', 'UID for username %s is < pwauth MIN_UNIX_UID', - $username); - break; + case 3: + // STATUS_BLOCKED: UID for username is < pwauth's MIN_UNIX_UID + dbg_error_log('pwauth', 'UID for username %s is < pwauth MIN_UNIX_UID', + $username); + break; - case 4: - // STATUS_EXPIRED: The user account has expired. - dbg_error_log('pwauth', 'The account for %s has expired', $username); - break; + case 4: + // STATUS_EXPIRED: The user account has expired. + dbg_error_log('pwauth', 'The account for %s has expired', $username); + break; - case 5: - // STATUS_PW_EXPIRED: The user account's password has expired. - dbg_error_log('pwauth', 'The account password for user %s has expired', - $username); - break; + case 5: + // STATUS_PW_EXPIRED: The user account's password has expired. + dbg_error_log('pwauth', 'The account password for user %s has expired', + $username); + break; - case 6: - // STATUS_NOLOGIN: Logins to the system are administratively disabled. - dbg_error_log('pwauth', 'Logins administratively disabled (%s)', $username); - break; + case 6: + // STATUS_NOLOGIN: Logins to the system are administratively disabled. + dbg_error_log('pwauth', 'Logins administratively disabled (%s)', $username); + break; - case 7: - // STATUS_MANYFAILS: Too many login failures for user account. - dbg_error_log('pwauth', 'Login rejected for %s, too many failures', - $username); - break; + case 7: + // STATUS_MANYFAILS: Too many login failures for user account. + dbg_error_log('pwauth', 'Login rejected for %s, too many failures', + $username); + break; - case 50: - // STATUS_INT_USER: Configuration error, Web server cannot use pwauth - dbg_error_log('pwauth', 'config error: see pwauth man page (%s)', - 'STATUS_INT_USER'); - break; + case 50: + // STATUS_INT_USER: Configuration error, Web server cannot use pwauth + dbg_error_log('pwauth', 'config error: see pwauth man page (%s)', + 'STATUS_INT_USER'); + break; - case 51: - // STATUS_INT_ARGS: pwauth received no username/passwd to check - dbg_error_log('pwauth', 'error: pwauth received no username/password'); - break; + case 51: + // STATUS_INT_ARGS: pwauth received no username/passwd to check + dbg_error_log('pwauth', 'error: pwauth received no username/password'); + break; - case 52: - // STATUS_INT_ERR: unknown error - dbg_error_log('pwauth', 'error: see pwauth man page (%s)', - 'STATUS_INT_ERR'); - break; + case 52: + // STATUS_INT_ERR: unknown error + dbg_error_log('pwauth', 'error: see pwauth man page (%s)', + 'STATUS_INT_ERR'); + break; - case 53: - // STATUS_INT_NOROOT: pwauth could not read the password database - dbg_error_log('pwauth', 'config error: cannot read password database (%s)', - 'STATUS_INT_NOROOT'); - - default: - // Unknown error code. - dbg_error_log('pwauth', 'An unknown error (%d) has occurred', - $return_status); + case 53: + // STATUS_INT_NOROOT: pwauth could not read the password database + dbg_error_log('pwauth', 'config error: cannot read password database (%s)', + 'STATUS_INT_NOROOT'); + break; + + default: + // Unknown error code. + dbg_error_log('pwauth', 'An unknown error (%d) has occurred', + $return_status); } return(FALSE); diff --git a/inc/drivers_squid_pam.php b/inc/drivers_squid_pam.php index 41139220..82637616 100644 --- a/inc/drivers_squid_pam.php +++ b/inc/drivers_squid_pam.php @@ -5,9 +5,10 @@ * @package davical * @category Technical * @subpackage ldap -* @author Eric Seigne +* @author Eric Seigne , +* Andrew McMillan * @copyright Eric Seigne -* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ require_once("auth-functions.php"); @@ -53,34 +54,30 @@ class squidPamDrivers function SQUID_PAM_check($username, $password ){ global $c; - /** - * @todo Think of the children! This is a horribly insecure use of unvalidated user input! Probably it should be done with a popen or something, and it seems remarkably dodgy to expect that naively quoted strings will work in any way reliably. - * Meanwhile, I've quickly hacked something basic in place to improve the situation. No quotes/backslashes in passwords for YOU! - */ - $username = str_replace("'","",str_replace('"',"",str_replace('\\',"",$username))); - $password = str_replace("'","",str_replace('"',"",str_replace('\\',"",$password))); - $cmd = "echo '" . $username . "' '" . $password . "' | " . $c->authenticate_hook['config']['script'] . " -n common-auth"; + $cmd = sprintf( 'echo %s %s | %s -n common-auth', escapeshellarg($username), escapeshellarg($password), + $c->authenticate_hook['config']['script']); $auth_result = exec($cmd); if ( $auth_result == "OK") { - if ( $usr = getUserByName($username) ) { - return $usr; - } - else { - dbg_error_log( "PAM", "user %s doesn't exist in local DB, we need to create it",$username ); - $fullname = exec('getent passwd "'.$username.'"' ); - $fullname = preg_replace( '{^[^:]+:[^:]+:\d+:\d+:([^:,]+)(,?[^:]*):.*$}', '$1', $fullname ); - $usr = (object) array( - 'user_no' => 0, - 'username' => $username, - 'active' => 't', - 'email' => $username . "@" . $c->authenticate_hook['config']['email_base'], - 'updated' => date(), - 'fullname' => $fullname - ); - - UpdateUserFromExternal( $usr ); - return $usr; + dbg_error_log('pwauth', 'User %s successfully authenticated', $username); + $principal = new Principal('username',$username); + if ( !$principal->Exists() ) { + dbg_error_log('pwauth', 'User %s does not exist in local db, creating', $username); + $pwent = posix_getpwnam($username); + $gecos = explode(',',$pwent['gecos']); + $fullname = $gecos[0]; + $principal->Create( array( + 'username' => $username, + 'user_active' => 't', + 'email' => sprintf('%s@%s', $username, $email_base), + 'fullname' => $fullname + )); + if ( ! $principal->Exists() ) { + dbg_error_log( "PAM", "Unable to create local principal for '%s'", $username ); + return false; + } + CreateHomeCalendar($username); } + return $principal; } else { dbg_error_log( "PAM", "User %s is not a valid username (or password was wrong)", $username ); diff --git a/inc/ui/collection-edit.php b/inc/ui/collection-edit.php index 6c66db4a..b1e6dfd3 100644 --- a/inc/ui/collection-edit.php +++ b/inc/ui/collection-edit.php @@ -6,8 +6,8 @@ param_to_global('id', 'int', 'old_id', 'collection_id' ); param_to_global('user_no', 'int' ); param_to_global('principal_id', 'int' ); param_to_global('collection_name', '{^.+$}' ); -if ( isset($user_no) ) $usr = getUserByID($user_no); -if ( isset($principal_id) ) $usr = getPrincipalByID($principal_id); +if ( isset($user_no) ) $principal = new Principal('user_no',$user_no); +if ( isset($principal_id) ) $principal = new Principal('principal_id',$principal_id); $editor->SetLookup( 'timezone', 'SELECT \'\', \'*** Unknown ***\' UNION SELECT tz_id, tz_locn FROM time_zone WHERE tz_id = tz_locn AND length(tz_spec) > 100 ORDER BY 1' ); $editor->SetLookup( 'schedule_transp', 'SELECT \'opaque\', \'Opaque\' UNION SELECT \'transp\', \'Transparent\'' ); @@ -39,9 +39,9 @@ $params = array( ); $is_update = ( $_POST['_editor_action'][$editor->Id] == 'update' ); if ( isset($collection_name) ) $collection_name = trim(str_replace( '/', '', $collection_name)); -if ( !$is_update && isset($collection_name) && $collection_name != '' && is_object($usr) ) { - $_POST['dav_name'] = sprintf('/%s/%s/', $usr->username, $collection_name ); - $_POST['parent_container'] = sprintf('/%s/', $usr->username ); +if ( !$is_update && isset($collection_name) && $collection_name != '' && is_object($principal) ) { + $_POST['dav_name'] = sprintf('/%s/%s/', $principal->username(), $collection_name ); + $_POST['parent_container'] = sprintf('/%s/', $principal->username() ); $params[':collection_path'] = $_POST['dav_name']; $privsql = 'SELECT path_privs( :session_principal, :collection_path, :scan_depth) AS priv'; } @@ -137,8 +137,8 @@ else { $c->page_title = $editor->Title(translate('Create New Collection')); $privs = decbin(privilege_to_bits($c->default_privileges)); $editor->Assign('default_privileges', $privs); - $editor->Assign('username', $usr->username); - $editor->Assign('user_no', $usr->user_no); + $editor->Assign('username', $principal->username()); + $editor->Assign('user_no', $principal->user_no()); $editor->Assign('is_calendar', 't' ); $editor->Assign('use_default_privs', 't'); $entries = 0;