mirror of
https://github.com/standardebooks/web.git
synced 2025-07-13 01:52:02 -04:00
Move HTTP auth to PHP
This commit is contained in:
parent
e290758a9a
commit
30442c0c62
11 changed files with 110 additions and 86 deletions
|
@ -6,7 +6,7 @@ PHP 7+ is required.
|
|||
|
||||
```shell
|
||||
# Install Apache, PHP, PHP-FPM, and various other dependencies.
|
||||
sudo apt install -y git composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl php-curl php-zip apache2 apache2-utils libfcgi0ldbl task-spooler ipv6calc mariadb-server libaprutil1-dbd-mysql attr libapache2-mod-xsendfile
|
||||
sudo apt install -y git composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl php-curl php-zip apache2 apache2-utils libfcgi0ldbl task-spooler ipv6calc mariadb-server attr libapache2-mod-xsendfile
|
||||
|
||||
# Create the site root and logs root and clone this repo into it.
|
||||
sudo mkdir /standardebooks.org/
|
||||
|
@ -26,7 +26,7 @@ echo -e "127.0.0.1\tstandardebooks.test" | sudo tee -a /etc/hosts
|
|||
openssl req -x509 -nodes -days 99999 -newkey rsa:4096 -subj "/CN=standardebooks.test" -keyout /standardebooks.org/web/config/ssl/standardebooks.test.key -sha256 -out /standardebooks.org/web/config/ssl/standardebooks.test.crt
|
||||
|
||||
# Enable the necessary Apache modules.
|
||||
sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi authn_dbd authn_socache xsendfile
|
||||
sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi xsendfile
|
||||
|
||||
# Link and enable the SE Apache configuration file.
|
||||
sudo ln -s /standardebooks.org/web/config/apache/standardebooks.test.conf /etc/apache2/sites-available/
|
||||
|
|
|
@ -273,8 +273,8 @@ Define webroot /standardebooks.org/web
|
|||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||
RewriteRule ^/polls/([^/\.]+)/votes$ /polls/votes/post.php?pollurlname=$1 [L]
|
||||
|
||||
# Feeds
|
||||
# Rewrite old links to feeds
|
||||
# Rewrite rules for feeds eeds
|
||||
# Redirect old feed URLs
|
||||
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
||||
|
||||
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
|
||||
|
@ -283,31 +283,12 @@ Define webroot /standardebooks.org/web
|
|||
|
||||
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2
|
||||
|
||||
RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1
|
||||
|
||||
# Rewrite rules for bulk downloads
|
||||
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
|
||||
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1
|
||||
|
||||
# Enable mod_authn_dbd
|
||||
# DBDriver mysql
|
||||
# DBDParams "dbname=se user=www-data"
|
||||
# # HTTP Basic Auth configuration for /feeds
|
||||
# <DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
||||
# AuthType Basic
|
||||
# AuthName "Enter your Patrons Circle email address and leave the password empty."
|
||||
# Require valid-user
|
||||
|
||||
# # Credentials caching to prevent slamming the DB. socache must be ahead of dbd
|
||||
# AuthBasicProvider socache dbd
|
||||
# AuthnCacheProvideFor dbd
|
||||
# AuthnCacheContext ${domain}
|
||||
|
||||
# # mod_authn_dbd SQL query to authenticate a user
|
||||
# # The hash is simply the hash of a blank password. We're only interested in the username/API key.
|
||||
# # We have to do this tortured query instead of a cleaner one, because the AuthDBDUserPWQuery
|
||||
# # function will only replace %s EXACTLY ONCE. We cannot have more than one %s in the query string.
|
||||
# AuthDBDUserPWQuery "select '$apr1$13q1pnGf$vQnIj94BXP1EPdL/4ISba.' from Users u inner join Benefits b using (UserId) where %s in (u.Email, u.Uuid) and b.CanAccessFeeds = true limit 1"
|
||||
# </DirectoryMatch>
|
||||
|
||||
# Specific config for /bulk-downloads
|
||||
<DirectoryMatch "${webroot}/www/bulk-downloads">
|
||||
# Both directives are required
|
||||
|
@ -316,22 +297,11 @@ Define webroot /standardebooks.org/web
|
|||
</DirectoryMatch>
|
||||
|
||||
# Specific config for /feeds
|
||||
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
||||
ErrorDocument 401 /feeds/401
|
||||
|
||||
<FilesMatch "^(style\.php|new-releases\.xml|index\.php|index\.xml)$">
|
||||
# Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
|
||||
Require all granted
|
||||
</FilesMatch>
|
||||
</DirectoryMatch>
|
||||
|
||||
# Emit content-types for OPDS feeds, as some clients require a strictly correct content-type in order to work
|
||||
<DirectoryMatch "^${webroot}/www/feeds/opds">
|
||||
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
|
||||
|
||||
<FilesMatch "^index\.xml$">
|
||||
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
|
||||
</FilesMatch>
|
||||
<DirectoryMatch "^${webroot}/www/feeds">
|
||||
# This must be defined at the top level /feeds/ directory
|
||||
# Both directives are required
|
||||
XSendFile on
|
||||
XSendFilePath /standardebooks.org/web/www/feeds
|
||||
</DirectoryMatch>
|
||||
|
||||
# Emit content-types for RSS/Atom feeds
|
||||
|
|
|
@ -255,8 +255,8 @@ Define webroot /standardebooks.org/web
|
|||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||
RewriteRule ^/polls/([^/\.]+)/votes$ /polls/votes/post.php?pollurlname=$1 [L]
|
||||
|
||||
# Feeds
|
||||
# Rewrite old links to feeds
|
||||
# Rewrite rules for feeds eeds
|
||||
# Redirect old feed URLs
|
||||
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
||||
|
||||
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
|
||||
|
@ -265,31 +265,12 @@ Define webroot /standardebooks.org/web
|
|||
|
||||
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2
|
||||
|
||||
RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1
|
||||
|
||||
# Rewrite rules for bulk downloads
|
||||
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
|
||||
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1
|
||||
|
||||
# Enable mod_authn_dbd
|
||||
DBDriver mysql
|
||||
DBDParams "dbname=se user=www-data"
|
||||
# HTTP Basic Auth configuration for /feeds
|
||||
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
||||
AuthType Basic
|
||||
AuthName "Enter your Patrons Circle email address and leave the password empty."
|
||||
Require valid-user
|
||||
|
||||
# Credentials caching to prevent slamming the DB. socache must be ahead of dbd
|
||||
AuthBasicProvider socache dbd
|
||||
AuthnCacheProvideFor dbd
|
||||
AuthnCacheContext ${domain}
|
||||
|
||||
# mod_authn_dbd SQL query to authenticate a user
|
||||
# The hash is simply the hash of a blank password. We're only interested in the username/API key.
|
||||
# We have to do this tortured query instead of a cleaner one, because the AuthDBDUserPWQuery
|
||||
# function will only replace %s EXACTLY ONCE. We cannot have more than one %s in the query string.
|
||||
AuthDBDUserPWQuery "select '$apr1$13q1pnGf$vQnIj94BXP1EPdL/4ISba.' from Users u inner join Benefits b using (UserId) where %s in (u.Email, u.Uuid) and b.CanAccessFeeds = true limit 1"
|
||||
</DirectoryMatch>
|
||||
|
||||
# Specific config for /bulk-downloads
|
||||
<DirectoryMatch "${webroot}/www/bulk-downloads">
|
||||
# Both directives are required
|
||||
|
@ -298,22 +279,11 @@ Define webroot /standardebooks.org/web
|
|||
</DirectoryMatch>
|
||||
|
||||
# Specific config for /feeds
|
||||
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
||||
ErrorDocument 401 /feeds/401
|
||||
|
||||
<FilesMatch "^(style\.php|new-releases\.xml|index\.php|index\.xml)$">
|
||||
# Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
|
||||
Require all granted
|
||||
</FilesMatch>
|
||||
</DirectoryMatch>
|
||||
|
||||
# Emit content-types for OPDS feeds, as some clients require a strictly correct content-type in order to work
|
||||
<DirectoryMatch "^${webroot}/www/feeds/opds">
|
||||
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
|
||||
|
||||
<FilesMatch "^index\.xml$">
|
||||
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
|
||||
</FilesMatch>
|
||||
<DirectoryMatch "^${webroot}/www/feeds">
|
||||
# This must be defined at the top level /feeds/ directory
|
||||
# Both directives are required
|
||||
XSendFile on
|
||||
XSendFilePath /standardebooks.org/web/www/feeds
|
||||
</DirectoryMatch>
|
||||
|
||||
# Emit content-types for RSS/Atom feeds
|
||||
|
|
6
config/sql/se/FeedUserAgents.sql
Normal file
6
config/sql/se/FeedUserAgents.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE `FeedUserAgents` (
|
||||
`UserAgentId` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`UserAgent` text NOT NULL,
|
||||
`Created` datetime NOT NULL,
|
||||
PRIMARY KEY (`UserAgentId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@ -37,6 +37,12 @@ if($GLOBALS['User'] === null){
|
|||
// log them in while we're here.
|
||||
|
||||
$session = new Session();
|
||||
$session->Create($httpBasicAuthLogin);
|
||||
try{
|
||||
$session->Create($httpBasicAuthLogin);
|
||||
$GLOBALS['User'] = $session->User;
|
||||
}
|
||||
catch(Exception $ex){
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ try{
|
|||
|
||||
// Everything OK, serve the file using Apache.
|
||||
// The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
|
||||
// Much more efficien than reading it in PHP and outputting it that way.
|
||||
// Much more efficient than reading it in PHP and outputting it that way.
|
||||
header('X-Sendfile: ' . WEB_ROOT . $path);
|
||||
header('Content-Type: application/zip');
|
||||
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
|
||||
|
|
|
@ -17,7 +17,7 @@ require_once('Core.php');
|
|||
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?>/feeds/atom/all">All ebooks</a></p>
|
||||
<p><a href="/feeds/atom/all">All ebooks</a></p>
|
||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/atom/all</p>
|
||||
<p>All Standard Ebooks, most-recently-released first.</p>
|
||||
</li>
|
||||
|
|
|
@ -68,7 +68,7 @@ catch(Safe\Exceptions\ApcuException $ex){
|
|||
<ul class="feed">
|
||||
<? foreach($feeds as $feed){ ?>
|
||||
<li>
|
||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?><?= Formatter::ToPlainText($feed->Url) ?>"><?= Formatter::ToPlainText($feed->Label) ?></a></p>
|
||||
<p><a href="<?= Formatter::ToPlainText($feed->Url) ?>"><?= Formatter::ToPlainText($feed->Label) ?></a></p>
|
||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?><?= Formatter::ToPlainText($feed->Url) ?></p>
|
||||
</li>
|
||||
<? } ?>
|
||||
|
|
72
www/feeds/download.php
Normal file
72
www/feeds/download.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?
|
||||
require_once('Core.php');
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
// This page is blocked by HTTP Basic auth.
|
||||
// Basic authorization is handled in Core.php. By the time we get here,
|
||||
// a valid user has a session.
|
||||
|
||||
$path = HttpInput::Str(GET, 'path', false) ?? '';
|
||||
$isUserAgentAllowed = false;
|
||||
|
||||
try{
|
||||
$path = '/feeds/' . $path;
|
||||
|
||||
if(!is_file(WEB_ROOT . $path)){
|
||||
throw new Exceptions\InvalidFileException();
|
||||
}
|
||||
|
||||
// Certain user agents may bypass login entirely
|
||||
if(isset($_SERVER['HTTP_USER_AGENT'])){
|
||||
$isUserAgentAllowed = (bool)Db::QueryInt('select count(*) from FeedUserAgents where instr(?, UserAgent) limit 1', [$_SERVER['HTTP_USER_AGENT']]);
|
||||
}
|
||||
|
||||
if(!$isUserAgentAllowed){
|
||||
if($GLOBALS['User'] === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(!preg_match('/\.xml$/ius', $path)){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
if(!$GLOBALS['User']->Benefits->CanAccessFeeds){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
}
|
||||
|
||||
// Everything OK, serve the file using Apache.
|
||||
// The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
|
||||
// Much more efficient than reading it in PHP and outputting it that way.
|
||||
header('X-Sendfile: ' . WEB_ROOT . $path);
|
||||
|
||||
if(preg_match('/^\/feeds\/opds/', $path)){
|
||||
header('Content-Type: application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8');
|
||||
|
||||
if(preg_match('/\/index\.xml$/', $path)){
|
||||
header('Content-Type: application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8');
|
||||
}
|
||||
}
|
||||
elseif(preg_match('/^\/feeds\/rss/', $path)){
|
||||
header('Content-Type: application/rss+xml');
|
||||
}
|
||||
elseif(preg_match('/^\/feeds\/atom/', $path)){
|
||||
header('Content-Type: application/atom+xml');
|
||||
}
|
||||
|
||||
exit();
|
||||
}
|
||||
catch(Exceptions\LoginRequiredException $ex){
|
||||
header('WWW-Authenticate: Basic realm="Enter your Patrons Circle email address and leave the password empty."');
|
||||
http_response_code(401);
|
||||
}
|
||||
catch(Exceptions\InvalidPermissionsException $ex){
|
||||
http_response_code(403);
|
||||
}
|
||||
catch(Exceptions\InvalidFileException $ex){
|
||||
Template::Emit404();
|
||||
}
|
||||
|
||||
// Print the login info page
|
||||
include(WEB_ROOT . '/feeds/401.php');
|
|
@ -64,7 +64,7 @@ catch(Exceptions\InvalidCollectionException $ex){
|
|||
<? } ?>
|
||||
<ul class="feed">
|
||||
<li>
|
||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?>/feeds/<?= $type ?>/<?= $name ?>/<?= $target?>"><?= Formatter::ToPlainText($label) ?></a></p>
|
||||
<p><a href="/feeds/<?= $type ?>/<?= $name ?>/<?= $target?>"><?= Formatter::ToPlainText($label) ?></a></p>
|
||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/<?= $type ?>/<?= $name ?>/<?= $target?></p>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -19,7 +19,7 @@ require_once('Core.php');
|
|||
<p>To connect your ereading app to our catalog, enter the URL below when prompted by your app:</p>
|
||||
<ul class="feed">
|
||||
<li>
|
||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?>/feeds/opds">The Standard Ebooks OPDS feed</a></p>
|
||||
<p><a href="/feeds/opds">The Standard Ebooks OPDS feed</a></p>
|
||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/opds</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue