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> --- schema/aur-schema.sql | 20 +++++++++++++ upgrading/4.6.0.txt | 20 +++++++++++++ web/html/tos.php | 50 ++++++++++++++++++++++++++++++++ web/lib/acctfuncs.inc.php | 74 +++++++++++++++++++++++++++++++++++++++++++++++ web/lib/aur.inc.php | 23 +++++++++++++++ web/lib/routing.inc.php | 1 + 6 files changed, 188 insertions(+) create mode 100644 web/html/tos.php diff --git a/schema/aur-schema.sql b/schema/aur-schema.sql index e584165..45272bb 100644 --- a/schema/aur-schema.sql +++ b/schema/aur-schema.sql @@ -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; diff --git a/upgrading/4.6.0.txt b/upgrading/4.6.0.txt index b051bac..816409d 100644 --- a/upgrading/4.6.0.txt +++ b/upgrading/4.6.0.txt @@ -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; +--- diff --git a/web/html/tos.php b/web/html/tos.php new file mode 100644 index 0000000..135fa1e --- /dev/null +++ b/web/html/tos.php @@ -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="<?= urlencode(sprintf($row["URL"], $row["Revision"])) ?>"><?= 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); diff --git a/web/lib/acctfuncs.inc.php b/web/lib/acctfuncs.inc.php index 22b3ca8..e45d735 100644 --- a/web/lib/acctfuncs.inc.php +++ b/web/lib/acctfuncs.inc.php @@ -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); + } +} diff --git a/web/lib/aur.inc.php b/web/lib/aur.inc.php index 063de8f..ce569ea 100644 --- a/web/lib/aur.inc.php +++ b/web/lib/aur.inc.php @@ -22,6 +22,7 @@ include_once('timezone.inc.php'); set_tz(); check_sid(); +check_tos(); /** * Check if a visitor is logged in @@ -92,6 +93,28 @@ function check_sid() { } /** + * 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 * * @return bool True if the CSRF token is the same as the cookie SID, otherwise false diff --git a/web/lib/routing.inc.php b/web/lib/routing.inc.php index 8c45c62..7d9750a 100644 --- a/web/lib/routing.inc.php +++ b/web/lib/routing.inc.php @@ -16,6 +16,7 @@ $ROUTES = array( '/passreset' => 'passreset.php', '/rpc' => 'rpc.php', '/rss' => 'rss.php', + '/tos' => 'tos.php', '/tu' => 'tu.php', '/addvote' => 'addvote.php', ); -- 2.12.2