xaizek / rocketgit (License: AGPLv3+) (since 2018-12-09)
Light and fast Git hosting solution suitable to serve both as a hub or as a personal code storage with its tickets, pull requests, API and much more.
<root> / inc / token.inc.php (3f49644273ec7d7b37b37767946192cae12adb4f) (4,718B) (mode 100644) [raw]
<?php
require_once($INC . "/util.inc.php");
require_once($INC . "/log.inc.php");
require_once($INC . "/sql.inc.php");
require_once($INC . "/prof.inc.php");

$rg_token_error = "";

function rg_token_set_error($str)
{
	global $rg_token_error;
	$rg_token_error = $str;
	rg_log($str);
}

function rg_token_error()
{
	global $rg_token_error;
	return $rg_token_error;
}

/*
 * Delete a token
 */
function rg_token_delete($db, $sid, $token)
{
	rg_prof_start("token_delete");
	rg_log_enter("token_delete: sid=$sid token=$token");

	$ret = array();
	$ret['ok'] = 0;
	while (1) {
		$params = array("sid" => $sid);
		$add_token = "";
		if (!empty($token)) {
			$add_token = " AND token = @@token@@";
			$params['token'] = $token;
		}

		$sql = "DELETE FROM tokens"
			. " WHERE sid = @@sid@@"
			. $add_token;
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE) {
			rg_token_set_error("cannot delete token (" . rg_sql_error() . ")");
			break;
		}
		rg_sql_free_result($res);

		$ret['ok'] = 1;
		break;
	}

	rg_log_exit();
	rg_prof_end("token_delete");
	return $ret;
}

/*
 * This function will get the master key from db
 */
function rg_token_get_master($db)
{
	rg_prof_start("token_get_master");
	rg_log_enter("token_get_master");

	$ret = FALSE;
	while (1) {
		$key = rg_state_get($db, "token_key");
		if ($key === FALSE) {
			rg_token_set_error("cannot get token_key:"
				. " " . rg_state_error());
			break;
		}

		if (empty($key)) {
			$key = rg_id(32);
			$r = rg_state_set($db, "token_key", $key);
			if ($r !== TRUE) {
				rg_token_set_error("cannot set state:"
					. " " . rg_state_error());
				break;
			}
		}

		$ret = $key;
		break;
	}

	rg_log_exit();
	rg_prof_end("token_get_master");
	return $ret;
}

/*
 * Returns TRUE if the token is valid
 */
function rg_token_valid($db, $rg, $double_allowed)
{
	rg_prof_start("token_valid");
	rg_log_enter("token_valid: sid=" . $rg['sid'] . " token=" . $rg['token']
		. " ua=" . $rg['ua']);

	$ret = FALSE;
	while (1) {
		$ua_hash = substr(sha512($rg['ua']), 0, 8);

		$len = strlen($rg['token']);
		if ($len != 32) {
			rg_token_set_error("invalid token");
			rg_security_violation_no_exit("invalid token ($len != 32)");
			break;
		}

		$key = rg_token_get_master($db);
		if ($key === FALSE)
			break;

		$rand = substr($rg['token'], 0, 16);
		$sign = substr($rg['token'], 16, 16);

		$data = $rand . $rg['sid'] . $ua_hash;
		$hash = hash_hmac("sha512", $data, $key);
		if ($hash === FALSE) {
			rg_token_set_error("cannot compute hmac");
			break;
		}

		$hash = substr($hash, 0, 16);
		if (strcmp($sign, $hash) != 0) {
			rg_log("DEBUG: sign=$sign != hash=$hash	data=$data");
			rg_token_set_error("token invalid");
			rg_security_violation_no_exit("invalid token (sign)");
			break;
		}

		if (strncmp($rg['sid'], "X", 1) == 0) {
			// We have a pre-login session: we do not have to mark
			// the token as used.
			$ret = TRUE;
			break;
		}

		if ($double_allowed) {
			$ret = TRUE;
			break;
		}

		$params = array("sid" => $rg['sid'],
			"token" => $rg['token'],
			"expire" => time() + 24 * 3600);

		// We check to see if token was already used
		$sql = "SELECT 1 FROM tokens"
			. " WHERE sid = @@sid@@"
			. " AND token = @@token@@";
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE) {
			rg_token_set_error("cannot check if token is used"
				. " (" . rg_sql_error() . ")");
			break;
		}
		$rows = rg_sql_num_rows($res);
		rg_sql_free_result($res);
		if ($rows == 1) {
			rg_token_set_error("token already used");
			break;
		}

		$sql = "INSERT INTO tokens (sid, token, expire)"
			. " VALUES (@@sid@@, @@token@@, @@expire@@)";
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE) {
			rg_token_set_error("cannot insert token"
				. " (" . rg_sql_error() . ")");
			break;
		}
		rg_sql_free_result($res);

		$ret = TRUE;
		break;
	}

	rg_log_exit();
	rg_prof_end("token_valid");
	return $ret;
}

/*
 * Returns a token to be used on a form/url
 * We generate only one per form, but multiple per session.
 */
$rg_token = FALSE;
function rg_token_get($db, $rg)
{
	global $rg_token;

	rg_log_enter("token_get: sid=" . $rg['sid']);

	$ret = FALSE;
	while (1) {
		if (empty($rg['sid']))
			break;

		if ($rg_token !== FALSE) {
			$ret = $rg_token;
			break;
		}

		$key = rg_token_get_master($db);
		if ($key === FALSE)
			break;

		$rand = rg_id(16);
		$ua_hash = substr(sha512($rg['ua']), 0, 8);

		$data = $rand . $rg['sid'] . $ua_hash;
		$sign = hash_hmac("sha512", $data, $key);
		if ($sign === FALSE) {
			rg_token_set_error("cannot compute hmac");
			break;
		}
		$sign = substr($sign, 0, 16);
		$rg_token = $rand . $sign;
		$ret = $rg_token;
		break;
	}

	rg_log_exit();
	return $ret;
}

?>
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/rocketgit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/rocketgit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master