Shoulder surfing protection with new default option. Enabled only

for logged users as we use logged user's tags to decide what
should be hidden. Tags linked to the tag defined in the
configuration, associated bookmarks, relevant links and
"last searches" block will be hidden. Hidden state is active
unless disabled by clicking on the top right button :
require user's password. This setting is remembered by
a session cookie.
This commit is contained in:
yohan 2015-09-18 19:41:12 +02:00
parent 2b4044a26a
commit 0600f02a13
15 changed files with 333 additions and 23 deletions

View File

@ -615,6 +615,13 @@ $menu2Tags = array(
'menu2', 'tags', 'configurable', 'in', 'data/config.php'
);
/**
* Tag protected from shoulder surfing.
* This tag, his children and the associated bookmarks won't appear anywhere unless enabled in the UI.
*
* @var string
*/
$shoulderSurfingProtectedTag = 's_hidden';
/****************************

View File

@ -11,14 +11,62 @@ if ($userservice->isLoggedOn() && is_object($currentUser)) {
<li><a href="<?php echo createURL('watchlist', $cUsername); ?>"><?php echo T_('Watchlist'); ?></a></li>
<li><a href="<?php echo $userservice->getProfileUrl($cUserId, $cUsername); ?>"><?php echo T_('Profile'); ?></a></li>
<li><a href="<?php echo createURL('bookmarks', $cUsername . '?action=add'); ?>"><?php echo T_('Add a Bookmark'); ?></a></li>
<?php if (isset($loadjs)) :?>
<li class="access"><button type="button" id="button" title="shoulder surfing protection" onclick="if(! $.cookie('noshoulderSurfingProtection')) {toggle();} else {$.removeCookie('noshoulderSurfingProtection', { path: '/' }); location.reload();}"><?php if(! isset($_COOKIE["noshoulderSurfingProtection"])) {echo "Protected";} else {echo "Unprotected";} ?></button></li>
<?php endif ?>
<li class="access"><?php echo $cUsername?><a href="<?php echo ROOT ?>?action=logout">(<?php echo T_('Log Out'); ?>)</a></li>
<li><a href="<?php echo createURL('about'); ?>"><?php echo T_('About'); ?></a></li>
<?php if($currentUser->isAdmin()): ?>
<li><a href="<?php echo createURL('admin', ''); ?>"><?php echo '['.T_('Admin').']'; ?></a></li>
<?php endif; ?>
</ul>
<?php if (isset($loadjs)) :?>
<div id="password-form" style="background:white; z-index: 2; position:absolute; top:55px; right:10px; visibility:hidden;">
<form id="noshoulderSurfingProtectionPassword">
<input type="password" name="password" id="password" size="40" placeholder="Type your password then press Enter to unprotect.">
<!-- Allow form submission with keyboard without duplicating the dialog button -->
<input type="submit" tabindex="-1" style="position:absolute; top:-1000px">
</form>
</div>
<script>
// Prevents browser autocompletion. autocomplete="off" as input type="password" attribute only works with HTML5.
setTimeout(
clear(),
1000 //1,000 milliseconds = 1 second
);
function clear() {
$('#password').val('');
}
function toggle() {
if ($("#password-form").css("visibility") == "visible") {
$("#password-form").css("visibility", "hidden");
}
else {
clear();
$("#password-form").css("visibility", "visible");
}
}
$( "#noshoulderSurfingProtectionPassword" ).submit(function( event ) {
$.post(
'<?php echo ROOT ?>ajax/checkpassword.php',
{
password : $("#password").val(),
},
function(data) {
if(data == 'true') {
$.cookie('noshoulderSurfingProtection', 'null', { path: '/' });
location.reload();
}
},
'text'
);
event.preventDefault();
});
</script>
<?php endif ?>
<?php
} else {
?>

View File

@ -22,9 +22,11 @@ if (isset($rsschannels)) {
<?php if (DEBUG_MODE) : ?>
<script type="text/javascript" src="<?php echo ROOT_JS ?>jquery-1.4.2.js"></script>
<script type="text/javascript" src="<?php echo ROOT_JS ?>jquery.jstree.js"></script>
<script type="text/javascript" src="<?php echo ROOT_JS ?>jquery.cookie-1.4.1.js"></script>
<?php else: ?>
<script type="text/javascript" src="<?php echo ROOT_JS ?>jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="<?php echo ROOT_JS ?>jquery.jstree.min.js"></script>
<script type="text/javascript" src="<?php echo ROOT_JS ?>jquery.cookie-1.4.1.js"></script>
<?php endif ?>
<script type="text/javascript" src="<?php echo ROOT ?>jsScuttle.php"></script>
<?php endif ?>

View File

@ -733,11 +733,26 @@ class SemanticScuttle_Service_Bookmark extends SemanticScuttle_DbService
if (!is_array($tags) && !is_null($tags)) {
$tags = explode('+', trim($tags));
}
$tagcount = count($tags);
for ($i = 0; $i < $tagcount; $i ++) {
$tags[$i] = trim($tags[$i]);
if (!is_null($tags)) {
$tags = array_map('trim', $tags);
}
// Remove shoulder surfing protected tags.
if (! empty($GLOBALS['shoulderSurfingProtectedTag']) && $userservice->isLoggedOn() && ! isset($_COOKIE["noshoulderSurfingProtection"])) {
$shoulderSurfingProtectedTags = $tag2tagservice->getAllLinkedTags($GLOBALS['shoulderSurfingProtectedTag'], '>', $sId, array());
$shoulderSurfingProtectedTags[] = $GLOBALS['shoulderSurfingProtectedTag'];
$tags2 = [];
foreach ($tags as $tag) {
if (! in_array($tag, $shoulderSurfingProtectedTags, true)) {
$tags2[] = $tag;
}
}
// If we filtered everything, we stop here and return nothing.
if(! empty($tags) && empty($tags2)) {
return array();
}
$tags = $tags2;
}
$tagcount = count($tags);
// Set up the SQL query.
$query_1 = 'SELECT DISTINCT ';
@ -899,6 +914,20 @@ class SemanticScuttle_Service_Bookmark extends SemanticScuttle_DbService
$query_4 .= ' AND B.bHash = "'. $hash .'"';
}
// Exclude bookmarks with shoulder surfing protected tags.
if (! empty($GLOBALS['shoulderSurfingProtectedTag']) && $userservice->isLoggedOn() && ! isset($_COOKIE["noshoulderSurfingProtection"])) {
$query_4 .= ' AND B.bId NOT IN (SELECT DISTINCT B0.bId FROM '.
$this->getTableName() .' AS B0, ' . $userservice->getTableName()
.' AS U, ' . $b2tservice->getTableName() .' AS T WHERE B0.uId = U.'
. $userservice->getFieldName('primary') . $privacy .' AND B0.uId = '
. $sId . ' AND (';
$count_s = count($shoulderSurfingProtectedTags);
for ($i = 0; $i < $count_s - 1; $i++) {
$query_4 .= 'T.tag = "'. $shoulderSurfingProtectedTags[$i] .'" OR ';
}
$query_4 .= 'T.tag = "'. $shoulderSurfingProtectedTags[$count_s - 1] .'")'
.' AND T.bId = B0.bId)';
}
$query = $query_1 . $query_2 . $query_3 . $query_4 . $query_5;
@ -951,6 +980,7 @@ class SemanticScuttle_Service_Bookmark extends SemanticScuttle_DbService
$this->db->sql_freeresult($dbresult);
$output = array ('bookmarks' => $bookmarks, 'total' => $total);
return $output;
}

View File

@ -353,7 +353,7 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
function &getTags($userid = NULL) {
$userservice =SemanticScuttle_Service_Factory::get('User');
$userservice = SemanticScuttle_Service_Factory::get('User');
$logged_on_user = $userservice->getCurrentUserId();
$query = 'SELECT T.tag, COUNT(B.bId) AS bCount FROM '. $GLOBALS['tableprefix'] .'bookmarks AS B INNER JOIN '. $userservice->getTableName() .' AS U ON B.uId = U.'. $userservice->getFieldName('primary') .' INNER JOIN '. $GLOBALS['tableprefix'] .'bookmarks2tags AS T ON B.bId = T.bId';
@ -366,7 +366,6 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
} else {
$conditions['B.bStatus'] = 0;
}
$query .= ' WHERE '. $this->db->sql_build_array('SELECT', $conditions) .' AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC, tag';
if (!($dbresult = $this->db->sql_query($query))) {
@ -376,9 +375,31 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
$output = $this->db->sql_fetchrowset($dbresult);
$this->db->sql_freeresult($dbresult);
return $output;
return $this->filterShoulderSurfingProtectedTags($output);
}
function &filterShoulderSurfingProtectedTags($dboutput) {
$userservice = SemanticScuttle_Service_Factory::get('User');
if (! empty($GLOBALS['shoulderSurfingProtectedTag']) && $userservice->isLoggedOn() && ! isset($_COOKIE["noshoulderSurfingProtection"])) {
$logged_on_user = $userservice->getCurrentUserId();
$ttt = SemanticScuttle_Service_Factory::get('Tag2Tag');
$shoulderSurfingProtectedTags = $ttt->getAllLinkedTags($GLOBALS['shoulderSurfingProtectedTag'], '>', $logged_on_user, array());
$shoulderSurfingProtectedTags[] = $GLOBALS['shoulderSurfingProtectedTag'];
$output = array();
foreach ($dboutput as $array) {
$flag = 1;
foreach ($shoulderSurfingProtectedTags as $tag) {
if ($array['tag'] === $tag) {
$flag = 0;
break;
}
}
if ($flag) {$output[] = $array;}
}
return $output;
}
else {return $dboutput;}
}
// Returns the tags related to the specified tags; i.e. attached to the same bookmarks
function &getRelatedTags($tags, $for_user = NULL, $logged_on_user = NULL, $limit = 10) {
@ -423,7 +444,7 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
}
$output = $this->db->sql_fetchrowset($dbresult);
$this->db->sql_freeresult($dbresult);
return $output;
return $this->filterShoulderSurfingProtectedTags($output);
}
// Returns the most popular tags used for a particular bookmark hash
@ -453,7 +474,7 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
}
$output = $this->db->sql_fetchrowset($dbresult);
$this->db->sql_freeresult($dbresult);
return $output;
return $this->filterShoulderSurfingProtectedTags($output);
}
@ -613,7 +634,7 @@ class SemanticScuttle_Service_Bookmark2Tag extends SemanticScuttle_DbService
$output = $this->db->sql_fetchrowset($dbresult);
$this->db->sql_freeresult($dbresult);
return $output;
return $this->filterShoulderSurfingProtectedTags($output);
}

View File

@ -151,6 +151,10 @@ class SemanticScuttle_Service_SearchHistory extends SemanticScuttle_DbService
$range = null, $uId = null, $nb = null,
$start = null, $distinct = false, $withResults = false
) {
$userservice = SemanticScuttle_Service_Factory::get('User');
if ($userservice->isLoggedOn() && ! isset($_COOKIE["noshoulderSurfingProtection"])) {
return array();
}
$sql = 'SELECT DISTINCT(shTerms),'
. ' shId, shRange, shNbResults, shDatetime, uId';
$sql.= ' FROM '. $this->getTableName();

View File

@ -318,7 +318,8 @@ class SemanticScuttle_Service_Tag2Tag extends SemanticScuttle_DbService
}
$output = $this->db->sql_fetchrowset($dbresult);
$this->db->sql_freeresult($dbresult);
return $output;
$btt = SemanticScuttle_Service_Factory::get('Bookmark2Tag');
return $btt->filterShoulderSurfingProtectedTags($output);
}
function getMenuTags($uId) {
@ -377,6 +378,25 @@ class SemanticScuttle_Service_Tag2Tag extends SemanticScuttle_DbService
$dbres = $this->db->sql_query($query);
$rowset = $this->db->sql_fetchrowset($dbres);
$this->db->sql_freeresult($dbres);
$userservice = SemanticScuttle_Service_Factory::get('User');
if (count($rowset)>0 && ! empty($GLOBALS['shoulderSurfingProtectedTag']) && $userservice->isLoggedOn() && ! isset($_COOKIE["noshoulderSurfingProtection"])) {
$logged_on_user = $userservice->getCurrentUserId();
$shoulderSurfingProtectedTags = $this->getAllLinkedTags($GLOBALS['shoulderSurfingProtectedTag'], '>', $logged_on_user, array());
$shoulderSurfingProtectedTags[] = $GLOBALS['shoulderSurfingProtectedTag'];
$output = array();
foreach($rowset as $link) {
$flag = 1;
foreach ($shoulderSurfingProtectedTags as $tag) {
if ($link['tag1'] === $tag || $link['tag2'] === $tag) {
$flag = 0;
break;
}
}
if ($flag) {$output[] = $link;}
}
$rowset = $output;
}
return $rowset;
}

View File

@ -22,6 +22,7 @@ require_once 'www-header.php';
$tplVars['pagetitle'] = T_('About');
$tplVars['subtitle'] = T_('About');
$tplVars['loadjs'] = true;
$templateservice->loadTemplate('about.tpl', $tplVars);
?>

View File

@ -0,0 +1,33 @@
<?php
require_once '../www-header.php';
if(isset($_POST['password']) && $userservice->isLoggedOn()){
$password = $userservice->sanitisePassword($_POST['password']);
$username = $currentUser->getUsername();
$db = SemanticScuttle_Service_Factory::getDb();
$query = 'SELECT '. $userservice->getFieldName('primary') .' FROM '. $userservice->getTableName() .' WHERE '. $userservice->getFieldName('username') .' = "'. $db->sql_escape($username) .'" AND '. $userservice->getFieldName('password') .' = "'. $db->sql_escape($password) .'"';
if (!($dbresult = $db->sql_query($query))) {
message_die(
GENERAL_ERROR,
'Could not get user',
'', __LINE__, __FILE__, $query, $db
);
echo 'false';
}
else {
$row = $db->sql_fetchrow($dbresult);
$db->sql_freeresult($dbresult);
if ($row) {
echo 'true';
}
else {
echo 'false';
}
}
}
else {
echo 'false';
}
?>

View File

@ -272,10 +272,36 @@ if ($templatename == 'editbookmark.tpl') {
$rssTitle = "Tags" . $catTitle;
$rssCat = '/'. filter($cat, 'url');
$tplVars['currenttag'] = $cat;
$tplVars['sidebar_blocks'][] = 'tagactions';
//$tplVars['sidebar_blocks'][] = 'menu2';
if (! empty($GLOBALS['shoulderSurfingProtectedTag']) && ! isset($_COOKIE["noshoulderSurfingProtection"])) {
$tag2tagservice = SemanticScuttle_Service_Factory::get('Tag2Tag');
$b2tservice = SemanticScuttle_Service_Factory::get('Bookmark2Tag');
$alltags = $b2tservice->getTags($currentUserID);
$shoulderSurfingProtectedTags = $tag2tagservice->getAllLinkedTags($GLOBALS['shoulderSurfingProtectedTag'], '>', $currentUserID, array());
$shoulderSurfingProtectedTags[] = $GLOBALS['shoulderSurfingProtectedTag'];
$flag = 0;
if (! in_array($cat, $shoulderSurfingProtectedTags, true)) {
foreach ($alltags as $tag) {
if ($tag['tag'] === $cat) {
$flag = 1;
break;
}
}
}
if ($flag) {
$tplVars['sidebar_blocks'][] = 'tagactions';
$tplVars['sidebar_blocks'][] = 'linked';
$tplVars['sidebar_blocks'][] = 'related';
}
}
else {
$tplVars['sidebar_blocks'][] = 'tagactions';
$tplVars['sidebar_blocks'][] = 'linked';
$tplVars['sidebar_blocks'][] = 'related';
}
/*$tplVars['sidebar_blocks'][] = 'menu';*/
}
$tplVars['sidebar_blocks'][] = 'menu2';

View File

@ -0,0 +1,117 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setTime(+t + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));

View File

@ -63,7 +63,7 @@ if (POST_CONFIRM != '') {
}
$tplVars['links'] = $tag2tagservice->getLinks($currentUser->getId());
$tplVars['loadjs'] = true;
$tplVars['tag1'] = $tag1;
$tplVars['tag2'] = '';
$tplVars['subtitle'] = T_('Add Tag Link') .': '. $tag1;

View File

@ -75,7 +75,7 @@ if (POST_CONFIRM) {
}
$tplVars['links'] = $tag2tagservice->getLinks($currentUser->getId());
$tplVars['loadjs'] = true;
$tplVars['tag1'] = $tag1;
$tplVars['tag2'] = $tag2;
$tplVars['subtitle'] = T_('Delete Link Between Tags') .': '. $tag1.' > '.$tag2;

View File

@ -73,6 +73,7 @@ if (POST_CONFIRM) {
$tplVars['formaction'] = $_SERVER['SCRIPT_NAME'] .'/'. $tag;
$tplVars['referrer'] = $_SERVER['HTTP_REFERER'];
$tplVars['old'] = $tag;
$tplVars['loadjs'] = true;
}
$templateservice->loadTemplate($template, $tplVars);
?>