Browse Source

Allow sending simple source+target webmentions when logged out

main
Aaron Parecki 6 years ago
parent
commit
76eeffda72
No known key found for this signature in database GPG Key ID: 276C2817346D6056
9 changed files with 182 additions and 27 deletions
  1. +2
    -1
      composer.json
  2. +47
    -1
      composer.lock
  3. +43
    -20
      controllers/API.php
  4. +20
    -2
      controllers/Controller.php
  5. +1
    -0
      public/index.php
  6. +1
    -0
      views/footer-block.php
  7. +10
    -2
      views/index.php
  8. +57
    -0
      views/send-a-webmention.php
  9. +1
    -1
      views/webmention-details.php

+ 2
- 1
composer.json View File

@ -14,7 +14,8 @@
"michelf/php-markdown": "1.6.*",
"camspiers/json-pretty": "1.0.*",
"monolog/monolog": "1.*",
"emgiezet/errbit-php": "1.1.*"
"emgiezet/errbit-php": "1.1.*",
"p3k/utils": "^1.2"
},
"require-dev": {
"phpunit/phpunit": "*"

+ 47
- 1
composer.lock View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "743002238143ee7368e8c5f8a6783cae",
"content-hash": "5d6fecd1a81db73f245f5a818c28e8ac",
"packages": [
{
"name": "barnabywalters/mf-cleaner",
@ -893,6 +893,52 @@
"description": "Caterpillar is a background queue manager",
"time": "2015-12-21T22:52:21+00:00"
},
{
"name": "p3k/utils",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/aaronpk/p3k-utils.git",
"reference": "ecd0ba1afca8e586f09792f7be5052e142d3cbde"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aaronpk/p3k-utils/zipball/ecd0ba1afca8e586f09792f7be5052e142d3cbde",
"reference": "ecd0ba1afca8e586f09792f7be5052e142d3cbde",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.8.13",
"predis/predis": "1.1.*"
},
"type": "library",
"autoload": {
"files": [
"src/global.php",
"src/url.php",
"src/utils.php",
"src/date.php",
"src/cache.php",
"src/geo.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Parecki",
"homepage": "https://aaronparecki.com"
}
],
"description": "Some helpful functions used by https://p3k.io projects",
"homepage": "https://github.com/aaronpk/p3k-utils",
"time": "2018-03-28T13:44:56+00:00"
},
{
"name": "pda/pheanstalk",
"version": "v3.1.0",

+ 43
- 20
controllers/API.php View File

@ -18,7 +18,7 @@ class API {
$response->headers->set($k, $v);
}
$response->headers->set('Content-Type', 'application/json');
$response->setContent(json_encode($params, JSON_UNESCAPED_SLASHES));
$response->setContent(json_encode($params, JSON_UNESCAPED_SLASHES+JSON_PRETTY_PRINT));
return $response;
}
@ -37,12 +37,31 @@ class API {
}
public function webmention(Request $request, Response $response) {
# Require the token parameter
if(!$token=$request->get('token')) {
# Require the token or csrf parameter
if($csrf=$request->get('_csrf')) {
session_start();
if($csrf != $_SESSION['_csrf']) {
return $this->respond($response, 401, [
'error' => 'invalid_csrf_token',
'error_description' => 'An error occurred. Make sure you have only one tab open.',
]);
}
} else if(!$token=$request->get('token')) {
return $this->respond($response, 401, [
'error' => 'authentication_required',
'error_description' => 'A token is required to use the API'
]);
# Verify the token is valid
$role = ORM::for_table('roles')->where('token', $token)->find_one();
if(!$role) {
return $this->respond($response, 401, [
'error' => 'invalid_token',
'error_description' => 'The token provided is not valid'
]);
}
}
# Require source and target or target_domain parameters
@ -60,6 +79,16 @@ class API {
]);
}
# Can only use source & target if no authentication(role) is set
if(!isset($role)) {
if($target_domain) {
return $this->respond($response, 400, [
'error' => 'unauthorized',
'error_description' => 'Can only use the target_domain feature when providing a token from the API',
]);
}
}
$urlregex = '/^https?:\/\/[^ ]+\.[^ ]+$/';
$domainregex = '/^[^ ]+$/';
@ -75,7 +104,7 @@ class API {
}
# Don't send anything if the source domain matches the target domain
# The problem is someone pushing to Superfeedr who is also subscribed, will cause a
# The problem is someone pushing to Superfeedr who is also subscribed, will cause a
# request to be sent with the source of one of their posts, and their own target domain.
# This causes a whole slew of webmentions to be queued up, almost all of which are not needed.
if($target_domain) {
@ -89,16 +118,6 @@ class API {
}
}
# Verify the token is valid
$role = ORM::for_table('roles')->where('token', $token)->find_one();
if(!$role) {
return $this->respond($response, 401, [
'error' => 'invalid_token',
'error_description' => 'The token provided is not valid'
]);
}
# Check the blacklist of domains that are known to not accept webmentions
if($target && !Telegraph\Webmention::isProbablySupported($target)) {
return $this->respond($response, 400, [
@ -138,9 +157,11 @@ class API {
$xpath = new DOMXPath($doc);
$found = [];
$links = [];
foreach($xpath->query('//a[@href]') as $href) {
$url = $href->getAttribute('href');
if($target) {
$links[] = $url;
# target parameter was provided
if($url == $target) {
$found[$url] = null;
@ -157,7 +178,8 @@ class API {
if(!$found) {
return $this->respond($response, 400, [
'error' => 'no_link_found',
'error_description' => 'The source document does not have a link to the target URL or domain'
'error_description' => 'The source document does not have a link to the target URL or domain',
'links' => $links
]);
}
}
@ -167,8 +189,8 @@ class API {
$statusURLs = [];
foreach($found as $url=>$_) {
$w = ORM::for_table('webmentions')->create();
$w->site_id = $role->site_id;
$w->created_by = $role->user_id;
$w->site_id = isset($role) ? $role->site_id : 0;
$w->created_by = isset($role) ? $role->user_id : 0;
$w->created_at = date('Y-m-d H:i:s');
$w->token = self::generateStatusToken();
$w->source = $source;
@ -184,7 +206,7 @@ class API {
$statusURLs[] = Config::$base . 'webmention/' . $w->token;
}
if ($target) {
if($target) {
$body = [
'status' => 'queued',
'location' => $statusURLs[0]
@ -226,7 +248,7 @@ class API {
$site = ORM::for_table('sites')->where('id', $role->site_id)->find_one();
if(is_array($input)
&& array_key_exists('items', $input)
&& array_key_exists('items', $input)
&& ($items = $input['items'])
&& is_array($items)
&& array_key_exists(0, $items)
@ -301,8 +323,9 @@ class API {
if($status && $status->http_code)
$data['http_code'] = (int)$status->http_code;
if($status && $status->raw_response)
if($status && $status->raw_response) {
$data['http_body'] = $status->raw_response;
}
if($summary)
$data['summary'] = $summary;

+ 20
- 2
controllers/Controller.php View File

@ -41,8 +41,11 @@ class Controller {
}
public function index(Request $request, Response $response) {
p3k\session_setup();
$response->setContent(view('index', [
'title' => 'Telegraph'
'title' => 'Telegraph',
'user' => $this->_user(),
]));
return $response;
}
@ -150,6 +153,20 @@ class Controller {
return $response;
}
public function send_a_webmention(Request $request, Response $response) {
p3k\session_setup();
$_SESSION['_csrf'] = random_string(16);
$response->setContent(view('send-a-webmention', [
'title' => 'Send a Webmention with Telegraph',
'user' => $this->_user(),
'accounts' => $this->_accounts(),
'csrf' => $_SESSION['_csrf'],
]));
return $response;
}
public function new_site(Request $request, Response $response) {
if(!$this->_is_logged_in($request, $response)) {
return $response;
@ -173,7 +190,7 @@ class Controller {
'role' => $role,
'site' => $site
]));
return $response;
return $response;
}
public function save_site(Request $request, Response $response) {
@ -341,6 +358,7 @@ class Controller {
}
private function _accounts() {
if(!session('user_id')) return [];
return ORM::for_table('sites')->join('roles', 'roles.site_id = sites.id')
->where('roles.user_id', session('user_id'))
->find_many();

+ 1
- 0
public/index.php View File

@ -24,6 +24,7 @@ $templates = new League\Plates\Engine(dirname(__FILE__).'/../views');
$router->addRoute('GET', '/', 'Controller::index');
$router->addRoute('GET', '/dashboard', 'Controller::dashboard');
$router->addRoute('GET', '/send-a-webmention', 'Controller::send_a_webmention');
$router->addRoute('GET', '/site/new', 'Controller::new_site');
$router->addRoute('GET', '/site/edit', 'Controller::new_site');
$router->addRoute('POST', '/site/save', 'Controller::save_site');

+ 1
- 0
views/footer-block.php View File

@ -4,6 +4,7 @@
<div class="three wide column">
<h4 class="ui inverted header">Telegraph</h4>
<div class="ui inverted link list">
<a href="/send-a-webmention" class="item">Send a Webmention</a>
<a href="/api" class="item">API</a>
<a href="https://github.com/aaronpk/Telegraph" class="item">Open Source</a>
<a href="https://github.com/aaronpk/Telegraph/issues" class="item">Issues</a>

+ 10
- 2
views/index.php View File

@ -172,7 +172,11 @@ $menu = [
<?php foreach($menu as $href=>$name): ?>
<a class="item" href="<?= $href ?>"><?= $name ?></a>
<?php endforeach; ?>
<a class="item" href="/login">Login</a>
<?php if($user): ?>
<a class="item" href="/dashboard">Dashboard</a>
<?php else: ?>
<a class="item" href="/login">Login</a>
<?php endif; ?>
</div>
@ -189,7 +193,11 @@ $menu = [
<a class="item" href="<?= $href ?>"><?= $name ?></a>
<?php endforeach; ?>
<div class="right item">
<a class="ui inverted button" href="/login">Log in</a>
<?php if($user): ?>
<a class="ui inverted button" href="/dashboard">Dashboard</a>
<?php else: ?>
<a class="ui inverted button" href="/login">Log in</a>
<?php endif; ?>
</div>
</div>
</div>

+ 57
- 0
views/send-a-webmention.php View File

@ -0,0 +1,57 @@
<?php $this->layout('layout-loggedin', ['title' => $title, 'user' => $user, 'accounts' => $accounts]); ?>
<div class="ui main text container" style="margin-top: 80px; margin-bottom: 40px;">
<h2 class="site-name">Send a Webmention</h2>
<form class="ui form" id="send-webmention-form">
<div class="two fields">
<div class="field"><label>Source URL</label><input type="url" placeholder="Source URL" id="send-source"></div>
<div class="field"><label>Target URL</label><input type="url" placeholder="Target URL" id="send-target"></div>
</div>
<div class="ui error message"></div>
<button class="ui button right floated" id="send-webmention-btn">Send Webmention</button>
<div style="clear:both;"></div>
</form>
<div style="margin-top: 2em;">
<p>Enter a source URL (your post) and target URL (the post you linked to).</p>
<p>Telegraph will discover the Webmention endpoint of the target URL and send the Webmention for you.</p>
<p>You'll be able to see the progress after you click "send".</p>
</div>
</div>
<script>
$(function(){
var csrf = "<?= $csrf ?>";
$("#send-source").focus();
$("#send-webmention-btn").click(function(){
$("#send-webmention-btn").addClass("loading");
$("#send-webmention-form").removeClass("error");
// Send the request to the API now, and then redirect to the status page
$.ajax({
url: "/webmention",
method: "POST",
data: {
_csrf: csrf,
source: $("#send-source").val(),
target: $("#send-target").val()
},
success: function(data){
$("#send-webmention-btn").removeClass("loading");
window.location = data.location+"/details";
},
error: function(data){
$("#send-webmention-btn").removeClass("loading");
$("#send-webmention-form").addClass("error");
$("#send-webmention-form .error.message").text(data.responseJSON.error_description);
}
});
return false;
});
});
</script>

+ 1
- 1
views/webmention-details.php View File

@ -53,7 +53,7 @@
<h2>Details</h2>
<? if(count($statuses) == 0): ?>
<p>The request is queued for processing. Check for updates again later.</p>
<p>The request is queued for processing. Refresh this page for updates.</p>
<? else: ?>
<table class="ui table single line"><tbody>

Loading…
Cancel
Save