main
Terence Eden 2024-02-12 21:21:05 +00:00
rodzic d9bad68be1
commit 911ba84220
5 zmienionych plików z 574 dodań i 83 usunięć

12
.htaccess 100644
Wyświetl plik

@ -0,0 +1,12 @@
Options -Indexes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Redirect all paths to queries
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?path=$1 [QSA,L]
</IfModule>

115
CRAPL-LICENSE.txt 100644
Wyświetl plik

@ -0,0 +1,115 @@
THE CRAPL v0 BETA 1
0. Information about the CRAPL
If you have questions or concerns about the CRAPL, or you need more
information about this license, please contact:
Matthew Might
http://matt.might.net/
I. Preamble
Science thrives on openness.
In modern science, it is often infeasible to replicate claims without
access to the software underlying those claims.
Let's all be honest: when scientists write code, aesthetics and
software engineering principles take a back seat to having running,
working code before a deadline.
So, let's release the ugly. And, let's be proud of that.
II. Definitions
1. "This License" refers to version 0 beta 1 of the Community
Research and Academic Programming License (the CRAPL).
2. "The Program" refers to the medley of source code, shell scripts,
executables, objects, libraries and build files supplied to You,
or these files as modified by You.
[Any appearance of design in the Program is purely coincidental and
should not in any way be mistaken for evidence of thoughtful
software construction.]
3. "You" refers to the person or persons brave and daft enough to use
the Program.
4. "The Documentation" refers to the Program.
5. "The Author" probably refers to the caffeine-addled graduate
student that got the Program to work moments before a submission
deadline.
III. Terms
1. By reading this sentence, You have agreed to the terms and
conditions of this License.
2. If the Program shows any evidence of having been properly tested
or verified, You will disregard this evidence.
3. You agree to hold the Author free from shame, embarrassment or
ridicule for any hacks, kludges or leaps of faith found within the
Program.
4. You recognize that any request for support for the Program will be
discarded with extreme prejudice.
5. The Author reserves all rights to the Program, except for any
rights granted under any additional licenses attached to the
Program.
IV. Permissions
1. You are permitted to use the Program to validate published
scientific claims.
2. You are permitted to use the Program to validate scientific claims
submitted for peer review, under the condition that You keep
modifications to the Program confidential until those claims have
been published.
3. You are permitted to use and/or modify the Program for the
validation of novel scientific claims if You make a good-faith
attempt to notify the Author of Your work and Your claims prior to
submission for publication.
4. If You publicly release any claims or data that were supported or
generated by the Program or a modification thereof, in whole or in
part, You will release any inputs supplied to the Program and any
modifications You made to the Progam. This License will be in
effect for the modified program.
V. Disclaimer of Warranty
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
VI. Limitation of Liability
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

Wyświetl plik

@ -1,93 +1,22 @@
# ActivityPub-Single-PHP-File
# ActivityPub Server in a Single PHP File
This is a single PHP file - and an `.htaccess file` - which acts as an extremely basic ActivityPub server.
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
This is designed to be a lightweight educational tool to show you the basics of how ActivityPub works.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
There are no tests, no checks, no security features, no header verifications, no containers, no gods, no masters.
## Add your files
Edit the `.php` file to add a username, password, and keypair.
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
Upload the `.php` and `.htaccess` file to the *root* directory of your domain. For example `test.example.com/`. It will not work in a subdirectory.
```
cd existing_repo
git remote add origin https://gitlab.com/edent/activitypub-single-php-file.git
git branch -M main
git push -uf origin main
```
Optionally, upload an `icon.png` as well to make the account look nice.
## Integrate with your tools
## How this works
- [ ] [Set up project integrations](https://gitlab.com/edent/activitypub-single-php-file/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
* The `.htaccess` file transforms requests from `example.com/whatever` to `example.com/index.php?path=whatever`.
* The `index.php` file performs a specific action depending on the path requested.
* Log files are saved as .txt in the root directory.
* Post files are saved as .json in the `/posts` directory.

BIN
icon.png 100644

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 44 KiB

435
index.php 100644
Wyświetl plik

@ -0,0 +1,435 @@
<?php
/*
* "This code is not a code of honour... no highly esteemed code is commemorated here... nothing valued is here."
* "What is here is dangerous and repulsive to us. This message is a warning about danger."
* This is a rudimentary, single-file, low complexity, minimum functionality, ActivityPub server.
* For educational purposes only.
* It produces an Actor who can be followed.
* It can send messages to followers.
* It saves logs about requests it receives and sends.
* It is NOT suitable for production use.
* This code is "licenced" under CRAPL v0 - https://matt.might.net/articles/crapl/
* "Any appearance of design in the Program is purely coincidental and should not in any way be mistaken for evidence of thoughtful software construction."
*/
// Set up the Actor's information
$username = rawurlencode("example"); // Encoded as it is often used as part of a URl
$realName = "E. Xample. Jr.";
$server = $_SERVER['SERVER_NAME']; // Domain name this is hosted on
// Generate locally or from https://cryptotools.net/rsagen
// Newlines must be replaced with "\n"
$key_private = "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----";
$key_public = "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----";
// Password for sending messages
$password = "P4ssW0rd";
// Get all headers and requests sent to this server
$headers = print_r( getallheaders(), true );
$postData = print_r( $_POST, true );
$getData = print_r( $_GET, true );
$filesData = print_r( $_FILES, true );
$body = json_decode( file_get_contents( "php://input"), true );
$bodyData = print_r( $input, true );
$requestData = print_r( $_REQUEST, true );
$serverData = print_r( $_SERVER, true );
// Get the type of request
if ( isset( $body["type"] ) ) {
$type = " " . $body["type"];
} else {
$type = "";
}
// Create a timestamp in ISO 8601 format for the filename
$timestamp = date('c');
// Filename for the log
$filename = "{$timestamp}{$type}.txt";
// Save headers and request data to the timestamped file
file_put_contents( $filename,
"Headers: \n$headers \n\n" .
"Body Data: \n$bodyData \n\n" .
"POST Data: \n$postData \n\n" .
"GET Data: \n$getData \n\n" .
"Files Data: \n$filesData \n\n" .
"Request Data:\n$requestData\n\n" .
"Server Data: \n$serverData \n\n"
);
// The .htaccess changes /whatever to /?path=whatever
// What path was requested?
$path = $_GET["path"];
switch ($path) {
case "":
echo "Silence";
case ".well-known/webfinger":
webfinger();
case "{$username}":
username();
case "following":
following();
case "followers":
followers();
case "inbox":
inbox();
case "write":
write();
case "send":
send();
default:
die();
}
function webfinger() {
// Display the WebFinger JSON
global $username, $server;
$webfinger = array(
"subject" => "acct:{$username}@{$server}",
"links" => array(
array(
"rel" => "self",
"type" => "application/activity+json",
"href" => "https://{$server}/{$username}"
)
)
);
header("Content-Type: application/json");
echo json_encode( $webfinger );
die();
}
function username() {
// Display the username JSON
global $username, $realName, $server, $key_public;
$user = array(
"@context" => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id" => "https://{$server}/{$username}",
"type" => "Person",
"following" => "https://{$server}/following",
"followers" => "https://{$server}/followers",
"inbox" => "https://{$server}/inbox",
"preferredUsername" => rawurldecode($username),
"name" => "{$realName}",
"summary" => "A single file ActivityPub server.",
"url" => "https://{$server}",
"manuallyApprovesFollowers" => true,
"discoverable" => true,
"published" => "2024-02-12T11:51:00Z",
"icon" => [
"type" => "Image",
"mediaType" => "image/png",
"url" => "https://{$server}/icon.png"
],
"publicKey" => [
"id" => "https://{$server}/{$username}#main-key",
"owner" => "https://{$server}/{$username}",
"publicKeyPem" => $key_public
]
);
header("Content-Type: application/activity+json");
echo json_encode( $user );
die();
}
function following() {
// Display the following JSON
global $server;
$following = array(
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/following",
"type" => "Collection",
"totalItems" => 0,
"items" => []
);
header("Content-Type: application/activity+json");
echo json_encode( $following );
die();
}
function followers() {
// Display the followers JSON
global $server;
$followers = array(
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/followers",
"type" => "Collection",
"totalItems" => 0,
"items" => []
);
header("Content-Type: application/activity+json");
echo json_encode( $followers );
die();
}
function inbox() {
// Respond to InBox requests
global $body, $server, $username, $key_private;
// Get the message and type
$inbox_message = $body;
$inbox_type = $inbox_message["type"];
// This inbox only responds to follow requests
if ( "Follow" != $inbox_type ) { die(); }
// Get the parameters
$inbox_id = $inbox_message["id"];
$inbox_actor = $inbox_message["actor"];
$inbox_url = parse_url($inbox_actor, PHP_URL_SCHEME) . "://" . parse_url($inbox_actor, PHP_URL_HOST);
$inbox_host = parse_url($inbox_actor, PHP_URL_HOST);
// Does this account have any followers?
if( file_exists( "followers.json" ) ) {
$followers_file = file_get_contents( "followers.json" );
$followers_json = json_decode( $followers_file, true );
} else {
$followers_json = array();
}
// Add user to list. Don't care about duplicate users, server is what's important
$followers_json[$inbox_host]["users"][] = $inbox_actor;
// Save the new followers file
file_put_contents( "followers.json", print_r( json_encode( $followers_json ), true ) );
// Response Message ID
// This isn't used for anything important so could just be a random number
$guid = uuid();
// Create the Accept message
$message = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/{$guid}",
"type" => "Accept",
"actor" => "https://{$server}/{$username}",
"object" => [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => $inbox_id,
"type" => $inbox_type,
"actor" => $inbox_actor,
"object" => "https://{$server}/{$username}",
]
];
$message_json = json_encode( $message );
// The Accept is sent to the server of the user who requested the follow
// TODO: The path doesn't *always* end with/inbox
$host = $inbox_host;
$path = parse_url($inbox_actor, PHP_URL_PATH) . "/inbox";
// Set up signing
$keyId = "https://{$server}/{$username}#main-key";
// Generate signing variables
$hash = hash('sha256', $message_json, true);
$digest = base64_encode($hash);
$date = date('D, d M Y H:i:s \G\M\T');
$signer = openssl_get_privatekey($key_private);
$stringToSign = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
openssl_sign($stringToSign, $signature, $signer, OPENSSL_ALGO_SHA256);
$signature_b64 = base64_encode($signature);
$header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
// Header for POST reply
$headers = array(
"Host: {$host}",
"Date: {$date}",
"Digest: SHA-256={$digest}",
"Signature: {$header}",
"Content-Type: application/activity+json",
"Accept: application/activity+json",
);
// Specify the URL of the remote server's inbox
// TODO: The path doesn't *always* end with /inbox
$remoteServerUrl = $inbox_actor . "/inbox";
// POST the message and header to the requester's inbox
$ch = curl_init( $remoteServerUrl );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $message_json );
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
$response = curl_exec( $ch );
// Check for errors
if( curl_errno( $ch ) ) {
file_put_contents( "error.txt", curl_error( $ch ) );
}
curl_close($ch);
die();
}
function uuid() {
// Date sortable UUID
return sprintf('%08x-%04x-%04x-%04x-%012x',
time(),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffffffffffff)
);
}
function write() {
// Display an HTML form for the user to enter a message.
echo <<< HTML
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="UTF-8">
<title>Send Message</title>
<style>
*{font-family:sans-serif;font-size:1.1em;}
</style>
</head>
<body>
<form action="/send" method="post" enctype="multipart/form-data">
<label for="content">Your message:</label><br>
<textarea id="content" name="content" rows="5" cols="32"></textarea><br>
<label for="password">Password</label><br>
<input type="password" name="password" id="password" size="32"><br>
<input type="submit" value="Post Message">
</form>
</body>
</html>
HTML;
die();
}
function send() {
global $password, $server, $username, $key_private;
// Does the posted password match the stored password?
if( $password != $_POST["password"] ) { die(); }
// Get the posted content
$content = $_POST["content"];
// Current time
$timestamp = date("c");
// Outgoing Message ID
$guid = uuid();
// Construct the Note
$note = [
"@context" => array(
"https://www.w3.org/ns/activitystreams"
),
"id" => "https://{$server}/posts/{$guid}.json",
"type" => "Note",
"published" => $timestamp,
"attributedTo" => "https://{$server}/{$username}",
"content" => $content,
"contentMap" => ["en" => $content],
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
];
// Construct the Message
$message = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/posts/{$guid}.json",
"type" => "Create",
"actor" => "https://{$server}/{$username}",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://{$server}/followers"
],
"object" => $note
];
$message_json = json_encode($message);
// Create the context for the permalink
$note = [ "@context" => "https://www.w3.org/ns/activitystreams", ...$note];
// Save the permalink
$note_json = json_encode( $note );
file_put_contents( "posts/{$guid}.json", print_r( $note_json, true ) );
// Read existing users and get their hosts
$followers_file = file_get_contents( "followers.json" );
$followers_json = json_decode( $followers_file, true );
$hosts = array_keys( $followers_json );
// Prepare to use the multiple cURL handle
$mh = curl_multi_init();
// Loop through all the severs of the followers
// Each server needs its own cURL handle
// Each POST to an inbox needs to be signed separately
foreach ( $hosts as $host ) {
$path = '/inbox';
// Set up signing
$privateKey = $key_private;
$keyId = "https://{$server}/{$username}#main-key";
$hash = hash( "sha256", $message_json, true );
$digest = base64_encode( $hash );
$date = date('D, d M Y H:i:s \G\M\T');
$signer = openssl_get_privatekey( $key_private );
$stringToSign = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
openssl_sign( $stringToSign, $signature, $signer, OPENSSL_ALGO_SHA256 );
$signature_b64 = base64_encode($signature);
$header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
// Header for POST reply
$headers = array(
"Host: {$host}",
"Date: {$date}",
"Digest: SHA-256={$digest}",
"Signature: {$header}",
"Content-Type: application/activity+json",
"Accept: application/activity+json",
);
// Specify the URL of the remote server
$remoteServerUrl = "https://{$host}{$path}";
// POST the message and header to the requester's inbox
$ch = curl_init( $remoteServerUrl );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $message_json );
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
// Add the handle to the multi-handle
curl_multi_add_handle( $mh, $ch );
}
// Execute the multi-handle
do {
$status = curl_multi_exec( $mh, $active );
if ( $active ) {
curl_multi_select( $mh );
}
} while ( $active && $status == CURLM_OK );
// Close the multi-handle
curl_multi_close( $mh );
// Render the JSON so the user can see the POST has worked
header( "Location: https://{$server}/posts/{$guid}.json" );
die();
}
die();
die();
die();