Overview

Packages

  • awl
    • caldav-client-v2
    • RRule
  • davical
    • authentication
      • drivers
    • caldav
    • DAViCalSession
    • DAVTicket
    • external-bind
    • feed
    • HTTPAuthSession
    • iSchedule
    • iSchedule-POST
    • logging
    • metrics
    • Principal
    • propfind
    • PublicSession
    • Request
    • Resource
    • tzservice
  • None
  • PHP

Functions

  • auth_functions_deprecated
  • AuthExternalAWL
  • CreateDefaultRelationships
  • CreateHomeCalendar
  • CreateHomeCollections
  • getPrincipalByID
  • getUserByEMail
  • getUserByID
  • getUserByName
  • UpdateCollectionTimezones
  • UpdateUserFromExternal
  • Overview
  • Package
  • Function
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: /**
  3: * The authentication handling plugins can be used by the Session class to
  4: * provide authentication.
  5: *
  6: * Each authenticate hook needs to:
  7: *   - Accept a username / password
  8: *   - Confirm the username / password are correct
  9: *   - Create (or update) a 'usr' record in our database
 10: *   - Return the 'usr' record as an object
 11: *   - Return === false when authentication fails
 12: *
 13: * It can expect that:
 14: *   - Configuration data will be in $c->authenticate_hook['config'], which might be an array, or whatever is needed.
 15: *
 16: * In order to be called:
 17: *   - This file should be included
 18: *   - $c->authenticate_hook['call'] should be set to the name of the plugin
 19: *   - $c->authenticate_hook['config'] should be set up with any configuration data for the plugin
 20: *
 21: * @package   davical
 22: * @subpackage   authentication
 23: * @author    Andrew McMillan <andrew@mcmillan.net.nz>
 24: * @copyright Catalyst IT Ltd, Morphoss Ltd
 25: * @license   http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
 26: */
 27: 
 28: require_once("DataUpdate.php");
 29: 
 30: if ( !function_exists('auth_functions_deprecated') ) {
 31:   /**
 32:    * Warn about deprecated auth functions
 33:    */
 34:   function auth_functions_deprecated( $method, $message = null ) {
 35:       $stack = debug_backtrace();
 36:       array_shift($stack);
 37:       dbg_error_log("ERROR", " auth-functions: Call to deprecated routine '%s'%s", $method, (isset($message)?': '.$message:'') );
 38:       foreach( $stack AS $k => $v ) {
 39:         dbg_error_log( 'ERROR', ' auth-functions: Deprecated call from line %4d of %s', $v['line'], $v['file']);
 40:       }
 41:   }
 42: }
 43: 
 44: /**
 45:  * @deprecated
 46:  */
 47: function getUserByName( $username, $use_cache=true ) {
 48:   auth_functions_deprecated('getUserByName','replaced by Principal class');
 49:   return new Principal('username', $username, $use_cache);
 50: }
 51: 
 52: /**
 53:  * @deprecated
 54:  */
 55: function getUserByEMail( $email, $use_cache = true ) {
 56:   auth_functions_deprecated('getUserByEMail','replaced by Principal class');
 57:   return new Principal('email', $email, $use_cache);
 58: }
 59: 
 60: /**
 61:  * @deprecated
 62:  */
 63: function getUserByID( $user_no, $use_cache = true ) {
 64:   auth_functions_deprecated('getUserByID','replaced by Principal class');
 65:   return new Principal('user_no', $user_no, $use_cache);
 66: }
 67: 
 68: /**
 69:  * @deprecated
 70:  */
 71: function getPrincipalByID( $principal_id, $use_cache = true ) {
 72:   auth_functions_deprecated('getPrincipalByID','replaced by Principal class');
 73:   return new Principal('principal_id', $principal_id, $use_cache);
 74: }
 75: 
 76: 
 77: /**
 78: * Creates some default home collections for the user.
 79: * @param string $username The username of the user we are creating relationships for.
 80: */
 81: function CreateHomeCollections( $username, $default_timezone = null ) {
 82:   global $session, $c;
 83: 
 84:   if ( !isset($c->default_collections) )
 85:   {
 86:     $c->default_collections = array();
 87: 
 88:     if( !empty($c->home_calendar_name) )
 89:       $c->default_collections[] = array( 'type' => 'calendar', 'name' => $c->home_calendar_name );
 90:     if( !empty($c->home_addressbook_name) )
 91:       $c->default_collections[] = array( 'type' => 'addressbook', 'name' => $c->home_addressbook_name );
 92:   }
 93: 
 94:   if ( !is_array($c->default_collections) || !count($c->default_collections) ) return true;
 95: 
 96:   $principal = new Principal('username',$username);
 97: 
 98:   $user_fullname = $principal->fullname;  // user fullname
 99:   $user_rfullname = implode(' ', array_reverse(explode(' ', $principal->fullname)));  // user fullname in reverse order
100: 
101:   $sql = 'INSERT INTO collection (user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, is_addressbook, default_privileges, created, modified, resourcetypes, timezone ) ';
102:   $sql .= 'VALUES( :user_no, :parent_container, :collection_path, :dav_etag, :displayname, :is_calendar, :is_addressbook, :privileges::BIT(24), current_timestamp, current_timestamp, :resourcetypes, :timezone );';
103: 
104:   foreach( $c->default_collections as $v ) {
105:     if ( $v['type'] == 'calendar' || $v['type']=='addressbook' ) {
106:       if ( !empty($v['name']) ) {
107:         $qry = new AwlQuery( 'SELECT 1 FROM collection WHERE dav_name = :dav_name', array( ':dav_name' => $principal->dav_name().$v['name'].'/') );
108:         if ( !$qry->Exec() ) {
109:           $c->messages[] = i18n('There was an error reading from the database.');
110:           return false;
111:         }
112:         if ( $qry->rows() > 0 ) {
113:           $c->messages[] = i18n('Home '.( $v['type']=='calendar' ? 'calendar' : 'addressbook' ).' already exists.');
114:           return true;
115:         }
116:         else {
117:           $params[':user_no'] = $principal->user_no();
118:           $params[':parent_container'] = $principal->dav_name();
119:           $params[':dav_etag'] = '-1';
120:           $params[':collection_path'] = $principal->dav_name().$v['name'].'/';
121:           $params[':displayname'] = ( !isset($v['displayname']) || empty($v['displayname']) ? $user_fullname.( $v['type']=='calendar' ? ' calendar' : ' addressbook' ) : str_replace(array('%fn', '%rfn'), array($user_fullname, $user_rfullname), $v['displayname']) );
122:           $params[':resourcetypes'] = ( $v['type']=='calendar' ? '<DAV::collection/><urn:ietf:params:xml:ns:caldav:calendar/>' : '<DAV::collection/><urn:ietf:params:xml:ns:carddav:addressbook/>' );
123:           $params[':is_calendar'] = ( $v['type']=='calendar' ? true : false );
124:           $params[':is_addressbook'] = ( $v['type']=='addressbook' ? true : false );
125:           $params[':privileges'] = ( !isset($v['privileges']) || $v['privileges']===null ? null : privilege_to_bits($v['privileges']) );
126:           $params[':timezone'] = ( ( !isset($v['timezone']) || empty($v['timezone']) ) && $v['type']=='calendar' ? $default_timezone : $v['timezone'] );
127: 
128:           $qry = new AwlQuery( $sql, $params );
129:           if ( $qry->Exec() ) {
130:             $c->messages[] = i18n('Home '.( $v['type']=='calendar' ? 'calendar' : 'addressbook' ).' added.');
131:             dbg_error_log("User",":Write: Created user's home ".( $v['type']=='calendar' ? 'calendar' : 'addressbook' )." at '%s'", $params[':collection_path'] );
132: 
133:             // create value for urn:ietf:params:xml:ns:caldav:supported-calendar-component-set property
134:             if($v['type'] == 'calendar' && isset($v['calendar_components']) && $v['calendar_components'] != null && is_array($v['calendar_components']) && count($v['calendar_components'])) {
135:                 // convert the array to uppercase and allow only real calendar compontents
136:                 $components_clean=array_intersect(array_map("strtoupper", $v['calendar_components']), array('VEVENT', 'VTODO', 'VJOURNAL', 'VTIMEZONE', 'VFREEBUSY', 'VPOLL', 'VAVAILABILITY'));
137: 
138:                 // convert the $components_clean array to XML string
139:                 $result_xml='';
140:                 foreach($components_clean as $curr)
141:                     $result_xml.=sprintf('<comp name="%s" xmlns="urn:ietf:params:xml:ns:caldav"/>', $curr);
142: 
143:                 // handle the components XML string as user defined property (see below)
144:                 if($result_xml!='')
145:                     $v['default_properties']['urn:ietf:params:xml:ns:caldav:supported-calendar-component-set']=$result_xml;
146:             }
147: 
148:             // store all user defined properties (note: it also handles 'calendar_components' - see above)
149:             if(isset($v['default_properties']) && $v['default_properties'] != null && is_array($v['default_properties']) && count($v['default_properties'])) {
150:               $sql2='INSERT INTO property (dav_name, property_name, property_value, changed_on, changed_by) ';
151:               $sql2.='VALUES (:collection_path, :property_name, :property_value, current_timestamp, :user_no);';
152:               $params2[':user_no'] = $principal->user_no();
153:               $params2[':collection_path'] = $principal->dav_name().$v['name'].'/';
154: 
155:               foreach( $v['default_properties'] AS $key => $val ) {
156:                 $params2[':property_name'] = $key;
157:                 $params2[':property_value'] = $val;
158: 
159:                 $qry2 = new AwlQuery( $sql2, $params2 );
160:                 if ( $qry2->Exec() ) {
161:                   dbg_error_log("User",":Write: Created property '%s' for ".( $v['type']=='calendar' ? 'calendar' : 'addressbook' )." at '%s'", $params2[':property_name'], $params2[':collection_path'] );
162:                 }
163:                 else {
164:                   $c->messages[] = i18n("There was an error writing to the database.");
165:                   return false;
166:                 }
167:               }
168:             }
169:           }
170:           else {
171:             $c->messages[] = i18n("There was an error writing to the database.");
172:             return false;
173:           }
174:         }
175:       }
176:     }
177:   }
178:   return true;
179: }
180: 
181: /**
182:  * @deprecated
183:  * @param string $username
184:  */
185: function CreateHomeCalendar($username) {
186:   auth_functions_deprecated('CreateHomeCalendar','renamed to CreateHomeCollections');
187:   return CreateHomeCollections($username);
188: }
189: 
190: /**
191: * Create default relationships.
192: * @param string $username The username of the user we are creating relationships for.
193: */
194: function CreateDefaultRelationships( $username ) {
195:   global $c;
196:   if(! isset($c->default_relationships) || count($c->default_relationships) == 0) return true;
197: 
198:   $changes = false;
199:   $principal = new Principal('username', $username, true);
200:   foreach($c->default_relationships as $group => $relationships)
201:   {
202:     $sql = 'INSERT INTO grants (by_principal, to_principal, privileges) VALUES(:by_principal, :to_principal, :privileges::INT::BIT(24))';
203:     $params = array(
204:       ':by_principal' => $principal->principal_id,
205:       ':to_principal' => $group,
206:       ':privileges' => privilege_to_bits($relationships)
207:     );
208:     $qry = new AwlQuery($sql, $params);
209: 
210:     if ( $qry->Exec() ) {
211:       $changes = true;
212:       dbg_error_log("User",":Write: Created user's default relationship by:'%s', to:'%s', privileges:'%s'",$params[':by_principal'],$params[':to_principal'],$params[':privileges']);
213:     }
214:     else {
215:       $c->messages[] = i18n("There was an error writing to the database.");
216:       return false;
217:     }
218:   }
219: 
220:   if($changes)
221:     $c->messages[] = i18n("Default relationships added.");
222: 
223:   return true;
224: }
225: 
226: /**
227:  * Set a new timezone for a user's calendars
228:  * @param string $username
229:  * @param string $new_timezone
230:  */
231: function UpdateCollectionTimezones( $username, $new_timezone=null ) {
232:   if ( empty($new_timezone) ) return;
233:   $qry = new AwlQuery('UPDATE collection SET timezone=? WHERE dav_name LIKE ? AND is_calendar', '/'.$username.'/%', $new_timezone);
234:   $qry->Exec();
235: }
236: 
237: /**
238: * @deprecated
239: * @param object $usr The user details we read from the remote.
240: */
241: function UpdateUserFromExternal( &$usr ) {
242:   global $c;
243: 
244:   auth_functions_deprecated('UpdateUserFromExternal','refactor to use the "Principal" class');
245:   /**
246:   * When we're doing the create we will usually need to generate a user number
247:   */
248:   if ( !isset($usr->user_no) || intval($usr->user_no) == 0 ) {
249:     $qry = new AwlQuery( "SELECT nextval('usr_user_no_seq');" );
250:     $qry->Exec('Login',__LINE__,__FILE__);
251:     $sequence_value = $qry->Fetch(true);  // Fetch as an array
252:     $usr->user_no = $sequence_value[0];
253:   }
254: 
255:   $qry = new AwlQuery('SELECT * FROM usr WHERE user_no = :user_no', array(':user_no' => $usr->user_no) );
256:   if ( $qry->Exec('Login',__LINE__,__FILE__) && $qry->rows() == 1 ) {
257:     $type = "UPDATE";
258:     if ( $old = $qry->Fetch() ) {
259:       $changes = false;
260:       foreach( $usr AS $k => $v ) {
261:         if ( $old->{$k} != $v ) {
262:           $changes = true;
263:           dbg_error_log("Login","User '%s' field '%s' changed from '%s' to '%s'", $usr->username, $k, $old->{$k}, $v );
264:           break;
265:         }
266:       }
267:       if ( !$changes ) {
268:         dbg_error_log("Login","No changes to user record for '%s' - leaving as-is.", $usr->username );
269:         if ( isset($usr->active) && $usr->active == 'f' ) return false;
270:         return; // Normal case, if there are no changes
271:       }
272:       else {
273:         dbg_error_log("Login","Changes to user record for '%s' - updating.", $usr->username );
274:       }
275:     }
276:   }
277:   else
278:     $type = "INSERT";
279: 
280:   $params = array();
281:   if ( $type != 'INSERT' ) $params[':user_no'] = $usr->user_no;
282:   $qry = new AwlQuery( sql_from_object( $usr, $type, 'usr', 'WHERE user_no= :user_no' ), $params );
283:   $qry->Exec('Login',__LINE__,__FILE__);
284: 
285:   /**
286:   * We disallow login by inactive users _after_ we have updated the local copy
287:   */
288:   if ( isset($usr->active) && ($usr->active === 'f' || $usr->active === false) ) return false;
289: 
290:   if ( $type == 'INSERT' ) {
291:     $qry = new AwlQuery( 'INSERT INTO principal( type_id, user_no, displayname, default_privileges) SELECT 1, user_no, fullname, :privs::INT::BIT(24) FROM usr WHERE username=(text(:username))',
292:                           array( ':privs' => privilege_to_bits($c->default_privileges), ':username' => $usr->username) );
293:     $qry->Exec('Login',__LINE__,__FILE__);
294:     CreateHomeCollections($usr->username, $c->default_timezone);
295:     CreateDefaultRelationships($usr->username);
296:   }
297:   else if ( $usr->fullname != $old->{'fullname'} ) {
298:     // Also update the displayname if the fullname has been updated.
299:     $qry->QDo( 'UPDATE principal SET displayname=:new_display WHERE user_no=:user_no',
300:                     array(':new_display' => $usr->fullname, ':user_no' => $usr->user_no)
301:              );
302:   }
303: }
304: 
305: 
306: /**
307: * Authenticate against a different PostgreSQL database which contains a usr table in
308: * the AWL format.
309: *
310: * Use this as in the following example config snippet:
311: *
312: * require_once('auth-functions.php');
313: *  $c->authenticate_hook = array(
314: *      'call'   => 'AuthExternalAwl',
315: *      'config' => array(
316: *           // A PgSQL database connection string for the database containing user records
317: *          'connection[]' => 'dbname=wrms host=otherhost port=5433 user=general',
318: *           // Which columns should be fetched from the database
319: *          'columns'    => "user_no, active, email_ok, joined, last_update AS updated, last_used, username, password, fullname, email",
320: *           // a WHERE clause to limit the records returned.
321: *          'where'    => "active AND org_code=7"
322: *      )
323: *  );
324: *
325: */
326: function AuthExternalAWL( $username, $password ) {
327:   global $c;
328: 
329:   $persistent = isset($c->authenticate_hook['config']['use_persistent']) && $c->authenticate_hook['config']['use_persistent'];
330: 
331:   if ( isset($c->authenticate_hook['config']['columns']) )
332:     $cols = $c->authenticate_hook['config']['columns'];
333:   else
334:     $cols = '*';
335: 
336:   if ( isset($c->authenticate_hook['config']['where']) )
337:     $andwhere = ' AND '.$c->authenticate_hook['config']['where'];
338:   else
339:     $andwhere = '';
340: 
341:   $qry = new AwlQuery('SELECT '.$cols.' FROM usr WHERE lower(username) = :username '. $andwhere, array( ':username' => strtolower($username) ));
342:   $authconn = $qry->SetConnection($c->authenticate_hook['config']['connection'], ($persistent ? array(PDO::ATTR_PERSISTENT => true) : null));
343:   if ( ! $authconn ) {
344:     echo <<<EOERRMSG
345:   <html><head><title>Database Connection Failure</title></head><body>
346:   <h1>Database Error</h1>
347:   <h3>Could not connect to PostgreSQL database</h3>
348:   </body>
349:   </html>
350: EOERRMSG;
351:     @ob_flush();  exit(1);
352:   }
353: 
354:   if ( $qry->Exec('Login',__LINE__,__FILE__) && $qry->rows() == 1 ) {
355:     $usr = $qry->Fetch();
356:     if ( session_validate_password( $password, $usr->password ) ) {
357:       $principal = new Principal('username',$username);
358:       if ( $principal->Exists() ) {
359:         if ( $principal->modified <= $usr->updated )
360:           $principal->Update($usr);
361:       }
362:       else {
363:         $principal->Create($usr);
364:         CreateHomeCollections($username);
365:     CreateDefaultRelationships($username);
366:       }
367: 
368:       /**
369:       * We disallow login by inactive users _after_ we have updated the local copy
370:       */
371:       if ( isset($usr->active) && $usr->active == 'f' ) return false;
372: 
373:       return $principal;
374:     }
375:   }
376: 
377:   return false;
378: 
379: }
380: 
DAViCal API documentation generated by ApiGen 2.8.0