public function keys() { $userID = $this->objectUserID; $key = $this->objectName; $this->allowMethods(['GET', 'POST', 'PUT', 'DELETE']); if ($this->method == 'GET') { // Single key if ($key) { $keyObj = Zotero_Keys::getByKey($key); if (!$keyObj) { $this->e404("Key not found"); } // /users/<userID>/keys/<keyID> (deprecated) if ($userID) { // If we have a userID, make sure it matches if ($keyObj->userID != $userID) { $this->e404("Key not found"); } } else { if ($this->apiVersion < 3) { $this->e404(); } } if ($this->apiVersion >= 3) { $json = $keyObj->toJSON(); // If not super-user, don't include name or recent IP addresses if (!$this->permissions->isSuper()) { unset($json['dateAdded']); unset($json['lastUsed']); unset($json['name']); unset($json['recentIPs']); } header('application/json'); echo Zotero_Utilities::formatJSON($json); } else { $this->responseXML = $keyObj->toXML(); // If not super-user, don't include name or recent IP addresses if (!$this->permissions->isSuper()) { unset($this->responseXML['dateAdded']); unset($this->responseXML['lastUsed']); unset($this->responseXML->name); unset($this->responseXML->recentIPs); } } } else { if (!$this->permissions->isSuper()) { $this->e403(); } $keyObjs = Zotero_Keys::getUserKeys($userID); if ($keyObjs) { if ($this->apiVersion >= 3) { $json = []; foreach ($keyObjs as $keyObj) { $json[] = $keyObj->toJSON(); } echo Zotero_Utilities::formatJSON($json); } else { $xml = new SimpleXMLElement('<keys/>'); $domXML = dom_import_simplexml($xml); foreach ($keyObjs as $keyObj) { $keyXML = $keyObj->toXML(); $domKeyXML = dom_import_simplexml($keyXML); $node = $domXML->ownerDocument->importNode($domKeyXML, true); $domXML->appendChild($node); } $this->responseXML = $xml; } } } } else { if ($this->method == 'DELETE') { if (!$key) { $this->e400("DELETE requests must end with a key"); } Zotero_DB::beginTransaction(); $keyObj = Zotero_Keys::getByKey($key); if (!$keyObj) { $this->e404("Key '{$key}' does not exist"); } $keyObj->erase(); Zotero_DB::commit(); header("HTTP/1.1 204 No Content"); exit; } else { // Require super-user for modifications if (!$this->permissions->isSuper()) { $this->e403(); } if ($this->method == 'POST') { if ($key) { $this->e400("POST requests cannot end with a key (did you mean PUT?)"); } if ($this->apiVersion >= 3) { $json = json_decode($this->body, true); if (!$json) { $this->e400("{$this->method} data is not valid JSON"); } if (!empty($json['key'])) { $this->e400("POST requests cannot contain a key in '" . $this->body . "'"); } $fields = $this->getFieldsFromJSON($json); } else { try { $keyXML = @new SimpleXMLElement($this->body); } catch (Exception $e) { $this->e400("{$this->method} data is not valid XML"); } if (!empty($key['key'])) { $this->e400("POST requests cannot contain a key in '" . $this->body . "'"); } $fields = $this->getFieldsFromKeyXML($keyXML); } Zotero_DB::beginTransaction(); try { $keyObj = new Zotero_Key(); $keyObj->userID = $userID; foreach ($fields as $field => $val) { if ($field == 'access') { foreach ($val as $access) { $this->setKeyPermissions($keyObj, $access); } } else { $keyObj->{$field} = $val; } } $keyObj->save(); } catch (Exception $e) { if ($e->getCode() == Z_ERROR_KEY_NAME_TOO_LONG) { $this->e400($e->getMessage()); } $this->handleException($e); } if ($this->apiVersion >= 3) { header('application/json'); echo Zotero_Utilities::formatJSON($keyObj->toJSON()); } else { $this->responseXML = $keyObj->toXML(); } Zotero_DB::commit(); $url = Zotero_API::getKeyURI($keyObj); $this->responseCode = 201; header("Location: " . $url, false, 201); } else { if ($this->method == 'PUT') { if (!$key) { $this->e400("PUT requests must end with a key (did you mean POST?)"); } if ($this->apiVersion >= 3) { $json = json_decode($this->body, true); if (!$json) { $this->e400("{$this->method} data is not valid JSON"); } $fields = $this->getFieldsFromJSON($json); } else { try { $keyXML = @new SimpleXMLElement($this->body); } catch (Exception $e) { $this->e400("{$this->method} data is not valid XML"); } $fields = $this->getFieldsFromKeyXML($keyXML); } // Key attribute is optional, but, if it's there, make sure it matches if (isset($fields['key']) && $fields['key'] != $key) { $this->e400("Key '{$fields['key']}' does not match key '{$key}' from URI"); } Zotero_DB::beginTransaction(); try { $keyObj = Zotero_Keys::getByKey($key); if (!$keyObj) { $this->e404("Key '{$key}' does not exist"); } foreach ($fields as $field => $val) { if ($field == 'access') { foreach ($val as $access) { $this->setKeyPermissions($keyObj, $access); } } else { $keyObj->{$field} = $val; } } $keyObj->save(); } catch (Exception $e) { if ($e->getCode() == Z_ERROR_KEY_NAME_TOO_LONG) { $this->e400($e->getMessage()); } $this->handleException($e); } if ($this->apiVersion >= 3) { echo Zotero_Utilities::formatJSON($keyObj->toJSON()); } else { $this->responseXML = $keyObj->toXML(); } Zotero_DB::commit(); } } } } if ($this->apiVersion >= 3) { $this->end(); } else { header('Content-Type: application/xml'); $xmlstr = $this->responseXML->asXML(); $doc = new DOMDocument('1.0'); $doc->loadXML($xmlstr); $doc->formatOutput = true; echo $doc->saveXML(); exit; } }
/** * Used for integration tests * * Valid only on testing site */ public function testSetup() { if (!$this->permissions->isSuper()) { $this->e404(); } if (!Z_ENV_TESTING_SITE) { $this->e404(); } $this->allowMethods(['POST']); if (empty($_GET['u'])) { throw new Exception("User not provided (e.g., ?u=1)"); } $userID = $_GET['u']; // Clear keys $keys = Zotero_Keys::getUserKeys($userID); foreach ($keys as $keyObj) { $keyObj->erase(); } $keys = Zotero_Keys::getUserKeys($userID); if ($keys) { throw new Exception("Keys still exist"); } // Create new key $keyObj = new Zotero_Key(); $keyObj->userID = $userID; $keyObj->name = "Tests Key"; $libraryID = Zotero_Users::getLibraryIDFromUserID($userID); $keyObj->setPermission($libraryID, 'library', true); $keyObj->setPermission($libraryID, 'notes', true); $keyObj->setPermission($libraryID, 'write', true); $keyObj->setPermission(0, 'group', true); $keyObj->setPermission(0, 'write', true); $keyObj->save(); $key = $keyObj->key; Zotero_DB::beginTransaction(); // Clear data Zotero_Users::clearAllData($userID); // Delete publications library, so we can test auto-creating it $publicationsLibraryID = Zotero_Users::getLibraryIDFromUserID($userID, 'publications'); if ($publicationsLibraryID) { // Delete user publications shard library $sql = "DELETE FROM shardLibraries WHERE libraryID=?"; Zotero_DB::query($sql, $publicationsLibraryID, Zotero_Shards::getByUserID($userID)); // Delete user publications library $sql = "DELETE FROM libraries WHERE libraryID=?"; Zotero_DB::query($sql, $publicationsLibraryID); Z_Core::$MC->delete('userPublicationsLibraryID_' . $userID); Z_Core::$MC->delete('libraryUserID_' . $publicationsLibraryID); } Zotero_DB::commit(); echo json_encode(["apiKey" => $key]); $this->end(); }
/** * Used for integration tests * * Valid only on testing site */ public function testSetup() { if (!$this->permissions->isSuper()) { $this->e404(); } if (!Z_ENV_TESTING_SITE) { $this->e404(); } if (empty($_GET['u'])) { throw new Exception("User not provided (e.g., ?u=1)"); } $userID = $_GET['u']; // Clear keys $keys = Zotero_Keys::getUserKeys($userID); foreach ($keys as $keyObj) { $keyObj->erase(); } $keys = Zotero_Keys::getUserKeys($userID); if ($keys) { throw new Exception("Keys still exist"); } // Clear data Zotero_Users::clearAllData($userID); $this->responseXML = new SimpleXMLElement("<ok/>"); $this->end(); }
public function save() { if (!$this->loaded) { Z_Core::debug("Not saving unloaded group {$this->id}"); return; } if (empty($this->changed)) { Z_Core::debug("Group {$this->id} has not changed", 4); return; } if (!$this->ownerUserID) { throw new Exception("Cannot save group without owner"); } if (!$this->name) { throw new Exception("Cannot save group without name"); } if (mb_strlen($this->description) > 1024) { throw new Exception("Group description too long", Z_ERROR_GROUP_DESCRIPTION_TOO_LONG); } Zotero_DB::beginTransaction(); $libraryID = $this->libraryID; if (!$libraryID) { $shardID = Zotero_Shards::getNextShard(); $libraryID = Zotero_Libraries::add('group', $shardID); if (!$libraryID) { throw new Exception('libraryID not available after Zotero_Libraries::add()'); } } $fields = array('name', 'slug', 'type', 'description', 'url', 'hasImage'); if ($this->isPublic()) { $existing = Zotero_Groups::publicNameExists($this->name); if ($existing && $existing != $this->id) { throw new Exception("Public group with name '{$this->name}' already exists", Z_ERROR_PUBLIC_GROUP_EXISTS); } } $fields = array_merge($fields, array('libraryEditing', 'libraryReading', 'fileEditing')); $sql = "INSERT INTO groups\n\t\t\t\t\t(groupID, libraryID, " . implode(", ", $fields) . ", dateModified)\n\t\t\t\t\tVALUES (?, ?, " . implode(", ", array_fill(0, sizeOf($fields), "?")) . ", CURRENT_TIMESTAMP)"; $params = array($this->id, $libraryID); foreach ($fields as $field) { if (is_bool($this->{$field})) { $params[] = (int) $this->{$field}; } else { $params[] = $this->{$field}; } } $sql .= " ON DUPLICATE KEY UPDATE "; $q = array(); foreach ($fields as $field) { $q[] = "{$field}=?"; if (is_bool($this->{$field})) { $params[] = (int) $this->{$field}; } else { $params[] = $this->{$field}; } } $sql .= implode(", ", $q) . ", " . "dateModified=CURRENT_TIMESTAMP, " . "version=IF(version = 255, 1, version + 1)"; $insertID = Zotero_DB::query($sql, $params); if (!$this->id) { if (!$insertID) { throw new Exception("Group id not available after INSERT"); } $this->id = $insertID; } if (!$this->libraryID) { $this->libraryID = $libraryID; } // If creating group or changing owner if (!empty($this->changed['ownerUserID'])) { $sql = "SELECT userID FROM groupUsers WHERE groupID=? AND role='owner'"; $currentOwner = Zotero_DB::valueQuery($sql, $this->id); $newOwner = $this->ownerUserID; // Move existing owner out of the way, if there is one if ($currentOwner) { $sql = "UPDATE groupUsers SET role='admin' WHERE groupID=? AND userID=?"; Zotero_DB::query($sql, array($this->id, $currentOwner)); } // Make sure new owner exists in DB if (!Zotero_Users::exists($newOwner)) { Zotero_Users::addFromWWW($newOwner); } // Add new owner to group $sql = "INSERT INTO groupUsers (groupID, userID, role, joined) VALUES\n\t\t\t\t\t(?, ?, 'owner', CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE\n\t\t\t\t\trole='owner', lastUpdated=CURRENT_TIMESTAMP"; Zotero_DB::query($sql, array($this->id, $newOwner)); // Delete any record of this user losing access to the group $libraryID = Zotero_Users::getLibraryIDFromUserID($this->ownerUserID); $sql = "DELETE FROM syncDeleteLogIDs WHERE libraryID=? AND objectType='group' AND id=?"; Zotero_DB::query($sql, array($libraryID, $this->id), Zotero_Shards::getByLibraryID($this->libraryID)); // Send library removal notification for all API keys belonging to the former owner // with access to this group $apiKeys = Zotero_Keys::getUserKeysWithLibrary($currentOwner, $this->libraryID); Zotero_Notifier::trigger('remove', 'apikey-library', array_map(function ($key) { return $key->key . "-" . $this->libraryID; }, $apiKeys)); // Send library add notification for all API keys belonging to the new owner // with access to this group $apiKeys = Zotero_Keys::getUserKeysWithLibrary($newOwner, $this->libraryID); Zotero_Notifier::trigger('add', 'apikey-library', array_map(function ($key) { return $key->key . "-" . $this->libraryID; }, $apiKeys)); } // If any of the group's users have a queued upload, flag group for a timestamp // update once the sync is done so that the uploading user gets the change try { $userIDs = self::getUsers(); foreach ($userIDs as $userID) { if ($syncUploadQueueID = Zotero_Sync::getUploadQueueIDByUserID($userID)) { Zotero_Sync::postWriteLog($syncUploadQueueID, 'group', $this->id, 'update'); } } } catch (Exception $e) { Z_Core::logError($e); } Zotero_DB::commit(); $this->load(); return $libraryID; }
public function save() { if (!$this->loaded) { Z_Core::debug("Not saving unloaded key {$this->id}"); return; } if (!$this->userID) { throw new Exception("Cannot save key without userID"); } if (!$this->name) { throw new Exception("Cannot save key without name"); } if (strlen($this->name) > 255) { throw new Exception("Key name too long", Z_ERROR_KEY_NAME_TOO_LONG); } Zotero_DB::beginTransaction(); if (!$this->key) { $isNew = true; $this->key = Zotero_Keys::generate(); } else { $isNew = false; } $fields = array('key', 'userID', 'name'); $sql = "INSERT INTO `keys` (keyID, `key`, userID, name) VALUES (?, ?, ?, ?)"; $params = array($this->id); foreach ($fields as $field) { $params[] = $this->{$field}; } $sql .= " ON DUPLICATE KEY UPDATE "; $q = array(); foreach ($fields as $field) { $q[] = "`{$field}`=?"; $params[] = $this->{$field}; } $sql .= implode(", ", $q); $insertID = Zotero_DB::query($sql, $params); if (!$this->id) { if (!$insertID) { throw new Exception("Key id not available after INSERT"); } $this->id = $insertID; } if (!$insertID) { $sql = "SELECT * FROM keyPermissions WHERE keyID=?"; $oldRows = Zotero_DB::query($sql, $this->id); } $oldPermissions = []; $newPermissions = []; $librariesToAdd = []; $librariesToRemove = []; // Massage rows into permissions format if (!$isNew && isset($oldRows)) { foreach ($oldRows as $row) { $oldPermissions[$row['libraryID']][$row['permission']] = !!$row['granted']; } } // Delete existing permissions $sql = "DELETE FROM keyPermissions WHERE keyID=?"; Zotero_DB::query($sql, $this->id); if (isset($this->changed['permissions'])) { foreach ($this->changed['permissions'] as $libraryID => $p) { foreach ($p as $permission => $changed) { $enabled = $this->permissions[$libraryID][$permission]; if (!$enabled) { continue; } $sql = "INSERT INTO keyPermissions VALUES (?, ?, ?, ?)"; // TODO: support negative permissions Zotero_DB::query($sql, array($this->id, $libraryID, $permission, 1)); $newPermissions[$libraryID][$permission] = true; } } } $this->permissions = $newPermissions; // Send notifications for added and removed API key – library pairs if (!$isNew) { $librariesToAdd = $this->permissionsDiff($oldPermissions, $newPermissions, $this->userID); $librariesToRemove = $this->permissionsDiff($newPermissions, $oldPermissions, $this->userID); if ($librariesToAdd) { Zotero_Notifier::trigger('add', 'apikey-library', array_map(function ($libraryID) { return $this->key . "-" . $libraryID; }, array_unique($librariesToAdd))); } if ($librariesToRemove) { Zotero_Notifier::trigger('remove', 'apikey-library', array_map(function ($libraryID) { return $this->key . "-" . $libraryID; }, array_unique($librariesToRemove))); } } Zotero_DB::commit(); $this->load(); return $this->id; }
public function save() { if (!$this->loaded) { Z_Core::debug("Not saving unloaded key {$this->id}"); return; } if (!$this->userID) { throw new Exception("Cannot save key without userID"); } if (!$this->name) { throw new Exception("Cannot save key without name"); } if (strlen($this->name) > 255) { throw new Exception("Key name too long", Z_ERROR_KEY_NAME_TOO_LONG); } Zotero_DB::beginTransaction(); if (!$this->key) { $this->key = Zotero_Keys::generate(); } $fields = array('key', 'userID', 'name'); $sql = "INSERT INTO `keys` (keyID, `key`, userID, name) VALUES (?, ?, ?, ?)"; $params = array($this->id); foreach ($fields as $field) { $params[] = $this->{$field}; } $sql .= " ON DUPLICATE KEY UPDATE "; $q = array(); foreach ($fields as $field) { $q[] = "`{$field}`=?"; $params[] = $this->{$field}; } $sql .= implode(", ", $q); $insertID = Zotero_DB::query($sql, $params); if (!$this->id) { if (!$insertID) { throw new Exception("Key id not available after INSERT"); } $this->id = $insertID; } // Delete existing permissions $sql = "DELETE FROM keyPermissions WHERE keyID=?"; Zotero_DB::query($sql, $this->id); if (isset($this->changed['permissions'])) { foreach ($this->changed['permissions'] as $libraryID => $p) { foreach ($p as $permission => $changed) { $enabled = $this->permissions[$libraryID][$permission]; if (!$enabled) { continue; } $sql = "INSERT INTO keyPermissions VALUES (?, ?, ?, ?)"; // TODO: support negative permissions Zotero_DB::query($sql, array($this->id, $libraryID, $permission, 1)); } } } Zotero_DB::commit(); $this->load(); return $this->id; }