mirror of
https://github.com/cuga-rajal/hgauth.git
synced 2026-08-14 00:57:50 +00:00
First commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
development/*
|
||||
old/*
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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()" />
|
||||
<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user