First commit

This commit is contained in:
Cuga Rajal
2022-08-25 16:41:04 -07:00
commit 1687273e65
7 changed files with 432 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.DS_Store
development/*
old/*
+119
View File
@@ -0,0 +1,119 @@
HGAuth
An Opensim authentication module that can enforce a Web Form submission before allowing
inbound HG teleport
Version 1.0, August 25, 2022
*** Summary
This is a re-write of Project Sasha which has not been developed since 2018.
It is a set of PHP scripts that provide a way to enforce inbound HG teleporting
avatars (from other grids) to agree to terms presented on a web page, before they
are allowed to enter.
Although I am not aware of any issues, please use it at your own risk.
*** How it works
Avatars attempting to HG teleport to a grid with this package installed, will
receive a rejection dialog in the Viewer with a customizable message and an
external link. Clicking that link will take them to an external web page with an
on-page message and a form. The web form pre-fills their avatar address so they
can not enter it manually. They are asked to confirm or reject the agreement.
Clicking the Confirm/Yes button on the form will authorize them for future
inbound HG teleports.
The package prevents the avatar name from being altered and prevents submitting
avatar names other than the one actually used in the viewer.
The verbiage on the web page can be changed or adapted to suit your needs.
Project Sasha was originally developed to enforce legal requirements of GDPR for
residents of the EU. However the form can be used to enforce TOS or other needs.
*** Changes from Project Sasha
- Eliminated transmitting the username and UUID in the clear in the URL namespace
- Eliminated vulnerability of the form processor that could result in database misuse
- Properly handle form submission through jQuery and AJAX
- Fix PHP code not compatible with PHP 8.x
- Consolidation of PHP templates to simplify UI changes
- Added developer information, to report bugs and feature requests
*** How to install
The file hgauth.sql will give you the table structure you need
import it into your mysql server, into the database you choose.
The file authconfig.php contains database credentials and configurations for the
other scripts. It is used only as an include file so it could be placed outside
the document root.
The file hgauth.php is expected to be used to receive HG teleport authorization
requests from an Opensim Authorization Service Connector via HTTP, and to send
responses back to that service. This file also includes a message that appears
in the viewer's dialog box when the initial inbound teleport request is
rejected. You may wish to update the message.
The file index.php is expected to be used to present an authorization form on a
web browser to a user who has clicked the link in the viewer's HG TP rejection
dialog. You may wish to change the on-screen message to suit your needs.
After the files are configured and placed in appropriate web server directories,
you need to make some changes to your Opensim configuration files for these to
take effect.
in file:
config-include/GridCommon.ini (Grid Mode)
or
config-include/StandaloneCommon.ini (Standalone Mode)
1) Add the following in the [Modules] section:
AuthorizationServices = "RemoteAuthorizationServicesConnector"
2) Add the following in the [AuthorizationService] section:
AuthorizationServerURI = "http://yourwebserver/path/to/hgauth.php"
For security it is recommended to restrict web access to these files. A sample
htaccess.txt file for Apache is included (apache 2.4 syntax). This should be
renamed .htaccess and placed in the directory containing the PHP scripts. Apache
may need to be configured to read the .htaccess file.
authconfig.php - should not be accessed directly, it is meant to be an include file only
hgauth.php - access should be restricted to the IP of the Opensim server's inbound HTTP connection
index.php - unrestricted web page
Though not critical, make sure your date.timezone is set in your php.ini.
Failure to do so may result in database records containing a mix of local and
GMT times. Creation time is written by the internal clock of mySQL while
confirmation time is written by PHP.
*** Requirements
* Webserver, tested on Apache
* PHP, tested and developed on PHP 8.x
* mySQL server, should work on all flavors of mySQL >= 7.x or MariaDB >= 10.x.
*** Credits
Portions of the code are taken from Project Sasha. The following people (avatars)
are credited from Project Sasha: Foto50, Hack13, FreakyTech and Leighton Marjoram.
----
This is a work in progress. Please notify me of bugs or feature requests.
Cuga Rajal (Second Life and OSGrid)
cuga@rajal.org
+21
View File
@@ -0,0 +1,21 @@
<?php
$db_server = '127.0.0.1';
$db_name = 'dbname';
$db_user = 'dbuser';
$db_pass = 'dbpass';
$tablename = "hgauth"; //this is the table name that will store your authorizations.
$authlink = "http://mydomain.com/path/to/index.php"; //name of the page your users will use to submit consent
function base64_url_encode($input) {
return strtr(base64_encode(str_rot13($input)), '+/=', '-_,');
}
function base64_url_decode($input) {
return str_rot13(base64_decode(strtr($input, '-_,', '+/=')));
}
?>
+84
View File
@@ -0,0 +1,84 @@
<?php
// hgauth.php - version 1.0 - 25-08-2022
// This file is expected to be used to receive HG teleport authorization requests from an
// Opensim Authorization Service and to send responses back to that service.
include 'authconfig.php';
$dbc = mysqli_connect($db_server, $db_user, $db_pass, $db_name);
$msgformat = "%s, please visit the following link to accept Terms of Service and GDPR: %s";
$failmsg = "Authentication has failed. Please try again or from another region. If the error persists, please contact the grid admin.";
class AuthorizationResponse {
private $m_isAuthorized;
private $m_message;
public function AuthorizationResponse($isAuthorized,$message) {
$this->m_isAuthorized = $isAuthorized;
$this->m_message = $message;
}
public function toXML() {
return '<?xml version="1.0" encoding="utf-8"?>' .
'<AuthorizationResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' .
'<IsAuthorized>'. $this->m_isAuthorized .'</IsAuthorized>' .
'<Message><![CDATA['. $this->m_message .']]></Message></AuthorizationResponse>';
}
}
$request = @file_get_contents('php://input');
$xml3 = simplexml_load_string($request);
$uuid = $xml3->ID;
$firstname = $xml3->FirstName;
$lastname = $xml3->SurName;
$nametest = substr($lastname,0,1);
$avatarname = $firstname.$lastname;
$query = "SELECT * FROM $tablename WHERE avatarname LIKE '" . $avatarname . "'";
$data = mysqli_query($dbc, $query);
if(($nametest == "@") && (mysqli_num_rows($data)>0)) {
$row = mysqli_fetch_array($data);
if($row['uuid']=='') {
$query2 = "UPDATE $tablename SET uuid='$uuid' WHERE id=" . $row['id'];
$data2 = mysqli_query($dbc, $query2);
}
$authResp = new AuthorizationResponse("true","Authorized");
echo $authResp->toXML();
mysqli_close($dbc);
exit();
}
if(($nametest == "@") && (mysqli_num_rows($data)==0)) {
$token = substr(hash("sha1", $avatarname, false),0,7);
$query = "INSERT INTO $tablename (`token`) VALUES ('". $token . "')";
$data = mysqli_query($dbc, $query);
$getstring = base64_url_encode("fn=" . $firstname . "&ln=" . $lastname);
$authlink2 = $authlink . "?token=" . $getstring;
$newmsg = sprintf($msgformat,$firstname,$authlink2);
$authResp = new AuthorizationResponse("false",$newmsg);
echo $authResp->toXML();
mysqli_close($dbc);
exit();
}
if($nametest != "@") {
$authResp = new AuthorizationResponse("true","Authorized");
echo $authResp->toXML();
mysqli_close($dbc);
exit();
}
if($nametest != "" ) {
$authResp = new AuthorizationResponse("false",$failmsg);
echo $authResp->toXML();
mysqli_close($dbc);
exit();
}
?>
+21
View File
@@ -0,0 +1,21 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS `hgauth` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`token` varchar(8) CHARACTER SET utf8 NOT NULL,
`uuid` char(36) CHARACTER SET utf8,
`avatarname` varchar(64) CHARACTER SET utf8,
`createtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`confirmtime` datetime,
PRIMARY KEY (`id`),
UNIQUE KEY uuid (`uuid`),
UNIQUE KEY avatarname (`avatarname`)
) DEFAULT CHARSET=utf8;
COMMIT;
+10
View File
@@ -0,0 +1,10 @@
Options All -Indexes
<Files "authconfig.php">
Require all denied
</Files>
<Files "hgauth.php">
Require all denied
Require forward-dns mydomain.com
</Files>
+174
View File
@@ -0,0 +1,174 @@
<?php
// This file is expected to be used to present an authorization from on a web browser
// to a user who has attempted to HG teleport to an Opensim region.
// Make sure your date.timezone is set in php-ini
$tokenerror = "You have accessed this page differently than intended. Please use the link provided from your Opensim Viewer.";
$alreadyreg = "It appears that your avatar is already authorized with the system. You may now HG teleport.";
$regmsg = "To accept the Terms of Service and GDPR for the Rajal.org grid, please complete the following form.";
$confirmyes = "Thank you for authorizing your avatar. You may now teleport to reach the target region.";
$confirmno = "We are sorry to see you go.";
$ajaxerror = "An error occurred. Please make sure nothing has been altered from the original link.";
session_start();
include 'authconfig.php';
$dbc = mysqli_connect($db_server, $db_user, $db_pass, $db_name);
// Handle AJAX connection
if((isset($_REQUEST["CONFIRMAUTH"])) && (isset($_REQUEST["avatarname"]))) {
if((! isset($_SESSION['id'])) || ($_SESSION['id'] != session_id())) { // If they are not sending the session cookie
header('Status: 204'); // terminate silently
} else if( $_REQUEST["CONFIRMAUTH"] == "YES") {
$avatarname = mysqli_real_escape_string($dbc,$_REQUEST['avatarname']);
$expected = substr(hash("sha1", $avatarname, false),0,7);
$confirmtime = date("Y/m/d H:i:s",time());
$query = "UPDATE $tablename SET avatarname='$avatarname', confirmtime='$confirmtime' WHERE token='$expected'";
if($data = mysqli_query($dbc, $query)) { $xml = "<status>1</status>"; } // If query successful report success
else { $xml = "<status>0</status><msg>$query</msg>"; } // If malformed query, report it as a problem
if(mysqli_affected_rows($dbc)==0) { $xml = "<status>0</status><msg>$query</msg>"; } // But if no rows were updated, report it as a problem
header('Content-type: text/xml');
echo "<data>\n$xml</data>\n";
}
exit(0);
}
// END of AJAX section
$skip = FALSE;
// Make sure URL has a "token" in the query string. This is an encoded string of name/value pairs
if((! isset($_REQUEST['token'])) || ((isset($_REQUEST['token'])) && ($_REQUEST['token']==''))) {
$message = $tokenerror;
$skip = TRUE;
goto postcheck;
}
$t = base64_url_decode($_REQUEST['token']);
// $t is the decoded query string with a list of name/value pairs
// Check that $t has the expected name/value pairs
if((strpos($t, "@")===false) || (strpos($t, "fn=")===false) || (strpos($t, "ln=")===false)) {
$message = $tokenerror;
$skip = TRUE;
goto postcheck;
}
// extract and sanitize the name/value pairs
list($sf, $sl) = explode("&", base64_url_decode($_REQUEST['token']));
$firstname = mysqli_real_escape_string($dbc,explode("=", $sf)[1]);
$lastname = mysqli_real_escape_string($dbc,explode("=", $sl)[1]);
$avatarname = $firstname."".$lastname;
$nametest = substr($lastname,0,1);
$expected = substr(hash("sha1", $avatarname, false),0,7);
// It's possible for someone to alter the query string and still have readable name/value pairs.
// Check that avatar name and stored hash match to make sure there they didn't alter the query string
$query = "SELECT * FROM $tablename WHERE token='$expected'";
$data = mysqli_query($dbc, $query);
if(mysqli_num_rows($data)==0) {
$message = $tokenerror;
$skip = TRUE;
goto postcheck;
}
// Now that we know the sent avatar name hasn't been altered, check if it's already registered
$query = "SELECT * FROM $tablename WHERE avatarname LIKE '$avatarname'";
$data = mysqli_query($dbc, $query);
if(mysqli_num_rows($data)>0) { // Avatar name exists, they already registered
$message = $avatarname . ", " . $alreadyreg;
$skip = TRUE;
} else { // Avatar name doesn't exist, proceed with registration
$message = $regmsg;
}
postcheck:
// Prevent hand-crafted forms from being able to submit
$_SESSION['id'] = session_id();
// Adjust the following HTML text as needed
?>
<html>
<head>
<title>Authorize HG Avatar</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
</head>
<body>
<div id="content">
<h2>Authorize HG Avatar</h2>
<hr/>
<?php echo $message;
if(! $skip) { ?>
<br /><br />
<strong><?php echo $avatarname; ?></strong>
<br /><br />
Our grid requires all people entering the grid to agree to the <a href="http://mydomain/TOS.html" target="_blank">Terms of Service</a> and to be at least 18 years of age.
<br />
<br />
If you are a member of the European Union, <a href="https://gdpr-info.eu/" taget="_blank">GDP Regulations</a>
require you to give us permission to store and use your data, including:
avatar first and last name, avatar UUID, and your IP address, for you and the avatars you may interact with.
<br >
Other activity in Opensim, such as Friendships,
Friendship Requests, Instant Messages, Profiles, and inventory exchanges, may also expose this information,
regardless if you actually travel to a foreign grid.
<br >
Your data will only be used for the purpose of your visits here and your interactions with other users. We will not share it with any 3rd party
unless required to do so by law.
<br />
<br />
Accepting the agreement below indicates acceptance of the Terms of Service and, if applicable, the GDPR.
<br />
<br />
<div id="formdata">
<form action="" method="post">
<label>Avatar Name : </label><?php echo $row['avatarname']; ?>
<input type="hidden" name="token" id="token" value="<?php echo $arr['token']; ?>">
<ul>
<li>I agree to the Terms of Service</li>
<li>I confirm I am 18 years of age or older</li>
<li>If I live in the EU, I give you my permission to collect and use my data as indicated above</li>
</ul>
<input id="b1" type="button" value="YES" name="CONFIRMAUTH" onclick="sendAjax()" />&nbsp;&nbsp;&nbsp;&nbsp;
<input id="b2" type="button" value="NO" name="CONFIRMAUTH" onclick="rejected()" />
</form>
</div>
<?php } ?>
</div> <!-- the last </div> on the page -->
<script type="text/javascript">
function sendAjax() {
$('#b1, #b2').attr('disabled', true);
req = new XMLHttpRequest();
req.open("GET", "<?php echo $_SERVER['PHP_SELF']; ?>?avatarname=<?php echo $avatarname; ?>&CONFIRMAUTH=YES", true);
req.onreadystatechange = function() {
if ((req.readyState == 4) && (req.status == 200)) {
xml = req.responseText;
if($(xml).find("status").text()=='1') {
$('#formdata').html("<strong><?php echo $avatarname; ?></strong><br /><?php echo $confirmyes; ?>");
} else if($(xml).find("status").text()=='0') {
$('#formdata').html("<?php echo $ajaxerror; ?>");
}
}
}
req.send();
}
function rejected() {
$('#formdata').html("<strong><?php echo $avatarname; ?></strong><br /><?php echo $confirmno; ?>");
}
</script>
</body>
</html>