diff --git a/.config/phpcs.xml.dist b/.config/phpcs.xml.dist new file mode 100644 index 0000000..bc62ad3 --- /dev/null +++ b/.config/phpcs.xml.dist @@ -0,0 +1,50 @@ + + + PHP coding standards for the PDS Interop PHP Solid Server project + + + + + + + + + + . + *(.config|vendor)/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7438acf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +root = true + +[*.php] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = tab +insert_final_newline = true +max_line_length = 120 +spelling_language = en-US +tab_width = 4 +trim_trailing_whitespace = true + +# Unset settings for vendor directories +[{node_modules,vendor}/**] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = unset +max_line_length = unset +spelling_language = unset +tab_width = unset +trim_trailing_whitespace = unset diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..523df56 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,16 @@ +# List of revisions that are hidden from git blame annotations. +# +# This concerns massive code re-formatting, renaming, large changes +# that were later reverted, etc. +# +# Configure your git so that it always ignores the revisions listed +# in this file: +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# When adding a new revision to the list, please put its commit message +# in a comment above it. + + +# Mass fix of PHP Codesniffer whitespace violations +aaeca415e39669aae893f2fde47cf2ae04a6a287 diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 85c820c..9fb4410 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -51,7 +51,8 @@ jobs: --exclude vendor --no-progress . -# # 01.quality.php.validate.dependencies-file.yml + + # 01.quality.php.validate.dependencies-file.yml validate-dependencies-file: name: Validate dependencies file runs-on: ubuntu-24.04 @@ -63,6 +64,7 @@ jobs: --no-plugins --no-scripts --strict + # 02.test.php.test-unit.yml php-unittest: name: PHP Unit Tests @@ -89,6 +91,7 @@ jobs: env: COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}' - run: vendor/bin/phpunit --configuration tests/phpunit/phpunit.xml + # 03.quality.php.scan.dependencies-vulnerabilities.yml scan-dependencies-vulnerabilities: name: Scan Dependencies Vulnerabilities @@ -107,6 +110,24 @@ jobs: --no-dev --no-plugins --no-scripts + + + # 03.quality.php.lint-quality.yml + php-lint-quality: + needs: + - lint-php-syntax + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: docker://pipelinecomponents/php-codesniffer + with: + args: >- + phpcs + --standard=.config/phpcs.xml.dist + --report-full + --report-summary + . + # 03.quality.php.lint-version-compatibility.yml php-check-version-compatibility: name: PHP Version Compatibility diff --git a/init.php b/init.php index af5668d..94c1b72 100644 --- a/init.php +++ b/init.php @@ -1,83 +1,90 @@ exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); } + } catch (\PDOException $e) { + 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 - )' - ]; +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); + try { + $pdo = new \PDO("sqlite:" . DBPATH); - // create tables - foreach($statements as $statement){ + // create tables + foreach ($statements as $statement) { $pdo->exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); } + } catch (\PDOException $e) { + echo $e->getMessage(); } - initKeys(); - initDatabase(); - initStorageDatabase(); \ No newline at end of file +} + +initKeys(); +initDatabase(); +initStorageDatabase(); diff --git a/lib/ClientRegistration.php b/lib/ClientRegistration.php index 741edbf..70d9f93 100644 --- a/lib/ClientRegistration.php +++ b/lib/ClientRegistration.php @@ -1,72 +1,78 @@ prepare( - 'SELECT clientData FROM clients WHERE clientId=:clientId' - ); - $query->execute([ - ':clientId' => $clientId - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - return json_decode($result[0]['clientData'], true); - } - if (preg_match("/^http(s)?:/", $clientId)) { - $clientData = self::getRemoteRegistration($clientId); - if (!isset($clientData['origin']) && isset($clientData['client_uri'])) { - $clientData['origin'] = rtrim($clientData['client_uri'], '/'); - } - self::saveClientRegistration($clientData); - return $clientData; +use Pdsinterop\PhpSolid\Db; + +class ClientRegistration +{ + public static function getRegistration($clientId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT clientData FROM clients WHERE clientId=:clientId' + ); + $query->execute([ + ':clientId' => $clientId + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + return json_decode($result[0]['clientData'], true); + } + if (preg_match("/^http(s)?:/", $clientId)) { + $clientData = self::getRemoteRegistration($clientId); + if (!isset($clientData['origin']) && isset($clientData['client_uri'])) { + $clientData['origin'] = rtrim($clientData['client_uri'], '/'); } - return false; + self::saveClientRegistration($clientData); + return $clientData; } + return false; + } - public static function getRemoteRegistration($url) { - $clientDocument = file_get_contents($url); - $clientRegistration = json_decode($clientDocument, true); - if (!isset($clientRegistration['client_id'])) { - throw new \Exception("No client ID found in client document"); - } - if (!isset($clientRegistration['redirect_uris'])) { - throw new \Exception("No redirect URIs found in client document"); - } - return $clientRegistration; + public static function getRemoteRegistration($url) + { + $clientDocument = file_get_contents($url); + $clientRegistration = json_decode($clientDocument, true); + if (!isset($clientRegistration['client_id'])) { + throw new \Exception("No client ID found in client document"); } + if (!isset($clientRegistration['redirect_uris'])) { + throw new \Exception("No redirect URIs found in client document"); + } + return $clientRegistration; + } - public static function saveClientRegistration($clientData) { - Db::connect(); - if (!isset($clientData['client_name'])) { - $clientData['client_name'] = $clientData['origin']; - } - $query = Db::$pdo->prepare( - 'INSERT INTO clients VALUES(:clientId, :origin, :clientData)' - ); - $query->execute([ - ':clientId' => $clientData['client_id'], - ':origin' => $clientData['origin'], - ':clientData' => json_encode($clientData) - ]); + public static function saveClientRegistration($clientData) + { + Db::connect(); + if (!isset($clientData['client_name'])) { + $clientData['client_name'] = $clientData['origin']; } - - public static function getClientByOrigin($origin) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT clientData FROM clients WHERE origin=:origin' - ); - $query->execute([ - ':origin' => $origin - ]); - $result = $query->fetchAll(); - - if (sizeof($result)=== 1) { - return json_decode($result[0]['clientData'], true); - } - return false; + $query = Db::$pdo->prepare( + 'INSERT INTO clients VALUES(:clientId, :origin, :clientData)' + ); + $query->execute([ + ':clientId' => $clientData['client_id'], + ':origin' => $clientData['origin'], + ':clientData' => json_encode($clientData) + ]); + } + + public static function getClientByOrigin($origin) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT clientData FROM clients WHERE origin=:origin' + ); + $query->execute([ + ':origin' => $origin + ]); + $result = $query->fetchAll(); + + if (sizeof($result) === 1) { + return json_decode($result[0]['clientData'], true); } - } \ No newline at end of file + return false; + } +} diff --git a/lib/Db.php b/lib/Db.php index 8eb46e1..7f738c2 100644 --- a/lib/Db.php +++ b/lib/Db.php @@ -1,11 +1,14 @@ prepare( - 'INSERT INTO ipAttempts VALUES(:ip, :type, :expires)' - ); - $query->execute([ - ':ip' => $ip, - ':type' => $type, - ':expires' => $expires - ]); + +namespace Pdsinterop\PhpSolid; + +use Pdsinterop\PhpSolid\Db; + +class IpAttempts +{ + public static function logFailedAttempt($ip, $type, $expires) + { + if (in_array($ip, TRUSTED_IPS)) { + return; } - public static function getAttemptsCount($ip, $type) { - if (in_array($ip, TRUSTED_IPS)) { - return 0; - } - - Db::connect(); - - $now = new \DateTime(); - $query = Db::$pdo->prepare( - 'SELECT count(ip) as count FROM ipAttempts WHERE ip=:ip AND type=:type AND expires > :now' - ); - $query->execute([ - ':ip' => $ip, - ':type' => $type, - ':now' => $now->getTimestamp() - ]); - $result = $query->fetch(); - if (isset($result['count'])) { - return $result['count']; - } + Db::connect(); + + $query = Db::$pdo->prepare( + 'INSERT INTO ipAttempts VALUES(:ip, :type, :expires)' + ); + $query->execute([ + ':ip' => $ip, + ':type' => $type, + ':expires' => $expires + ]); + } + + public static function getAttemptsCount($ip, $type) + { + if (in_array($ip, TRUSTED_IPS)) { return 0; } - public static function cleanupAttempts() { - Db::connect(); - - $now = new \DateTime(); - $query = Db::$pdo->prepare( - 'DELETE FROM ipAttempts WHERE expires < :now' - ); - $query->execute([ - ':now' => $now->getTimestamp() - ]); + + Db::connect(); + + $now = new \DateTime(); + $query = Db::$pdo->prepare( + 'SELECT count(ip) as count FROM ipAttempts WHERE ip=:ip AND type=:type AND expires > :now' + ); + $query->execute([ + ':ip' => $ip, + ':type' => $type, + ':now' => $now->getTimestamp() + ]); + $result = $query->fetch(); + if (isset($result['count'])) { + return $result['count']; } + return 0; + } + public static function cleanupAttempts() + { + Db::connect(); + + $now = new \DateTime(); + $query = Db::$pdo->prepare( + 'DELETE FROM ipAttempts WHERE expires < :now' + ); + $query->execute([ + ':now' => $now->getTimestamp() + ]); } +} diff --git a/lib/JtiStore.php b/lib/JtiStore.php index b487059..04c6b3c 100644 --- a/lib/JtiStore.php +++ b/lib/JtiStore.php @@ -1,46 +1,52 @@ prepare( - 'SELECT jti FROM jti WHERE jti=:jti AND expires>:now' - ); - $query->execute([ - ':jti' => $jti, - ':now' => $now->getTimestamp() - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - return true; - } - return false; - } - - public static function saveJti($jti) { - Db::connect(); - $query = Db::$pdo->prepare( - 'INSERT INTO jti VALUES(:jti, :expires)' - ); - $expires = new \DateTime(); - $expires->add(new \DateInterval("PT1H")); - $query->execute([ - ':jti' => $jti, - ':expires' => $expires->getTimestamp() - ]); - } +namespace Pdsinterop\PhpSolid; + +use Pdsinterop\PhpSolid\Db; - public static function cleanupJti() { - Db::connect(); - $now = new \DateTime(); - $query = Db::$pdo->prepare( - 'DELETE FROM jti WHERE expires < :now' - ); - $query->execute([ - ':now' => $now->getTimestamp() - ]); +class JtiStore +{ + public static function hasJti($jti) + { + Db::connect(); + $now = new \DateTime(); + $query = Db::$pdo->prepare( + 'SELECT jti FROM jti WHERE jti=:jti AND expires>:now' + ); + $query->execute([ + ':jti' => $jti, + ':now' => $now->getTimestamp() + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + return true; } - } \ No newline at end of file + return false; + } + + public static function saveJti($jti) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'INSERT INTO jti VALUES(:jti, :expires)' + ); + $expires = new \DateTime(); + $expires->add(new \DateInterval("PT1H")); + $query->execute([ + ':jti' => $jti, + ':expires' => $expires->getTimestamp() + ]); + } + + public static function cleanupJti() + { + Db::connect(); + $now = new \DateTime(); + $query = Db::$pdo->prepare( + 'DELETE FROM jti WHERE expires < :now' + ); + $query->execute([ + ':now' => $now->getTimestamp() + ]); + } +} diff --git a/lib/MailTemplateGenerator.php b/lib/MailTemplateGenerator.php index 670ae19..47a9e83 100644 --- a/lib/MailTemplateGenerator.php +++ b/lib/MailTemplateGenerator.php @@ -1,11 +1,17 @@ + +class MailTemplateGenerator +{ + public static function mailtemplate($mailTokens) + { + $backgroundColor = MAILSTYLES['container']['backgroundColor'] ?? "#eeeeee"; + ob_start(); + ?> @@ -51,15 +57,17 @@ public static function mailtemplate($mailTokens) { - + return $mailTemplate; + } + + private static function mailTemplateHeader($mailTokens) + { + $backgroundColor = MAILSTYLES['header']['backgroundColor'] ?? "#3b3b3b"; + ?> @@ -75,44 +83,47 @@ private static function mailTemplateHeader($mailTokens) {
- + private static function mailTemplateCallToAction($mailTokens) + { + $backgroundColor = MAILSTYLES['call-to-action']['backgroundColor'] ?? "#ffffff"; + $color = MAILSTYLES['call-to-action']['color'] ?? "#181818"; + $colorMuted = MAILSTYLES['call-to-action']['colorMuted'] ?? "#333333"; + $fontFamily = MAILSTYLES['call-to-action']['fontFamily'] ?? "Arial, Helvetica, sans-serif"; + ?>
- + } + self::mailTemplateCallToActionDescription($mailTokens); + ?>
- +
@@ -120,17 +131,18 @@ private static function mailTemplateCallToActionButton($mailTokens) {
- + private static function mailTemplateCallToActionButtonNoLink($mailTokens) + { + $backgroundColor = MAILSTYLES['call-to-action']['backgroundColor'] ?? "#ffffff"; + $color = MAILSTYLES['call-to-action']['color'] ?? "#181818"; + $colorMuted = MAILSTYLES['call-to-action']['colorMuted'] ?? "#333333"; + $fontFamily = MAILSTYLES['call-to-action']['fontFamily'] ?? "Arial, Helvetica, sans-serif"; + $buttonTextColor = MAILSTYLES['call-to-action']['buttonTextColor'] ?? "##eeeeee"; + $buttonBackgroundColor = MAILSTYLES['call-to-action']['buttonBackgroundColor'] ?? "#262c2f"; + ?>
@@ -138,15 +150,16 @@ private static function mailTemplateCallToActionButtonNoLink($mailTokens) {
- + private static function mailTemplateCallToActionTitle($mailTokens) + { + $backgroundColor = MAILSTYLES['call-to-action']['backgroundColor'] ?? "#ffffff"; + $color = MAILSTYLES['call-to-action']['color'] ?? "#181818"; + $colorMuted = MAILSTYLES['call-to-action']['colorMuted'] ?? "#333333"; + $fontFamily = MAILSTYLES['call-to-action']['fontFamily'] ?? "Arial, Helvetica, sans-serif"; + ?>
@@ -165,15 +178,16 @@ private static function mailTemplateCallToActionTitle($mailTokens) {
- + private static function mailTemplateCallToActionDescription($mailTokens) + { + $backgroundColor = MAILSTYLES['call-to-action']['backgroundColor'] ?? "#ffffff"; + $color = MAILSTYLES['call-to-action']['color'] ?? "#181818"; + $colorMuted = MAILSTYLES['call-to-action']['colorMuted'] ?? "#333333"; + $fontFamily = MAILSTYLES['call-to-action']['fontFamily'] ?? "Arial, Helvetica, sans-serif"; + ?>
@@ -192,13 +206,15 @@ private static function mailTemplateCallToActionDescription($mailTokens) {
- + @@ -232,6 +248,7 @@ private static function mailTemplateFooter($mailTokens) {
- \ No newline at end of file + diff --git a/lib/MailTemplates.php b/lib/MailTemplates.php index d1f85ef..a66d2b1 100644 --- a/lib/MailTemplates.php +++ b/lib/MailTemplates.php @@ -1,134 +1,142 @@ "Confirm your e-mail", - "description" => "Your online identity is almost ready. Enter to code to complete your registration", - "footer" => "", - "buttonText" => "{code}", - ); - - $mailSubject = "Confirm your e-mail: {code}"; - $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); - - $mailPlainBody = implode("\n\n", array( - $mailTokens['title'], - $mailTokens['buttonText'], - $mailTokens['description'], - $mailTokens['footer'] - )); - - foreach ($mailTokens as $token => $value) { - $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); - } - - return array( - "mailSubject" => $mailSubject, - "mailHtmlBody" => $mailHtmlBody, - "mailPlainBody" => $mailPlainBody - ); + +// phpcs:disable Generic.Files.LineLength.TooLong + +namespace Pdsinterop\PhpSolid; + +use Pdsinterop\PhpSolid\MailTemplateGenerator; + +class MailTemplates +{ + public static function verify() + { + $mailTokens = array( + "title" => "Confirm your e-mail", + "description" => "Your online identity is almost ready. Enter to code to complete your registration", + "footer" => "", + "buttonText" => "{code}", + ); + + $mailSubject = "Confirm your e-mail: {code}"; + $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); + + $mailPlainBody = implode("\n\n", array( + $mailTokens['title'], + $mailTokens['buttonText'], + $mailTokens['description'], + $mailTokens['footer'] + )); + + foreach ($mailTokens as $token => $value) { + $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); } - - public static function resetPassword() { - $mailSubject = "Password reset"; - $mailTokens = array( - "title" => "Password reset", - "description" => "If the link does not work, copy and paste this in your browser: " . BASEURL . "/change-password/?token={code}", - "footer" => "", - "buttonText" => "Reset password", - "buttonLink" => BASEURL . "/change-password/?token={code}" - ); - - $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); - - $mailPlainBody = implode("\n\n", array( - $mailTokens['title'], - $mailTokens['buttonText'], - $mailTokens['description'], - $mailTokens['footer'] - )); - $mailPlainBody = $mailPlainBody; - - foreach ($mailTokens as $token => $value) { - $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); - } - - return array( - "mailSubject" => $mailSubject, - "mailHtmlBody" => $mailHtmlBody, - "mailPlainBody" => $mailPlainBody - ); + + return array( + "mailSubject" => $mailSubject, + "mailHtmlBody" => $mailHtmlBody, + "mailPlainBody" => $mailPlainBody + ); + } + + public static function resetPassword() + { + $mailSubject = "Password reset"; + $mailTokens = array( + "title" => "Password reset", + "description" => "If the link does not work, copy and paste this in your browser: " . BASEURL . "/change-password/?token={code}", + "footer" => "", + "buttonText" => "Reset password", + "buttonLink" => BASEURL . "/change-password/?token={code}" + ); + + $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); + + $mailPlainBody = implode("\n\n", array( + $mailTokens['title'], + $mailTokens['buttonText'], + $mailTokens['description'], + $mailTokens['footer'] + )); + $mailPlainBody = $mailPlainBody; + + foreach ($mailTokens as $token => $value) { + $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); } - public static function deleteAccount() { - $mailSubject = "Delete your account"; - $mailTokens = array( - "title" => "Delete your account", - "description" => "If the link does not work, copy and paste this in your browser: " . BASEURL . "/account/delete/confirm/?token={code}", - "footer" => "", - "buttonText" => "Delete account", - "buttonLink" => BASEURL . "/account/delete/confirm/?token={code}" - ); - - $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); - - $mailPlainBody = implode("\n\n", array( - $mailTokens['title'], - $mailTokens['buttonText'], - $mailTokens['description'], - $mailTokens['footer'] - )); - $mailPlainBody = $mailPlainBody; - - foreach ($mailTokens as $token => $value) { - $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); - } - - return array( - "mailSubject" => $mailSubject, - "mailHtmlBody" => $mailHtmlBody, - "mailPlainBody" => $mailPlainBody - ); + return array( + "mailSubject" => $mailSubject, + "mailHtmlBody" => $mailHtmlBody, + "mailPlainBody" => $mailPlainBody + ); + } + + public static function deleteAccount() + { + $mailSubject = "Delete your account"; + $mailTokens = array( + "title" => "Delete your account", + "description" => "If the link does not work, copy and paste this in your browser: " . BASEURL . "/account/delete/confirm/?token={code}", + "footer" => "", + "buttonText" => "Delete account", + "buttonLink" => BASEURL . "/account/delete/confirm/?token={code}" + ); + + $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); + + $mailPlainBody = implode("\n\n", array( + $mailTokens['title'], + $mailTokens['buttonText'], + $mailTokens['description'], + $mailTokens['footer'] + )); + $mailPlainBody = $mailPlainBody; + + foreach ($mailTokens as $token => $value) { + $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); } - - public static function accountCreated() { - $mailTokens = array( - "title" => "Welcome to Solid!", - "description" => "Your online identity is ready to use. Your WebID is: {webId}", - "footer" => "", - ); - - $mailSubject = "Welcome to Solid!"; - - $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); - - $mailPlainBody = implode("\n\n", array( - $mailTokens['title'], - // $mailTokens['buttonText'], - $mailTokens['description'], - $mailTokens['footer'] - )); - - foreach ($mailTokens as $token => $value) { - $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); - } - - return array( - "mailSubject" => $mailSubject, - "mailHtmlBody" => $mailHtmlBody, - "mailPlainBody" => $mailPlainBody - ); + + return array( + "mailSubject" => $mailSubject, + "mailHtmlBody" => $mailHtmlBody, + "mailPlainBody" => $mailPlainBody + ); + } + + public static function accountCreated() + { + $mailTokens = array( + "title" => "Welcome to Solid!", + "description" => "Your online identity is ready to use. Your WebID is: {webId}", + "footer" => "", + ); + + $mailSubject = "Welcome to Solid!"; + + $mailHtmlBody = MailTemplateGenerator::mailTemplate($mailTokens); + + $mailPlainBody = implode("\n\n", array( + $mailTokens['title'], + // $mailTokens['buttonText'], + $mailTokens['description'], + $mailTokens['footer'] + )); + + foreach ($mailTokens as $token => $value) { + $mailSubject = str_replace("{" . $token . "}", $mailTokens[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $mailTokens[$token], $mailPlainBody); } + + return array( + "mailSubject" => $mailSubject, + "mailHtmlBody" => $mailHtmlBody, + "mailPlainBody" => $mailPlainBody + ); } +} diff --git a/lib/Mailer.php b/lib/Mailer.php index 7d50853..aae8a83 100644 --- a/lib/Mailer.php +++ b/lib/Mailer.php @@ -1,143 +1,151 @@ IsSMTP(); - $mailer->CharSet = 'UTF-8'; - $mailer->Host = MAILER['host']; - $mailer->SMTPDebug = 0; - $mailer->Port = MAILER['port']; - if (isset(MAILER['user'])) { - $mailer->SMTPAuth = true; - $mailer->Username = MAILER['user']; - $mailer->Password = MAILER['password']; - } - $mailer->isHTML(true); - $mailer->setFrom(MAILER['from']); - $mailer->XMailer = null; // don't add PHPmailer user agent information; - return $mailer; + +namespace Pdsinterop\PhpSolid; + +use Pdsinterop\PhpSolid\MailTemplates; +use PHPMailer\PHPMailer\PHPMailer; + +class Mailer +{ + public static $mailer = null; + + public static function getMailer() + { + if (self::$mailer) { + return self::$mailer; } - - public static function sendAccountCreated($data) { - $mailTemplate = MailTemplates::accountCreated(); - $mailSubject = $mailTemplate["mailSubject"]; - $mailHtmlBody = $mailTemplate["mailHtmlBody"]; - $mailPlainBody = $mailTemplate["mailPlainBody"]; - - $mailTo = $data['email']; - $mailTokens = array("webId", "email"); - - foreach ($mailTokens as $token) { - $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); - } - - // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; - - $mailer = self::getMailer(); - // Content - $mailer->addAddress($mailTo); - - $mailer->Subject = $mailSubject; - $mailer->Body = $mailHtmlBody; - $mailer->AltBody = $mailPlainBody; - - return $mailer->send(); + $mailer = new PHPMailer(); + // Settings + $mailer->IsSMTP(); + $mailer->CharSet = 'UTF-8'; + $mailer->Host = MAILER['host']; + $mailer->SMTPDebug = 0; + $mailer->Port = MAILER['port']; + if (isset(MAILER['user'])) { + $mailer->SMTPAuth = true; + $mailer->Username = MAILER['user']; + $mailer->Password = MAILER['password']; } + $mailer->isHTML(true); + $mailer->setFrom(MAILER['from']); + $mailer->XMailer = null; // don't add PHPmailer user agent information; + return $mailer; + } + + public static function sendAccountCreated($data) + { + $mailTemplate = MailTemplates::accountCreated(); + $mailSubject = $mailTemplate["mailSubject"]; + $mailHtmlBody = $mailTemplate["mailHtmlBody"]; + $mailPlainBody = $mailTemplate["mailPlainBody"]; - public static function sendVerify($data) { - $mailTemplate = MailTemplates::verify(); + $mailTo = $data['email']; + $mailTokens = array("webId", "email"); - $mailSubject = $mailTemplate["mailSubject"]; - $mailHtmlBody = $mailTemplate["mailHtmlBody"]; - $mailPlainBody = $mailTemplate["mailPlainBody"]; + foreach ($mailTokens as $token) { + $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); + } - $mailTo = $data['email']; - $mailTokens = array("code", "email"); + // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; - foreach ($mailTokens as $token) { - $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); - } + $mailer = self::getMailer(); + // Content + $mailer->addAddress($mailTo); - // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; + $mailer->Subject = $mailSubject; + $mailer->Body = $mailHtmlBody; + $mailer->AltBody = $mailPlainBody; - $mailer = self::getMailer(); - // Content - $mailer->addAddress($mailTo); - - $mailer->Subject = $mailSubject; - $mailer->Body = $mailHtmlBody; - $mailer->AltBody = $mailPlainBody; + return $mailer->send(); + } - return $mailer->send(); + public static function sendVerify($data) + { + $mailTemplate = MailTemplates::verify(); + + $mailSubject = $mailTemplate["mailSubject"]; + $mailHtmlBody = $mailTemplate["mailHtmlBody"]; + $mailPlainBody = $mailTemplate["mailPlainBody"]; + + $mailTo = $data['email']; + $mailTokens = array("code", "email"); + + foreach ($mailTokens as $token) { + $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); } - public static function sendResetPassword($data) { - $mailTemplate = MailTemplates::resetPassword(); - $mailSubject = $mailTemplate["mailSubject"]; - $mailHtmlBody = $mailTemplate["mailHtmlBody"]; - $mailPlainBody = $mailTemplate["mailPlainBody"]; - - $mailTo = $data['email']; - $mailTokens = array("code", "email"); - - foreach ($mailTokens as $token) { - $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); - } - - // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; - - $mailer = self::getMailer(); - // Content - $mailer->addAddress($mailTo); - - $mailer->Subject = $mailSubject; - $mailer->Body = $mailHtmlBody; - $mailer->AltBody = $mailPlainBody; - - return $mailer->send(); + // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; + + $mailer = self::getMailer(); + // Content + $mailer->addAddress($mailTo); + + $mailer->Subject = $mailSubject; + $mailer->Body = $mailHtmlBody; + $mailer->AltBody = $mailPlainBody; + + return $mailer->send(); + } + + public static function sendResetPassword($data) + { + $mailTemplate = MailTemplates::resetPassword(); + $mailSubject = $mailTemplate["mailSubject"]; + $mailHtmlBody = $mailTemplate["mailHtmlBody"]; + $mailPlainBody = $mailTemplate["mailPlainBody"]; + + $mailTo = $data['email']; + $mailTokens = array("code", "email"); + + foreach ($mailTokens as $token) { + $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); } - public static function sendDeleteAccount($data) { - $mailTemplate = MailTemplates::deleteAccount(); - $mailSubject = $mailTemplate["mailSubject"]; - $mailHtmlBody = $mailTemplate["mailHtmlBody"]; - $mailPlainBody = $mailTemplate["mailPlainBody"]; - - $mailTo = $data['email']; - $mailTokens = array("code", "email"); - - foreach ($mailTokens as $token) { - $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); - $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); - $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); - } - - // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; - - $mailer = self::getMailer(); - // Content - $mailer->addAddress($mailTo); - - $mailer->Subject = $mailSubject; - $mailer->Body = $mailHtmlBody; - $mailer->AltBody = $mailPlainBody; - - return $mailer->send(); + // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; + + $mailer = self::getMailer(); + // Content + $mailer->addAddress($mailTo); + + $mailer->Subject = $mailSubject; + $mailer->Body = $mailHtmlBody; + $mailer->AltBody = $mailPlainBody; + + return $mailer->send(); + } + + public static function sendDeleteAccount($data) + { + $mailTemplate = MailTemplates::deleteAccount(); + $mailSubject = $mailTemplate["mailSubject"]; + $mailHtmlBody = $mailTemplate["mailHtmlBody"]; + $mailPlainBody = $mailTemplate["mailPlainBody"]; + + $mailTo = $data['email']; + $mailTokens = array("code", "email"); + + foreach ($mailTokens as $token) { + $mailSubject = str_replace("{" . $token . "}", $data[$token], $mailSubject); + $mailHtmlBody = str_replace("{" . $token . "}", $data[$token], $mailHtmlBody); + $mailPlainBody = str_replace("{" . $token . "}", $data[$token], $mailPlainBody); } + + // $emailCheck = "#^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})" . '$#'; + + $mailer = self::getMailer(); + // Content + $mailer->addAddress($mailTo); + + $mailer->Subject = $mailSubject; + $mailer->Body = $mailHtmlBody; + $mailer->AltBody = $mailPlainBody; + + return $mailer->send(); } +} diff --git a/lib/Middleware.php b/lib/Middleware.php index 76cb63c..91c6c25 100644 --- a/lib/Middleware.php +++ b/lib/Middleware.php @@ -1,33 +1,39 @@ addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats)); - $plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph); - $filesystem->addPlugin($plugin); - return $filesystem; - } - public static function respond($response) { - $statusCode = $response->getStatusCode(); - $response->getBody()->rewind(); - $headers = $response->getHeaders(); - - $body = $response->getBody()->getContents(); - header("HTTP/1.1 $statusCode"); - foreach ($headers as $header => $values) { - foreach ($values as $value) { - if ($header == "Location") { - $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding - } - header($header . ":" . $value); +namespace Pdsinterop\PhpSolid; + +use Pdsinterop\PhpSolid\Server; +use Pdsinterop\PhpSolid\User; +use Pdsinterop\PhpSolid\Util; + +class ProfileServer extends Server +{ + public static function getFileSystem() + { + $profileId = self::getProfileId(); + + // The internal adapter + $adapter = new \League\Flysystem\Adapter\Local( + // Determine root directory + PROFILEBASE . "$profileId/" + ); + + $graph = new \EasyRdf\Graph(); + // Create Formats objects + $formats = new \Pdsinterop\Rdf\Formats(); + $serverUri = Util::getServerUri(); + + // Create the RDF Adapter + $rdfAdapter = new \Pdsinterop\Rdf\Flysystem\Adapter\Rdf($adapter, $graph, $formats, $serverUri); + + $filesystem = new \League\Flysystem\Filesystem($rdfAdapter); + $filesystem->addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats)); + $plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph); + $filesystem->addPlugin($plugin); + return $filesystem; + } + + public static function respond($response) + { + $statusCode = $response->getStatusCode(); + $response->getBody()->rewind(); + $headers = $response->getHeaders(); + + $body = $response->getBody()->getContents(); + header("HTTP/1.1 $statusCode"); + foreach ($headers as $header => $values) { + foreach ($values as $value) { + if ($header == "Location") { + $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding } + header($header . ":" . $value); } - echo $body; } + echo $body; + } - public static function getWebId($rawRequest) { - $dpop = self::getDpop(); - $webId = $dpop->getWebId($rawRequest); - if (!isset($webId)) { - $bearer = self::getBearer(); - $webId = $bearer->getWebId($rawRequest); - } - return $webId; + public static function getWebId($rawRequest) + { + $dpop = self::getDpop(); + $webId = $dpop->getWebId($rawRequest); + if (!isset($webId)) { + $bearer = self::getBearer(); + $webId = $bearer->getWebId($rawRequest); } + return $webId; + } - private static function getProfileId() { - $serverName = Util::getServerName(); - $idParts = explode(".", $serverName, 2); - $profileId = preg_replace("/^id-/", "", $idParts[0]); - return $profileId; - } - - public static function getOwner() { - $profileId = self::getProfileId(); - return User::getUserById($profileId); - } + private static function getProfileId() + { + $serverName = Util::getServerName(); + $idParts = explode(".", $serverName, 2); + $profileId = preg_replace("/^id-/", "", $idParts[0]); + return $profileId; + } + + public static function getOwner() + { + $profileId = self::getProfileId(); + return User::getUserById($profileId); + } + + public static function getOwnerWebId() + { + $owner = self::getOwner(); + return $owner['webId']; + } - public static function getOwnerWebId() { - $owner = self::getOwner(); - return $owner['webId']; + public static function initializeProfile() + { + $filesystem = self::getFilesystem(); + if (!$filesystem->has("/.acl")) { + $defaultAcl = self::generateDefaultAcl(); + $filesystem->write("/.acl", $defaultAcl); } - - public static function initializeProfile() { - $filesystem = self::getFilesystem(); - if (!$filesystem->has("/.acl")) { - $defaultAcl = self::generateDefaultAcl(); - $filesystem->write("/.acl", $defaultAcl); - } - // Generate default folders and ACLs: - if (!$filesystem->has("/profile.ttl")) { - $profile = self::generateDefaultProfile(); - $filesystem->write("/profile.ttl", $profile); - } + // Generate default folders and ACLs: + if (!$filesystem->has("/profile.ttl")) { + $profile = self::generateDefaultProfile(); + $filesystem->write("/profile.ttl", $profile); } - - public static function generateDefaultAcl() { - $webId = self::getOwnerWebId(); - $acl = <<< "EOF" + } + + public static function generateDefaultAcl() + { + $webId = self::getOwnerWebId(); + $acl = <<< "EOF" # Root ACL resource for the user account @prefix acl: . @prefix foaf: . # The homepage is readable by the public <#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./>; - # All resources will inherit this authorization, by default - acl:default <./>; - acl:mode acl:Read. +a acl:Authorization; +acl:agentClass foaf:Agent; +acl:accessTo <./>; +# All resources will inherit this authorization, by default +acl:default <./>; +acl:mode acl:Read. # The owner has full access to every resource in their pod. # Other agents have no access rights, # unless specifically authorized in other .acl resources. <#owner> - a acl:Authorization; - acl:agent <$webId>; - # Set the access to the root storage folder itself - acl:accessTo <./>; - # All resources will inherit this authorization, by default - acl:default <./>; - # The owner has all of the access modes allowed - acl:mode - acl:Read, acl:Write, acl:Control. +a acl:Authorization; +acl:agent <$webId>; +# Set the access to the root storage folder itself +acl:accessTo <./>; +# All resources will inherit this authorization, by default +acl:default <./>; +# The owner has all of the access modes allowed +acl:mode + acl:Read, acl:Write, acl:Control. EOF; - return $acl; - } + return $acl; + } - public static function generateDefaultProfile() { - $user = self::getOwner(); - if (!isset($user['storage']) || !$user['storage']) { - $user['storage'] = "https://storage-" . self::getProfileId() . "." . BASEDOMAIN . "/"; - } - if (is_array($user['storage'])) { // empty array is already handled - $user['storage'] = array_values($user['storage'])[0]; // FIXME: Handle multiple storage pods - } - if (!isset($user['issuer'])) { - $user['issuer'] = BASEURL; - } + public static function generateDefaultProfile() + { + $user = self::getOwner(); + if (!isset($user['storage']) || !$user['storage']) { + $user['storage'] = "https://storage-" . self::getProfileId() . "." . BASEDOMAIN . "/"; + } + if (is_array($user['storage'])) { // empty array is already handled + $user['storage'] = array_values($user['storage'])[0]; // FIXME: Handle multiple storage pods + } + if (!isset($user['issuer'])) { + $user['issuer'] = BASEURL; + } - $profile = <<< "EOF" + $profile = <<< "EOF" @prefix : <#>. @prefix acl: . @prefix foaf: . @@ -149,15 +160,15 @@ public static function generateDefaultProfile() { <> a foaf:PersonalProfileDocument; foaf:maker :me; foaf:primaryTopic :me. :me - a schema:Person, foaf:Person; - ldp:inbox inbox:; - space:preferencesFile <{$user['storage']}settings/preferences.ttl>; - space:storage <{$user['storage']}>; - solid:account <{$user['storage']}>; - solid:oidcIssuer <{$user['issuer']}>; - solid:privateTypeIndex <{$user['storage']}settings/privateTypeIndex.ttl>; - solid:publicTypeIndex <{$user['storage']}settings/publicTypeIndex.ttl>. +a schema:Person, foaf:Person; +ldp:inbox inbox:; +space:preferencesFile <{$user['storage']}settings/preferences.ttl>; +space:storage <{$user['storage']}>; +solid:account <{$user['storage']}>; +solid:oidcIssuer <{$user['issuer']}>; +solid:privateTypeIndex <{$user['storage']}settings/privateTypeIndex.ttl>; +solid:publicTypeIndex <{$user['storage']}settings/publicTypeIndex.ttl>. EOF; - return $profile; - } + return $profile; } +} diff --git a/lib/Routes/Account.php b/lib/Routes/Account.php index 55e08df..d9fb789 100644 --- a/lib/Routes/Account.php +++ b/lib/Routes/Account.php @@ -1,190 +1,202 @@ $_POST['email'] - ]; + public static function respondToAccountVerify() + { + $verifyData = [ + 'email' => $_POST['email'] + ]; - $verifyToken = User::saveVerifyToken('verify', $verifyData); - Mailer::sendVerify($verifyToken); + $verifyToken = User::saveVerifyToken('verify', $verifyData); + Mailer::sendVerify($verifyToken); - $responseData = "OK"; - header("HTTP/1.1 201 Created"); - header("Content-type: application/json"); - echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + $responseData = "OK"; + header("HTTP/1.1 201 Created"); + header("Content-type: application/json"); + echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } + + public static function respondToAccountNew() + { + $verifyToken = User::getVerifyToken($_POST['confirm']); + if (!$verifyToken) { + error_log("Could not read verify token"); + header("HTTP/1.1 400 Bad Request"); + exit(); + } + if ($verifyToken['email'] !== $_POST['email']) { + error_log("Verify token does not match email"); + header("HTTP/1.1 400 Bad Request"); + exit(); + } + if (User::userEmailExists($_POST['email'])) { + error_log("Account already exists"); + header("HTTP/1.1 400 Bad Request"); + exit(); + } + if (!$_POST['password'] === $_POST['repeat_password']) { + error_log("Password repeat does not match"); + header("HTTP/1.1 400 Bad Request"); + exit(); } - public static function respondToAccountNew() { - $verifyToken = User::getVerifyToken($_POST['confirm']); - if (!$verifyToken) { - error_log("Could not read verify token"); - header("HTTP/1.1 400 Bad Request"); - exit(); - } - if ($verifyToken['email'] !== $_POST['email']) { - error_log("Verify token does not match email"); - header("HTTP/1.1 400 Bad Request"); - exit(); - } - if (User::userEmailExists($_POST['email'])) { - error_log("Account already exists"); - header("HTTP/1.1 400 Bad Request"); - exit(); - } - if (!$_POST['password'] === $_POST['repeat_password']) { - error_log("Password repeat does not match"); - header("HTTP/1.1 400 Bad Request"); - exit(); - } + $newUser = [ + "email" => $_POST['email'], + "password" => $_POST['password'] + ]; - $newUser = [ - "email" => $_POST['email'], - "password" => $_POST['password'] - ]; + $createdUser = User::createUser($newUser); + if (!$createdUser) { + error_log("Failed to create user"); + header("HTTP/1.1 400 Bad Request"); + exit(); + } + $createdStorage = StorageServer::createStorage($createdUser['webId']); + User::setStorage($createdUser['userId'], $createdStorage['storageUrl']); - $createdUser = User::createUser($newUser); - if (!$createdUser) { - error_log("Failed to create user"); - header("HTTP/1.1 400 Bad Request"); - exit(); - } - $createdStorage = StorageServer::createStorage($createdUser['webId']); - User::setStorage($createdUser['userId'], $createdStorage['storageUrl']); + Mailer::sendAccountCreated($createdUser); - Mailer::sendAccountCreated($createdUser); + $responseData = array( + "webId" => $createdUser['webId'], + "storageUrl" => $createdStorage['storageUrl'] + ); - $responseData = array( - "webId" => $createdUser['webId'], - "storageUrl" => $createdStorage['storageUrl'] - ); + header("HTTP/1.1 201 Created"); + header("Content-type: application/json"); + Session::start($_POST['email']); + echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } - header("HTTP/1.1 201 Created"); + public static function respondToAccountResetPassword() + { + if (!User::userEmailExists($_POST['email'])) { + header("HTTP/1.1 200 OK"); // Return OK even when user is not found; header("Content-type: application/json"); - Session::start($_POST['email']); - echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + echo json_encode("OK"); + exit(); } - - public static function respondToAccountResetPassword() { - if (!User::userEmailExists($_POST['email'])) { - header("HTTP/1.1 200 OK"); // Return OK even when user is not found; - header("Content-type: application/json"); - echo json_encode("OK"); - exit(); - } - $verifyData = [ - 'email' => $_POST['email'] - ]; + $verifyData = [ + 'email' => $_POST['email'] + ]; - $verifyToken = User::saveVerifyToken('passwordReset', $verifyData); - Mailer::sendResetPassword($verifyToken); - header("HTTP/1.1 200 OK"); - header("Content-type: application/json"); - echo json_encode("OK"); + $verifyToken = User::saveVerifyToken('passwordReset', $verifyData); + Mailer::sendResetPassword($verifyToken); + header("HTTP/1.1 200 OK"); + header("Content-type: application/json"); + echo json_encode("OK"); + } + + public static function respondToAccountChangePassword() + { + $verifyToken = User::getVerifyToken($_POST['token']); + if (!$verifyToken) { + header("HTTP/1.1 400 Bad Request"); + exit(); } - - public static function respondToAccountChangePassword() { - $verifyToken = User::getVerifyToken($_POST['token']); - if (!$verifyToken) { - header("HTTP/1.1 400 Bad Request"); - exit(); - } - $result = User::setUserPassword($verifyToken['email'], $_POST['newPassword']); - if (!$result) { - header("HTTP/1.1 400 Bad Request"); - exit(); - } - header("HTTP/1.1 200 OK"); - header("Content-type: application/json"); - echo json_encode("OK"); + $result = User::setUserPassword($verifyToken['email'], $_POST['newPassword']); + if (!$result) { + header("HTTP/1.1 400 Bad Request"); + exit(); } - - public static function respondToAccountDelete() { - if (!User::userEmailExists($_POST['email'])) { - header("HTTP/1.1 200 OK"); // Return OK even when user is not found; - header("Content-type: application/json"); - echo json_encode("OK"); - exit(); - } - $verifyData = [ - 'email' => $_POST['email'] - ]; + header("HTTP/1.1 200 OK"); + header("Content-type: application/json"); + echo json_encode("OK"); + } - $verifyToken = User::saveVerifyToken('deleteAccount', $verifyData); - Mailer::sendDeleteAccount($verifyToken); - header("HTTP/1.1 200 OK"); + public static function respondToAccountDelete() + { + if (!User::userEmailExists($_POST['email'])) { + header("HTTP/1.1 200 OK"); // Return OK even when user is not found; header("Content-type: application/json"); echo json_encode("OK"); + exit(); } - - public static function respondToAccountDeleteConfirm() { - $verifyToken = User::getVerifyToken($_POST['token']); - if (!$verifyToken) { - header("HTTP/1.1 400 Bad Request"); - exit(); - } - User::deleteAccount($verifyToken['email']); - header("HTTP/1.1 200 OK"); - header("Content-type: application/json"); - echo json_encode("OK"); + $verifyData = [ + 'email' => $_POST['email'] + ]; + + $verifyToken = User::saveVerifyToken('deleteAccount', $verifyData); + Mailer::sendDeleteAccount($verifyToken); + header("HTTP/1.1 200 OK"); + header("Content-type: application/json"); + echo json_encode("OK"); + } + + public static function respondToAccountDeleteConfirm() + { + $verifyToken = User::getVerifyToken($_POST['token']); + if (!$verifyToken) { + header("HTTP/1.1 400 Bad Request"); + exit(); + } + User::deleteAccount($verifyToken['email']); + header("HTTP/1.1 200 OK"); + header("Content-type: application/json"); + echo json_encode("OK"); + } + + public static function respondToLogin() + { + $failureCount = IpAttempts::getAttemptsCount($_SERVER['REMOTE_ADDR'], "login"); + if ($failureCount > 5) { + header("HTTP/1.1 400 Bad Request"); + exit(); } - - public static function respondToLogin() { - $failureCount = IpAttempts::getAttemptsCount($_SERVER['REMOTE_ADDR'], "login"); - if ($failureCount > 5) { - header("HTTP/1.1 400 Bad Request"); + if (User::checkPassword($_POST['username'], $_POST['password'])) { + Session::start($_POST['username']); + $user = User::getUser($_POST['username']); + if (!isset($_POST['redirect_uri']) || $_POST['redirect_uri'] === '') { + header("Location: /dashboard/#autologin/" . urlencode($user['webId'])); exit(); } - if (User::checkPassword($_POST['username'], $_POST['password'])) { - Session::start($_POST['username']); - $user = User::getUser($_POST['username']); - if (!isset($_POST['redirect_uri']) || $_POST['redirect_uri'] === '') { - header("Location: /dashboard/#autologin/" . urlencode($user['webId'])); - exit(); - } - header("Location: " . urldecode($_POST['redirect_uri'])); // FIXME: Do we need to harden this? - } else { - IpAttempts::logFailedAttempt($_SERVER['REMOTE_ADDR'], "login", time() + 3600); - header("Location: /login/"); - } + header("Location: " . urldecode($_POST['redirect_uri'])); // FIXME: Do we need to harden this? + } else { + IpAttempts::logFailedAttempt($_SERVER['REMOTE_ADDR'], "login", time() + 3600); + header("Location: /login/"); } } +} diff --git a/lib/Routes/SolidIdp.php b/lib/Routes/SolidIdp.php index 6cc3225..9e628bc 100644 --- a/lib/Routes/SolidIdp.php +++ b/lib/Routes/SolidIdp.php @@ -1,220 +1,230 @@ respondToJwksMetadataRequest(); - Server::respond($response); + +// phpcs:disable Generic.Files.LineLength.TooLong + +namespace Pdsinterop\PhpSolid\Routes; + +use Pdsinterop\PhpSolid\Server; +use Pdsinterop\PhpSolid\ClientRegistration; +use Pdsinterop\PhpSolid\User; +use Pdsinterop\PhpSolid\Session; + +class SolidIdp +{ + public static function respondToJwks() + { + $authServer = Server::getAuthServer(); + $response = $authServer->respondToJwksMetadataRequest(); + Server::respond($response); + } + + public static function respondToWellKnownOpenIdConfiguration() + { + $authServer = Server::getAuthServer(); + $response = $authServer->respondToOpenIdMetadataRequest(); + Server::respond($response); + } + + public static function respondToAuthorize() + { + $user = User::getUser(Session::getLoggedInUser()); + + $clientId = $_GET['client_id']; + $getVars = $_GET; + + if (!isset($getVars['grant_type'])) { + $getVars['grant_type'] = 'implicit'; } + $getVars['scope'] = "openid" ; + $getVars['response_type'] = "token"; - public static function respondToWellKnownOpenIdConfiguration() { - $authServer = Server::getAuthServer(); - $response = $authServer->respondToOpenIdMetadataRequest(); - Server::respond($response); + $requestedResponseTypes = explode(" ", ($_GET['response_type'] ?? '')); + if (in_array("code", $requestedResponseTypes)) { + $getVars['response_type'] = "code"; } - public static function respondToAuthorize() { - $user = User::getUser(Session::getLoggedInUser()); + $keys = Server::getKeys(); + if (isset($_GET['request'])) { + $jwtConfig = \Lcobucci\JWT\Configuration::forSymmetricSigner( + new \Lcobucci\JWT\Signer\Rsa\Sha256(), + \Lcobucci\JWT\Signer\Key\InMemory::plainText($keys['privateKey']) + ); - $clientId = $_GET['client_id']; - $getVars = $_GET; - - if (!isset($getVars['grant_type'])) { - $getVars['grant_type'] = 'implicit'; + if (isset($_GET['nonce'])) { + $_SESSION['nonce'] = $_GET['nonce']; + } else if (isset($_GET['request'])) { + $token = $jwtConfig->parser()->parse($_GET['request']); + $_SESSION['nonce'] = $token->claims()->get('nonce'); } - $getVars['scope'] = "openid" ; - $getVars['response_type'] = "token"; - - $requestedResponseTypes = explode(" ", ($_GET['response_type'] ?? '')); - if (in_array("code", $requestedResponseTypes)) { - $getVars['response_type'] = "code"; - } - - $keys = Server::getKeys(); - if (isset($_GET['request'])) { - $jwtConfig = \Lcobucci\JWT\Configuration::forSymmetricSigner( - new \Lcobucci\JWT\Signer\Rsa\Sha256(), - \Lcobucci\JWT\Signer\Key\InMemory::plainText($keys['privateKey'] - )); - - if (isset($_GET['nonce'])) { - $_SESSION['nonce'] = $_GET['nonce']; - } else if (isset($_GET['request'])) { - $token = $jwtConfig->parser()->parse($_GET['request']); - $_SESSION['nonce'] = $token->claims()->get('nonce'); - } - if (!isset($getVars["redirect_uri"])) { - if (isset($token)) { - $getVars['redirect_uri'] = $token->claims()->get("redirect_uri"); - } + if (!isset($getVars["redirect_uri"])) { + if (isset($token)) { + $getVars['redirect_uri'] = $token->claims()->get("redirect_uri"); } } + } - $requestFactory = new \Laminas\Diactoros\ServerRequestFactory(); - $request = $requestFactory->fromGlobals($_SERVER, $getVars, $_POST, $_COOKIE, $_FILES); + $requestFactory = new \Laminas\Diactoros\ServerRequestFactory(); + $request = $requestFactory->fromGlobals($_SERVER, $getVars, $_POST, $_COOKIE, $_FILES); - $authServer = Server::getAuthServer(); - - $approval = false; - // check clientId approval for the user - if (in_array($clientId, ($user['allowedClients'] ?? []))) { - $approval = true; - } else { - $clientRegistration = ClientRegistration::getRegistration($clientId); - if (isset($clientRegistration['origin']) && in_array($clientRegistration['origin'], TRUSTED_APPS)) { - $approval = true; - } - } + $authServer = Server::getAuthServer(); - if (!$approval) { - header('Location: ' . BASEURL . '/sharing/' . "?" . http_build_query( - array( - "returnUrl" => urlencode($_SERVER["REQUEST_URI"]), - "client_id" => $clientId, - "redirect_uri" => $getVars['redirect_uri'] - ) - )); - exit(); - } - - $webId = "https://id-" . $user['userId'] . "." . BASEDOMAIN . "/#me"; - $user = new \Pdsinterop\Solid\Auth\Entity\User(); - $user->setIdentifier($webId); - - $response = $authServer->respondToAuthorizationRequest($request, $user, $approval); - - if (in_array("id_token", $requestedResponseTypes)) { - $tokenGenerator = Server::getTokenGenerator(); - - $response = $tokenGenerator->addIdTokenToResponse( - $response, - $clientId, - $webId, - $_SESSION['nonce'] ?? '', - Server::getKeys()["privateKey"] - ); + $approval = false; + // check clientId approval for the user + if (in_array($clientId, ($user['allowedClients'] ?? []))) { + $approval = true; + } else { + $clientRegistration = ClientRegistration::getRegistration($clientId); + if (isset($clientRegistration['origin']) && in_array($clientRegistration['origin'], TRUSTED_APPS)) { + $approval = true; } + } - Server::respond($response); + if (!$approval) { + header('Location: ' . BASEURL . '/sharing/' . "?" . http_build_query( + array( + "returnUrl" => urlencode($_SERVER["REQUEST_URI"]), + "client_id" => $clientId, + "redirect_uri" => $getVars['redirect_uri'] + ) + )); + exit(); } - public static function respondToRegister() { - $postData = file_get_contents("php://input"); - $clientData = json_decode($postData, true); - if (!isset($clientData)) { - header("HTTP/1.1 400 Bad request"); - return; - } - $parsedOrigin = parse_url($clientData['redirect_uris'][0]); // FIXME: Should we have multiple origins? - $origin = $parsedOrigin['scheme'] . '://' . $parsedOrigin['host']; - if (isset($parsedOrigin['port'])) { - $origin .= ":" . $parsedOrigin['port']; - } + $webId = "https://id-" . $user['userId'] . "." . BASEDOMAIN . "/#me"; + $user = new \Pdsinterop\Solid\Auth\Entity\User(); + $user->setIdentifier($webId); + $response = $authServer->respondToAuthorizationRequest($request, $user, $approval); - $generatedClientId = bin2hex(random_bytes(16)); // 32 chars for the client Id - $generatedClientSecret = bin2hex(random_bytes(32)); // and 64 chars for the client secret - - $clientData['client_id_issued_at'] = time(); - $clientData['client_id'] = $generatedClientId; - $clientData['client_secret'] = $generatedClientSecret; - $clientData['origin'] = $origin; - ClientRegistration::saveClientRegistration($clientData); - - $client = ClientRegistration::getRegistration($generatedClientId); - - $responseData = array( - 'redirect_uris' => $client['redirect_uris'], - 'client_id' => $client['client_id'], - 'client_secret' => $client['client_secret'], - 'response_types' => array('code'), - 'grant_types' => array('authorization_code', 'refresh_token'), - 'application_type' => $client['application_type'] ?? 'web', - 'client_name' => $client['client_name'] ?? $client['client_id'], - 'id_token_signed_response_alg' => 'RS256', - 'token_endpoint_auth_method' => 'client_secret_basic', - 'client_id_issued_at' => $client['client_id_issued_at'], - 'client_secret_expires_at' => 0 + if (in_array("id_token", $requestedResponseTypes)) { + $tokenGenerator = Server::getTokenGenerator(); + + $response = $tokenGenerator->addIdTokenToResponse( + $response, + $clientId, + $webId, + $_SESSION['nonce'] ?? '', + Server::getKeys()["privateKey"] ); - header("HTTP/1.1 201 Created"); - header("Content-type: application/json"); - echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); } - - public static function respondToSharing() { - $user = User::getUser(Session::getLoggedInUser()); - $clientId = $_POST['client_id']; - $userId = $user['userId']; - if ($_POST['consent'] === 'true') { - User::allowClientForUser($clientId, $userId); - } - $returnUrl = urldecode($_POST['returnUrl']); - header("Location: $returnUrl"); + + Server::respond($response); + } + + public static function respondToRegister() + { + $postData = file_get_contents("php://input"); + $clientData = json_decode($postData, true); + if (!isset($clientData)) { + header("HTTP/1.1 400 Bad request"); + return; + } + $parsedOrigin = parse_url($clientData['redirect_uris'][0]); // FIXME: Should we have multiple origins? + $origin = $parsedOrigin['scheme'] . '://' . $parsedOrigin['host']; + if (isset($parsedOrigin['port'])) { + $origin .= ":" . $parsedOrigin['port']; } - - public static function respondToToken() { - $authServer = Server::getAuthServer(); - $tokenGenerator = Server::getTokenGenerator(); - $requestFactory = new \Laminas\Diactoros\ServerRequestFactory(); - $request = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); - $requestBody = $request->getParsedBody(); - - $grantType = $requestBody['grant_type'] ?? null; - $clientId = $requestBody['client_id'] ?? null; - switch ($grantType) { - case "authorization_code": - $code = $requestBody['code']; - $codeInfo = $tokenGenerator->getCodeInfo($code); - $userId = $codeInfo['user_id']; - if (!$clientId) { - $clientId = $codeInfo['client_id']; - } - break; - case "refresh_token": - $refreshToken = $requestBody['refresh_token']; - $tokenInfo = $tokenGenerator->getCodeInfo($refreshToken); // FIXME: getCodeInfo should be named 'decrypt' or 'getInfo'? - $userId = $tokenInfo['user_id']; - if (!$clientId) { - $clientId = $tokenInfo['client_id']; - } - break; - default: - $userId = false; - break; - } - - $httpDpop = $request->getServerParams()['HTTP_DPOP']; - - $response = $authServer->respondToAccessTokenRequest($request); - - if (isset($userId)) { - $response = $tokenGenerator->addIdTokenToResponse( - $response, - $clientId, - $userId, - ($_SESSION['nonce'] ?? ''), - Server::getKeys()['privateKey'], - $httpDpop - ); - } - // Hack for podpro - if (PODPRO_COMPATIBILITY && strstr($clientId, "podpro.dev")) { - $response->getBody()->rewind(); - $responseBody = $response->getBody()->getContents(); - $body = json_decode($responseBody, true); - $body['refresh_token'] = str_repeat('a', 209); // Podpro doesn't like refresh tokens longer than 209 characters; Sad. - Server::respondPodPro($response, $body); - } else { - Server::respond($response); - } + $generatedClientId = bin2hex(random_bytes(16)); // 32 chars for the client Id + $generatedClientSecret = bin2hex(random_bytes(32)); // and 64 chars for the client secret + + $clientData['client_id_issued_at'] = time(); + $clientData['client_id'] = $generatedClientId; + $clientData['client_secret'] = $generatedClientSecret; + $clientData['origin'] = $origin; + ClientRegistration::saveClientRegistration($clientData); + + $client = ClientRegistration::getRegistration($generatedClientId); + + $responseData = array( + 'redirect_uris' => $client['redirect_uris'], + 'client_id' => $client['client_id'], + 'client_secret' => $client['client_secret'], + 'response_types' => array('code'), + 'grant_types' => array('authorization_code', 'refresh_token'), + 'application_type' => $client['application_type'] ?? 'web', + 'client_name' => $client['client_name'] ?? $client['client_id'], + 'id_token_signed_response_alg' => 'RS256', + 'token_endpoint_auth_method' => 'client_secret_basic', + 'client_id_issued_at' => $client['client_id_issued_at'], + 'client_secret_expires_at' => 0 + ); + header("HTTP/1.1 201 Created"); + header("Content-type: application/json"); + echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } + + public static function respondToSharing() + { + $user = User::getUser(Session::getLoggedInUser()); + $clientId = $_POST['client_id']; + $userId = $user['userId']; + if ($_POST['consent'] === 'true') { + User::allowClientForUser($clientId, $userId); + } + $returnUrl = urldecode($_POST['returnUrl']); + header("Location: $returnUrl"); + } + + public static function respondToToken() + { + $authServer = Server::getAuthServer(); + $tokenGenerator = Server::getTokenGenerator(); + + $requestFactory = new \Laminas\Diactoros\ServerRequestFactory(); + $request = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); + $requestBody = $request->getParsedBody(); + + $grantType = $requestBody['grant_type'] ?? null; + $clientId = $requestBody['client_id'] ?? null; + switch ($grantType) { + case "authorization_code": + $code = $requestBody['code']; + $codeInfo = $tokenGenerator->getCodeInfo($code); + $userId = $codeInfo['user_id']; + if (!$clientId) { + $clientId = $codeInfo['client_id']; + } + break; + case "refresh_token": + $refreshToken = $requestBody['refresh_token']; + $tokenInfo = $tokenGenerator->getCodeInfo($refreshToken); // FIXME: getCodeInfo should be named 'decrypt' or 'getInfo'? + $userId = $tokenInfo['user_id']; + if (!$clientId) { + $clientId = $tokenInfo['client_id']; + } + break; + default: + $userId = false; + break; + } + + $httpDpop = $request->getServerParams()['HTTP_DPOP']; + + $response = $authServer->respondToAccessTokenRequest($request); + + if (isset($userId)) { + $response = $tokenGenerator->addIdTokenToResponse( + $response, + $clientId, + $userId, + ($_SESSION['nonce'] ?? ''), + Server::getKeys()['privateKey'], + $httpDpop + ); + } + + // Hack for podpro + if (PODPRO_COMPATIBILITY && strstr($clientId, "podpro.dev")) { + $response->getBody()->rewind(); + $responseBody = $response->getBody()->getContents(); + $body = json_decode($responseBody, true); + $body['refresh_token'] = str_repeat('a', 209); // Podpro doesn't like refresh tokens longer than 209 characters; Sad. + Server::respondPodPro($response, $body); + } else { + Server::respond($response); } } +} diff --git a/lib/Routes/SolidStorage.php b/lib/Routes/SolidStorage.php index 1201725..8f942a8 100644 --- a/lib/Routes/SolidStorage.php +++ b/lib/Routes/SolidStorage.php @@ -1,86 +1,88 @@ fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); - - 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(); - $resourceServer->setNotifications($solidNotifications); - - $wac = new WAC($filesystem); - - $baseUrl = Util::getServerBaseUrl(); - $resourceServer->setBaseUrl($baseUrl); - $wac->setBaseUrl($baseUrl); - - try { - $webId = StorageServer::getWebId($rawRequest); - } catch(\Exception $e) { - $response = $resourceServer->getResponse() - -> withStatus(400, "Bad request"); - StorageServer::respond($response); - exit(); - } - - if (!isset($webId)) { - $response = $resourceServer->getResponse() - ->withStatus(409, "Invalid token"); - StorageServer::respond($response); - exit(); - } - - $origin = $rawRequest->getHeaderLine("Origin"); - - // FIXME: Read allowed clients from the profile instead; - - $ownerWebId = StorageServer::getOwnerWebId(); - $owner = User::getUserByWebId($ownerWebId); - - $allowedClients = $owner['allowedClients'] ?? []; - $allowedOrigins = array_merge( - ($owner['allowedOrigins'] ?? []), - (TRUSTED_APPS ?? []) - ); - $allowedOrigins = array_unique($allowedOrigins); - - if (!isset($origin) || ($origin === "")) { - $allowedOrigins[] = "app://unset"; // FIXME: this should not be here. - $origin = "app://unset"; - } - - if (!$wac->isAllowed($rawRequest, $webId, $origin, $allowedOrigins)) { - $response = new Response(); - $response = $response->withStatus(403, "Access denied!"); - StorageServer::respond($response); - exit(); - } - - $response = $resourceServer->respondToRequest($rawRequest); - $response = $wac->addWACHeaders($rawRequest, $response, $webId); + +namespace Pdsinterop\PhpSolid\Routes; + +use Pdsinterop\PhpSolid\User; +use Pdsinterop\PhpSolid\StorageServer; +use Pdsinterop\PhpSolid\ClientRegistration; +use Pdsinterop\PhpSolid\SolidNotifications; +use Pdsinterop\PhpSolid\Util; +use Pdsinterop\Solid\Auth\WAC; +use Pdsinterop\Solid\Resources\Server as ResourceServer; +use Laminas\Diactoros\ServerRequestFactory; +use Laminas\Diactoros\Response; + +class SolidStorage +{ + public static function respondToStorage() + { + $requestFactory = new ServerRequestFactory(); + $rawRequest = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); + + 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(); + $resourceServer->setNotifications($solidNotifications); + + $wac = new WAC($filesystem); + + $baseUrl = Util::getServerBaseUrl(); + $resourceServer->setBaseUrl($baseUrl); + $wac->setBaseUrl($baseUrl); + + try { + $webId = StorageServer::getWebId($rawRequest); + } catch (\Exception $e) { + $response = $resourceServer->getResponse() + -> withStatus(400, "Bad request"); StorageServer::respond($response); + exit(); } + + if (!isset($webId)) { + $response = $resourceServer->getResponse() + ->withStatus(409, "Invalid token"); + StorageServer::respond($response); + exit(); + } + + $origin = $rawRequest->getHeaderLine("Origin"); + + // FIXME: Read allowed clients from the profile instead; + + $ownerWebId = StorageServer::getOwnerWebId(); + $owner = User::getUserByWebId($ownerWebId); + + $allowedClients = $owner['allowedClients'] ?? []; + $allowedOrigins = array_merge( + ($owner['allowedOrigins'] ?? []), + (TRUSTED_APPS ?? []) + ); + $allowedOrigins = array_unique($allowedOrigins); + + if (!isset($origin) || ($origin === "")) { + $allowedOrigins[] = "app://unset"; // FIXME: this should not be here. + $origin = "app://unset"; + } + + if (!$wac->isAllowed($rawRequest, $webId, $origin, $allowedOrigins)) { + $response = new Response(); + $response = $response->withStatus(403, "Access denied!"); + StorageServer::respond($response); + exit(); + } + + $response = $resourceServer->respondToRequest($rawRequest); + $response = $wac->addWACHeaders($rawRequest, $response, $webId); + StorageServer::respond($response); } - \ No newline at end of file +} diff --git a/lib/Routes/SolidStorageProvider.php b/lib/Routes/SolidStorageProvider.php index 6bc3a31..d20eec5 100644 --- a/lib/Routes/SolidStorageProvider.php +++ b/lib/Routes/SolidStorageProvider.php @@ -1,35 +1,37 @@ fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); - $webId = StorageServer::getWebId($rawRequest); +use Pdsinterop\PhpSolid\StorageServer; +use Laminas\Diactoros\ServerRequestFactory; - if (!isset($webId) || $webId === "public") { - header("HTTP/1.1 400 Bad Request"); - exit(); - } +class SolidStorageProvider +{ + public static function respondToStorageNew() + { + $requestFactory = new ServerRequestFactory(); + $rawRequest = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); + $webId = StorageServer::getWebId($rawRequest); - // FIXME: Get the webID issuer and validate that we allow storage creation for that issuer + if (!isset($webId) || $webId === "public") { + header("HTTP/1.1 400 Bad Request"); + exit(); + } - $createdStorage = StorageServer::createStorage($webId); - if (!$createdStorage) { - error_log("Failed to create storage"); - header("HTTP/1.1 400 Bad Request"); - exit(); - } + // FIXME: Get the webID issuer and validate that we allow storage creation for that issuer - $responseData = array( - "storage" => $createdStorage['storageUrl'] - ); - header("HTTP/1.1 201 Created"); - header("Content-type: application/json"); - echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + $createdStorage = StorageServer::createStorage($webId); + if (!$createdStorage) { + error_log("Failed to create storage"); + header("HTTP/1.1 400 Bad Request"); + exit(); } + + $responseData = array( + "storage" => $createdStorage['storageUrl'] + ); + header("HTTP/1.1 201 Created"); + header("Content-type: application/json"); + echo json_encode($responseData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); } - \ No newline at end of file +} diff --git a/lib/Routes/SolidUserProfile.php b/lib/Routes/SolidUserProfile.php index f650917..d900458 100644 --- a/lib/Routes/SolidUserProfile.php +++ b/lib/Routes/SolidUserProfile.php @@ -1,71 +1,73 @@ fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); - ProfileServer::initializeProfile(); - $filesystem = ProfileServer::getFileSystem(); +class SolidUserProfile +{ + public static function respondToProfile() + { + $requestFactory = new ServerRequestFactory(); + $serverData = $_SERVER; - $resourceServer = new ResourceServer($filesystem, new Response(), null); - $solidNotifications = new SolidNotifications(); - $resourceServer->setNotifications($solidNotifications); + $rawRequest = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES); + ProfileServer::initializeProfile(); + $filesystem = ProfileServer::getFileSystem(); - $wac = new WAC($filesystem); + $resourceServer = new ResourceServer($filesystem, new Response(), null); + $solidNotifications = new SolidNotifications(); + $resourceServer->setNotifications($solidNotifications); - $baseUrl = Util::getServerBaseUrl(); - $resourceServer->setBaseUrl($baseUrl); - $resourceServer->lockToPath("/profile.ttl"); - $wac->setBaseUrl($baseUrl); + $wac = new WAC($filesystem); - // use the original $_SERVER without modified path, otherwise the htu check for DPOP will fail - $webId = ProfileServer::getWebId($requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES)); + $baseUrl = Util::getServerBaseUrl(); + $resourceServer->setBaseUrl($baseUrl); + $resourceServer->lockToPath("/profile.ttl"); + $wac->setBaseUrl($baseUrl); - if (!isset($webId)) { - $response = $resourceServer->getResponse() - ->withStatus(409, "Invalid token"); - ProfileServer::respond($response); - exit(); - } + // use the original $_SERVER without modified path, otherwise the htu check for DPOP will fail + $webId = ProfileServer::getWebId($requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES)); - $origin = $rawRequest->getHeaderLine("Origin"); + if (!isset($webId)) { + $response = $resourceServer->getResponse() + ->withStatus(409, "Invalid token"); + ProfileServer::respond($response); + exit(); + } - // FIXME: Read allowed clients from the profile instead; - $owner = ProfileServer::getOwner(); + $origin = $rawRequest->getHeaderLine("Origin"); - $allowedClients = $owner['allowedClients'] ?? []; - $allowedOrigins = array_merge( - ($owner['allowedOrigins'] ?? []), - (TRUSTED_APPS ?? []) - ); - $allowedOrigins = array_unique($allowedOrigins); - if (!isset($origin) || ($origin === "")) { - $allowedOrigins[] = "app://unset"; // FIXME: this should not be here. - $origin = "app://unset"; - } + // FIXME: Read allowed clients from the profile instead; + $owner = ProfileServer::getOwner(); - if (!$wac->isAllowed($rawRequest, $webId, $origin, $allowedOrigins)) { - $response = new Response(); - $response = $response->withStatus(403, "Access denied!"); - ProfileServer::respond($response); - exit(); - } + $allowedClients = $owner['allowedClients'] ?? []; + $allowedOrigins = array_merge( + ($owner['allowedOrigins'] ?? []), + (TRUSTED_APPS ?? []) + ); + $allowedOrigins = array_unique($allowedOrigins); + if (!isset($origin) || ($origin === "")) { + $allowedOrigins[] = "app://unset"; // FIXME: this should not be here. + $origin = "app://unset"; + } - $response = $resourceServer->respondToRequest($rawRequest); - $response = $wac->addWACHeaders($rawRequest, $response, $webId); + if (!$wac->isAllowed($rawRequest, $webId, $origin, $allowedOrigins)) { + $response = new Response(); + $response = $response->withStatus(403, "Access denied!"); ProfileServer::respond($response); + exit(); } + + $response = $resourceServer->respondToRequest($rawRequest); + $response = $wac->addWACHeaders($rawRequest, $response, $webId); + ProfileServer::respond($response); } - \ No newline at end of file +} diff --git a/lib/Server.php b/lib/Server.php index ab128a8..204f5ab 100644 --- a/lib/Server.php +++ b/lib/Server.php @@ -1,183 +1,196 @@ "sha256", - "private_key_bits" => 2048, - "private_key_type" => OPENSSL_KEYTYPE_RSA, - ); - // Create the private and public key - $key = openssl_pkey_new($config); - $publicKey = openssl_pkey_get_details($key)['key']; - - // Extract the private key from $key to $privateKey - openssl_pkey_export($key, $privateKey); - $encryptionKey = base64_encode(random_bytes(32)); - $result = array( - "privateKey" => $privateKey, - "publicKey" => $publicKey, - "encryptionKey" => $encryptionKey - ); - return $result; - } +namespace Pdsinterop\PhpSolid; - public static function getAuthServer() { - $authServerConfig = self::getAuthServerConfig(); - $authServerFactory = new AuthorizationServerFactory($authServerConfig); - $authServer = $authServerFactory->create(); - $response = new Response(); - $server = new SolidAuthServer($authServer, $authServerConfig, $response); - return $server; - } +use Pdsinterop\Solid\Auth\Factory\AuthorizationServerFactory; +use Laminas\Diactoros\Response; +use Pdsinterop\Solid\Auth\Server as SolidAuthServer; +use Pdsinterop\Solid\Auth\Factory\ConfigFactory; +use Pdsinterop\Solid\Auth\Config\Client as ConfigClient; +use Pdsinterop\Solid\Auth\Utils\ReplayDetector; +use Pdsinterop\Solid\Auth\Utils\DPop; +use Pdsinterop\Solid\Auth\Utils\Bearer; +use Pdsinterop\Solid\Auth\Utils\JtiValidator; +use Pdsinterop\Solid\Auth\TokenGenerator; +use Pdsinterop\PhpSolid\ClientRegistration; +use Pdsinterop\PhpSolid\JtiStore; - public static function getAuthServerConfig() { - $keys = self::getKeys(); - $configClient = self::getConfigClient(); - $endpoints = self::getEndpoints(); - $authServerConfigFactory = new ConfigFactory( - $configClient, - $keys['encryptionKey'], - $keys['privateKey'], - $keys['publicKey'], - $endpoints - ); - $authServerConfig = $authServerConfigFactory->create(); - return $authServerConfig; +class Server +{ + public static function generateKeySet() + { + $config = array( + "digest_alg" => "sha256", + "private_key_bits" => 2048, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + ); + // Create the private and public key + $key = openssl_pkey_new($config); + $publicKey = openssl_pkey_get_details($key)['key']; + + // Extract the private key from $key to $privateKey + openssl_pkey_export($key, $privateKey); + $encryptionKey = base64_encode(random_bytes(32)); + $result = array( + "privateKey" => $privateKey, + "publicKey" => $publicKey, + "encryptionKey" => $encryptionKey + ); + + return $result; + } + + public static function getAuthServer() + { + $authServerConfig = self::getAuthServerConfig(); + $authServerFactory = new AuthorizationServerFactory($authServerConfig); + $authServer = $authServerFactory->create(); + $response = new Response(); + $server = new SolidAuthServer($authServer, $authServerConfig, $response); + return $server; + } + + public static function getAuthServerConfig() + { + $keys = self::getKeys(); + $configClient = self::getConfigClient(); + $endpoints = self::getEndpoints(); + $authServerConfigFactory = new ConfigFactory( + $configClient, + $keys['encryptionKey'], + $keys['privateKey'], + $keys['publicKey'], + $endpoints + ); + $authServerConfig = $authServerConfigFactory->create(); + return $authServerConfig; + } + + public static function getConfigClient() + { + $clientId = $_GET['client_id'] ?? $_POST['client_id'] ?? null; + if ($clientId) { + $registeredClient = ClientRegistration::getRegistration($clientId); } - - public static function getConfigClient() { - $clientId = $_GET['client_id'] ?? $_POST['client_id'] ?? null; - if ($clientId) { - $registeredClient = ClientRegistration::getRegistration($clientId); - } - if (isset($registeredClient)) { - return new ConfigClient( - $clientId, - $registeredClient['client_secret'] ?? '', - $registeredClient['redirect_uris'], - $registeredClient['client_name'] - ); - } else { - return new ConfigClient( - '', - '', - array(), - '' - ); - } + if (isset($registeredClient)) { + return new ConfigClient( + $clientId, + $registeredClient['client_secret'] ?? '', + $registeredClient['redirect_uris'], + $registeredClient['client_name'] + ); + } else { + return new ConfigClient( + '', + '', + array(), + '' + ); } - - public static function getDpop() { - $replayDetector = new ReplayDetector( - function($jti, $targetUri) { - if (JtiStore::hasJti($jti)) { - return true; - } else { - JtiStore::saveJti($jti); - return false; - } + } + + public static function getDpop() + { + $replayDetector = new ReplayDetector( + function ($jti, $targetUri) { + if (JtiStore::hasJti($jti)) { + return true; + } else { + JtiStore::saveJti($jti); return false; } - ); - $jtiValidator = new JtiValidator($replayDetector); - return new DPop($jtiValidator); - } - - public static function getBearer() { - $replayDetector = new ReplayDetector( - function($jti, $targetUri) { - if (JtiStore::hasJti($jti)) { - return true; - } else { - JtiStore::saveJti($jti); - return false; - } + return false; + } + ); + $jtiValidator = new JtiValidator($replayDetector); + return new DPop($jtiValidator); + } + + public static function getBearer() + { + $replayDetector = new ReplayDetector( + function ($jti, $targetUri) { + if (JtiStore::hasJti($jti)) { + return true; + } else { + JtiStore::saveJti($jti); return false; } - ); - $jtiValidator = new JtiValidator($replayDetector); - return new Bearer($jtiValidator); - } - - public static function getEndpoints() { - return array( - "issuer" => BASEURL, - "jwks_uri" => BASEURL . "/jwks/", - "check_session_iframe" => BASEURL . "/session/", - "end_session_endpoint" => BASEURL . "/logout/", - "authorization_endpoint" => BASEURL . "/authorize/", - "token_endpoint" => BASEURL . "/token/", - "userinfo_endpoint" => BASEURL . "/userinfo/", - "registration_endpoint" => BASEURL . "/register/" - ); - } - - public static function getKeys() { - $keys = array( - 'encryptionKey' => file_get_contents(KEYDIR . "encryption.key"), - 'privateKey' => file_get_contents(KEYDIR . "private.key"), - 'publicKey' => file_get_contents(KEYDIR . "public.key") - ); - return $keys; - } + return false; + } + ); + $jtiValidator = new JtiValidator($replayDetector); + return new Bearer($jtiValidator); + } - public static function getTokenGenerator() { - $dpopValidFor = new \DateInterval('PT10M'); - return new TokenGenerator( - self::getAuthServerConfig(), - $dpopValidFor, - self::getDpop() - ); - } - - public static function respond($response) { - $statusCode = $response->getStatusCode(); - $response->getBody()->rewind(); - $headers = $response->getHeaders(); - - $body = json_decode($response->getBody()->getContents()); - header("HTTP/1.1 $statusCode"); - foreach ($headers as $header => $values) { - foreach ($values as $value) { - if ($header == "Location") { - $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding - } - header($header . ":" . $value); + public static function getEndpoints() + { + return array( + "issuer" => BASEURL, + "jwks_uri" => BASEURL . "/jwks/", + "check_session_iframe" => BASEURL . "/session/", + "end_session_endpoint" => BASEURL . "/logout/", + "authorization_endpoint" => BASEURL . "/authorize/", + "token_endpoint" => BASEURL . "/token/", + "userinfo_endpoint" => BASEURL . "/userinfo/", + "registration_endpoint" => BASEURL . "/register/" + ); + } + + public static function getKeys() + { + $keys = array( + 'encryptionKey' => file_get_contents(KEYDIR . "encryption.key"), + 'privateKey' => file_get_contents(KEYDIR . "private.key"), + 'publicKey' => file_get_contents(KEYDIR . "public.key") + ); + return $keys; + } + + public static function getTokenGenerator() + { + $dpopValidFor = new \DateInterval('PT10M'); + return new TokenGenerator( + self::getAuthServerConfig(), + $dpopValidFor, + self::getDpop() + ); + } + + public static function respond($response) + { + $statusCode = $response->getStatusCode(); + $response->getBody()->rewind(); + $headers = $response->getHeaders(); + + $body = json_decode($response->getBody()->getContents()); + header("HTTP/1.1 $statusCode"); + foreach ($headers as $header => $values) { + foreach ($values as $value) { + if ($header == "Location") { + $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding } + header($header . ":" . $value); } - echo json_encode($body, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); } + echo json_encode($body, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } + + public static function respondPodPro($response, $body) + { + $statusCode = $response->getStatusCode(); + $response->getBody()->rewind(); + $headers = $response->getHeaders(); - public static function respondPodPro($response, $body) { - $statusCode = $response->getStatusCode(); - $response->getBody()->rewind(); - $headers = $response->getHeaders(); - - header("HTTP/1.1 $statusCode"); - foreach ($headers as $header => $values) { - foreach ($values as $value) { - if ($header == "Location") { - $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding - } - header($header . ":" . $value); + header("HTTP/1.1 $statusCode"); + foreach ($headers as $header => $values) { + foreach ($values as $value) { + if ($header == "Location") { + $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding } + header($header . ":" . $value); } - echo json_encode($body, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); } + echo json_encode($body, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); } +} diff --git a/lib/Session.php b/lib/Session.php index 7a707e3..dd1235e 100644 --- a/lib/Session.php +++ b/lib/Session.php @@ -1,23 +1,27 @@ 24*60*60 // 1 day - ]); - } - $_SESSION['username'] = $username; +namespace Pdsinterop\PhpSolid; + +class Session +{ + public static function start($username) + { + if (session_status() === PHP_SESSION_NONE) { + session_start([ + 'cookie_lifetime' => 24 * 60 * 60 // 1 day + ]); + } + $_SESSION['username'] = $username; + } + + public static function getLoggedInUser() + { + if (session_status() === PHP_SESSION_NONE) { + session_start(); } - - public static function getLoggedInUser() { - if (session_status() === PHP_SESSION_NONE) { - session_start(); - } - if (!isset($_SESSION['username'])) { - return false; - } - return $_SESSION['username']; + if (!isset($_SESSION['username'])) { + return false; } + return $_SESSION['username']; } +} diff --git a/lib/SolidNotifications.php b/lib/SolidNotifications.php index ecafa9f..b1d5cab 100644 --- a/lib/SolidNotifications.php +++ b/lib/SolidNotifications.php @@ -1,26 +1,30 @@ notifications = $notifications; + public function __construct() + { + $pubsub = PUBSUB_SERVER; + if ($pubsub) { + $notifications[] = new SolidPubSub($pubsub); } - public function send($path, $type) { - ob_start(); - foreach ($this->notifications as $notification) { - $notification->send($path, $type); - } - ob_end_clean(); + $this->notifications = $notifications; + } + + public function send($path, $type) + { + ob_start(); + foreach ($this->notifications as $notification) { + $notification->send($path, $type); } + ob_end_clean(); } +} diff --git a/lib/SolidPubSub.php b/lib/SolidPubSub.php index 36f49d1..b7df02c 100644 --- a/lib/SolidPubSub.php +++ b/lib/SolidPubSub.php @@ -1,30 +1,33 @@ pubsub = $pubsubUrl; - } + $this->pubsub = $pubsubUrl; + } - public function send($path, $type) { - $pubsub = str_replace(["https://", "http://"], "wss://", $this->pubsub); + public function send($path, $type) + { + $pubsub = str_replace(["https://", "http://"], "wss://", $this->pubsub); - $client = new Client($pubsub); - $client->setContext(['ssl' => [ - 'verify_peer' => false, // if false, accept SSL handshake without client certificate - 'verify_peer_name' => false, - 'allow_self_signed' => true, - ]]); - $client->addHeader("Sec-WebSocket-Protocol", "solid-0.1"); - try { - $client->text("pub $path\n"); - } catch (\Throwable $exception) { - throw new \Exception('Could not write to pubsub server', 502, $exception); - } + $client = new Client($pubsub); + $client->setContext(['ssl' => [ + 'verify_peer' => false, // if false, accept SSL handshake without client certificate + 'verify_peer_name' => false, + 'allow_self_signed' => true, + ]]); + $client->addHeader("Sec-WebSocket-Protocol", "solid-0.1"); + try { + $client->text("pub $path\n"); + } catch (\Throwable $exception) { + throw new \Exception('Could not write to pubsub server', 502, $exception); } } +} diff --git a/lib/StorageServer.php b/lib/StorageServer.php index 5b43e3d..fcb09c7 100644 --- a/lib/StorageServer.php +++ b/lib/StorageServer.php @@ -1,318 +1,336 @@ prepare( - 'SELECT * FROM storage WHERE storage_id=:storageId' - ); - $query->execute([ - ':storageId' => $storageId - ]); - return $query->fetchAll(); + +namespace Pdsinterop\PhpSolid; + +use Pdsinterop\PhpSolid\Server; +use Pdsinterop\PhpSolid\User; +use Pdsinterop\PhpSolid\Util; +use Pdsinterop\PhpSolid\Db; + +class StorageServer extends Server +{ + 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, + "storageUrl" => "https://storage-" . $generatedStorageId . "." . BASEDOMAIN . "/" + ]; + } - 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 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 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, - "storageUrl" => "https://storage-" . $generatedStorageId . "." . BASEDOMAIN . "/" - ]; + 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 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"); + } + if (is_dir(STORAGEBASE . "$storageId/")) { // backwards compatiblity check + $storagePath = $storageId; + } else { + $storagePath = implode("/", str_split($storageId, 4)); } - public static function getFileSystem() { - $storageId = self::getStorageId(); - if (!self::storageIdExists($storageId)) { - throw new \Exception("Storage does not exist"); - } - if (is_dir(STORAGEBASE . "$storageId/")) { // backwards compatiblity check - $storagePath = $storageId; - } else { - $storagePath = implode("/", str_split($storageId, 4)); - } + // The internal adapter + $adapter = new \League\Flysystem\Adapter\Local( + // Determine root directory + STORAGEBASE . "$storagePath/" + ); - // The internal adapter - $adapter = new \League\Flysystem\Adapter\Local( - // Determine root directory - STORAGEBASE . "$storagePath/" - ); - - $graph = new \EasyRdf\Graph(); - // Create Formats objects - $formats = new \Pdsinterop\Rdf\Formats(); - $serverUri = Util::getServerUri(); - - // Create the RDF Adapter - $rdfAdapter = new \Pdsinterop\Rdf\Flysystem\Adapter\Rdf($adapter, $graph, $formats, $serverUri); - - $filesystem = new \League\Flysystem\Filesystem($rdfAdapter); - $filesystem->addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats)); - $plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph); - $filesystem->addPlugin($plugin); - return $filesystem; - } + $graph = new \EasyRdf\Graph(); + // Create Formats objects + $formats = new \Pdsinterop\Rdf\Formats(); + $serverUri = Util::getServerUri(); + + // Create the RDF Adapter + $rdfAdapter = new \Pdsinterop\Rdf\Flysystem\Adapter\Rdf($adapter, $graph, $formats, $serverUri); - public static function respond($response) { - $statusCode = $response->getStatusCode(); - $response->getBody()->rewind(); - $headers = $response->getHeaders(); - - $body = $response->getBody()->getContents(); - header("HTTP/1.1 $statusCode"); - foreach ($headers as $header => $values) { - foreach ($values as $value) { - if ($header == "Location") { - $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding - } - header($header . ":" . $value); + $filesystem = new \League\Flysystem\Filesystem($rdfAdapter); + $filesystem->addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats)); + $plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph); + $filesystem->addPlugin($plugin); + return $filesystem; + } + + public static function respond($response) + { + $statusCode = $response->getStatusCode(); + $response->getBody()->rewind(); + $headers = $response->getHeaders(); + + $body = $response->getBody()->getContents(); + header("HTTP/1.1 $statusCode"); + foreach ($headers as $header => $values) { + foreach ($values as $value) { + if ($header == "Location") { + $value = preg_replace("|%26%2334%3B|", "%22", $value); // odoo weird encoding } + header($header . ":" . $value); } - echo $body; } + echo $body; + } - public static function getWebId($rawRequest) { - $dpop = self::getDpop(); - $webId = $dpop->getWebId($rawRequest); - if (!isset($webId)) { - $bearer = self::getBearer(); - $webId = $bearer->getWebId($rawRequest); - } - return $webId; + public static function getWebId($rawRequest) + { + $dpop = self::getDpop(); + $webId = $dpop->getWebId($rawRequest); + if (!isset($webId)) { + $bearer = self::getBearer(); + $webId = $bearer->getWebId($rawRequest); } + return $webId; + } + + private static function getStorageId() + { + $serverName = Util::getServerName(); + $idParts = explode(".", $serverName, 2); + $storageId = preg_replace("/^storage-/", "", $idParts[0]); + return $storageId; + } - private static function getStorageId() { - $serverName = Util::getServerName(); - $idParts = explode(".", $serverName, 2); - $storageId = preg_replace("/^storage-/", "", $idParts[0]); - return $storageId; + public static function initializeStorage() + { + $filesystem = self::getFilesystem(); + if (!$filesystem->has("/.acl")) { + $defaultAcl = self::generateDefaultAcl(); + $filesystem->write("/.acl", $defaultAcl); } - - public static function initializeStorage() { - $filesystem = self::getFilesystem(); - if (!$filesystem->has("/.acl")) { - $defaultAcl = self::generateDefaultAcl(); - $filesystem->write("/.acl", $defaultAcl); - } - // Generate default folders and ACLs: - if (!$filesystem->has("/inbox")) { - $filesystem->createDir("/inbox"); - } - if (!$filesystem->has("/inbox/.acl")) { - $inboxAcl = self::generatePublicAppendAcl(); - $filesystem->write("/inbox/.acl", $inboxAcl); - } - if (!$filesystem->has("/settings")) { - $filesystem->createDir("/settings"); - } - if (!$filesystem->has("/settings/privateTypeIndex.ttl")) { - $privateTypeIndex = self::generateDefaultPrivateTypeIndex(); - $filesystem->write("/settings/privateTypeIndex.ttl", $privateTypeIndex); - } - if (!$filesystem->has("/settings/publicTypeIndex.ttl")) { - $publicTypeIndex = self::generateDefaultPublicTypeIndex(); - $filesystem->write("/settings/publicTypeIndex.ttl", $publicTypeIndex); - } - if (!$filesystem->has("/settings/preferences.ttl")) { - $preferences = self::generateDefaultPreferences(); - $filesystem->write("/settings/preferences.ttl", $preferences); - } - if (!$filesystem->has("/public")) { - $filesystem->createDir("/public"); - } - if (!$filesystem->has("/public/.acl")) { - $publicAcl = self::generatePublicReadAcl(); - $filesystem->write("/public/.acl", $publicAcl); - } - if (!$filesystem->has("/private")) { - $filesystem->createDir("/private"); - } + // Generate default folders and ACLs: + if (!$filesystem->has("/inbox")) { + $filesystem->createDir("/inbox"); } - - public static function generateDefaultAcl() { - $webId = self::getOwnerWebId(); - if (!$webId) { - throw new \Exception("No owner found for storage"); - } - $acl = <<< "EOF" + if (!$filesystem->has("/inbox/.acl")) { + $inboxAcl = self::generatePublicAppendAcl(); + $filesystem->write("/inbox/.acl", $inboxAcl); + } + if (!$filesystem->has("/settings")) { + $filesystem->createDir("/settings"); + } + if (!$filesystem->has("/settings/privateTypeIndex.ttl")) { + $privateTypeIndex = self::generateDefaultPrivateTypeIndex(); + $filesystem->write("/settings/privateTypeIndex.ttl", $privateTypeIndex); + } + if (!$filesystem->has("/settings/publicTypeIndex.ttl")) { + $publicTypeIndex = self::generateDefaultPublicTypeIndex(); + $filesystem->write("/settings/publicTypeIndex.ttl", $publicTypeIndex); + } + if (!$filesystem->has("/settings/preferences.ttl")) { + $preferences = self::generateDefaultPreferences(); + $filesystem->write("/settings/preferences.ttl", $preferences); + } + if (!$filesystem->has("/public")) { + $filesystem->createDir("/public"); + } + if (!$filesystem->has("/public/.acl")) { + $publicAcl = self::generatePublicReadAcl(); + $filesystem->write("/public/.acl", $publicAcl); + } + if (!$filesystem->has("/private")) { + $filesystem->createDir("/private"); + } + } + + 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: . @prefix foaf: . # The homepage is readable by the public <#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./>; - acl:mode acl:Read. +a acl:Authorization; +acl:agentClass foaf:Agent; +acl:accessTo <./>; +acl:mode acl:Read. # The owner has full access to every resource in their pod. # Other agents have no access rights, # unless specifically authorized in other .acl resources. <#owner> - a acl:Authorization; - acl:agent <$webId>; - # Set the access to the root storage folder itself - acl:accessTo <./>; - # All resources will inherit this authorization, by default - acl:default <./>; - # The owner has all of the access modes allowed - acl:mode - acl:Read, acl:Write, acl:Control. +a acl:Authorization; +acl:agent <$webId>; +# Set the access to the root storage folder itself +acl:accessTo <./>; +# All resources will inherit this authorization, by default +acl:default <./>; +# The owner has all of the access modes allowed +acl:mode + acl:Read, acl:Write, acl:Control. EOF; - return $acl; - } + return $acl; + } - public static function generatePublicAppendAcl() { - $webId = self::getOwnerWebId(); - if (!$webId) { - throw new \Exception("No owner found for storage ID"); - } - $acl = <<< "EOF" + 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: . @prefix foaf: . <#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./>; - acl:default <./>; - acl:mode - acl:Append. +a acl:Authorization; +acl:agentClass foaf:Agent; +acl:accessTo <./>; +acl:default <./>; +acl:mode + acl:Append. <#owner> - a acl:Authorization; - acl:agent <$webId>; - # Set the access to the root storage folder itself - acl:accessTo <./>; - # All resources will inherit this authorization, by default - acl:default <./>; - # The owner has all of the access modes allowed - acl:mode - acl:Read, acl:Write, acl:Control. +a acl:Authorization; +acl:agent <$webId>; +# Set the access to the root storage folder itself +acl:accessTo <./>; +# All resources will inherit this authorization, by default +acl:default <./>; +# The owner has all of the access modes allowed +acl:mode + acl:Read, acl:Write, acl:Control. EOF; - return $acl; - } + return $acl; + } - public static function generatePublicReadAcl() { - $webId = self::getOwnerWebId(); - if (!$webId) { - throw new \Exception("No owner found for storage ID"); - } - $acl = <<< "EOF" + 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: . @prefix foaf: . <#public> - a acl:Authorization; - acl:agentClass foaf:Agent; - acl:accessTo <./>; - acl:default <./>; - acl:mode - acl:Read. +a acl:Authorization; +acl:agentClass foaf:Agent; +acl:accessTo <./>; +acl:default <./>; +acl:mode + acl:Read. <#owner> - a acl:Authorization; - acl:agent <$webId>; - # Set the access to the root storage folder itself - acl:accessTo <./>; - # All resources will inherit this authorization, by default - acl:default <./>; - # The owner has all of the access modes allowed - acl:mode - acl:Read, acl:Write, acl:Control. +a acl:Authorization; +acl:agent <$webId>; +# Set the access to the root storage folder itself +acl:accessTo <./>; +# All resources will inherit this authorization, by default +acl:default <./>; +# The owner has all of the access modes allowed +acl:mode + acl:Read, acl:Write, acl:Control. EOF; - return $acl; - } + return $acl; + } - public static function generateDefaultPrivateTypeIndex() { - $typeIndex = <<< "EOF" + public static function generateDefaultPrivateTypeIndex() + { + $typeIndex = <<< "EOF" # Private type index @prefix : <#>. @prefix solid: . <> - a solid:UnlistedDocument, solid:TypeIndex. +a solid:UnlistedDocument, solid:TypeIndex. EOF; - return $typeIndex; - } + return $typeIndex; + } - public static function generateDefaultPublicTypeIndex() { - $typeIndex = <<< "EOF" + public static function generateDefaultPublicTypeIndex() + { + $typeIndex = <<< "EOF" # Public type index @prefix : <#>. @prefix solid: . <> - a solid:ListedDocument, solid:TypeIndex. +a solid:ListedDocument, solid:TypeIndex. EOF; - return $typeIndex; - } + return $typeIndex; + } - public static function generateDefaultPreferences() { - $webId = self::getOwnerWebId(); - $preferences = <<< "EOF" + public static function generateDefaultPreferences() + { + $webId = self::getOwnerWebId(); + $preferences = <<< "EOF" # Preferences @prefix : <#>. @prefix sp: . @@ -320,15 +338,14 @@ public static function generateDefaultPreferences() { @prefix solid: . <> - a sp:ConfigurationFile; - dct:title "Preferences file". +a sp:ConfigurationFile; +dct:title "Preferences file". <$webId> - a solid:Developer; - solid:privateTypeIndex ; - solid:publicTypeIndex . +a solid:Developer; +solid:privateTypeIndex ; +solid:publicTypeIndex . EOF; - return $preferences; - } + return $preferences; } - \ No newline at end of file +} diff --git a/lib/User.php b/lib/User.php index bfb758b..34ae5fe 100644 --- a/lib/User.php +++ b/lib/User.php @@ -1,384 +1,413 @@ add(new \DateInterval($lifetime)); - return $expires->getTimestamp(); + while (in_array($code, $existingTokens)) { // make sure we have no collissions; + $code = random_int(0, 1000000); + $code = str_pad($code, $digits, '0', STR_PAD_LEFT); } - public static function saveVerifyToken($tokenType, $tokenData) { - switch ($tokenType) { - case "verify": - $tokenData['code'] = self::generateTokenCode(); - $tokenData['expires'] = self::generateExpiresTimestamp('PT30M'); // expires after 30 minutes - break; - case "passwordReset": - case "deleteAccount": - default: - $tokenData['code'] = self::generateTokenHex(); - $tokenData['expires'] = self::generateExpiresTimestamp('PT30M'); // expires after 30 minutes - break; - } - - Db::connect(); - $query = Db::$pdo->prepare( - 'INSERT INTO verify VALUES(:code, :data)' - ); - $query->execute([ - ':code' => $tokenData['code'], - ':data' => json_encode($tokenData) - ]); - return $tokenData; - } - - public static function getVerifyToken($code) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT data FROM verify WHERE code=:code' - ); - $query->execute([ - ':code' => $code - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - $result = json_decode($result[0]['data'], true); - if (!self::isExpired($result)) { - return $result; - } - } - return false; - } - - private static function isExpired($token) { - $now = new \DateTime(); - if ($token['expires'] > $now->getTimestamp()) { - return false; - } - - return true; - } - - public static function validatePasswordStrength($password) { - $entropy = PasswordValidator::getEntropy($password, BANNED_PASSWORDS); - $minimumEntropy = MINIMUM_PASSWORD_ENTROPY; - if ($entropy < $minimumEntropy) { - error_log("Entered pasword does not satisfy minimum entropy"); - return false; - } - return true; + return $code; + } + + private static function generateTokenHex() + { + return bin2hex(random_bytes(16)); + } + + private static function generateExpiresTimestamp($lifetime) + { + $expires = new \DateTime(); + $expires->add(new \DateInterval($lifetime)); + return $expires->getTimestamp(); + } + + public static function saveVerifyToken($tokenType, $tokenData) + { + switch ($tokenType) { + case "verify": + $tokenData['code'] = self::generateTokenCode(); + $tokenData['expires'] = self::generateExpiresTimestamp('PT30M'); // expires after 30 minutes + break; + case "passwordReset": + case "deleteAccount": + default: + $tokenData['code'] = self::generateTokenHex(); + $tokenData['expires'] = self::generateExpiresTimestamp('PT30M'); // expires after 30 minutes + break; } - public static function createUser($newUser) { - Db::connect(); - if (!self::validatePasswordStrength($newUser['password'])) { - return false; - } - $generatedUserId = bin2hex(random_bytes(16)); - while (self::userIdExists($generatedUserId)) { - $generatedUserId = bin2hex(random_bytes(16)); + Db::connect(); + $query = Db::$pdo->prepare( + 'INSERT INTO verify VALUES(:code, :data)' + ); + $query->execute([ + ':code' => $tokenData['code'], + ':data' => json_encode($tokenData) + ]); + return $tokenData; + } + + public static function getVerifyToken($code) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT data FROM verify WHERE code=:code' + ); + $query->execute([ + ':code' => $code + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + $result = json_decode($result[0]['data'], true); + if (!self::isExpired($result)) { + return $result; } - $query = Db::$pdo->prepare( - 'INSERT INTO users VALUES (:userId, :email, :passwordHash, :data)' - ); - - $queryParams = []; - $queryParams[':userId'] = $generatedUserId; - $queryParams[':email'] = $newUser['email']; - $queryParams[':passwordHash'] = password_hash($newUser['password'], PASSWORD_BCRYPT); - unset($newUser['password']); - - $newUser['webId'] = "https://id-" . $generatedUserId . "." . BASEDOMAIN . "/#me"; - $queryParams[':data'] = json_encode($newUser); - $query->execute($queryParams); - - return [ - "userId" => $generatedUserId, - "email" => $newUser['email'], - "webId" => $newUser['webId'] - ]; } + return false; + } - public static function setUserPassword($email, $newPassword) { - if (!self::userEmailExists($email)) { - return false; - } - if (!self::validatePasswordStrength($newPassword)) { - return false; - } - Db::connect(); - $query = Db::$pdo->prepare( - 'UPDATE users SET password=:passwordHash WHERE email=:email' - ); - $queryParams = []; - $queryParams[':email'] = $email; - $queryParams[':passwordHash'] = password_hash($newPassword, PASSWORD_BCRYPT); - - $query->execute($queryParams); - return true; + private static function isExpired($token) + { + $now = new \DateTime(); + if ($token['expires'] > $now->getTimestamp()) { + return false; } - public static function allowClientForUser($clientId, $userId) { - Db::connect(); - $query = Db::$pdo->prepare( - 'INSERT OR REPLACE INTO allowedClients VALUES(:userId, :clientId)' - ); - $query->execute([ - ':userId' => $userId, - ':clientId' => $clientId - ]); - return true; - } + return true; + } - public static function getAllowedClients($userId) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT clientId FROM allowedClients WHERE userId=:userId' - ); - $query->execute([ - ':userId' => $userId - ]); - $result = []; - while($row = $query->fetch()) { - $result[] = $row['clientId']; - } - return $result; + public static function validatePasswordStrength($password) + { + $entropy = PasswordValidator::getEntropy($password, BANNED_PASSWORDS); + $minimumEntropy = MINIMUM_PASSWORD_ENTROPY; + if ($entropy < $minimumEntropy) { + error_log("Entered pasword does not satisfy minimum entropy"); + return false; } + return true; + } - public static function getAllowedOrigins($userId) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT origin from clients LEFT JOIN allowedClients ON clients.clientId=allowedClients.clientId where allowedClients.userId=:userId' - ); - $query->execute([ - ':userId' => $userId - ]); - $result = []; - while($row = $query->fetch()) { - $result[] = $row['origin']; - } - return $result; + public static function createUser($newUser) + { + Db::connect(); + if (!self::validatePasswordStrength($newUser['password'])) { + return false; } - - public static function getStorage($userId) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT storageUrl FROM userStorage WHERE userId=:userId' - ); - $query->execute([ - ':userId' => $userId - ]); - $result = []; - while($row = $query->fetch()) { - $result[] = $row['storageUrl']; - } - return $result; + $generatedUserId = bin2hex(random_bytes(16)); + while (self::userIdExists($generatedUserId)) { + $generatedUserId = bin2hex(random_bytes(16)); } - - public static function setStorage($userId, $storageUrl) { - Db::connect(); - $query = Db::$pdo->prepare( - 'INSERT OR REPLACE INTO userStorage VALUES(:userId, :storageUrl)' - ); - $query->execute([ - ':userId' => $userId, - ':storageUrl' => $storageUrl - ]); + $query = Db::$pdo->prepare( + 'INSERT INTO users VALUES (:userId, :email, :passwordHash, :data)' + ); + + $queryParams = []; + $queryParams[':userId'] = $generatedUserId; + $queryParams[':email'] = $newUser['email']; + $queryParams[':passwordHash'] = password_hash($newUser['password'], PASSWORD_BCRYPT); + unset($newUser['password']); + + $newUser['webId'] = "https://id-" . $generatedUserId . "." . BASEDOMAIN . "/#me"; + $queryParams[':data'] = json_encode($newUser); + $query->execute($queryParams); + + return [ + "userId" => $generatedUserId, + "email" => $newUser['email'], + "webId" => $newUser['webId'] + ]; + } + + public static function setUserPassword($email, $newPassword) + { + if (!self::userEmailExists($email)) { + return false; } - - public static function getUser($email) { - if (!isset($email)) { - return false; - } - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT user_id, data FROM users WHERE email=:email' - ); - $query->execute([ - ':email' => $email - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - $userData = json_decode($result[0]['data'], true); - $userData['userId'] = $result[0]['user_id']; - - $allowedClients = self::getAllowedClients($userData['userId']); - $userData['allowedClients'] = $allowedClients; - $allowedOrigins = self::getAllowedOrigins($userData['userId']); - $userData['allowedOrigins'] = $allowedOrigins; - $userData['issuer'] = BASEURL; - $storage = self::getStorage($userData['userId']); - if ($storage) { - $userData['storage'] = $storage; - } - return $userData; - } + if (!self::validatePasswordStrength($newPassword)) { return false; } - - public static function getUserById($userId) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT user_id, data FROM users WHERE user_id=:userId' - ); - $query->execute([ - ':userId' => $userId - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - $userData = json_decode($result[0]['data'], true); - $userData['userId'] = $result[0]['user_id']; - - $allowedClients = self::getAllowedClients($userData['userId']); - $userData['allowedClients'] = $allowedClients; - $allowedOrigins = self::getAllowedOrigins($userData['userId']); - $userData['allowedOrigins'] = $allowedOrigins; - $userData['issuer'] = BASEURL; - $storage = self::getStorage($userData['userId']); - if ($storage) { - $userData['storage'] = $storage; - } - return $userData; - } - return false; + Db::connect(); + $query = Db::$pdo->prepare( + 'UPDATE users SET password=:passwordHash WHERE email=:email' + ); + $queryParams = []; + $queryParams[':email'] = $email; + $queryParams[':passwordHash'] = password_hash($newPassword, PASSWORD_BCRYPT); + + $query->execute($queryParams); + return true; + } + + public static function allowClientForUser($clientId, $userId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'INSERT OR REPLACE INTO allowedClients VALUES(:userId, :clientId)' + ); + $query->execute([ + ':userId' => $userId, + ':clientId' => $clientId + ]); + return true; + } + + public static function getAllowedClients($userId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT clientId FROM allowedClients WHERE userId=:userId' + ); + $query->execute([ + ':userId' => $userId + ]); + $result = []; + while ($row = $query->fetch()) { + $result[] = $row['clientId']; } - - public static function getUserByWebId($webId) { - $webIdParts = parse_url($webId); - $idParts = explode(".", $webIdParts['host'], 2); - if ($idParts[1] !== BASEDOMAIN) { - return false; - } - $userId = preg_replace("/^.*?id-/", "", $idParts[0]); - - return self::getUserById($userId); + return $result; + } + + public static function getAllowedOrigins($userId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT origin from clients LEFT JOIN allowedClients ON clients.clientId=allowedClients.clientId where allowedClients.userId=:userId' + ); + $query->execute([ + ':userId' => $userId + ]); + $result = []; + while ($row = $query->fetch()) { + $result[] = $row['origin']; } - - public static function checkPassword($email, $password) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT password FROM users WHERE email=:email' - ); - $query->execute([ - ':email' => $email - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - if (password_verify($password, $result[0]['password'])) { - return true; - } - } + return $result; + } + + public static function getStorage($userId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT storageUrl FROM userStorage WHERE userId=:userId' + ); + $query->execute([ + ':userId' => $userId + ]); + $result = []; + while ($row = $query->fetch()) { + $result[] = $row['storageUrl']; + } + return $result; + } + + public static function setStorage($userId, $storageUrl) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'INSERT OR REPLACE INTO userStorage VALUES(:userId, :storageUrl)' + ); + $query->execute([ + ':userId' => $userId, + ':storageUrl' => $storageUrl + ]); + } + + public static function getUser($email) + { + if (!isset($email)) { return false; } - - public static function userIdExists($userId) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT user_id FROM users WHERE user_id=:userId' - ); - $query->execute([ - ':userId' => $userId - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - return true; + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT user_id, data FROM users WHERE email=:email' + ); + $query->execute([ + ':email' => $email + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + $userData = json_decode($result[0]['data'], true); + $userData['userId'] = $result[0]['user_id']; + + $allowedClients = self::getAllowedClients($userData['userId']); + $userData['allowedClients'] = $allowedClients; + $allowedOrigins = self::getAllowedOrigins($userData['userId']); + $userData['allowedOrigins'] = $allowedOrigins; + $userData['issuer'] = BASEURL; + $storage = self::getStorage($userData['userId']); + if ($storage) { + $userData['storage'] = $storage; } - return false; + return $userData; } - - public static function userEmailExists($email) { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT user_id FROM users WHERE email=:email' - ); - $query->execute([ - ':email' => $email - ]); - $result = $query->fetchAll(); - if (sizeof($result) === 1) { - return true; + return false; + } + + public static function getUserById($userId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT user_id, data FROM users WHERE user_id=:userId' + ); + $query->execute([ + ':userId' => $userId + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + $userData = json_decode($result[0]['data'], true); + $userData['userId'] = $result[0]['user_id']; + + $allowedClients = self::getAllowedClients($userData['userId']); + $userData['allowedClients'] = $allowedClients; + $allowedOrigins = self::getAllowedOrigins($userData['userId']); + $userData['allowedOrigins'] = $allowedOrigins; + $userData['issuer'] = BASEURL; + $storage = self::getStorage($userData['userId']); + if ($storage) { + $userData['storage'] = $storage; } - return false; + return $userData; } - - private static function deleteUser($email) { - Db::connect(); - $query = Db::$pdo->prepare( - 'DELETE FROM users WHERE email=:email' - ); - $query->execute([ - ':email' => $email - ]); + return false; + } + + public static function getUserByWebId($webId) + { + $webIdParts = parse_url($webId); + $idParts = explode(".", $webIdParts['host'], 2); + if ($idParts[1] !== BASEDOMAIN) { + return false; } - - private static function deleteAllowedClients($email) { - $user = self::getUser($email); - if (!$user) { - return; + $userId = preg_replace("/^.*?id-/", "", $idParts[0]); + + return self::getUserById($userId); + } + + public static function checkPassword($email, $password) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT password FROM users WHERE email=:email' + ); + $query->execute([ + ':email' => $email + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + if (password_verify($password, $result[0]['password'])) { + return true; } - - Db::connect(); - $query = Db::$pdo->prepare( - 'DELETE FROM allowedClients WHERE userId=:userId' - ); - $query->execute([ - ':userId' => $user['userId'] - ]); } - - public static function deleteAccount($email) { - if (!self::userEmailExists($email)) { - return; - } - // FIXME: Delete storage; - self::deleteAllowedClients($email); - self::deleteUser($email); + return false; + } + + public static function userIdExists($userId) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT user_id FROM users WHERE user_id=:userId' + ); + $query->execute([ + ':userId' => $userId + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + return true; } - - public static function cleanupTokens() { - Db::connect(); - - $now = new \DateTime(); - $query = Db::$pdo->prepare( - 'DELETE FROM verify WHERE json_extract(data, \'$.expires\') < :now' - ); - $query->execute([ - ':now' => $now->getTimestamp() - ]); + return false; + } + + public static function userEmailExists($email) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT user_id FROM users WHERE email=:email' + ); + $query->execute([ + ':email' => $email + ]); + $result = $query->fetchAll(); + if (sizeof($result) === 1) { + return true; + } + return false; + } + + private static function deleteUser($email) + { + Db::connect(); + $query = Db::$pdo->prepare( + 'DELETE FROM users WHERE email=:email' + ); + $query->execute([ + ':email' => $email + ]); + } + + private static function deleteAllowedClients($email) + { + $user = self::getUser($email); + if (!$user) { + return; } - public static function getExistingVerifyTokens() { - Db::connect(); - $query = Db::$pdo->prepare( - 'SELECT code FROM verify' - ); - $query->execute(); - $existingTokens = $query->fetchAll(); - return $existingTokens; + Db::connect(); + $query = Db::$pdo->prepare( + 'DELETE FROM allowedClients WHERE userId=:userId' + ); + $query->execute([ + ':userId' => $user['userId'] + ]); + } + + public static function deleteAccount($email) + { + if (!self::userEmailExists($email)) { + return; } - } + // FIXME: Delete storage; + self::deleteAllowedClients($email); + self::deleteUser($email); + } + + public static function cleanupTokens() + { + Db::connect(); + + $now = new \DateTime(); + $query = Db::$pdo->prepare( + 'DELETE FROM verify WHERE json_extract(data, \'$.expires\') < :now' + ); + $query->execute([ + ':now' => $now->getTimestamp() + ]); + } + + public static function getExistingVerifyTokens() + { + Db::connect(); + $query = Db::$pdo->prepare( + 'SELECT code FROM verify' + ); + $query->execute(); + $existingTokens = $query->fetchAll(); + return $existingTokens; + } +} diff --git a/lib/Util.php b/lib/Util.php index 642e687..639a61c 100644 --- a/lib/Util.php +++ b/lib/Util.php @@ -1,27 +1,39 @@ exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); - } + Db::connect(); + try { + // create tables + foreach ($statements as $statement) { + Db::$pdo->exec($statement); + } + } catch (\PDOException $e) { + echo $e->getMessage(); + } - ClientRegistration::saveClientRegistration([ - "client_id" => "12345", - "origin" => "https://example.com", - "client_name" => "Client name" - ]); + ClientRegistration::saveClientRegistration([ + "client_id" => "12345", + "origin" => "https://example.com", + "client_name" => "Client name" + ]); - ClientRegistration::saveClientRegistration([ - "client_id" => "23456", - "origin" => "https://example2.com" - ]); + ClientRegistration::saveClientRegistration([ + "client_id" => "23456", + "origin" => "https://example2.com" + ]); - ClientRegistration::saveClientRegistration([ - "client_id" => "34567", - "origin" => "https://example2.com" - ]); - } + ClientRegistration::saveClientRegistration([ + "client_id" => "34567", + "origin" => "https://example2.com" + ]); + } - public function testGetRegistration() { - $storedData = ClientRegistration::getRegistration("12345"); - $this->assertEquals("12345", $storedData['client_id']); - $this->assertEquals("https://example.com", $storedData['origin']); - $this->assertEquals("Client name", $storedData['client_name']); - } + public function testGetRegistration() + { + $storedData = ClientRegistration::getRegistration("12345"); + $this->assertEquals("12345", $storedData['client_id']); + $this->assertEquals("https://example.com", $storedData['origin']); + $this->assertEquals("Client name", $storedData['client_name']); + } - public function testClientNameAutofill() { - $storedData = ClientRegistration::getRegistration("23456"); - $this->assertEquals("23456", $storedData['client_id']); - $this->assertEquals("https://example2.com", $storedData['origin']); - $this->assertEquals("https://example2.com", $storedData['client_name']); - } + public function testClientNameAutofill() + { + $storedData = ClientRegistration::getRegistration("23456"); + $this->assertEquals("23456", $storedData['client_id']); + $this->assertEquals("https://example2.com", $storedData['origin']); + $this->assertEquals("https://example2.com", $storedData['client_name']); + } - public function testClientByOrigin() { - $storedData = ClientRegistration::getClientByOrigin("https://example.com"); - $this->assertEquals("12345", $storedData['client_id']); - $this->assertEquals("https://example.com", $storedData['origin']); - $this->assertEquals("Client name", $storedData['client_name']); - } + public function testClientByOrigin() + { + $storedData = ClientRegistration::getClientByOrigin("https://example.com"); + $this->assertEquals("12345", $storedData['client_id']); + $this->assertEquals("https://example.com", $storedData['origin']); + $this->assertEquals("Client name", $storedData['client_name']); + } - public function testClientByDuplicateOrigin() { - $storedData = ClientRegistration::getClientByOrigin("https://example2.com"); - $this->assertFalse($storedData); // false because we have 2 clients with the same origin - } + public function testClientByDuplicateOrigin() + { + $storedData = ClientRegistration::getClientByOrigin("https://example2.com"); + $this->assertFalse($storedData); // false because we have 2 clients with the same origin + } } diff --git a/tests/phpunit/DbTest.php b/tests/phpunit/DbTest.php index b4e1d07..1a8885f 100644 --- a/tests/phpunit/DbTest.php +++ b/tests/phpunit/DbTest.php @@ -1,13 +1,15 @@ assertInstanceOf("PDO", Db::$pdo); - } - } +use Pdsinterop\PhpSolid\Db; + +const DBPATH = ":memory:"; +class DbTest extends \PHPUnit\Framework\TestCase +{ + public function testConnect() + { + Db::connect(); + $this->assertInstanceOf("PDO", Db::$pdo); + } +} diff --git a/tests/phpunit/IpAttemptsTest.php b/tests/phpunit/IpAttemptsTest.php index df0373d..06d3635 100644 --- a/tests/phpunit/IpAttemptsTest.php +++ b/tests/phpunit/IpAttemptsTest.php @@ -1,4 +1,7 @@ exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); - } - } - - public function testGetAttemptsCountZero() { - $ip = "10.0.0.1"; - $count = IpAttempts::getAttemptsCount($ip, "test"); - $this->assertEquals(0, $count); - } - - public function testGetAttemptsCountOne() { - $ip = "10.0.0.1"; - - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - $count = IpAttempts::getAttemptsCount($ip, "test"); - $this->assertEquals(1, $count); - } - - public function testGetAttemptsCountTwo() { - $ip = "10.0.0.1"; - - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - - $count = IpAttempts::getAttemptsCount($ip, "test"); - $this->assertEquals(2, $count); - } - - public function testGetAttemptsCountExpired() { - $ip = "10.0.0.1"; - - IpAttempts::logFailedAttempt($ip, "test", time() - 1); - IpAttempts::logFailedAttempt($ip, "test", time() - 1); - - $count = IpAttempts::getAttemptsCount($ip, "test"); - $this->assertEquals(0, $count); - } - - public function testGetAttemptsCountOneExpired() { - $ip = "10.0.0.1"; - - IpAttempts::logFailedAttempt($ip, "test", time() + 10); - IpAttempts::logFailedAttempt($ip, "test", time() - 1); - - $count = IpAttempts::getAttemptsCount($ip, "test"); - $this->assertEquals(1, $count); - } - - public function testCleanup() { - $ip = "10.0.0.1"; - - IpAttempts::logFailedAttempt($ip, "test", time() - 1); - IpAttempts::logFailedAttempt($ip, "test", time() - 1); - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - - $query = Db::$pdo->prepare('SELECT count(*) AS count FROM ipAttempts'); - $query->execute(); - $result = $query->fetchAll(); - $beforeCleanup = $result[0]['count']; - - $this->assertEquals(4, $beforeCleanup); - - IpAttempts::cleanupAttempts(); - $query = Db::$pdo->prepare('SELECT count(*) AS count FROM ipAttempts'); - $query->execute(); - $result = $query->fetchAll(); - $afterCleanup = $result[0]['count']; - - $this->assertEquals(2, $afterCleanup); - } - - public function testTrustedIpGetAttempts() { - $ip = "127.0.0.100"; // trusted IP - - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - - $count = IpAttempts::getAttemptsCount($ip, "test"); - $this->assertEquals(0, $count); - } - - public function testTrustedIpGetAttemptsSkipsDb() { - $ip = "127.0.0.100"; // trusted IP - - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - IpAttempts::logFailedAttempt($ip, "test", time() + 3600); - - $query = Db::$pdo->prepare('SELECT count(*) AS count FROM ipAttempts'); - $query->execute(); - $result = $query->fetchAll(); - $count = $result[0]['count']; - $this->assertEquals(0, $count); - } + )' + ]; + + Db::connect(); + try { + // create tables + foreach ($statements as $statement) { + Db::$pdo->exec($statement); + } + } catch (\PDOException $e) { + echo $e->getMessage(); + } + } + + public function testGetAttemptsCountZero() + { + $ip = "10.0.0.1"; + $count = IpAttempts::getAttemptsCount($ip, "test"); + $this->assertEquals(0, $count); + } + + public function testGetAttemptsCountOne() + { + $ip = "10.0.0.1"; + + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + $count = IpAttempts::getAttemptsCount($ip, "test"); + $this->assertEquals(1, $count); + } + + public function testGetAttemptsCountTwo() + { + $ip = "10.0.0.1"; + + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + + $count = IpAttempts::getAttemptsCount($ip, "test"); + $this->assertEquals(2, $count); + } + + public function testGetAttemptsCountExpired() + { + $ip = "10.0.0.1"; + + IpAttempts::logFailedAttempt($ip, "test", time() - 1); + IpAttempts::logFailedAttempt($ip, "test", time() - 1); + + $count = IpAttempts::getAttemptsCount($ip, "test"); + $this->assertEquals(0, $count); + } + + public function testGetAttemptsCountOneExpired() + { + $ip = "10.0.0.1"; + + IpAttempts::logFailedAttempt($ip, "test", time() + 10); + IpAttempts::logFailedAttempt($ip, "test", time() - 1); + + $count = IpAttempts::getAttemptsCount($ip, "test"); + $this->assertEquals(1, $count); + } + + public function testCleanup() + { + $ip = "10.0.0.1"; + + IpAttempts::logFailedAttempt($ip, "test", time() - 1); + IpAttempts::logFailedAttempt($ip, "test", time() - 1); + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + + $query = Db::$pdo->prepare('SELECT count(*) AS count FROM ipAttempts'); + $query->execute(); + $result = $query->fetchAll(); + $beforeCleanup = $result[0]['count']; + + $this->assertEquals(4, $beforeCleanup); + + IpAttempts::cleanupAttempts(); + $query = Db::$pdo->prepare('SELECT count(*) AS count FROM ipAttempts'); + $query->execute(); + $result = $query->fetchAll(); + $afterCleanup = $result[0]['count']; + + $this->assertEquals(2, $afterCleanup); + } + + public function testTrustedIpGetAttempts() + { + $ip = "127.0.0.100"; // trusted IP + + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + + $count = IpAttempts::getAttemptsCount($ip, "test"); + $this->assertEquals(0, $count); + } + + public function testTrustedIpGetAttemptsSkipsDb() + { + $ip = "127.0.0.100"; // trusted IP + + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + IpAttempts::logFailedAttempt($ip, "test", time() + 3600); + + $query = Db::$pdo->prepare('SELECT count(*) AS count FROM ipAttempts'); + $query->execute(); + $result = $query->fetchAll(); + $count = $result[0]['count']; + $this->assertEquals(0, $count); + } } diff --git a/tests/phpunit/JtiStoreTest.php b/tests/phpunit/JtiStoreTest.php index 602afa4..b0b2b53 100644 --- a/tests/phpunit/JtiStoreTest.php +++ b/tests/phpunit/JtiStoreTest.php @@ -1,4 +1,7 @@ exec($statement); + } + } catch (\PDOException $e) { + echo $e->getMessage(); + } + } - Db::connect(); - try { - // create tables - foreach($statements as $statement){ - Db::$pdo->exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); - } - } + public function testNonExistingJti() + { + $jti = "123"; + $found = JtiStore::hasJti($jti); + $this->assertFalse($found); + } - public function testNonExistingJti() { - $jti = "123"; - $found = JtiStore::hasJti($jti); - $this->assertFalse($found); - } + public function testExistingJti() + { + $jti = "123"; + JtiStore::saveJti($jti, time() + 3600); + $found = JtiStore::hasJti($jti); + $this->assertTrue($found); + } - public function testExistingJti() { - $jti = "123"; - JtiStore::saveJti($jti, time() + 3600); - $found = JtiStore::hasJti($jti); - $this->assertTrue($found); - } + public function testExpiredJti() + { + $jti = "123"; + JtiStore::saveJti($jti); + $query = Db::$pdo->prepare('UPDATE jti SET expires=:expires WHERE jti=:jti'); + $query->execute([ + 'expires' => time() - 10, + 'jti' => $jti + ]); + $found = JtiStore::hasJti($jti); + $this->assertFalse($found); + } - public function testExpiredJti() { - $jti = "123"; - JtiStore::saveJti($jti); - $query = Db::$pdo->prepare('UPDATE jti SET expires=:expires WHERE jti=:jti'); - $query->execute([ - 'expires' => time() - 10, - 'jti' => $jti - ]); - $found = JtiStore::hasJti($jti); - $this->assertFalse($found); - } + public function testCleanup() + { + JtiStore::saveJti("123"); + JtiStore::saveJti("234"); - public function testCleanup() { - JtiStore::saveJti("123"); - JtiStore::saveJti("234"); + $query = Db::$pdo->prepare('UPDATE jti SET expires=:expires WHERE jti=:jti'); + $query->execute([ + 'expires' => time() - 10, + 'jti' => "123" + ]); + $query = Db::$pdo->prepare('UPDATE jti SET expires=:expires WHERE jti=:jti'); + $query->execute([ + 'expires' => time() - 10, + 'jti' => "234" + ]); - $query = Db::$pdo->prepare('UPDATE jti SET expires=:expires WHERE jti=:jti'); - $query->execute([ - 'expires' => time() - 10, - 'jti' => "123" - ]); - $query = Db::$pdo->prepare('UPDATE jti SET expires=:expires WHERE jti=:jti'); - $query->execute([ - 'expires' => time() - 10, - 'jti' => "234" - ]); + $query = Db::$pdo->prepare('SELECT count(*) AS count FROM jti'); + $query->execute(); + $result = $query->fetchAll(); + $beforeCleanup = $result[0]['count']; + $this->assertEquals(2, $beforeCleanup); - $query = Db::$pdo->prepare('SELECT count(*) AS count FROM jti'); - $query->execute(); - $result = $query->fetchAll(); - $beforeCleanup = $result[0]['count']; - $this->assertEquals(2, $beforeCleanup); - - JtiStore::cleanupJti(); - $query = Db::$pdo->prepare('SELECT count(*) AS count FROM jti'); - $query->execute(); - $result = $query->fetchAll(); - $afterCleanup = $result[0]['count']; + JtiStore::cleanupJti(); + $query = Db::$pdo->prepare('SELECT count(*) AS count FROM jti'); + $query->execute(); + $result = $query->fetchAll(); + $afterCleanup = $result[0]['count']; - $this->assertEquals(0, $afterCleanup); - } + $this->assertEquals(0, $afterCleanup); + } } diff --git a/tests/phpunit/MailerTest.php b/tests/phpunit/MailerTest.php index 05bb284..222f592 100644 --- a/tests/phpunit/MailerTest.php +++ b/tests/phpunit/MailerTest.php @@ -1,132 +1,145 @@ "mailerHost", - "user" => "mailerUser", - "password" => "mailerPass", - "port" => "1337", - "from" => "alice@example.com" - ]; +namespace Pdsinterop\PhpSolid; - const MAILSTYLES = []; +use Pdsinterop\PhpSolid\Mailer; - const BASEURL = "https://example.com"; +const MAILER = [ + "host" => "mailerHost", + "user" => "mailerUser", + "password" => "mailerPass", + "port" => "1337", + "from" => "alice@example.com" +]; - class MailerMock { - public $Subject; - public $Body; - public $AltBody; - public $addresses = []; +const MAILSTYLES = []; - public function addAddress($address) { - $this->addresses[] = $address; - } - public function send() { - return true; - } - } +const BASEURL = "https://example.com"; - class MailerTest extends \PHPUnit\Framework\TestCase - { - public function testGetMailer() { - $mailer = Mailer::getMailer(); - $this->assertInstanceOf('\PHPMailer\PHPMailer\PHPMailer', $mailer); - $this->assertEquals($mailer->Host, MAILER['host']); - $this->assertEquals($mailer->From, MAILER['from']); - $this->assertEquals($mailer->Port, MAILER['port']); - $this->assertEquals($mailer->Username, MAILER['user']); - $this->assertEquals($mailer->Password, MAILER['password']); - $this->assertEquals($mailer->SMTPAuth, true); - $this->assertEquals($mailer->SMTPDebug, 0); - $this->assertEquals($mailer->XMailer, null); - $this->assertEquals($mailer->ContentType, "text/html"); - $this->assertEquals($mailer->Mailer, "smtp"); - } +class MailerMock +{ + public $Subject; + public $Body; + public $AltBody; + public $addresses = []; - public function testAccountCreated() { - Mailer::$mailer = new MailerMock(); - Mailer::sendAccountCreated([ - 'email' => 'alice@example.com', - 'webId' => 'aliceWebId' - ]); - $this->assertContains("alice@example.com", Mailer::$mailer->addresses); - $this->assertMatchesRegularExpression("/aliceWebId/", Mailer::$mailer->AltBody); - $this->assertMatchesRegularExpression("/aliceWebId/", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("|Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $doc = new \DOMDocument(); - $doc->loadHTML(Mailer::$mailer->Body); - $this->assertEquals("Welcome to Solid!", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. - } + public function addAddress($address) + { + $this->addresses[] = $address; + } - public function testVerify() { - Mailer::$mailer = new MailerMock(); - Mailer::sendVerify([ - 'email' => 'alice@example.com', - 'code' => '654321' - ]); - $this->assertContains("alice@example.com", Mailer::$mailer->addresses); - $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->AltBody); - $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("|Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $doc = new \DOMDocument(); - $doc->loadHTML(Mailer::$mailer->Body); - $this->assertEquals("Confirm your e-mail", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. - } + public function send() + { + return true; + } +} - public function testResetPassword() { - Mailer::$mailer = new MailerMock(); - Mailer::sendResetPassword([ - 'email' => 'alice@example.com', - 'code' => '654321' - ]); - $this->assertContains("alice@example.com", Mailer::$mailer->addresses); - $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->AltBody); - $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("|Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $doc = new \DOMDocument(); - $doc->loadHTML(Mailer::$mailer->Body); - $this->assertEquals("Password reset", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. - } +class MailerTest extends \PHPUnit\Framework\TestCase +{ + public function testGetMailer() + { + $mailer = Mailer::getMailer(); + $this->assertInstanceOf('\PHPMailer\PHPMailer\PHPMailer', $mailer); + $this->assertEquals($mailer->Host, MAILER['host']); + $this->assertEquals($mailer->From, MAILER['from']); + $this->assertEquals($mailer->Port, MAILER['port']); + $this->assertEquals($mailer->Username, MAILER['user']); + $this->assertEquals($mailer->Password, MAILER['password']); + $this->assertEquals($mailer->SMTPAuth, true); + $this->assertEquals($mailer->SMTPDebug, 0); + $this->assertEquals($mailer->XMailer, null); + $this->assertEquals($mailer->ContentType, "text/html"); + $this->assertEquals($mailer->Mailer, "smtp"); + } - public function testDeleteAccount() { - Mailer::$mailer = new MailerMock(); - Mailer::sendDeleteAccount([ - 'email' => 'alice@example.com', - 'code' => '654321' - ]); - $this->assertContains("alice@example.com", Mailer::$mailer->addresses); - $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->AltBody); - $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $this->assertMatchesRegularExpression("|Body); - $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); - $doc = new \DOMDocument(); - $doc->loadHTML(Mailer::$mailer->Body); - $this->assertEquals("Delete your account", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. - } - } + public function testAccountCreated() + { + Mailer::$mailer = new MailerMock(); + Mailer::sendAccountCreated([ + 'email' => 'alice@example.com', + 'webId' => 'aliceWebId' + ]); + $this->assertContains("alice@example.com", Mailer::$mailer->addresses); + $this->assertMatchesRegularExpression("/aliceWebId/", Mailer::$mailer->AltBody); + $this->assertMatchesRegularExpression("/aliceWebId/", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("|Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $doc = new \DOMDocument(); + $doc->loadHTML(Mailer::$mailer->Body); + $this->assertEquals("Welcome to Solid!", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. + } + + public function testVerify() + { + Mailer::$mailer = new MailerMock(); + Mailer::sendVerify([ + 'email' => 'alice@example.com', + 'code' => '654321' + ]); + $this->assertContains("alice@example.com", Mailer::$mailer->addresses); + $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->AltBody); + $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("|Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $doc = new \DOMDocument(); + $doc->loadHTML(Mailer::$mailer->Body); + $this->assertEquals("Confirm your e-mail", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. + } + + public function testResetPassword() + { + Mailer::$mailer = new MailerMock(); + Mailer::sendResetPassword([ + 'email' => 'alice@example.com', + 'code' => '654321' + ]); + $this->assertContains("alice@example.com", Mailer::$mailer->addresses); + $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->AltBody); + $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("|Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $doc = new \DOMDocument(); + $doc->loadHTML(Mailer::$mailer->Body); + $this->assertEquals("Password reset", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. + } + + public function testDeleteAccount() + { + Mailer::$mailer = new MailerMock(); + Mailer::sendDeleteAccount([ + 'email' => 'alice@example.com', + 'code' => '654321' + ]); + $this->assertContains("alice@example.com", Mailer::$mailer->addresses); + $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->AltBody); + $this->assertMatchesRegularExpression("/654321/", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("//", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $this->assertMatchesRegularExpression("|Body); + $this->assertMatchesRegularExpression("||", Mailer::$mailer->Body); + $doc = new \DOMDocument(); + $doc->loadHTML(Mailer::$mailer->Body); + $this->assertEquals("Delete your account", $doc->getElementsByTagName("title")[0]->textContent); // If this works, I'm assuming it is valid HTML. + } +} diff --git a/tests/phpunit/MiddlewareTest.php b/tests/phpunit/MiddlewareTest.php index ca741c6..4f90347 100644 --- a/tests/phpunit/MiddlewareTest.php +++ b/tests/phpunit/MiddlewareTest.php @@ -1,40 +1,47 @@ assertTrue(in_array("Access-Control-Allow-Origin: *", self::$headers)); - $this->assertTrue(in_array("Access-Control-Allow-Headers: *, allow, accept, authorization, content-type, dpop, slug, link", self::$headers)); - $this->assertTrue(in_array("Access-Control-Allow-Methods: GET, PUT, POST, OPTIONS, DELETE, PATCH", self::$headers)); - $this->assertTrue(in_array("Access-Control-Max-Age: 1728000", self::$headers)); - $this->assertTrue(in_array("Access-Control-Allow-Credentials: true", self::$headers)); - $this->assertTrue(in_array("Accept-Patch: text/n3", self::$headers)); - $this->assertTrue(in_array("Access-Control-Expose-Headers: Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via", self::$headers)); - } - - public function testCorsWithOrigin() { - $origin = "https://example.com"; - $_REQUEST['HTTP_ORIGIN'] = $origin; - - Middleware::cors(); - $this->assertTrue(in_array("Access-Control-Allow-Origin: $origin", self::$headers)); - $this->assertTrue(in_array("Access-Control-Allow-Headers: *, allow, accept, authorization, content-type, dpop, slug, link", self::$headers)); - $this->assertTrue(in_array("Access-Control-Allow-Methods: GET, PUT, POST, OPTIONS, DELETE, PATCH", self::$headers)); - $this->assertTrue(in_array("Access-Control-Max-Age: 1728000", self::$headers)); - $this->assertTrue(in_array("Access-Control-Allow-Credentials: true", self::$headers)); - $this->assertTrue(in_array("Accept-Patch: text/n3", self::$headers)); - $this->assertTrue(in_array("Access-Control-Expose-Headers: Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via", self::$headers)); - } - - public function testPubSub() { - Middleware::pubsub(); - $this->assertTrue(in_array("updates-via: " . PUBSUB_SERVER, self::$headers)); - } - } + +// phpcs:disable Generic.Files.LineLength.TooLong +// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols + +namespace Pdsinterop\PhpSolid; + +require_once(__DIR__ . "/test-config.php"); + +use Pdsinterop\PhpSolid\Middleware; + +class MiddlewareTest extends \PHPUnit\Framework\TestCase +{ + public static $headers = []; + public function testCors() + { + Middleware::cors(); + $this->assertTrue(in_array("Access-Control-Allow-Origin: *", self::$headers)); + $this->assertTrue(in_array("Access-Control-Allow-Headers: *, allow, accept, authorization, content-type, dpop, slug, link", self::$headers)); + $this->assertTrue(in_array("Access-Control-Allow-Methods: GET, PUT, POST, OPTIONS, DELETE, PATCH", self::$headers)); + $this->assertTrue(in_array("Access-Control-Max-Age: 1728000", self::$headers)); + $this->assertTrue(in_array("Access-Control-Allow-Credentials: true", self::$headers)); + $this->assertTrue(in_array("Accept-Patch: text/n3", self::$headers)); + $this->assertTrue(in_array("Access-Control-Expose-Headers: Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via", self::$headers)); + } + + public function testCorsWithOrigin() + { + $origin = "https://example.com"; + $_REQUEST['HTTP_ORIGIN'] = $origin; + + Middleware::cors(); + $this->assertTrue(in_array("Access-Control-Allow-Origin: $origin", self::$headers)); + $this->assertTrue(in_array("Access-Control-Allow-Headers: *, allow, accept, authorization, content-type, dpop, slug, link", self::$headers)); + $this->assertTrue(in_array("Access-Control-Allow-Methods: GET, PUT, POST, OPTIONS, DELETE, PATCH", self::$headers)); + $this->assertTrue(in_array("Access-Control-Max-Age: 1728000", self::$headers)); + $this->assertTrue(in_array("Access-Control-Allow-Credentials: true", self::$headers)); + $this->assertTrue(in_array("Accept-Patch: text/n3", self::$headers)); + $this->assertTrue(in_array("Access-Control-Expose-Headers: Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via", self::$headers)); + } + + public function testPubSub() + { + Middleware::pubsub(); + $this->assertTrue(in_array("updates-via: " . PUBSUB_SERVER, self::$headers)); + } +} diff --git a/tests/phpunit/PasswordValidatorTest.php b/tests/phpunit/PasswordValidatorTest.php index 9930219..8a62493 100644 --- a/tests/phpunit/PasswordValidatorTest.php +++ b/tests/phpunit/PasswordValidatorTest.php @@ -1,79 +1,80 @@ assertEquals(26, PasswordValidator::getBase($password)); - } + public function testBaseLower() + { + $password = "aaa"; + $this->assertEquals(26, PasswordValidator::getBase($password)); + } - public function testBaseUpper() - { - $password = "AAA"; - $this->assertEquals(26, PasswordValidator::getBase($password)); - } + public function testBaseUpper() + { + $password = "AAA"; + $this->assertEquals(26, PasswordValidator::getBase($password)); + } - public function testBaseNumbers() - { - $password = "123"; - $this->assertEquals(10, PasswordValidator::getBase($password)); - } + public function testBaseNumbers() + { + $password = "123"; + $this->assertEquals(10, PasswordValidator::getBase($password)); + } - public function testBaseSpecial() - { - $password = "!@#$"; - $this->assertEquals(32, PasswordValidator::getBase($password)); - } + public function testBaseSpecial() + { + $password = "!@#$"; + $this->assertEquals(32, PasswordValidator::getBase($password)); + } - public function testBaseUpperAndLower() - { - $password = "aaaAAA"; - $this->assertEquals(52, PasswordValidator::getBase($password)); - } + public function testBaseUpperAndLower() + { + $password = "aaaAAA"; + $this->assertEquals(52, PasswordValidator::getBase($password)); + } - public function testLengthLower() - { - $password = "aaa"; - $this->assertEquals(2, PasswordValidator::getLength($password)); - } + public function testLengthLower() + { + $password = "aaa"; + $this->assertEquals(2, PasswordValidator::getLength($password)); + } - public function testLengthUpper() - { - $password = "AAA"; - $this->assertEquals(2, PasswordValidator::getLength($password)); - } + public function testLengthUpper() + { + $password = "AAA"; + $this->assertEquals(2, PasswordValidator::getLength($password)); + } - public function testLengthNumbers() - { - $password = "123"; - $this->assertEquals(3, PasswordValidator::getLength($password)); - } + public function testLengthNumbers() + { + $password = "123"; + $this->assertEquals(3, PasswordValidator::getLength($password)); + } - public function testLengthSpecial() - { - $password = "!@#$"; - $this->assertEquals(4, PasswordValidator::getLength($password)); - } + public function testLengthSpecial() + { + $password = "!@#$"; + $this->assertEquals(4, PasswordValidator::getLength($password)); + } - public function testLengthUpperAndLower() - { - $password = "aaaAAA"; - $this->assertEquals(4, PasswordValidator::getLength($password)); - } + public function testLengthUpperAndLower() + { + $password = "aaaAAA"; + $this->assertEquals(4, PasswordValidator::getLength($password)); + } - public function testSimplyEntropy() - { - $values = [ - ["password" => "aaa", "expected" => 6.52], - ["password" => "abc", "expected" => 9.77] - ]; + public function testSimplyEntropy() + { + $values = [ + ["password" => "aaa", "expected" => 6.52], + ["password" => "abc", "expected" => 9.77] + ]; - foreach ($values as $value) { - $this->assertEquals($value['expected'], PasswordValidator::getEntropy($value['password'])); - } - } + foreach ($values as $value) { + $this->assertEquals($value['expected'], PasswordValidator::getEntropy($value['password'])); + } + } } diff --git a/tests/phpunit/ServerTest.php b/tests/phpunit/ServerTest.php index 4709147..f60cc83 100644 --- a/tests/phpunit/ServerTest.php +++ b/tests/phpunit/ServerTest.php @@ -1,125 +1,139 @@ exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); - } - - ClientRegistration::saveClientRegistration([ - "client_id" => "1234", - "origin" => "https://example.com", - "redirect_uris" => ["https://example.com"], - "client_name" => "Client name" - ]); - } - - public function testGenerateKeySet() { - $keys = Server::generateKeySet(); - $this->assertTrue(isset($keys['encryptionKey'])); - $this->assertTrue(isset($keys['publicKey'])); - $this->assertTrue(isset($keys['privateKey'])); - $this->assertMatchesRegularExpression("/BEGIN PUBLIC KEY/", $keys['publicKey']); - $this->assertMatchesRegularExpression("/BEGIN PRIVATE KEY/", $keys['privateKey']); - } - - - public function testGetAuthServer() { - $authServer = Server::getAuthServer(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Server', $authServer); - } - - public function testGetAuthServerConfig() { - $authServerConfig = Server::getAuthServerConfig(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config', $authServerConfig); - } - - public function testGetConfigClient() { - $configClient = Server::getConfigClient(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config\Client', $configClient); - } - - public function testGetConfigClientWithGetId() { - $_GET['client_id'] = '1234'; - $configClient = Server::getConfigClient(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config\Client', $configClient); - } - - public function testGetConfigClientWithPostd() { - $_POST['client_id'] = '1234'; - $configClient = Server::getConfigClient(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config\Client', $configClient); - } - public function testGetDpop() { - $dpop = Server::getDpop(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Utils\Dpop', $dpop); - } - public function testGetBearer() { - $bearer = Server::getBearer(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Utils\Bearer', $bearer); - } - public function testGetEndpoints() { - $endpoints = Server::getEndpoints(); - $this->assertEquals($endpoints["issuer"], "https://solid.example.com"); - $this->assertEquals($endpoints["jwks_uri"], "https://solid.example.com/jwks/"); - $this->assertEquals($endpoints["check_session_iframe"], "https://solid.example.com/session/"); - $this->assertEquals($endpoints["end_session_endpoint"], "https://solid.example.com/logout/"); - $this->assertEquals($endpoints["authorization_endpoint"], "https://solid.example.com/authorize/"); - $this->assertEquals($endpoints["token_endpoint"], "https://solid.example.com/token/"); - $this->assertEquals($endpoints["userinfo_endpoint"], "https://solid.example.com/userinfo/"); - $this->assertEquals($endpoints["registration_endpoint"], "https://solid.example.com/register/"); - } - - public function testGetKeys() { - $keys = Server::getKeys(); - $this->assertTrue(isset($keys['encryptionKey'])); - $this->assertTrue(isset($keys['publicKey'])); - $this->assertTrue(isset($keys['privateKey'])); - $this->assertMatchesRegularExpression("/BEGIN PUBLIC KEY/", $keys['publicKey']); - $this->assertMatchesRegularExpression("/BEGIN PRIVATE KEY/", $keys['privateKey']); - } - - public function testGetTokenGenerator() { - $tokenGenerator = Server::getTokenGenerator(); - $this->assertInstanceOf('\Pdsinterop\Solid\Auth\TokenGenerator', $tokenGenerator); - } - - public function testRespond() { - $response = new MockResponse(); - ob_start(); - Server::respond($response); - $sentBody = ob_get_contents(); - ob_end_clean(); - $this->assertTrue(in_array("HTTP/1.1 200", ServerTest::$headers)); - $this->assertTrue(in_array("Foo:Bar", ServerTest::$headers)); - $this->assertTrue(in_array("Foo:Blah", ServerTest::$headers)); - - $this->assertEquals($sentBody, "{\n \"Hello\": \"world\"\n}"); - } - } +// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols + +namespace Pdsinterop\PhpSolid; + +require_once(__DIR__ . "/test-config.php"); + +use Pdsinterop\PhpSolid\Server; + +class ServerTest extends \PHPUnit\Framework\TestCase +{ + public static $headers = []; + public static $keys; + + protected function setUp(): void + { + $statements = [ + 'DROP TABLE IF EXISTS clients', + 'CREATE TABLE clients ( + clientId VARCHAR(255) NOT NULL PRIMARY KEY, + origin TEXT NOT NULL, + clientData TEXT NOT NULL + )' + ]; + + Db::connect(); + try { + // create tables + foreach ($statements as $statement) { + Db::$pdo->exec($statement); + } + } catch (\PDOException $e) { + echo $e->getMessage(); + } + + ClientRegistration::saveClientRegistration([ + "client_id" => "1234", + "origin" => "https://example.com", + "redirect_uris" => ["https://example.com"], + "client_name" => "Client name" + ]); + } + + public function testGenerateKeySet() + { + $keys = Server::generateKeySet(); + $this->assertTrue(isset($keys['encryptionKey'])); + $this->assertTrue(isset($keys['publicKey'])); + $this->assertTrue(isset($keys['privateKey'])); + $this->assertMatchesRegularExpression("/BEGIN PUBLIC KEY/", $keys['publicKey']); + $this->assertMatchesRegularExpression("/BEGIN PRIVATE KEY/", $keys['privateKey']); + } + + + public function testGetAuthServer() + { + $authServer = Server::getAuthServer(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Server', $authServer); + } + + public function testGetAuthServerConfig() + { + $authServerConfig = Server::getAuthServerConfig(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config', $authServerConfig); + } + + public function testGetConfigClient() + { + $configClient = Server::getConfigClient(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config\Client', $configClient); + } + + public function testGetConfigClientWithGetId() + { + $_GET['client_id'] = '1234'; + $configClient = Server::getConfigClient(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config\Client', $configClient); + } + + public function testGetConfigClientWithPostd() + { + $_POST['client_id'] = '1234'; + $configClient = Server::getConfigClient(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Config\Client', $configClient); + } + public function testGetDpop() + { + $dpop = Server::getDpop(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Utils\Dpop', $dpop); + } + public function testGetBearer() + { + $bearer = Server::getBearer(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\Utils\Bearer', $bearer); + } + public function testGetEndpoints() + { + $endpoints = Server::getEndpoints(); + $this->assertEquals($endpoints["issuer"], "https://solid.example.com"); + $this->assertEquals($endpoints["jwks_uri"], "https://solid.example.com/jwks/"); + $this->assertEquals($endpoints["check_session_iframe"], "https://solid.example.com/session/"); + $this->assertEquals($endpoints["end_session_endpoint"], "https://solid.example.com/logout/"); + $this->assertEquals($endpoints["authorization_endpoint"], "https://solid.example.com/authorize/"); + $this->assertEquals($endpoints["token_endpoint"], "https://solid.example.com/token/"); + $this->assertEquals($endpoints["userinfo_endpoint"], "https://solid.example.com/userinfo/"); + $this->assertEquals($endpoints["registration_endpoint"], "https://solid.example.com/register/"); + } + + public function testGetKeys() + { + $keys = Server::getKeys(); + $this->assertTrue(isset($keys['encryptionKey'])); + $this->assertTrue(isset($keys['publicKey'])); + $this->assertTrue(isset($keys['privateKey'])); + $this->assertMatchesRegularExpression("/BEGIN PUBLIC KEY/", $keys['publicKey']); + $this->assertMatchesRegularExpression("/BEGIN PRIVATE KEY/", $keys['privateKey']); + } + + public function testGetTokenGenerator() + { + $tokenGenerator = Server::getTokenGenerator(); + $this->assertInstanceOf('\Pdsinterop\Solid\Auth\TokenGenerator', $tokenGenerator); + } + + public function testRespond() + { + $response = new MockResponse(); + ob_start(); + Server::respond($response); + $sentBody = ob_get_contents(); + ob_end_clean(); + $this->assertTrue(in_array("HTTP/1.1 200", ServerTest::$headers)); + $this->assertTrue(in_array("Foo:Bar", ServerTest::$headers)); + $this->assertTrue(in_array("Foo:Blah", ServerTest::$headers)); + + $this->assertEquals($sentBody, "{\n \"Hello\": \"world\"\n}"); + } +} diff --git a/tests/phpunit/SessionTest.php b/tests/phpunit/SessionTest.php index cf176e0..1fa40e8 100644 --- a/tests/phpunit/SessionTest.php +++ b/tests/phpunit/SessionTest.php @@ -1,24 +1,28 @@ assertEquals($_SESSION['username'], "alice"); - $this->assertEquals(self::$sessionOptions, ['cookie_lifetime' => 86400]); - } +function session_start($options = []) +{ + SessionTest::$sessionOptions = $options; +} - public function testGetLoggedInUser() { - Session::start("alice"); - $user = Session::getLoggedInUser(); - $this->assertEquals($user, "alice"); - } - } +class SessionTest extends \PHPUnit\Framework\TestCase +{ + public static $sessionOptions = []; + public function testStart() + { + Session::start("alice"); + $this->assertEquals($_SESSION['username'], "alice"); + $this->assertEquals(self::$sessionOptions, ['cookie_lifetime' => 86400]); + } + + public function testGetLoggedInUser() + { + Session::start("alice"); + $user = Session::getLoggedInUser(); + $this->assertEquals($user, "alice"); + } +} diff --git a/tests/phpunit/StorageServerTest.php b/tests/phpunit/StorageServerTest.php index aa35038..c1c7fc7 100644 --- a/tests/phpunit/StorageServerTest.php +++ b/tests/phpunit/StorageServerTest.php @@ -1,137 +1,144 @@ exec($statement); - } - } catch(\PDOException $e) { - echo $e->getMessage(); - } - - $newUser = [ - "password" => "hello123!@#ABC", - "email" => "alice@example.com", - "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::$createdStorage['storageId'] . ".example.com"; - } - - public function testGetFileSystem() { - $filesystem = StorageServer::getFileSystem(); - $this->assertInstanceOf('\League\Flysystem\Filesystem', $filesystem); - } - - - public function testRespond() { - $response = new MockResponse(); - ob_start(); - StorageServer::respond($response); - $sentBody = ob_get_contents(); - ob_end_clean(); - $this->assertTrue(in_array("HTTP/1.1 200", StorageServerTest::$headers)); - $this->assertTrue(in_array("Foo:Bar", StorageServerTest::$headers)); - $this->assertTrue(in_array("Foo:Blah", StorageServerTest::$headers)); - - $this->assertEquals($sentBody, "{\"Hello\":\"world\"}"); - } - - public function testGetOwnerWebId() { - $webId = StorageServer::getOwnerWebId(); - $this->assertEquals(self::$createdUser['webId'], $webId); - } - - public function testGenerateDefaultAcl() { - $defaultAcl = StorageServer::generateDefaultAcl(); - $this->assertTrue(strpos($defaultAcl, self::$createdUser['webId']) > 0); - $this->assertMatchesRegularExpression("/@prefix/", $defaultAcl); - } - - public function testGeneratePublicAppendAcl() { - $publicAppendAcl = StorageServer::generatePublicAppendAcl(); - $this->assertTrue(strpos($publicAppendAcl, self::$createdUser['webId']) > 0); - $this->assertMatchesRegularExpression("/@prefix/", $publicAppendAcl); - } - - public function testGeneratePublicReadAcl() { - $publicReadAcl = StorageServer::generatePublicReadAcl(); - $this->assertTrue(strpos($publicReadAcl, self::$createdUser['webId']) > 0); - $this->assertMatchesRegularExpression("/@prefix/", $publicReadAcl); - } - - public function testGenerateDefaultPrivateTypeIndex() { - $privateTypeIndex = StorageServer::generateDefaultPrivateTypeIndex(); - $this->assertTrue(strpos($privateTypeIndex, "UnlistedDocument") > 0); - $this->assertMatchesRegularExpression("/@prefix/", $privateTypeIndex); - } - - public function testGenerateDefaultPublicTypeIndex() { - $publicTypeIndex = StorageServer::generateDefaultPublicTypeIndex(); - $this->assertTrue(strpos($publicTypeIndex, "ListedDocument") > 0); - $this->assertMatchesRegularExpression("/@prefix/", $publicTypeIndex); - } - - public function testGenerateDefaultPreferences() { - $preferences = StorageServer::generateDefaultPreferences(); - $this->assertTrue(strpos($preferences, "ConfigurationFile") > 0); - $this->assertMatchesRegularExpression("/@prefix/", $preferences); - } - - /* - 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) { - */ - } - - - - \ No newline at end of file + +// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols + +namespace Pdsinterop\PhpSolid; + +require_once(__DIR__ . "/test-config.php"); + +use Pdsinterop\PhpSolid\StorageServer; + +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 + )', + 'CREATE TABLE IF NOT EXISTS userStorage ( + userId VARCHAR(255) NOT NULL PRIMARY KEY, + storageUrl VARCHAR(255) NOT NULL + )', + 'CREATE TABLE IF NOT EXISTS users ( + user_id VARCHAR(255) NOT NULL PRIMARY KEY, + email TEXT NOT NULL, + 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(); + try { + // create tables + foreach ($statements as $statement) { + Db::$pdo->exec($statement); + } + } catch (\PDOException $e) { + echo $e->getMessage(); + } + + $newUser = [ + "password" => "hello123!@#ABC", + "email" => "alice@example.com", + "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::$createdStorage['storageId'] . ".example.com"; + } + + public function testGetFileSystem() + { + $filesystem = StorageServer::getFileSystem(); + $this->assertInstanceOf('\League\Flysystem\Filesystem', $filesystem); + } + + public function testRespond() + { + $response = new MockResponse(); + ob_start(); + StorageServer::respond($response); + $sentBody = ob_get_contents(); + ob_end_clean(); + $this->assertTrue(in_array("HTTP/1.1 200", StorageServerTest::$headers)); + $this->assertTrue(in_array("Foo:Bar", StorageServerTest::$headers)); + $this->assertTrue(in_array("Foo:Blah", StorageServerTest::$headers)); + + $this->assertEquals($sentBody, "{\"Hello\":\"world\"}"); + } + + public function testGetOwnerWebId() + { + $webId = StorageServer::getOwnerWebId(); + $this->assertEquals(self::$createdUser['webId'], $webId); + } + + public function testGenerateDefaultAcl() + { + $defaultAcl = StorageServer::generateDefaultAcl(); + $this->assertTrue(strpos($defaultAcl, self::$createdUser['webId']) > 0); + $this->assertMatchesRegularExpression("/@prefix/", $defaultAcl); + } + + public function testGeneratePublicAppendAcl() + { + $publicAppendAcl = StorageServer::generatePublicAppendAcl(); + $this->assertTrue(strpos($publicAppendAcl, self::$createdUser['webId']) > 0); + $this->assertMatchesRegularExpression("/@prefix/", $publicAppendAcl); + } + + public function testGeneratePublicReadAcl() + { + $publicReadAcl = StorageServer::generatePublicReadAcl(); + $this->assertTrue(strpos($publicReadAcl, self::$createdUser['webId']) > 0); + $this->assertMatchesRegularExpression("/@prefix/", $publicReadAcl); + } + + public function testGenerateDefaultPrivateTypeIndex() + { + $privateTypeIndex = StorageServer::generateDefaultPrivateTypeIndex(); + $this->assertTrue(strpos($privateTypeIndex, "UnlistedDocument") > 0); + $this->assertMatchesRegularExpression("/@prefix/", $privateTypeIndex); + } + + public function testGenerateDefaultPublicTypeIndex() + { + $publicTypeIndex = StorageServer::generateDefaultPublicTypeIndex(); + $this->assertTrue(strpos($publicTypeIndex, "ListedDocument") > 0); + $this->assertMatchesRegularExpression("/@prefix/", $publicTypeIndex); + } + + public function testGenerateDefaultPreferences() + { + $preferences = StorageServer::generateDefaultPreferences(); + $this->assertTrue(strpos($preferences, "ConfigurationFile") > 0); + $this->assertMatchesRegularExpression("/@prefix/", $preferences); + } + + /* + 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) { + */ +} diff --git a/tests/phpunit/UserTest.php b/tests/phpunit/UserTest.php index 80cd184..be5e07f 100644 --- a/tests/phpunit/UserTest.php +++ b/tests/phpunit/UserTest.php @@ -1,4 +1,8 @@ exec($statement); } - } catch(\PDOException $e) { + } catch (\PDOException $e) { echo $e->getMessage(); } } - public function testSaveVerifyToken() { + public function testSaveVerifyToken() + { $beforeExpires = new \DateTime(); $beforeExpires->add(new \DateInterval('PT29M')); @@ -66,7 +71,8 @@ public function testSaveVerifyToken() { $this->assertEquals($storedToken['hello'], "world"); } - public function testSavePasswordResetToken() { + public function testSavePasswordResetToken() + { $beforeExpires = new \DateTime(); $beforeExpires->add(new \DateInterval('PT29M')); @@ -82,7 +88,8 @@ public function testSavePasswordResetToken() { $this->assertEquals($storedToken['hello'], "world"); } - public function testSaveAccountDeleteToken() { + public function testSaveAccountDeleteToken() + { $beforeExpires = new \DateTime(); $beforeExpires->add(new \DateInterval('PT29M')); @@ -98,17 +105,20 @@ public function testSaveAccountDeleteToken() { $this->assertEquals($storedToken['hello'], "world"); } - public function testExpiredToken() { + public function testExpiredToken() + { $token = User::getVerifyToken("test1"); $this->assertFalse($token); } - public function testNonExpiredToken() { + public function testNonExpiredToken() + { $token = User::getVerifyToken("test2"); $this->assertEquals($token['hello'], "world"); } - public function testCreateUser() { + public function testCreateUser() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user@example.com", @@ -123,8 +133,9 @@ public function testCreateUser() { $canLogIn = User::checkPassword($newUser['email'], $newUser['password']); $this->assertTrue($canLogIn); } - - public function testGetUser() { + + public function testGetUser() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user2@example.com", @@ -133,13 +144,14 @@ public function testGetUser() { $createdUser = User::createUser($newUser); $userByEmail = User::getUser($newUser['email']); - $this->assertEquals($userByEmail['webId'], $createdUser['webId']); + $this->assertEquals($userByEmail['webId'], $createdUser['webId']); $this->assertEquals($userByEmail['hello'], 'world'); $this->assertTrue(isset($userByEmail['allowedClients'])); $this->assertEquals($userByEmail['issuer'], "https://solid.example.com"); } - - public function testGetUserById() { + + public function testGetUserById() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user3@example.com", @@ -148,18 +160,20 @@ public function testGetUserById() { $createdUser = User::createUser($newUser); $userById = User::getUserById($createdUser['userId']); - $this->assertEquals($userById['webId'], $createdUser['webId']); + $this->assertEquals($userById['webId'], $createdUser['webId']); $this->assertEquals($userById['hello'], 'world'); $this->assertTrue(isset($userById['allowedClients'])); $this->assertEquals($userById['issuer'], "https://solid.example.com"); } - public function testSetPasswordNonExistingUser() { + public function testSetPasswordNonExistingUser() + { $result = User::setUserPassword("not_here@example.com", "hello123!@#ABC"); $this->assertFalse($result); } - public function testSetWeakPassword() { + public function testSetWeakPassword() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user4@example.com", @@ -170,43 +184,47 @@ public function testSetWeakPassword() { $this->assertFalse($result); } - public function testLogin() { + public function testLogin() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user5@example.com", "hello" => "world" ]; $createdUser = User::createUser($newUser); - + $canLogIn = User::checkPassword($newUser['email'], $newUser['password']); $this->assertTrue($canLogIn); } - public function testLoginFailed() { + public function testLoginFailed() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user6@example.com", "hello" => "world" ]; $createdUser = User::createUser($newUser); - + $canLogIn = User::checkPassword($newUser['email'], "something else"); $this->assertFalse($canLogIn); } - public function testSetStrongPassword() { + public function testSetStrongPassword() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user7@example.com", "hello" => "world" ]; $createdUser = User::createUser($newUser); - + $result = User::setUserPassword($newUser['email'], "this is a strong password because it is long enough"); $this->assertTrue($result); } - public function testLoginAfterChange() { + public function testLoginAfterChange() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user8@example.com", @@ -215,7 +233,7 @@ public function testLoginAfterChange() { $createdUser = User::createUser($newUser); $canLogIn = User::checkPassword($newUser['email'], $newUser['password']); $this->assertTrue($canLogIn); - + $newPassword = "this is a strong password because it is long enough"; $result = User::setUserPassword($newUser['email'], $newPassword); $this->assertTrue($result); @@ -229,8 +247,9 @@ public function testLoginAfterChange() { $canLogIn = User::checkPassword($newUser['email'], $newPassword); $this->assertTrue($canLogIn); } - - public function testUserStorage() { + + public function testUserStorage() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user9@example.com", @@ -239,50 +258,55 @@ public function testUserStorage() { $createdUser = User::createUser($newUser); $storageUrl = "https://storage.example.com"; User::setStorage($createdUser['userId'], $storageUrl); - + $savedStorage = User::getStorage($createdUser['userId']); - + $this->assertTrue(in_array($storageUrl, $savedStorage)); $user = User::getUser($newUser['email']); $this->assertTrue(in_array($storageUrl, $user['storage'])); } - public function testUserExistsById() { + public function testUserExistsById() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user10@example.com", "hello" => "world" ]; $createdUser = User::createUser($newUser); - + $userExists = User::userIdExists($createdUser['userId']); $this->assertTrue($userExists); } - public function testUserDoesNotExistsById() { + public function testUserDoesNotExistsById() + { $userExists = User::userIdExists("foo"); $this->assertFalse($userExists); } - public function testUserExistsByEmail() { + public function testUserExistsByEmail() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user11@example.com", "hello" => "world" ]; $createdUser = User::createUser($newUser); - + $userExists = User::userEmailExists($newUser['email']); $this->assertTrue($userExists); } - public function testUserDoesNotExistsByEmail() { + public function testUserDoesNotExistsByEmail() + { $userExists = User::userEmailExists("foo@example.com"); $this->assertFalse($userExists); } - public function testAllowClientForUser() { + public function testAllowClientForUser() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user11@example.com", @@ -301,7 +325,8 @@ public function testAllowClientForUser() { $this->assertTrue(in_array($clientId, $user['allowedClients'])); } - public function testDeleteAccount() { + public function testDeleteAccount() + { $newUser = [ "password" => "hello123!@#ABC", "email" => "user11@example.com", @@ -324,7 +349,8 @@ public function testDeleteAccount() { $this->assertEmpty($allowedClients); } - public function testCleanup() { + public function testCleanup() + { // empty the verify table first so we have dependable numbers $query = Db::$pdo->prepare('DELETE FROM verify WHERE NOT code=""'); $query->execute(); diff --git a/tests/phpunit/UtilTest.php b/tests/phpunit/UtilTest.php index c92b71b..ea0b776 100644 --- a/tests/phpunit/UtilTest.php +++ b/tests/phpunit/UtilTest.php @@ -1,14 +1,16 @@ assertEquals($string, $decoded); - } + public function testBase64EncodeDecode() + { + $string = "this is a test string with more stuffing"; + $encoded = Util::base64_url_encode($string); + $decoded = Util::base64_url_decode($encoded); + $this->assertEquals($string, $decoded); + } } diff --git a/tests/phpunit/test-config.php b/tests/phpunit/test-config.php index f91f9ac..57f99e1 100644 --- a/tests/phpunit/test-config.php +++ b/tests/phpunit/test-config.php @@ -1,4 +1,7 @@ "world"]); - } +class MockBody +{ + public function rewind() + { + return true; + } + public function getContents() + { + return json_encode(["Hello" => "world"]); + } } -class MockResponse { - public function getStatusCode() { - return 200; - } - public function getBody() { - return new MockBody(); - } - public function getHeaders() { - return [ - "Foo" => ["Bar", "Blah"] - ]; - } +class MockResponse +{ + public function getStatusCode() + { + return 200; + } + public function getBody() + { + return new MockBody(); + } + public function getHeaders() + { + return [ + "Foo" => ["Bar", "Blah"] + ]; + } } diff --git a/tests/testsuite/init-testsuite.php b/tests/testsuite/init-testsuite.php index cb8026a..fa2f55c 100644 --- a/tests/testsuite/init-testsuite.php +++ b/tests/testsuite/init-testsuite.php @@ -1,58 +1,66 @@ prepare( - 'INSERT INTO users VALUES (:userId, :email, :passwordHash, :data)' - ); - - $queryParams = []; - $queryParams[':userId'] = $testUser['id']; - $queryParams[':email'] = $testUser['email']; - $queryParams[':passwordHash'] = password_hash($testUser['password'], PASSWORD_BCRYPT); - - $testUser['webId'] = "https://id-" . $testUser['id'] . "." . BASEDOMAIN . "/#me"; - $queryParams[':data'] = json_encode($testUser); - $query->execute($queryParams); +class TestUser +{ + private static $pdo; + private static function connect() + { + if (!isset(self::$pdo)) { + self::$pdo = new \PDO("sqlite:" . DBPATH); } + } - public static function createStorage($testUser) { - self::connect(); - $query = self::$pdo->prepare( - 'INSERT INTO storage VALUES (:storageId, :owner)' - ); + public static function createUser($testUser) + { + self::connect(); + $query = self::$pdo->prepare( + 'INSERT INTO users VALUES (:userId, :email, :passwordHash, :data)' + ); - $queryParams = []; - $queryParams[':storageId'] = $testUser['id']; - $queryParams[':owner'] = "https://id-" . $testUser['id'] . "." . BASEDOMAIN . "/#me"; - $query->execute($queryParams); - } - } - - TestUser::createUser([ - "id" => "alice", - "password" => "alice123", - "email" => "alice" - ]); - TestUser::createStorage([ - "id" => "alice" - ]); - - TestUser::createUser([ - "id" => "bob", - "password" => "bob345", - "email" => "bob" - ]); - TestUser::createStorage([ - "id" => "bob" - ]); - \ No newline at end of file + $queryParams = []; + $queryParams[':userId'] = $testUser['id']; + $queryParams[':email'] = $testUser['email']; + $queryParams[':passwordHash'] = password_hash($testUser['password'], PASSWORD_BCRYPT); + + $testUser['webId'] = "https://id-" . $testUser['id'] . "." . BASEDOMAIN . "/#me"; + $queryParams[':data'] = json_encode($testUser); + $query->execute($queryParams); + } + + public static function createStorage($testUser) + { + self::connect(); + $query = self::$pdo->prepare( + 'INSERT INTO storage VALUES (:storageId, :owner)' + ); + + $queryParams = []; + $queryParams[':storageId'] = $testUser['id']; + $queryParams[':owner'] = "https://id-" . $testUser['id'] . "." . BASEDOMAIN . "/#me"; + $query->execute($queryParams); + } +} + +TestUser::createUser([ + "id" => "alice", + "password" => "alice123", + "email" => "alice" +]); +TestUser::createStorage([ + "id" => "alice" +]); + +TestUser::createUser([ + "id" => "bob", + "password" => "bob345", + "email" => "bob" +]); + +TestUser::createStorage([ + "id" => "bob" +]); diff --git a/www/idp/index.php b/www/idp/index.php index 40d0473..b094054 100644 --- a/www/idp/index.php +++ b/www/idp/index.php @@ -1,141 +1,141 @@