public function method_POST(&$headers) { $displayname = $_POST['displayname']; $description = $_POST['description']; $group_name = $_POST['group_name']; $user_sponsor = BeeHub::getAuth()->current_user()->user_prop(BeeHub::PROP_SPONSOR); // If you don't have a (default) sponsor, you're not allowed to add a group if (empty($user_sponsor)) { throw DAV::forbidden("Only users with a sponsor are allowed to create groups"); } // Group name must be one of the following characters a-zA-Z0-9_-., starting with an alphanumeric character and must be between 1 and 255 characters long and can't be one of the forbidden names if (empty($displayname) || in_array(strtolower($group_name), BeeHub::$FORBIDDEN_GROUP_NAMES) || !preg_match('/^[a-zA-Z0-9]{1}[a-zA-Z0-9_\\-\\.]{0,254}$/D', $group_name)) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'Group name has the wrong format. The name can be a maximum of 255 characters long and should start with an alphanumeric character, followed by alphanumeric characters or one of the following: _-.'); } // Check if the group name doesn't exist $collection = BeeHub::getNoSQL()->groups; $result = $collection->findOne(array('name' => $group_name), array('name' => true)); if (!is_null($result)) { // Duplicate key: bad request! throw new DAV_Status(DAV::HTTP_CONFLICT, "Group name already exists, please choose a different group name!"); } $groupdir = DAV::unslashify(BeeHub::$CONFIG['environment']['datadir']) . DIRECTORY_SEPARATOR . $group_name; // Check for existing groupdir if (file_exists($groupdir)) { throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR); } // Store in the database $collection->insert(array('name' => $group_name)); // Fetch the group and store extra properties $group = DAV::$REGISTRY->resource(BeeHub::GROUPS_PATH . $group_name); $group->user_set(DAV::PROP_DISPLAYNAME, $displayname); if (!empty($description)) { $group->user_set(BeeHub::PROP_DESCRIPTION, $description); } $group->storeProperties(); // Add the current user as admin of the group $group->change_memberships(basename($this->user_prop_current_user_principal()), BeeHub_Group::USER_ACCEPT); $group->change_memberships(basename($this->user_prop_current_user_principal()), BeeHub_Group::ADMIN_ACCEPT); $group->change_memberships(basename($this->user_prop_current_user_principal()), BeeHub_Group::SET_ADMIN); // And create a group directory if (!mkdir($groupdir)) { throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR); } // And create the directory in the database $document = array('path' => $group_name, 'depth' => 1, 'collection' => true); $filesCollection = BeeHub::getNoSQL()->files; $filesCollection->save($document); $groupdir_resource = DAV::$REGISTRY->resource('/' . $group_name); $groupdir_resource->user_set(BeeHub::PROP_SPONSOR, $user_sponsor); $groupdir_resource->user_set(DAV::PROP_ACL, '[["' . BeeHub::GROUPS_PATH . $group->name . '",false,["DAV: read", "DAV: write"],false]]'); $groupdir_resource->storeProperties(); // Group created, redirect to the group page DAV::redirect(DAV::HTTP_SEE_OTHER, BeeHub::GROUPS_PATH . $group->name); }
/** * Traverse over the files and subdirectories * * @global MongoCollection $collection The MongoDB collection * @global Array $CONFIG The configuration parameters * @param DirectoryIterator $iterator The DirectoryIterator to iterate over * @return void */ function traverse($iterator) { global $collection, $CONFIG; foreach ($iterator as $fileinfo) { $file = $fileinfo->getPathname(); if ($fileinfo->isDot()) { continue; } elseif ($fileinfo->isDir()) { traverse(new DirectoryIterator($file)); } $attributes = xattr_list($file); $stored_props = array(); if (!$fileinfo->isDir()) { $encodedKey = str_replace(array('%', '$', '.'), array('%25', '%24', '%2E'), DAV::PROP_GETCONTENTLENGTH); $stored_props[$encodedKey] = $fileinfo->getSize(); } foreach ($attributes as $attribute) { $decodedKey = rawurldecode($attribute); $value = xattr_get($file, $attribute); // Transform the value of the owner and sponsor properties (but only if necessary) if (($decodedKey === 'DAV: owner' || $decodedKey === 'http://beehub.nl/ sponsor') && substr($value, 0, 1) === '/') { $value = rawurldecode(basename($value)); } // Property names are already stored url encoded in extended attributes, but we just need it a few characters to be encoded. // This url encodes only the characters needed to create valid mongoDB keys. You can just run rawurldecode to decode it. $encodedKey = str_replace(array('%', '$', '.'), array('%25', '%24', '%2E'), $decodedKey); $stored_props[$encodedKey] = mb_convert_encoding($value, 'UTF-8'); } $unslashifiedPath = \DAV::unslashify(substr($file, strlen($CONFIG['environment']['datadir']))); if (substr($unslashifiedPath, 0, 1) === '/') { $unslashifiedPath = substr($unslashifiedPath, 1); } if ($unslashifiedPath === '') { $depth = 0; } else { $depth = substr_count($unslashifiedPath, '/') + 1; } $document = array('path' => mb_convert_encoding($unslashifiedPath, 'UTF-8'), 'depth' => $depth, 'props' => $stored_props); if ($fileinfo->isDir()) { $document['collection'] = true; } $collection->save($document); } }
/** * @param string $path */ public function resource($path) { if (is_array($path)) { $document = $path; $path = '/' . $document['path']; } else { $document = null; } $path = DAV::unslashify($path); $systemPath = DAV::unslashify(BeeHub::SYSTEM_PATH); $usersPath = DAV::unslashify(BeeHub::USERS_PATH); $groupsPath = DAV::unslashify(BeeHub::GROUPS_PATH); $sponsorsPath = DAV::unslashify(BeeHub::SPONSORS_PATH); if (isset($this->resourceCache[$path])) { return $this->resourceCache[$path]; } $localPath = BeeHub::localPath($path); $retval = null; if ($path === '/') { $retval = new BeeHub_Directory($path); } elseif ($path === $systemPath) { $retval = new BeeHub_System_Collection($path); } elseif (substr($path, 0, strlen($usersPath)) === $usersPath) { if ($path === $usersPath) { $retval = new BeeHub_Users($path); } else { try { $retval = new BeeHub_User($path); } catch (Exception $e) { } } } elseif (substr($path, 0, strlen($groupsPath)) === $groupsPath) { if ($path === $groupsPath) { $retval = new BeeHub_Groups($path); } else { try { $retval = new BeeHub_Group($path); } catch (Exception $e) { } } } elseif (substr($path, 0, strlen($sponsorsPath)) === $sponsorsPath) { if ($path === $sponsorsPath) { $retval = new BeeHub_Sponsors($path); } else { try { $retval = new BeeHub_Sponsor($path); } catch (Exception $e) { } } } else { $unslashifiedPath = $path; if (substr($unslashifiedPath, 0, 1) === '/') { $unslashifiedPath = substr($unslashifiedPath, 1); } $collection = BeeHub::getNoSQL()->files; if (!is_array($document)) { $document = $collection->findOne(array('path' => $unslashifiedPath)); } if (!is_null($document)) { if (isset($document['collection']) && $document['collection']) { $retval = new BeeHub_Directory($document); } else { $retval = new BeeHub_File($document); } } else { return null; } } return $this->resourceCache[$path] = $retval; }
$sysDir = substr($sysDir, 1); } $fileDocument = array('path' => $sysDir, 'depth' => substr_count($sysDir, '/') + 1, 'collection' => true, 'props' => array()); $filesCollection->insert($fileDocument); } // Add the user's home directory with different properties $fileDocument = array('path' => \DAV::unslashify($userdir), 'collection' => true, 'props' => array(\DAV::PROP_OWNER => $username)); if (substr($fileDocument['path'], 0, 1) === '/') { $fileDocument['path'] = substr($fileDocument['path'], 1); } $fileDocument['depth'] = substr_count($fileDocument['path'], '/') + 1; $encodedKey = str_replace(array('%', '$', '.'), array('%25', '%24', '%2E'), \BeeHub::PROP_SPONSOR); $fileDocument['props'][$encodedKey] = DEFAULT_SPONSOR_NAME; $filesCollection->insert($fileDocument); // Add the group directory with different properties $fileDocument = array('path' => \DAV::unslashify(\basename($config['namespace']['admin_group'])), 'collection' => true, 'props' => array(\DAV::PROP_ACL => '[["' . $config['namespace']['admin_group'] . '",false,["DAV: read", "DAV: write"],false]]')); if (substr($fileDocument['path'], 0, 1) === '/') { $fileDocument['path'] = substr($fileDocument['path'], 1); } $fileDocument['depth'] = substr_count($fileDocument['path'], '/') + 1; $encodedKey = str_replace(array('%', '$', '.'), array('%25', '%24', '%2E'), \BeeHub::PROP_SPONSOR); $fileDocument['props'][$encodedKey] = DEFAULT_SPONSOR_NAME; $filesCollection->insert($fileDocument); } else { \header('HTTP/1.1 500 Internal Server Error'); \ob_end_flush(); print "\nUnable to create the system directories\n"; exit; } print "ok\n"; // Then import the database structure
public function testUnslashify() { $this->assertSame('/something/with/a/slash/at/the/end', DAV::unslashify('/something/with/a/slash/at/the/end/'), 'DAV::slashify() should remove the trailing slash from a string which ends with a slash'); $this->assertSame('/something/with/no/slash/at/the/end', DAV::unslashify('/something/with/no/slash/at/the/end'), 'DAV::slashify() should not do anything to a string which doesn\'t end with a slash'); }
/** * Check any HTTP If-* header applicable with the current request method * * @return boolean TRUE if shallowLock() was called; */ private function check_if_headers() { $write_locks = $read_locks = array(); switch ($_SERVER['REQUEST_METHOD']) { case 'ACL': case 'DELETE': case 'LOCK': case 'MKCOL': case 'POST': case 'PROPPATCH': case 'PUT': case 'UNLOCK': // For all actions above you need a write lock for the path/resource $write_locks[DAV::unslashify(DAV::getPath())] = 1; break; case 'COPY': case 'MOVE': // Here, things get a bit more complicated if (!$this->destination()) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'Missing required Destination: header'); } if ('/' === substr($this->destination(), 0, 1)) { // For destinations as absolute paths, you'll need a write lock on the destination $write_locks[DAV::unslashify($this->destination())] = 1; } if ('COPY' === $_SERVER['REQUEST_METHOD']) { // If you want to copy, you'll just need a read lock on the source $read_locks[DAV::unslashify(DAV::getPath())] = 1; } else { // But if you want to move, you'll need a write lock on the source (as it will be deleted) $write_locks[DAV::unslashify(DAV::getPath())] = 1; } break; } // If there are write locks, you'll also need read locks on all parents if (!empty($write_locks)) { foreach (array_keys($write_locks) as $p) { while ($p !== '/') { $p = dirname($p); $read_locks[$p] = 1; } } } foreach (array('MATCH', 'UNMODIFIED_SINCE') as $value) { // Conditions 'NONE_MATCH', 'MODIFIED_SINCE' are not relevant if (isset($_SERVER['HTTP_IF_' . $value])) { $read_locks[DAV::unslashify(DAV::getPath())] = 1; break; } } foreach (array_keys($this->if_header) as $path) { $read_locks[DAV::unslashify($path)] = 1; } // If we already want a write lock, we should not also want a read lock foreach (array_keys($write_locks) as $path) { unset($read_locks[$path]); } // No locks required? Than just return false so the caller knows that there are no (shallow) locks set if (empty($write_locks) && empty($read_locks)) { return false; } // Get the (shallow) locks DAV::$REGISTRY->shallowLock(array_keys($write_locks), array_keys($read_locks)); try { // to guarantee unlocking i.c.o. exceptions $this->check_if_match_header(); $this->check_if_modified_since_header(); $this->check_if_header(); } catch (Exception $e) { DAV::$REGISTRY->shallowUnlock(); throw $e; } return true; }
/** * Gets all members who have a certain property set * @param string $prop The property which should be set on the member * @return array An array with all paths to members who have the property set */ public function get_members_with_prop($prop) { $collection = BeeHub::getNoSQL()->files; $unslashifiedPath = DAV::unslashify($this->path); while (substr($unslashifiedPath, 0, 1) === '/') { $unslashifiedPath = substr($unslashifiedPath, 1); } if ($unslashifiedPath === '') { $queryArray = array('depth' => array('$gt' => 0), 'props.' . $prop => array('$exists' => true)); } else { $queryArray = array('depth' => array('$gt' => substr_count($unslashifiedPath, '/') + 1), 'path' => array('$regex' => '^' . preg_quote(DAV::slashify($unslashifiedPath)) . '.*'), 'props.' . $prop => array('$exists' => true)); } $results = $collection->find($queryArray, array('path' => 1, 'props.' . $prop => 1)); $returnVal = array(); foreach ($results as $result) { $returnVal[$result['path']] = $result['props'][$prop]; } return $returnVal; }
/** * Determine the local path in the storage backend * * @param string $path The path in the webDAV namespace * @return string The location where the data is stored in the storage backend */ public static function localPath($path) { if (substr($path, 0, 1) === '/') { $path = substr($path, 1); } return DAV::unslashify(self::$CONFIG['environment']['datadir'] . $path); }
/** * @return DirectoryIterator */ private function dir() { if (is_null($this->dir)) { $collection = BeeHub::getNoSQL()->files; $unslashifiedPath = DAV::unslashify($this->path); while (substr($unslashifiedPath, 0, 1) === '/') { $unslashifiedPath = substr($unslashifiedPath, 1); } if (!empty($unslashifiedPath)) { $query = array('depth' => substr_count($unslashifiedPath, '/') + 2, 'path' => array('$regex' => '^' . preg_quote($unslashifiedPath) . '/[^/]*$')); } else { $query = array('depth' => 1); } $allChildren = $collection->find($query); $this->dir = array(); foreach ($allChildren as $document) { $child = basename($document['path']); if (isset($document['collection']) && $document['collection']) { $child .= '/'; } if (!DAV::$REGISTRY->resource($document)->isVisible()) { DAV::$REGISTRY->forget($this->path . $child); } else { $this->dir[] = $child; } } } return $this->dir; }
/** * Determines whether you need to authenticate based on the method and URL of the request * @return boolean True if authentication is required, false otherwise */ public static function is_authentication_required() { $path = DAV::unslashify(DAV::getPath()); /** * You don't need to authenticate when: * - GET (or HEAD) or POST on the users collection (required to create a new user) * - GET (or HEAD) on the system collection (required to read the 'homepage') * In other cases you do need to authenticate */ $noRequireAuth = $path === DAV::unslashify(BeeHub::USERS_PATH) && in_array($_SERVER['REQUEST_METHOD'], array('GET', 'POST', 'HEAD')) || $path === DAV::unslashify(BeeHub::SYSTEM_PATH) && in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')); return !$noRequireAuth; }
/** * Handles the form to register a new user. No authentication required. * @see DAV_Resource::method_POST() */ public function method_POST(&$headers) { $displayname = $_POST['displayname']; $email = $_POST['email']; $password = !empty($_POST['password']) ? $_POST['password'] : null; $user_name = $_POST['user_name']; // User name must be one of the following characters a-zA-Z0-9_-., starting with an alphanumeric character and must be between 1 and 255 characters long if (empty($displayname) || !preg_match('/^[a-zA-Z0-9]{1}[a-zA-Z0-9_\\-\\.]{0,254}$/D', $user_name)) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'User name has the wrong format. The name can be a maximum of 255 characters long and should start with an alphanumeric character, followed by alphanumeric character, followed by alphanumeric characters or one of the following: _-.'); } // Check if the username doesn't exist $collection = BeeHub::getNoSQL()->users; $result = $collection->findOne(array('name' => $user_name), array('name' => true)); if (!is_null($result)) { // Duplicate key: bad request! throw new DAV_Status(DAV::HTTP_CONFLICT, "User name already exists, please choose a different user name!"); } $userdir = DAV::unslashify(BeeHub::$CONFIG['environment']['datadir']) . DIRECTORY_SEPARATOR . 'home' . DIRECTORY_SEPARATOR . $user_name; // Check for existing userdir if (file_exists($userdir)) { throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR); } // Store in the database $collection->insert(array('name' => $user_name)); // Fetch the user and store extra properties $user = DAV::$REGISTRY->resource(BeeHub::USERS_PATH . $user_name); $user->set_password($password); $user->user_set(DAV::PROP_DISPLAYNAME, $displayname); $user->user_set(BeeHub::PROP_EMAIL, $email); // Just to be clear: the above lines will have to be deleted somewhere in the future, but the lines below should stay $auth = BeeHub::getAuth(); if ($auth->simpleSaml()->isAuthenticated()) { $surfId = $auth->simpleSaml()->getAuthData("saml:sp:NameID"); $surfId = $surfId['Value']; $attributes = $auth->simpleSaml()->getAttributes(); $surfconext_description = @$attributes['urn:mace:terena.org:attribute-def:schacHomeOrganization'][0]; if (empty($surfconext_description)) { $surfconext_description = 'Unknown account'; } $user->user_set(BeeHub::PROP_SURFCONEXT, $surfId); $user->user_set(BeeHub::PROP_SURFCONEXT_DESCRIPTION, $surfconext_description); } $user->storeProperties(); // TODO: This should not be hard coded, a new user should not have a sponsor but request one after his account is created, but I want to inform the user about his through the not-yet-existing notification system $sponsor = DAV::$REGISTRY->resource('/system/sponsors/e-infra'); $sponsor->change_memberships($user_name, BeeHub_Sponsor::USER_ACCEPT); $sponsor->change_memberships($user_name, BeeHub_Sponsor::ADMIN_ACCEPT); // And create a user directory if (!mkdir($userdir)) { throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR); } // And create the directory in the database $document = array('path' => 'home/' . $user_name, 'depth' => 2, 'collection' => true); $filesCollection = BeeHub::getNoSQL()->files; $filesCollection->save($document); $userdir_resource = DAV::$REGISTRY->resource('/home/' . $user_name); $userdir_resource->user_set(DAV::PROP_OWNER, $user->path); // TODO: this should not be hard coded. When a users is accepted by his/her first sponsor, this should automatically be set. $userdir_resource->user_set(BeeHub::PROP_SPONSOR, '/system/sponsors/e-infra'); $userdir_resource->storeProperties(); // Show the confirmation $this->include_view('new_user_confirmation', array('email_address' => $email)); }
<tr> <th>Principal</th> <th>Permissions</th> <!-- Hidden dropdown column --> <th hidden="hidden"></th> <th>Comment</th> <!-- Move up --> <th class="bh-dir-small-column"></th> <!-- Move down --> <th class="bh-dir-small-column"></th> <!-- Delete row --> <th class="bh-dir-small-column"></th> </tr> </thead> <tbody class="bh-dir-acl-contents" data-value="<?php echo DAV::xmlescape(DAV::unslashify($this->path)); ?> "> <?php $acl = $this->user_prop_acl(); $acl_length = count($acl); for ($key = 0; $key < $acl_length; $key++) { $ace = $acl[$key]; // The protected property which grants everybody the 'DAV: unbind' privilege will be omitted from the list if ($ace->protected && $ace->principal === DAVACL::PRINCIPAL_ALL && !$ace->deny && count($ace->privileges) === 1 && in_array(DAVACL::PRIV_UNBIND, $ace->privileges)) { continue; } ?> <tr class="bh-dir-acl-row <?php echo $ace->protected || $ace->inherited ? 'info' : ''; ?>
/** * Determines whether the copy request is valid and if so, copies the resources * * @param DAV_Resource $resource * @return void * @throws DAV_Status */ protected function handle($resource) { $destination = $this->destination(); if ($resource instanceof DAV_Collection) { $destination = DAV::slashify($destination); } else { // The next line is here to make the litmus test succeed. The author of // litmus had eir own doubts wether this is actually desirable behaviour, // but chose to require this behaviour anyway: $destination = DAV::unslashify($destination); } // Can't move the root collection: if ($this instanceof DAV_Request_MOVE && '/' === DAV::getPath()) { throw new DAV_Status(DAV::HTTP_FORBIDDEN); } // Assert proper Depth: header value: if (DAV::DEPTH_1 === $this->depth() or $this instanceof DAV_Request_MOVE && DAV::DEPTH_INF !== $this->depth()) { throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'Illegal value for Depth: header.'); } // Check: Can't move a collection to one of its members. if ($this instanceof DAV_Request_MOVE && '/' === substr(DAV::getPath(), -1) && 0 === strpos($destination, DAV::getPath())) { throw new DAV_Status(DAV::HTTP_FORBIDDEN, "Can't move a collection to itself or one of its members."); } $resourceCollection = $resource->collection(); if ($this instanceof DAV_Request_MOVE) { $resourceCollection->assertLock(); $resource->assertLock(); $resource->assertMemberLocks(); } if ('/' !== $destination[0]) { // Copy to an external URI? $isCreated = $resource->method_COPY_external($destination, $this->overwrite()); if ($this instanceof DAV_Request_MOVE && !DAV_Multistatus::active()) { DAV_Request_DELETE::delete($resource); } if (DAV_Multistatus::active()) { DAV_Multistatus::inst()->close(); } elseif ($isCreated) { DAV::redirect(DAV::HTTP_CREATED, $destination); } else { DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); } return; } // Check: Won't move a resource to one of its parents. if (0 === strpos(DAV::slashify(DAV::getPath()), DAV::slashify($destination))) { throw new DAV_Status(DAV::HTTP_NOT_IMPLEMENTED, "Won't move or copy a resource to one of its parents."); } $destinationResource = DAV::$REGISTRY->resource($destination); $destinationCollection = DAV::$REGISTRY->resource(dirname($destination)); if (!$destinationCollection) { throw new DAV_Status(DAV::HTTP_CONFLICT, 'Unable to COPY to unexisting destination collection'); } if ($destinationResource) { if (!$this->overwrite()) { throw new DAV_Status(DAV::HTTP_PRECONDITION_FAILED); } else { $destinationResource->assertLock(); } } else { $destinationCollection->assertLock(); } if ($this instanceof DAV_Request_MOVE) { if (DAV::$LOCKPROVIDER) { foreach (DAV::$LOCKPROVIDER->memberLocks(DAV::getPath()) as $lock) { DAV::$LOCKPROVIDER->unlock($lock->lockroot); } if ($lock = DAV::$LOCKPROVIDER->getlock(DAV::getPath())) { DAV::$LOCKPROVIDER->unlock($lock->lockroot); } } $resourceCollection->method_MOVE(basename($resource->path), $destination); } else { $this->copy_recursively($resource, $destination); } #<<<<<<<< #// This version always returns a 207 Multistatus wrapper: #if (!DAV_Multistatus::active()) # if ( $destinationResource ) # DAV_Multistatus::inst()->addStatus( # $resource->path, # new DAV_Status( DAV::HTTP_NO_CONTENT ) # ); # else # DAV_Multistatus::inst()->addStatus( # $resource->path, # new DAV_Status( # DAV::HTTP_CREATED, DAV::path2uri($destination) # ) # ); #DAV_Multistatus::inst()->close(); #======== if (DAV_Multistatus::active()) { DAV_Multistatus::inst()->close(); } elseif ($destinationResource) { DAV::header(array('status' => DAV::HTTP_NO_CONTENT)); } else { DAV::redirect(DAV::HTTP_CREATED, $destination); } #>>>>>>>> }