/** * Creates a new observer for lock refreshs. * * This observer collects the base for all affected locks of a request and * creates PROPPATCH requests to update the affected locks. * * The PROPPATCH requests can be obtained after collecting, using the * {@link getRequests()} or can be send using the {@link sendRequests()} * method. * * @param ezcWebdavRequest $request * @param int $timeout * @return void */ public function __construct(ezcWebdavRequest $request, $timeout = null) { $this->issuingRequest = $request; $this->ifHeader = $request->getHeader('If'); $this->affectedTokens = $this->ifHeader === null ? array() : $this->ifHeader->getLockTokens(); $this->timeout = $timeout; }
/** * Handles DELETE requests. * * Performs all lock related checks necessary for the DELETE request. In case * a violation with locks is detected or any other pre-condition check * fails, this method returns an instance of {@link ezcWebdavResponse}. If * everything is correct, null is returned, so that the $request is handled * by the backend. * * @param ezcWebdavRequest $request ezcWebdavDeleteRequest * @return ezcWebdavResponse|null */ public function receivedRequest(ezcWebdavRequest $request) { $ifHeader = $request->getHeader('If'); $targetLockRefresher = null; if ($ifHeader !== null) { $targetLockRefresher = new ezcWebdavLockRefreshRequestGenerator($request); } $violation = $this->tools->checkViolations(new ezcWebdavLockCheckInfo($request->requestUri, ezcWebdavRequest::DEPTH_INFINITY, $request->getHeader('If'), $request->getHeader('Authorization'), ezcWebdavAuthorizer::ACCESS_WRITE, $targetLockRefresher), true); // Lock refresh must occur no matter if the request succeeds if ($targetLockRefresher !== null) { $targetLockRefresher->sendRequests(); } if ($violation !== null) { // ezcWebdavErrorResponse return $violation; } }
/** * Clones headers in $from to headers in $to. * * Clones all headers with names given in $heades from the request $from to * the request in $to. In case $defaultHeaders is set to true, the headers * mentioned in {@link $defaultCloneHeaders} are cloned in addition. * * Note, that this method does not call {@link * ezcWebdavRequest::validateHeaders()}, since headers in $to might still * be incomplete. You need to call this method manually, before sending $to * to the backend or accessing its headers for reading. * * @param ezcWebdavRequest $from * @param ezcWebdavRequest $to * @param array $headers * @param bool $defaultHeaders */ public static function cloneRequestHeaders(ezcWebdavRequest $from, ezcWebdavRequest $to, $headers = array(), $defaultHeaders = true) { if ($defaultHeaders) { $headers = array_merge(self::$defaultCloneHeaders, $headers); $headers = array_unique($headers); } foreach ($headers as $headerName) { $to->setHeader($headerName, $from->getHeader($headerName)); } }
/** * Handles COPY requests. * * Performs all lock related checks necessary for the COPY request. In case * a violation with locks is detected or any other pre-condition check * fails, this method returns an instance of {@link ezcWebdavResponse}. If * everything is correct, null is returned, so that the $request is handled * by the backend. * * @param ezcWebdavRequest $request ezcWebdavCopyRequest * @return ezcWebdavResponse|null */ public function receivedRequest(ezcWebdavRequest $request) { $backend = ezcWebdavServer::getInstance()->backend; $this->request = $request; $destination = $request->getHeader('Destination'); $destParent = dirname($destination); $ifHeader = $request->getHeader('If'); $authHeader = $request->getHeader('Authorization'); // Check destination parent and collect the lock properties to // set after successfully moving $destinationLockRefresher = new ezcWebdavLockRefreshRequestGenerator($request); $violation = $this->tools->checkViolations(new ezcWebdavLockCheckInfo($destParent, ezcWebdavRequest::DEPTH_ZERO, $ifHeader, $authHeader, ezcWebdavAuthorizer::ACCESS_WRITE, $destinationLockRefresher), true); if ($violation !== null) { if ($violation->status === ezcWebdavResponse::STATUS_404) { // Destination parent not found return new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_409, $violation->requestUri); } return $violation; } // Check destination itself, if it exsists $violation = $this->tools->checkViolations(new ezcWebdavLockCheckInfo($destination, ezcWebdavRequest::DEPTH_INFINITY, $ifHeader, $authHeader, ezcWebdavAuthorizer::ACCESS_WRITE, $destinationLockRefresher), true); // Destination might be there but not violated, or might not be there if ($violation !== null && $violation->status !== ezcWebdavResponse::STATUS_404) { // ezcWebdavErrorResponse return $violation; } // Perform lock refresh (must occur no matter if request succeeds) $destinationLockRefresher->sendRequests(); // Store infos for use on correct moving // @TODO: Do we always get the correct property here? $this->lockDiscoveryProp = $destinationLockRefresher->getLockDiscoveryProperty($destParent); $sourcePaths = $this->getSourcePaths(); if (is_object($sourcePaths)) { // ezcWebdavErrorResponse return $sourcePaths; } $this->sourcePaths = $sourcePaths; // Backend now handles the request return null; }
/** * Handles MKCOL requests. * * Performs all lock related checks necessary for the MKCOL request. In * case a violation with locks is detected or any other pre-condition check * fails, this method returns an instance of {@link ezcWebdavResponse}. If * everything is correct, null is returned, so that the $request is handled * by the backend. * * @param ezcWebdavRequest $request ezcWebdavMakeCollectionRequest * @return ezcWebdavResponse|null */ public function receivedRequest(ezcWebdavRequest $request) { $this->request = $request; $target = $request->requestUri; $parent = dirname($target); $ifHeader = $request->getHeader('If'); $authHeader = $request->getHeader('Authorization'); $targetLockRefresher = null; if ($ifHeader !== null) { $targetLockRefresher = new ezcWebdavLockRefreshRequestGenerator($request); } $violation = $this->tools->checkViolations(new ezcWebdavLockCheckInfo($target, ezcWebdavRequest::DEPTH_ZERO, $ifHeader, $authHeader, ezcWebdavAuthorizer::ACCESS_WRITE, $targetLockRefresher), true); if ($violation !== null && $violation->status !== ezcWebdavResponse::STATUS_404) { // Desired collection exists and conditions are violated return $violation; } if ($violation !== null && $violation->status === ezcWebdavResponse::STATUS_404) { // Desired collection does not exist, check parent $violation = $this->tools->checkViolations(new ezcWebdavLockCheckInfo($parent, ezcWebdavRequest::DEPTH_ZERO, $ifHeader, $authHeader, ezcWebdavAuthorizer::ACCESS_WRITE, $targetLockRefresher), true); if ($violation !== null) { if ($violation->status === ezcWebdavResponse::STATUS_404) { // The parent does not exist, not the target. $violation->status = ezcWebdavResponse::STATUS_409; } return $violation; } } // Lock refresh must occur no matter if the request succeeds if ($targetLockRefresher !== null) { $targetLockRefresher->sendRequests(); // Store property for later patching $this->lockDiscoveryProp = $targetLockRefresher->getLockDiscoveryProperty($target); if ($this->lockDiscoveryProp === null) { $this->lockDiscoveryProp = $targetLockRefresher->getLockDiscoveryProperty($parent); $this->isParentProp = true; } } return null; }
/** * Handles PROPPATCH requests. * * Performs all lock related checks necessary for the PROPPATCH request. In * case a violation with locks is detected or any other pre-condition check * fails, this method returns an instance of {@link ezcWebdavResponse}. If * everything is correct, null is returned, so that the $request is handled * by the backend. * * @param ezcWebdavPropPatchRequest $request * @return ezcWebdavResponse */ public function receivedRequest(ezcWebdavRequest $request) { $ifHeader = $request->getHeader('If'); $targetLockRefresher = null; if ($ifHeader !== null) { $targetLockRefresher = new ezcWebdavLockRefreshRequestGenerator($request); } $violation = $this->tools->checkViolations(new ezcWebdavLockCheckInfo($request->requestUri, ezcWebdavRequest::DEPTH_ZERO, $request->getHeader('If'), $request->getHeader('Authorization'), ezcWebdavAuthorizer::ACCESS_WRITE, $targetLockRefresher, false), true); if ($violation !== null) { // ezcWebdavErrorResponse return $violation; } // Lock refresh must occur no matter if the request succeeds if ($targetLockRefresher !== null) { $targetLockRefresher->sendRequests(); } if ($request->updates->contains('lockdiscovery')) { return new ezcWebdavMultistatusResponse(new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_409, $request->requestUri, "Property 'lockdiscovery' is readonly.")); } if ($request->updates->contains('lockinfo')) { return new ezcWebdavMultistatusResponse(new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_409, $request->requestUri, "Property 'lockinfo' is readonly.")); } }
/** * Handles UNLOCK requests. * * This method determines the base of the lock determined by the Lock-Token * header of $request and releases the lock from all locked resources. In * case a lock null resource is beyond these, it will be deleted. * * @param ezcWebdavRequest $request ezcWebdavUnlockRequest * @return ezcWebdavResponse */ public function receivedRequest(ezcWebdavRequest $request) { $srv = ezcWebdavServer::getInstance(); $token = $request->getHeader('Lock-Token'); $authHeader = $request->getHeader('Authorization'); if ($token === null) { // UNLOCK must have a lock token return new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_412); } // Check permission if (!$srv->isAuthorized($request->requestUri, $authHeader, ezcWebdavAuthorizer::ACCESS_WRITE) || !$srv->auth->ownsLock($authHeader->username, $token)) { return $srv->createUnauthorizedResponse($request->requestUri, 'Authorization failed.'); } // Find properties to determine lock base $propFindReq = new ezcWebdavPropFindRequest($request->requestUri); $propFindReq->prop = new ezcWebdavBasicPropertyStorage(); $propFindReq->prop->attach(new ezcWebdavLockDiscoveryProperty()); ezcWebdavLockTools::cloneRequestHeaders($request, $propFindReq); $propFindReq->setHeader('Depth', ezcWebdavRequest::DEPTH_ZERO); $propFindReq->validateHeaders(); $propFindMultistatusRes = $srv->backend->propFind($propFindReq); if (!$propFindMultistatusRes instanceof ezcWebdavMultistatusResponse) { return $propFindMultistatusRes; } $lockDiscoveryProp = null; foreach ($propFindMultistatusRes->responses as $propFindRes) { foreach ($propFindRes->responses as $propStatRes) { if ($propStatRes->storage->contains('lockdiscovery')) { $lockDiscoveryProp = clone $propStatRes->storage->get('lockdiscovery'); } } } if ($lockDiscoveryProp === null) { // Lock was not found (purged?)! Finish successfully. return new ezcWebdavResponse(ezcWebdavResponse::STATUS_204); } $affectedActiveLock = null; foreach ($lockDiscoveryProp->activeLock as $id => $activeLock) { // Note the ==, sinde $activeLock->token is an instance of // ezcWebdavPotentialUriContent if ($activeLock->token == $token) { $affectedActiveLock = $activeLock; break; } } if ($affectedActiveLock === null) { // Lock not present (purged)! Finish successfully. return new ezcWebdavUnlockResponse(ezcWebdavResponse::STATUS_204); } if ($affectedActiveLock->baseUri !== null) { // Requested resource is not the lock base, recurse $newRequest = new ezcWebdavUnlockRequest($affectedActiveLock->baseUri); ezcWebdavLockTools::cloneRequestHeaders($request, $newRequest, array('If', 'Lock-Token')); $newRequest->validateHeaders(); // @TODO Should be protected against infinite recursion return $this->receivedRequest($newRequest); } // If lock depth is 0, we issue 1 propfind too much here // @TODO: Analyse if clients usually lock 0 or infinity $res = $this->performUnlock($request, $token, $affectedActiveLock->depth); if ($res instanceof ezcWebdavUnlockResponse) { $srv->auth->releaseLock($authHeader->username, $token); } return $res; }
/** * Performs authentication and authorization. * * @param ezcWebdavRequest $req * @return ezcWebdavErrorResponse|null */ private function authenticate(ezcWebdavRequest $req) { if ($this->properties['auth'] === null) { // No authentication return null; } $creds = $req->getHeader('Authorization'); $res = null; // Authenticate user switch (get_class($creds)) { case 'ezcWebdavAnonymousAuth': if ($this->properties['auth'] instanceof ezcWebdavAnonymousAuthenticator) { $res = $this->properties['auth']->authenticateAnonymous($creds); } break; case 'ezcWebdavBasicAuth': if ($this->properties['auth'] instanceof ezcWebdavBasicAuthenticator) { $res = $this->properties['auth']->authenticateBasic($creds); } break; case 'ezcWebdavDigestAuth': if ($this->properties['auth'] instanceof ezcWebdavDigestAuthenticator) { $res = $this->properties['auth']->authenticateDigest($creds); } break; } // $res is now null or bool, if not evaluates to true, authentication failed if (!$res) { return $this->createUnauthenticatedResponse($req->requestUri, 'Authentication failed.'); } return null; }
/** * Recursively checks authorization for the COPY, MOVE and other requests. * * This method performs a recursive authorization check on the given $path * using the credentials provided in $request. It returns a * multidimensional array, indicating the authorization errors occurred and * the paths that may by copied. * * The structure looks like this: * <code> * array( * 'errors' => array( * ezcWebdavErrorResponse(), * ezcWebdavErrorResponse(), * // ... * ) * 'paths' => array( * '/some/path' => ezcWebdavRequest::DEPTH_INFINITY, * '/some/other/path' => ezcWebdavRequest::DEPTH_ZERO, * // ... * ) * ) * </code> * * The 'errors' key is assigned to an array of authorization error * responses that will be merged to the ezcWebdavMultistatusResponse * returned by the copy() method. The 'paths' array contains all paths that * may be copied by the method. A path is assigned to the depth that it * might be copied. The depth can be {@link * ezcWebdavRequest::DEPTH_INFINITY} to indicate that a complete sub tree * is save for copying, or {@link ezcWebdavRequest::DEPTH_ZERO}, to * indicate that only the path itself may be copied, but none of its * descendants. * * The $access parameter specifies which permission is to be checked {@link * ezcWebdavAuthorizer::ACCESS_READ} is the default, {@link * ezcWebdavAuthorizer::ACCESS_WRITE} may be set to indicate write * permissions. * * If the $breakOnError parameter is set to true, no further checks will be * applied to sibling resources, but the method will instantly return. This * parameter is set to true for the MOVE request, since this request must * be processed completly or not at all. The COPY request in contrast may * also be processed partially, so this parameter is left as is. * * @param ezcWebdavRequest $request * @param string $path * @param int $access * @param bool $breakOnError * @return array * * @todo Mark protected as soon as API is final. */ private function recursiveAuthCheck(ezcWebdavRequest $request, $path, $access = ezcWebdavAuthorizer::ACCESS_WRITE, $breakOnError = false) { $result = array('errors' => array(), 'paths' => array()); // Check auth for collections and resources equally if (!ezcWebdavServer::getInstance()->isAuthorized($path, $request->getHeader('Authorization'), $access)) { $result['errors'][] = $this->createUnauthorizedResponse($path, $request->getHeader('Authorization')); } else { if ($this->isCollection($path)) { foreach ($this->getCollectionMembers($path) as $member) { $tmpRes = $this->recursiveAuthCheck($request, $member->path, $access); if (count($tmpRes['errors']) !== 0) { if ($breakOnError) { return $tmpRes; } $result['errors'] = array_merge($result['errors'], $tmpRes['errors']); $result['paths'] = array_merge($result['paths'], $tmpRes['paths']); } } $result['paths'][$path] = count($result['errors']) ? ezcWebdavRequest::DEPTH_ZERO : ezcWebdavRequest::DEPTH_INFINITY; } else { // Only a resource, so depth infinity does not make sense $result['paths'][$path] = ezcWebdavRequest::DEPTH_ZERO; } } return $result; }