aboutsummaryrefslogtreecommitdiffstats
path: root/include/security.php
diff options
context:
space:
mode:
authorzotlabs <mike@macgirvin.com>2019-03-11 16:29:12 -0700
committerzotlabs <mike@macgirvin.com>2019-03-11 16:29:12 -0700
commit5fb0d38ad8b59bef92655b56cf7145cc979b6dea (patch)
tree3a37c82b3bf9a64b5a47055aeb98d46cffd30ae9 /include/security.php
parent4e97fb0e587eed6875b1d3a2615b0997a7f13c63 (diff)
downloadvolse-hubzilla-5fb0d38ad8b59bef92655b56cf7145cc979b6dea.tar.gz
volse-hubzilla-5fb0d38ad8b59bef92655b56cf7145cc979b6dea.tar.bz2
volse-hubzilla-5fb0d38ad8b59bef92655b56cf7145cc979b6dea.zip
security updates for multiple xchans
Diffstat (limited to 'include/security.php')
-rw-r--r--include/security.php248
1 files changed, 189 insertions, 59 deletions
diff --git a/include/security.php b/include/security.php
index 44cd605dc..b9705a0e4 100644
--- a/include/security.php
+++ b/include/security.php
@@ -306,6 +306,7 @@ function change_channel($change_channel) {
*
* @return string additional SQL where statement
*/
+
function permissions_sql($owner_id, $remote_observer = null, $table = '') {
$local_channel = local_channel();
@@ -316,7 +317,7 @@ function permissions_sql($owner_id, $remote_observer = null, $table = '') {
* default permissions - anonymous user
*/
- if($table)
+ if ($table)
$table .= '.';
$sql = " AND {$table}allow_cid = ''
@@ -329,38 +330,63 @@ function permissions_sql($owner_id, $remote_observer = null, $table = '') {
* Profile owner - everything is visible
*/
- if(($local_channel) && ($local_channel == $owner_id)) {
- $sql = '';
+ if (($local_channel) && ($local_channel == $owner_id)) {
+ return EMPTY_STR;
}
/**
- * Authenticated visitor. Unless pre-verified,
- * check that the contact belongs to this $owner_id
- * and load the groups the visitor belongs to.
- * If pre-verified, the caller is expected to have already
- * done this and passed the groups into this function.
+ * Authenticated visitor.
*/
else {
+
$observer = ((! is_null($remote_observer)) ? $remote_observer : get_observer_hash());
- if($observer) {
- $groups = init_groups_visitor($observer);
- $gs = '<<>>'; // should be impossible to match
+ if ($observer) {
+
+ $sec = get_security_ids($owner_id,$observer);
+
+ // always allow the channel owner, even if authenticated as a visitor
+
+ if ($sec['channel_id']) {
+ foreach ($sec['channel_id'] as $ch) {
+ if ($observer === $ch) {
+ return EMPTY_STR;
+ }
+ }
+ }
+
+ if (is_array($sec['allow_cid']) && count($sec['allow_cid'])) {
+ $ca = [];
+ foreach ($sec['allow_cid'] as $c) {
+ $ca[] = '<' . $c . '>';
+ }
+ $cs = implode('|',$ca);
+ }
+ else {
+ $cs = '<<>>'; // should be impossible to match
+ }
- if(is_array($groups) && count($groups)) {
- foreach($groups as $g)
- $gs .= '|<' . $g . '>';
+ if (is_array($sec['allow_gid']) && count($sec['allow_gid'])) {
+ $ga = [];
+ foreach ($sec['allow_gid'] as $g) {
+ $ga[] = '<' . $g . '>';
+ }
+ $gs = implode('|',$ga);
+ }
+ else {
+ $gs = '<<>>'; // should be impossible to match
}
+
$regexop = db_getfunc('REGEXP');
$sql = sprintf(
- " AND ( NOT ({$table}deny_cid like '%s' OR {$table}deny_gid $regexop '%s')
- AND ( {$table}allow_cid like '%s' OR {$table}allow_gid $regexop '%s' OR ( {$table}allow_cid = '' AND {$table}allow_gid = '') )
+ " AND ( NOT ({$table}deny_cid regexop '%s' OR {$table}deny_gid $regexop '%s')
+ AND ( {$table}allow_cid regexop '%s' OR {$table}allow_gid $regexop '%s' OR ( {$table}allow_cid = '' AND {$table}allow_gid = '') )
)
",
- dbesc(protect_sprintf( '%<' . $observer . '>%')),
+ dbesc($cs),
dbesc($gs),
- dbesc(protect_sprintf( '%<' . $observer . '>%')),
+ dbesc($cs),
dbesc($gs)
);
}
@@ -377,6 +403,7 @@ function permissions_sql($owner_id, $remote_observer = null, $table = '') {
*
* @return string additional SQL where statement
*/
+
function item_permissions_sql($owner_id, $remote_observer = null) {
$local_channel = local_channel();
@@ -398,37 +425,59 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
}
/**
- * Authenticated visitor. Unless pre-verified,
- * check that the contact belongs to this $owner_id
- * and load the groups the visitor belongs to.
- * If pre-verified, the caller is expected to have already
- * done this and passed the groups into this function.
+ * Authenticated visitor.
*/
else {
- $observer = (($remote_observer) ? $remote_observer : get_observer_hash());
- if($observer) {
+ $observer = (($remote_observer) ? $remote_observer : get_observer_hash());
- $s = scopes_sql($owner_id,$observer);
+ if($observer) {
- $groups = init_groups_visitor($observer);
+ $scope = scopes_sql($owner_id,$observer);
+ $sec = get_security_ids($owner_id,$observer);
- $gs = '<<>>'; // should be impossible to match
+ // always allow the channel owner, even if authenticated as a visitor
+
+ if($sec['channel_id']) {
+ foreach($sec['channel_id'] as $ch) {
+ if($observer === $ch) {
+ return EMPTY_STR;
+ }
+ }
+ }
- if(is_array($groups) && count($groups)) {
- foreach($groups as $g)
- $gs .= '|<' . $g . '>';
+ if (is_array($sec['allow_cid']) && count($sec['allow_cid'])) {
+ $ca = [];
+ foreach ($sec['allow_cid'] as $c) {
+ $ca[] = '<' . $c . '>';
+ }
+ $cs = implode('|',$ca);
+ }
+ else {
+ $cs = '<<>>'; // should be impossible to match
+ }
+
+ if (is_array($sec['allow_gid']) && count($sec['allow_gid'])) {
+ $ga = [];
+ foreach ($sec['allow_gid'] as $g) {
+ $ga[] = '<' . $g . '>';
+ }
+ $gs = implode('|',$ga);
}
+ else {
+ $gs = '<<>>'; // should be impossible to match
+ }
+
$regexop = db_getfunc('REGEXP');
$sql = sprintf(
- " AND (( NOT (deny_cid like '%s' OR deny_gid $regexop '%s')
- AND ( allow_cid like '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ))
- ) OR ( item_private = 1 $s ))
+ " AND (( NOT (deny_cid regexop '%s' OR deny_gid $regexop '%s')
+ AND ( allow_cid regexop '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ))
+ ) OR ( item_private = 1 $scope ))
",
- dbesc(protect_sprintf( '%<' . $observer . '>%')),
+ dbesc($cs),
dbesc($gs),
- dbesc(protect_sprintf( '%<' . $observer . '>%')),
+ dbesc($cs),
dbesc($gs)
);
}
@@ -465,40 +514,57 @@ function scopes_sql($uid,$observer) {
}
-
-
-
-
-
/**
* @param string $observer_hash
*
* @return string additional SQL where statement
*/
+
function public_permissions_sql($observer_hash) {
- $groups = init_groups_visitor($observer_hash);
+ $owner_id = 0;
- $gs = '<<>>'; // should be impossible to match
+ if ($observer_hash) {
+
+ $sec = get_security_ids($owner_id,$observer_hash);
+
+ if (is_array($sec['allow_cid']) && count($sec['allow_cid'])) {
+ $ca = [];
+ foreach ($sec['allow_cid'] as $c) {
+ $ca[] = '<' . $c . '>';
+ }
+ $cs = implode('|',$ca);
+ }
+ else {
+ $cs = '<<>>'; // should be impossible to match
+ }
+
+ if (is_array($sec['allow_gid']) && count($sec['allow_gid'])) {
+ $ga = [];
+ foreach ($sec['allow_gid'] as $g) {
+ $ga[] = '<' . $g . '>';
+ }
+ $gs = implode('|',$ga);
+ }
+ else {
+ $gs = '<<>>'; // should be impossible to match
+ }
- if(is_array($groups) && count($groups)) {
- foreach($groups as $g)
- $gs .= '|<' . $g . '>';
- }
- $sql = '';
- if($observer_hash) {
$regexop = db_getfunc('REGEXP');
$sql = sprintf(
- " OR (( NOT (deny_cid like '%s' OR deny_gid $regexop '%s')
- AND ( allow_cid like '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ) )
- ))
+ " AND ( NOT (deny_cid regexop '%s' OR deny_gid $regexop '%s')
+ AND ( allow_cid regexop '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0) )
+ )
",
- dbesc(protect_sprintf( '%<' . $observer_hash . '>%')),
+ dbesc($cs),
dbesc($gs),
- dbesc(protect_sprintf( '%<' . $observer_hash . '>%')),
+ dbesc($cs),
dbesc($gs)
);
}
+ else {
+ $sql = EMPTY_STR;
+ }
return $sql;
}
@@ -510,7 +576,7 @@ function public_permissions_sql($observer_hash) {
* In this implementation, a security token is reusable (if the user submits a form, goes back and resubmits the form, maybe with small changes;
* or if the security token is used for ajax-calls that happen several times), but only valid for a certain amout of time (3hours).
* The "typename" seperates the security tokens of different types of forms. This could be relevant in the following case:
- * A security token is used to protekt a link from CSRF (e.g. the "delete this profile"-link).
+ * A security token is used to protect a link from CSRF (e.g. the "delete this profile"-link).
* If the new page contains by any chance external elements, then the used security token is exposed by the referrer.
* Actually, important actions should not be triggered by Links / GET-Requests at all, but somethimes they still are,
* so this mechanism brings in some damage control (the attacker would be able to forge a request to a form of this type, but not to forms of other types).
@@ -587,8 +653,8 @@ function init_groups_visitor($contact_id) {
// private profiles are treated as a virtual group
$r = q("SELECT abook_profile from abook where abook_xchan in ( " . protect_sprintf($hashes) . " ) and abook_profile != '' ");
- if($r) {
- foreach($r as $rv) {
+ if ($r) {
+ foreach ($r as $rv) {
$groups[] = 'vp.' . $rv['abook_profile'];
}
}
@@ -596,8 +662,8 @@ function init_groups_visitor($contact_id) {
// physical groups this identity is a member of
$r = q("SELECT hash FROM pgrp left join pgrp_member on pgrp.id = pgrp_member.gid WHERE xchan in ( " . protect_sprintf($hashes) . " ) ");
- if($r) {
- foreach($r as $rr)
+ if ($r) {
+ foreach ($r as $rr)
$groups[] = $rr['hash'];
}
return $groups;
@@ -605,6 +671,70 @@ function init_groups_visitor($contact_id) {
+
+function get_security_ids($channel_id, $ob_hash) {
+
+ $ret = [
+ 'channel_id' => [],
+ 'allow_cid' => [],
+ 'allow_gid' => []
+ ];
+
+ if($channel_id) {
+ $ch = q("select channel_hash, portable_id from channel where channel_id = %d",
+ intval($channel_id)
+ );
+ if($ch) {
+ $ret['channel_id'][] = $ch[0]['channel_hash'];
+ $ret['channel_id'][] = $ch[0]['portable_id'];
+ }
+ }
+
+ $groups = [];
+
+ $x = q("select * from xchan where xchan_hash = '%s'",
+ dbesc($ob_hash)
+ );
+
+ if ($x) {
+
+ // include xchans for all zot-like networks
+
+ $xchans = q("select xchan_hash from xchan where xchan_hash = '%s' OR ( xchan_guid = '%s' AND xchan_pubkey = '%s' ) ",
+ dbesc($ob_hash),
+ dbesc($x[0]['xchan_guid']),
+ dbesc($x[0]['xchan_pubkey'])
+ );
+
+ if ($xchans) {
+ $ret['allow_cid'] = ids_to_array($xchans,'xchan_hash');
+ $hashes = ids_to_querystr($xchans,'xchan_hash',true);
+
+ // private profiles are treated as a virtual group
+
+ $r = q("SELECT abook_profile from abook where abook_xchan in ( " . protect_sprintf($hashes) . " ) and abook_profile != '' ");
+ if($r) {
+ foreach ($r as $rv) {
+ $groups[] = 'vp.' . $rv['abook_profile'];
+ }
+ }
+
+ // physical groups this identity is a member of
+
+ $r = q("SELECT hash FROM pgrp left join pgrp_member on pgrp.id = pgrp_member.gid WHERE xchan in ( " . protect_sprintf($hashes) . " ) ");
+ if($r) {
+ foreach ($r as $rv) {
+ $groups[] = $rv['hash'];
+ }
+ }
+ $ret['allow_gid'] = $groups;
+ }
+ }
+
+ return $ret;
+}
+
+
// This is used to determine which uid have posts which are visible to the logged in user (from the API) for the
// public_timeline, and we can use this in a community page by making
// $perms = (PERMS_NETWORK|PERMS_PUBLIC) unless logged in.