Add support for Terms of Service documents

This allows for adding Terms of Service documents to the database that
registered users need to accept before using the AUR. A revision field
can be used to indicate whether a document was updated. If it is
increased, all users are again asked to accept the new terms.

Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
This commit is contained in:
Lukas Fleischer 2017-04-27 09:24:11 +02:00
parent 6892ec7791
commit a8ac2004d3
6 changed files with 188 additions and 0 deletions

View file

@ -379,3 +379,23 @@ CREATE TABLE Bans (
BanTS TIMESTAMP NOT NULL,
PRIMARY KEY (IPAddress)
) ENGINE = InnoDB;
-- Terms and Conditions
--
CREATE TABLE Terms (
ID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
Description VARCHAR(255) NOT NULL,
URL VARCHAR(8000) NOT NULL,
Revision INTEGER UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (ID)
) ENGINE = InnoDB;
-- Terms and Conditions accepted by users
--
CREATE TABLE AcceptedTerms (
UsersID INTEGER UNSIGNED NOT NULL,
TermsID INTEGER UNSIGNED NOT NULL,
Revision INTEGER UNSIGNED NOT NULL DEFAULT 0,
FOREIGN KEY (UsersID) REFERENCES Users(ID) ON DELETE CASCADE,
FOREIGN KEY (TermsID) REFERENCES Terms(ID) ON DELETE CASCADE
) ENGINE = InnoDB;

View file

@ -15,3 +15,23 @@ UPDATE PackageDepends
---
ALTER TABLE PackageComments ADD COLUMN RenderedComment TEXT NOT NULL;
---
3. Add Terms and AcceptedTerms tables:
---
CREATE TABLE Terms (
ID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
Description VARCHAR(255) NOT NULL,
URL VARCHAR(8000) NOT NULL,
Revision INTEGER UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (ID)
) ENGINE = InnoDB;
CREATE TABLE AcceptedTerms (
UsersID INTEGER UNSIGNED NOT NULL,
TermsID INTEGER UNSIGNED NOT NULL,
Revision INTEGER UNSIGNED NOT NULL DEFAULT 0,
FOREIGN KEY (UsersID) REFERENCES Users(ID) ON DELETE CASCADE,
FOREIGN KEY (TermsID) REFERENCES Terms(ID) ON DELETE CASCADE
) ENGINE = InnoDB;
---

50
web/html/tos.php Normal file
View file

@ -0,0 +1,50 @@
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '../lib');
include_once("aur.inc.php");
$uid = uid_from_sid($_COOKIE["AURSID"]);
if (isset($_POST["accept"]) && $_POST["accept"]) {
accept_terms($uid, $_POST["rev"]);
header("Location: " . get_uri('/'));
}
$terms = fetch_updated_terms($uid);
if (!$terms) {
header("Location: " . get_uri('/'));
}
html_header('AUR ' . __("Terms of Service"));
?>
<div id="dev-login" class="box">
<h2>AUR <?= __('Terms of Service') ?></h2>
<?php if (isset($_COOKIE["AURSID"])): ?>
<form method="post" action="<?= get_uri('/tos') ?>">
<fieldset>
<p>
<?= __("Logged-in as: %s", '<strong>' . username_from_sid($_COOKIE["AURSID"]) . '</strong>'); ?>
</p>
<p>
<?= __("The following documents have been updated. Please review them carefully:"); ?>
</p>
<ul>
<?php foreach($terms as $row): ?>
<li><a href="<?= htmlspecialchars(sprintf($row["URL"], $row["Revision"]), ENT_QUOTES) ?>"><?= htmlspecialchars($row["Description"]) ?></a> (<?= __('revision %d', $row["Revision"]) ?>)</li>
<?php endforeach; ?>
</ul>
<p>
<?php foreach($terms as $row): ?>
<input type="hidden" name="rev[<?= $row["ID"] ?>]" value="<?= $row["Revision"] ?>" />
<?php endforeach; ?>
<input type="checkbox" name="accept" /> <?= __("I accept the terms and conditions above."); ?>
</p>
<p>
<input type="submit" name="submit" value="<?= __("Continue") ?>" />
</p>
</fieldset>
</form>
<?php endif; ?>
</div>
<?php
html_footer(AURWEB_VERSION);

View file

@ -1325,3 +1325,77 @@ function notify($params) {
return proc_close($p);
}
/*
* Obtain a list of terms a given user has not yet accepted.
*
* @param int $uid The ID of the user to obtain terms for.
*
* @return array A list of terms the user has not yet accepted.
*/
function fetch_updated_terms($uid) {
$dbh = DB::connect();
$q = "SELECT ID, Terms.Revision, Description, URL ";
$q .= "FROM Terms LEFT JOIN AcceptedTerms ";
$q .= "ON AcceptedTerms.TermsID = Terms.ID ";
$q .= "AND AcceptedTerms.UsersID = " . intval($uid) . " ";
$q .= "WHERE AcceptedTerms.Revision IS NULL OR ";
$q .= "AcceptedTerms.Revision < Terms.Revision";
$result = $dbh->query($q);
if ($result) {
return $result->fetchAll();
} else {
return array();
}
}
/*
* Accept a list of given terms.
*
* @param int $uid The ID of the user to accept the terms.
* @param array $termrev An array mapping each term to the accepted revision.
*
* @return void
*/
function accept_terms($uid, $termrev) {
$dbh = DB::connect();
$q = "SELECT TermsID, Revision FROM AcceptedTerms ";
$q .= "WHERE UsersID = " . intval($uid);
$result = $dbh->query($q);
if (!$result) {
return;
}
$termrev_update = array();
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$id = $row['TermsID'];
if (!array_key_exists($id, $termrev)) {
continue;
}
if ($row['Revision'] < $termrev[$id]) {
$termrev_update[$id] = $termrev[$id];
}
}
$termrev_add = array_diff_key($termrev, $termrev_update);
foreach ($termrev_add as $id => $rev) {
$q = "INSERT INTO AcceptedTerms (TermsID, UsersID, Revision) ";
$q .= "VALUES (" . intval($id) . ", " . intval($uid) . ", ";
$q .= intval($rev) . ")";
$dbh->exec($q);
}
foreach ($termrev_update as $id => $rev) {
$q = "UPDATE AcceptedTerms ";
$q .= "SET Revision = " . intval($rev) . " ";
$q .= "WHERE TermsID = " . intval($id) . " AND ";
$q .= "UsersID = " . intval($uid);
$dbh->exec($q);
}
}

View file

@ -22,6 +22,7 @@ include_once('timezone.inc.php');
set_tz();
check_sid();
check_tos();
/**
* Check if a visitor is logged in
@ -91,6 +92,28 @@ function check_sid() {
return;
}
/**
* Redirect user to the Terms of Service agreement if there are updated terms.
*
* @return void
*/
function check_tos() {
if (!isset($_COOKIE["AURSID"])) {
return;
}
$path = $_SERVER['PATH_INFO'];
$route = get_route($path);
if (!$route || $route == "tos.php") {
return;
}
if (count(fetch_updated_terms(uid_from_sid($_COOKIE["AURSID"]))) > 0) {
header('Location: ' . get_uri('/tos'));
exit();
}
}
/**
* Verify the supplied CSRF token matches expected token
*

View file

@ -16,6 +16,7 @@ $ROUTES = array(
'/passreset' => 'passreset.php',
'/rpc' => 'rpc.php',
'/rss' => 'rss.php',
'/tos' => 'tos.php',
'/tu' => 'tu.php',
'/addvote' => 'addvote.php',
);