279 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			279 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace Core\API {
 | |
| 
 | |
|   use Core\Objects\Context;
 | |
| 
 | |
|   abstract class GpgKeyAPI extends \Core\API\Request {
 | |
|     public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
 | |
|       parent::__construct($context, $externalCall, $params);
 | |
|       $this->loginRequired = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace Core\API\GpgKey {
 | |
| 
 | |
| 
 | |
|   use Core\API\GpgKeyAPI;
 | |
|   use Core\API\Parameter\Parameter;
 | |
|   use Core\API\Parameter\StringType;
 | |
|   use Core\API\Template\Render;
 | |
|   use Core\API\Traits\GpgKeyValidation;
 | |
|   use Core\Driver\SQL\Condition\Compare;
 | |
|   use Core\Objects\Context;
 | |
|   use Core\Objects\DatabaseEntity\GpgKey;
 | |
|   use Core\Objects\DatabaseEntity\User;
 | |
|   use Core\Objects\DatabaseEntity\UserToken;
 | |
| 
 | |
|   class Import extends GpgKeyAPI {
 | |
| 
 | |
|     use GpgKeyValidation;
 | |
| 
 | |
|     public function __construct(Context $context, bool $externalCall = false) {
 | |
|       parent::__construct($context, $externalCall, [
 | |
|         "publicKey" => new StringType("publicKey")
 | |
|       ]);
 | |
|       $this->loginRequired = true;
 | |
|       $this->forbidMethod("GET");
 | |
|     }
 | |
| 
 | |
|     public function _execute(): bool {
 | |
| 
 | |
|       $currentUser = $this->context->getUser();
 | |
|       $gpgKey = $currentUser->getGPG();
 | |
|       if ($gpgKey) {
 | |
|         return $this->createError("You already added a GPG key to your account.");
 | |
|       } else if (!$currentUser->getEmail()) {
 | |
|         return $this->createError("You do not have an e-mail address");
 | |
|       }
 | |
| 
 | |
|       // fix key first, enforce a newline after
 | |
|       $keyString = $this->formatKey($this->getParam("publicKey"));
 | |
|       $keyData = $this->testKey($keyString);
 | |
|       if ($keyData === false) {
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       $res = GpgKey::importKey($keyString);
 | |
|       if (!$res["success"]) {
 | |
|         return $this->createError($res["error"]);
 | |
|       }
 | |
| 
 | |
|       $sql = $this->context->getSQL();
 | |
|       $gpgKey = new GpgKey($keyData["fingerprint"], $keyData["algorithm"], $keyData["expires"]);
 | |
|       if (!$gpgKey->save($sql)) {
 | |
|         return $this->createError("Error creating gpg key: " . $sql->getLastError());
 | |
|       }
 | |
| 
 | |
|       $token = generateRandomString(36);
 | |
|       $userToken = new UserToken($currentUser, $token, UserToken::TYPE_GPG_CONFIRM, 1);
 | |
|       if (!$userToken->save($sql)) {
 | |
|         return $this->createError("Error saving user token: " . $sql->getLastError());
 | |
|       }
 | |
| 
 | |
|       $validHours = 1;
 | |
|       $settings = $this->context->getSettings();
 | |
|       $baseUrl = $settings->getBaseUrl();
 | |
|       $siteName = $settings->getSiteName();
 | |
|       $req = new Render($this->context);
 | |
|       $this->success = $req->execute([
 | |
|         "file" => "mail/gpg_import.twig",
 | |
|         "parameters" => [
 | |
|           "link" => "$baseUrl/confirmGPG?token=$token",
 | |
|           "site_name" => $siteName,
 | |
|           "base_url" => $baseUrl,
 | |
|           "username" => $currentUser->getDisplayName(),
 | |
|           "valid_time" => $this->formatDuration($validHours, "hour")
 | |
|         ]
 | |
|       ]);
 | |
| 
 | |
|       $this->lastError = $req->getLastError();
 | |
| 
 | |
|       if ($this->success) {
 | |
|         $messageBody = $req->getResult()["html"];
 | |
|         $sendMail = new \Core\API\Mail\Send($this->context);
 | |
|         $this->success = $sendMail->execute(array(
 | |
|           "to" => $currentUser->getEmail(),
 | |
|           "subject" => "[$siteName] Confirm GPG-Key",
 | |
|           "body" => $messageBody,
 | |
|           "gpgFingerprint" => $gpgKey->getFingerprint()
 | |
|         ));
 | |
| 
 | |
|         $this->lastError = $sendMail->getLastError();
 | |
| 
 | |
|         if ($this->success) {
 | |
|           $currentUser->gpgKey = $gpgKey;
 | |
|           if ($currentUser->save($sql, ["gpgKey"])) {
 | |
|             $this->result["gpgKey"] = $gpgKey->jsonSerialize();
 | |
|           } else {
 | |
|             return $this->createError("Error updating user details: " . $sql->getLastError());
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return $this->success;
 | |
|     }
 | |
| 
 | |
|     public static function getDescription(): string {
 | |
|       return "Allows users to import gpg keys for a secure e-mail communication";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   class Remove extends GpgKeyAPI {
 | |
|     public function __construct(Context $context, bool $externalCall = false) {
 | |
|       parent::__construct($context, $externalCall, array(
 | |
|         "password" => new StringType("password")
 | |
|       ));
 | |
|       $this->loginRequired = true;
 | |
|       $this->forbidMethod("GET");
 | |
|     }
 | |
| 
 | |
|     public function _execute(): bool {
 | |
| 
 | |
|       $currentUser = $this->context->getUser();
 | |
|       $gpgKey = $currentUser->getGPG();
 | |
|       if (!$gpgKey) {
 | |
|         return $this->createError("You have not added a GPG public key to your account yet.");
 | |
|       }
 | |
| 
 | |
|       $sql = $this->context->getSQL();
 | |
|       $password = $this->getParam("password");
 | |
|       if (!password_verify($password, $currentUser->password)) {
 | |
|         return $this->createError("Incorrect password.");
 | |
|       } else if (!$gpgKey->delete($sql)) {
 | |
|         return $this->createError("Error deleting gpg key: " . $sql->getLastError());
 | |
|       }
 | |
| 
 | |
|       return $this->success;
 | |
|     }
 | |
| 
 | |
|     public static function getDescription(): string {
 | |
|       return "Allows users to unlink gpg keys from their profile";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   class Confirm extends GpgKeyAPI {
 | |
| 
 | |
|     public function __construct(Context $context, bool $externalCall = false) {
 | |
|       parent::__construct($context, $externalCall, [
 | |
|         "token" => new StringType("token", 36)
 | |
|       ]);
 | |
|       $this->loginRequired = true;
 | |
|     }
 | |
| 
 | |
|     public function _execute(): bool {
 | |
| 
 | |
|       $currentUser = $this->context->getUser();
 | |
|       $gpgKey = $currentUser->getGPG();
 | |
|       if (!$gpgKey) {
 | |
|         return $this->createError("You have not added a GPG key yet");
 | |
|       } else if ($gpgKey->isConfirmed()) {
 | |
|         return $this->createError("Your GPG key is already confirmed");
 | |
|       }
 | |
| 
 | |
|       $token = $this->getParam("token");
 | |
|       $sql = $this->context->getSQL();
 | |
| 
 | |
|       $userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
 | |
|         ->whereEq("token", hash("sha512", $token, false))
 | |
|         ->where(new Compare("valid_until", $sql->now(), ">="))
 | |
|         ->whereEq("user_id", $currentUser->getId())
 | |
|         ->whereEq("token_type", UserToken::TYPE_GPG_CONFIRM));
 | |
| 
 | |
|       if ($userToken !== false) {
 | |
|         if ($userToken === null) {
 | |
|           return $this->createError("Invalid token");
 | |
|         } else {
 | |
|           if (!$gpgKey->confirm($sql)) {
 | |
|             return $this->createError("Error updating GPG key: " . $sql->getLastError());
 | |
|           }
 | |
| 
 | |
|           $userToken->invalidate($sql);
 | |
|         }
 | |
|       } else {
 | |
|         return $this->createError("Error validating token: " . $sql->getLastError());
 | |
|       }
 | |
| 
 | |
|       return $this->success;
 | |
|     }
 | |
| 
 | |
|     public static function getDescription(): string {
 | |
|       return "Allows users to confirm their imported gpg key";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   class Download extends GpgKeyAPI {
 | |
|     public function __construct(Context $context, bool $externalCall = false) {
 | |
|       parent::__construct($context, $externalCall, array(
 | |
|         "id" => new Parameter("id", Parameter::TYPE_INT, true, null),
 | |
|         "format" => new StringType("format", 16, true, "ascii")
 | |
|       ));
 | |
|       $this->loginRequired = true;
 | |
|       $this->csrfTokenRequired = false;
 | |
|     }
 | |
| 
 | |
|     public function _execute(): bool {
 | |
| 
 | |
|       $allowedFormats = ["json", "ascii", "gpg"];
 | |
|       $format = $this->getParam("format");
 | |
|       if (!in_array($format, $allowedFormats)) {
 | |
|         return $this->getParam("Invalid requested format. Allowed formats: " . implode(",", $allowedFormats));
 | |
|       }
 | |
| 
 | |
|       $currentUser = $this->context->getUser();
 | |
|       $userId = $this->getParam("id");
 | |
|       if ($userId === null || $userId == $currentUser->getId()) {
 | |
|         $gpgKey = $currentUser->getGPG();
 | |
|         if (!$gpgKey) {
 | |
|           return $this->createError("You did not add a gpg key yet.");
 | |
|         }
 | |
| 
 | |
|         $email = $currentUser->getEmail();
 | |
|       } else {
 | |
|         $sql = $this->context->getSQL();
 | |
|         $user = User::find($sql, $userId, true);
 | |
|         if ($user === false) {
 | |
|           return $this->createError("Error fetching user details: " . $sql->getLastError());
 | |
|         } else if ($user === null) {
 | |
|           return $this->createError("User not found");
 | |
|         }
 | |
| 
 | |
|         $email = $user->getEmail();
 | |
|         $gpgKey = $user->getGPG();
 | |
|         if (!$gpgKey || !$gpgKey->isConfirmed()) {
 | |
|           return $this->createError("This user has not added a gpg key yet or has not confirmed it yet.");
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       $res = GpgKey::export($gpgKey->getFingerprint(), $format !== "gpg");
 | |
|       if (!$res["success"]) {
 | |
|         return $this->createError($res["error"]);
 | |
|       }
 | |
| 
 | |
|       $key = $res["data"];
 | |
|       if ($format === "json") {
 | |
|         $this->result["key"] = $key;
 | |
|         return true;
 | |
|       } else if ($format === "ascii") {
 | |
|         $contentType = "application/pgp-keys";
 | |
|         $ext = "asc";
 | |
|       } else if ($format === "gpg") {
 | |
|         $contentType = "application/octet-stream";
 | |
|         $ext = "gpg";
 | |
|       } else {
 | |
|         die("Invalid format");
 | |
|       }
 | |
| 
 | |
|       $fileName = "$email.$ext";
 | |
|       header("Content-Type: $contentType");
 | |
|       header("Content-Length: " . strlen($key));
 | |
|       header("Content-Disposition: attachment; filename=\"$fileName\"");
 | |
|       die($key);
 | |
|     }
 | |
| 
 | |
|     public static function getDescription(): string {
 | |
|       return "Allows users to download any gpg public key";
 | |
|     }
 | |
|   }
 | |
| }
 |