Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docker/solid.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@
RewriteRule ^(.+)$ index.php [QSA,L]
</Directory>
</VirtualHost>
<VirtualHost *:443>
ServerName storage.solid.local
DocumentRoot /opt/solid/www/storage

SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory />
Require all granted
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]
</Directory>
</VirtualHost>
<VirtualHost *:443>
ServerName identity.solid.local
ServerAlias *.solid.local
Expand Down
22 changes: 21 additions & 1 deletion init.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ function initDatabase() {
echo $e->getMessage();
}
}


function initStorageDatabase() {
$statements = [
'CREATE TABLE IF NOT EXISTS storage (
storage_id VARCHAR(255) NOT NULL PRIMARY KEY,
owner VARCHAR(255) NOT NULL
)'
];

try {
$pdo = new \PDO("sqlite:" . DBPATH);

// create tables
foreach($statements as $statement){
$pdo->exec($statement);
}
} catch(\PDOException $e) {
echo $e->getMessage();
}
}
initKeys();
initDatabase();
initStorageDatabase();
7 changes: 6 additions & 1 deletion lib/Routes/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Pdsinterop\PhpSolid\Session;
use Pdsinterop\PhpSolid\Mailer;
use Pdsinterop\PhpSolid\IpAttempts;
use Pdsinterop\PhpSolid\StorageServer;

class Account {
public static function requireLoggedInUser() {
Expand Down Expand Up @@ -87,11 +88,15 @@ public static function respondToAccountNew() {
header("HTTP/1.1 400 Bad Request");
exit();
}
$createdStorage = StorageServer::createStorage($createdUser['webId']);

Mailer::sendAccountCreated($createdUser);

$responseData = array(
"webId" => $createdUser['webId']
"webId" => $createdUser['webId'],
"storageUrl" => $createdStorage['storageUrl']
);

header("HTTP/1.1 201 Created");
header("Content-type: application/json");
Session::start($_POST['email']);
Expand Down
18 changes: 13 additions & 5 deletions lib/Routes/SolidStorage.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Pdsinterop\PhpSolid\Routes;

use Pdsinterop\PhpSolid\User;
use Pdsinterop\PhpSolid\StorageServer;
use Pdsinterop\PhpSolid\ClientRegistration;
use Pdsinterop\PhpSolid\SolidNotifications;
Expand All @@ -15,8 +16,15 @@ public static function respondToStorage() {
$requestFactory = new ServerRequestFactory();
$rawRequest = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);

StorageServer::initializeStorage();
$filesystem = StorageServer::getFileSystem();
try {
StorageServer::initializeStorage();
$filesystem = StorageServer::getFileSystem();
} catch (\Exception $e) {
$response = new Response();
$response = $response->withStatus(404, "Not found");
StorageServer::respond($response);
exit();
}

$resourceServer = new ResourceServer($filesystem, new Response(), null);
$solidNotifications = new SolidNotifications();
Expand All @@ -40,9 +48,9 @@ public static function respondToStorage() {
$origin = $rawRequest->getHeaderLine("Origin");

// FIXME: Read allowed clients from the profile instead;
$owner = StorageServer::getOwner();

$allowedClients = $owner['allowedClients'] ?? [];
// $owner = StorageServer::getOwner();
$ownerWebId = StorageServer::getOwnerWebId();
$owner = User::getUserByWebId($ownerWebId);
$allowedOrigins = ($owner['allowedOrigins'] ?? []) + (TRUSTED_APPS ?? []);

if (!isset($origin) || ($origin === "")) {
Expand Down
39 changes: 39 additions & 0 deletions lib/Routes/SolidStorageProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
namespace Pdsinterop\PhpSolid\Routes;

use Pdsinterop\PhpSolid\StorageServer;
use Laminas\Diactoros\ServerRequestFactory;

class SolidStorageProvider {
public static function respondToStorageNew() {
$requestFactory = new ServerRequestFactory();
$rawRequest = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);
$webId = StorageServer::getWebId($rawRequest);

if (!isset($webId) || $webId === "public") {
header("HTTP/1.1 400 Bad Request");
exit();
}

// FIXME: Get the webID issuer and validate that we allow storage creation for that issuer

$createdStorage = StorageServer::createStorage($webId);
if (!$createdStorage) {
error_log("Failed to create storage");
header("HTTP/1.1 400 Bad Request");
exit();
}

//Mailer::sendStorageCreated($createdStoage);

$storageUrl = "https://storage-" . $createdStorage['storageId'] . "." . BASEDOMAIN . "/";

$responseData = array(
"storage" => $storageUrl
);
header("HTTP/1.1 201 Created");
header("Content-type: application/json");
echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
}
}

95 changes: 84 additions & 11 deletions lib/StorageServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,85 @@
use Pdsinterop\PhpSolid\Server;
use Pdsinterop\PhpSolid\User;
use Pdsinterop\PhpSolid\Util;
use Pdsinterop\PhpSolid\Db;

class StorageServer extends Server {
public static function getFileSystem() {
public static function getStorage($storageId) {
Db::connect();
$query = Db::$pdo->prepare(
'SELECT * FROM storage WHERE storage_id=:storageId'
);
$query->execute([
':storageId' => $storageId
]);
return $query->fetchAll();
}

public static function setStorageOwner($storageId, $owner) {
Db::connect();
$query = Db::$pdo->prepare(
'UPDATE storage SET owner=:owner WHERE storage_id=:storageId'
);
$query->execute([
':storageId' => $storageId,
':owner' => $owner
]);
}

public static function createStorage($ownerWebId) {
$generatedStorageId = bin2hex(random_bytes(16));
while (self::storageIdExists($generatedStorageId)) {
$generatedStorageId = bin2hex(random_bytes(16));
}
Db::connect();
$query = Db::$pdo->prepare(
'INSERT OR REPLACE INTO storage VALUES(:storageId, :owner)'
);
$query->execute([
':storageId' => $generatedStorageId,
':owner' => $ownerWebId
]);
return [
"storageId" => $generatedStorageId
];
}

public static function storageIdExists($storageId) {
Db::connect();
$query = Db::$pdo->prepare(
'SELECT storage_id FROM storage WHERE storage_id=:storageId'
);
$query->execute([
':storageId' => $storageId
]);
$result = $query->fetchAll();
if (sizeof($result) === 1) {
return true;
}
return false;
}

public static function getOwnerWebId() {
$storageId = self::getStorageId();
Db::connect();
$query = Db::$pdo->prepare(
'SELECT owner FROM storage WHERE storage_id=:storageId'
);
$query->execute([
':storageId' => $storageId
]);
$result = $query->fetchAll();
if (sizeof($result) === 1) {
return $result[0]['owner'];
}
return false;
}

public static function getFileSystem() {
$storageId = self::getStorageId();
if (!self::storageIdExists($storageId)) {
throw new \Exception("Storage does not exist");
}
// The internal adapter
$adapter = new \League\Flysystem\Adapter\Local(
// Determine root directory
Expand Down Expand Up @@ -65,16 +139,6 @@ private static function getStorageId() {
return $storageId;
}

public static function getOwner() {
$storageId = self::getStorageId();
return User::getUserById($storageId);
}

public static function getOwnerWebId() {
$owner = self::getOwner();
return $owner['webId'];
}

public static function initializeStorage() {
$filesystem = self::getFilesystem();
if (!$filesystem->has("/.acl")) {
Expand Down Expand Up @@ -119,6 +183,9 @@ public static function initializeStorage() {

public static function generateDefaultAcl() {
$webId = self::getOwnerWebId();
if (!$webId) {
throw new \Exception("No owner found for storage");
}
$acl = <<< "EOF"
# Root ACL resource for the user account
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
Expand Down Expand Up @@ -150,6 +217,9 @@ public static function generateDefaultAcl() {

public static function generatePublicAppendAcl() {
$webId = self::getOwnerWebId();
if (!$webId) {
throw new \Exception("No owner found for storage ID");
}
$acl = <<< "EOF"
# Inbox ACL resource for the user account
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
Expand Down Expand Up @@ -179,6 +249,9 @@ public static function generatePublicAppendAcl() {

public static function generatePublicReadAcl() {
$webId = self::getOwnerWebId();
if (!$webId) {
throw new \Exception("No owner found for storage ID");
}
$acl = <<< "EOF"
# Inbox ACL resource for the user account
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
Expand Down
9 changes: 9 additions & 0 deletions lib/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ public static function getUserById($userId) {
return false;
}

public static function getUserByWebId($webId) {
$idParts = explode(".", $webId, 2);
if ($idParts[1] !== BASEDOMAIN . "/#me") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a scenario where the provided $webId does not have #me appended?
Otherwise if ( ! str_starts_with( $idParts[1], BASEDOMAIN)) { feels more appropriate

return false;
}
$userId = preg_replace("/^id-/", "", $idParts[0]);
return self::getUserById($userId);
}

public static function checkPassword($email, $password) {
Db::connect();
$query = Db::$pdo->prepare(
Expand Down
23 changes: 16 additions & 7 deletions tests/phpunit/StorageServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ class StorageServerTest extends \PHPUnit\Framework\TestCase
{
public static $headers = [];
public static $createdUser;
public static $createdStorage;

protected function setUp(): void
{
$statements = [
'DROP TABLE IF EXISTS allowedClients',
'DROP TABLE IF EXISTS userStorage',
'DROP TABLE IF EXISTS users',
'DROP TABLE IF EXISTS storage',
'CREATE TABLE IF NOT EXISTS allowedClients (
userId VARCHAR(255) NOT NULL PRIMARY KEY,
clientId VARCHAR(255) NOT NULL
Expand All @@ -30,6 +32,10 @@ protected function setUp(): void
password TEXT NOT NULL,
data TEXT
)',
'CREATE TABLE IF NOT EXISTS storage (
storage_id VARCHAR(255) NOT NULL PRIMARY KEY,
owner VARCHAR(255) NOT NULL
)',
];

Db::connect();
Expand All @@ -48,9 +54,11 @@ protected function setUp(): void
"hello" => "world"
];
self::$createdUser = User::createUser($newUser);
self::$createdStorage = StorageServer::createStorage(self::$createdUser['webId']);

$_SERVER['REQUEST_URI'] = "/test/";
$_SERVER['REQUEST_SCHEME'] = "https";
$_SERVER['SERVER_NAME'] = "storage-" . self::$createdUser['userId'] . ".example.com";
$_SERVER['SERVER_NAME'] = "storage-" . self::$createdStorage['storageId'] . ".example.com";
}

public function testGetFileSystem() {
Expand All @@ -72,12 +80,6 @@ public function testRespond() {
$this->assertEquals($sentBody, "{\"Hello\":\"world\"}");
}

public function testGetOwner() {
$owner = StorageServer::getOwner();
$this->assertEquals(self::$createdUser['webId'], $owner['webId']);
$this->assertEquals(self::$createdUser['email'], $owner['email']);
}

public function testGetOwnerWebId() {
$webId = StorageServer::getOwnerWebId();
$this->assertEquals(self::$createdUser['webId'], $webId);
Expand Down Expand Up @@ -123,6 +125,13 @@ public function testGenerateDefaultPreferences() {
Currently untested:
public static function getWebId($rawRequest) {
public static function initializeStorage() {
public static function getStorage($storageId) {
public static function setStorageOwner($storageId, $owner) {
public static function createStorage($ownerWebId) {
public static function storageIdExists($storageId) {
*/
}




Loading
Loading