From bfd3da43ac9226e53188a03ff1414a18422e91b4 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 17 Dec 2021 19:48:09 +0100 Subject: access token refactor --- Zotlabs/Module/Tokens.php | 194 +++++++++++++++++++++++++++++++++++--------- boot.php | 2 +- include/channel.php | 24 +++++- include/connections.php | 13 +++ include/import.php | 58 +++++++++++++ include/security.php | 28 +++++-- include/text.php | 2 +- install/schema_mysql.sql | 2 + install/schema_postgres.sql | 2 + view/tpl/tokens.tpl | 7 +- 10 files changed, 282 insertions(+), 50 deletions(-) diff --git a/Zotlabs/Module/Tokens.php b/Zotlabs/Module/Tokens.php index 31b219019..632e816ce 100644 --- a/Zotlabs/Module/Tokens.php +++ b/Zotlabs/Module/Tokens.php @@ -5,6 +5,11 @@ namespace Zotlabs\Module; use App; use Zotlabs\Web\Controller; use Zotlabs\Lib\Apps; +use Zotlabs\Lib\AccessList; +use Zotlabs\Lib\Permcat; +use Zotlabs\Lib\Libsync; + +require_once('include/security.php'); class Tokens extends Controller { @@ -13,15 +18,20 @@ class Tokens extends Controller { if(! local_channel()) return; - if(! Apps::system_app_installed(local_channel(), 'Guest Access')) - return; - $channel = App::get_channel(); + if(! Apps::system_app_installed($channel['channel_id'], 'Guest Access')) + return; + check_form_security_token_redirectOnErr('tokens', 'tokens'); $token_errs = 0; if(array_key_exists('token',$_POST)) { $atoken_id = (($_POST['atoken_id']) ? intval($_POST['atoken_id']) : 0); + + if (! $atoken_id) { + $atoken_guid = new_uuid(); + } + $name = trim(escape_tags($_POST['name'])); $token = trim($_POST['token']); if((! $name) || (! $token)) @@ -30,10 +40,10 @@ class Tokens extends Controller { $expires = datetime_convert(date_default_timezone_get(),'UTC',$_POST['expires']); else $expires = NULL_DATE; - $max_atokens = service_class_fetch(local_channel(),'access_tokens'); + $max_atokens = service_class_fetch($channel['channel_id'],'access_tokens'); if($max_atokens) { $r = q("select count(atoken_id) as total where atoken_uid = %d", - intval(local_channel()) + intval($channel['channel_id']) ); if($r && intval($r[0]['total']) >= $max_tokens) { notice( sprintf( t('This channel is limited to %d tokens'), $max_tokens) . EOL); @@ -45,6 +55,17 @@ class Tokens extends Controller { notice( t('Name and Password are required.') . EOL); return; } + + $old_atok = q("select * from atoken where atoken_uid = %d and atoken_name = '%s'", + intval($channel['channel_id']), + dbesc($name) + ); + + if ($old_atok) { + $old_atok = $old_atok[0]; + $old_xchan = atoken_xchan($old_atok); + } + if($atoken_id) { $r = q("update atoken set atoken_name = '%s', atoken_token = '%s', atoken_expires = '%s' where atoken_id = %d and atoken_uid = %d", @@ -56,8 +77,9 @@ class Tokens extends Controller { ); } else { - $r = q("insert into atoken ( atoken_aid, atoken_uid, atoken_name, atoken_token, atoken_expires ) - values ( %d, %d, '%s', '%s', '%s' ) ", + $r = q("insert into atoken (atoken_guid, atoken_aid, atoken_uid, atoken_name, atoken_token, atoken_expires ) + values ('%s', %d, %d, '%s', '%s', '%s' ) ", + dbesc($atoken_guid), intval($channel['channel_account_id']), intval($channel['channel_id']), dbesc($name), @@ -66,21 +88,85 @@ class Tokens extends Controller { ); } - $atoken_xchan = substr($channel['channel_hash'],0,16) . '.' . $name; + $atok = q("select * from atoken where atoken_uid = %d and atoken_name = '%s'", + intval($channel['channel_id']), + dbesc($name) + ); - $all_perms = \Zotlabs\Access\Permissions::Perms(); + if ($atok) { + $xchan = atoken_xchan($atok[0]); + atoken_create_xchan($xchan); + $atoken_xchan = $xchan['xchan_hash']; + if ($old_atok && $old_xchan) { + $r = q("update xchan set xchan_name = '%s' where xchan_hash = '%s'", + dbesc($xchan['xchan_name']), + dbesc($old_xchan['xchan_hash']) + ); + } + } - if($all_perms) { - foreach($all_perms as $perm => $desc) { - if(array_key_exists('perms_' . $perm, $_POST)) { - set_abconfig($channel['channel_id'],$atoken_xchan,'my_perms',$perm,intval($_POST['perms_' . $perm])); - } - else { - set_abconfig($channel['channel_id'],$atoken_xchan,'my_perms',$perm,0); + + if (! $atoken_id) { + + // If this is a new token, create a new abook record + + $closeness = get_pconfig($uid,'system','new_abook_closeness',80); + $profile_assign = get_pconfig($uid,'system','profile_assign',''); + + $r = abook_store_lowlevel( + [ + 'abook_account' => $channel['channel_account_id'], + 'abook_channel' => $channel['channel_id'], + 'abook_closeness' => intval($closeness), + 'abook_xchan' => $atoken_xchan, + 'abook_profile' => $profile_assign, + 'abook_feed' => 0, + 'abook_created' => datetime_convert(), + 'abook_updated' => datetime_convert(), + 'abook_instance' => z_root(), + ] + ); + + if (! $r) { + logger('abook creation failed'); + } + + /** If there is a default group for this channel, add this connection to it */ + + if ($channel['channel_default_group']) { + $g = AccessList::by_hash($uid,$channel['channel_default_group']); + if ($g) { + AccessList::member_add($uid,'',$atoken_xchan,$g['id']); } } } + $role = ((array_key_exists('permcat', $_POST)) ? escape_tags($_POST['permcat']) : ''); + \Zotlabs\Lib\Permcat::assign($channel, $role, [$atoken_xchan]); + + $r = q("SELECT abook.*, xchan.* + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1", + intval($channel['chnnel_id']), + dbesc($atoken_xchan) + ); + + if (! $r) { + return; + } + + $clone = $r[0]; + + unset($clone['abook_id']); + unset($clone['abook_account']); + unset($clone['abook_channel']); + + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); + if ($abconfig) { + $clone['abconfig'] = $abconfig; + } + + Libsync::build_sync_packet($channel['channel_id'], [ 'abook' => [ $clone ], 'atoken' => $atok ], true); info( t('Token saved.') . EOL); return; @@ -103,6 +189,7 @@ class Tokens extends Controller { $atoken = null; $atoken_xchan = ''; + $atoken_abook = []; if(argc() > 1) { $id = argv(1); @@ -114,13 +201,47 @@ class Tokens extends Controller { if($atoken) { $atoken = $atoken[0]; - $atoken_xchan = substr($channel['channel_hash'],0,16) . '.' . $atoken['atoken_name']; + $atoken_xchan = substr($channel['channel_hash'],0,16) . '.' . $atoken['atoken_guid']; + + $atoken_abook = q("select * from abook where abook_channel = %d and abook_xchan = '%s'", + intval(local_channel()), + dbesc($atoken_xchan) + ); + + $atoken_abook = $atoken_abook[0]; } if($atoken && argc() > 2 && argv(2) === 'drop') { + $atoken['deleted'] = true; + + $r = q("SELECT abook.*, xchan.* + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1", + intval($channel['chnnel_id']), + dbesc($atoken_xchan) + ); + if (! $r) { + return; + } + + $clone = $r[0]; + + unset($clone['abook_id']); + unset($clone['abook_account']); + unset($clone['abook_channel']); + $clone['deleted'] = true; + + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); + if ($abconfig) { + $clone['abconfig'] = $abconfig; + } + atoken_delete($id); + Libsync::build_sync_packet($channel['channel_id'], [ 'abook' => [ $clone ], 'atoken' => [ $atoken ] ], true); + $atoken = null; $atoken_xchan = ''; + $atoken_abook = null; } } @@ -132,39 +253,34 @@ class Tokens extends Controller { $desc2 = t('You may also provide dropbox style access links to friends and associates by adding the Login Password to any specific site URL as shown. Examples:'); - $global_perms = \Zotlabs\Access\Permissions::Perms(); - $their_perms = []; - $existing = get_all_perms(local_channel(),(($atoken_xchan) ? $atoken_xchan : ''),false); + //TODO: assign role + $pcat = new Permcat(local_channel()); + $pcatlist = $pcat->listing(); + $default_role = get_pconfig(local_channel(), 'system', 'default_permcat'); + $current_permcat = (($atoken_abook) ? $atoken_abook['abook_role'] : $default_role); - if($atoken_xchan) { - $theirs = q("select * from abconfig where chan = %d and xchan = '%s' and cat = 'their_perms'", - intval(local_channel()), - dbesc($atoken_xchan) - ); - if($theirs) { - foreach($theirs as $t) { - $their_perms[$t['k']] = $t['v']; - } - } + $roles_dict = []; + foreach ($pcatlist as $role) { + $roles_dict[$role['name']] = $role['localname']; } - foreach($global_perms as $k => $v) { - $thisperm = get_abconfig(local_channel(),$contact['abook_xchan'],'my_perms',$k); -//fixme - - $checkinherited = \Zotlabs\Access\PermissionLimits::Get(local_channel(),$k); - if($existing[$k]) - $thisperm = "1"; - $perms[] = array('perms_' . $k, $v, ((array_key_exists($k,$their_perms)) ? intval($their_perms[$k]) : ''),$thisperm, 1, (($checkinherited & PERMS_SPECIFIC) ? '' : '1'), '', $checkinherited); + if (!$current_permcat) { + notice(t('Please select a role for this contact!') . EOL); + $permcats[] = ''; } - + if ($pcatlist) { + foreach ($pcatlist as $pc) { + $permcats[$pc['name']] = $pc['localname']; + } + } $tpl = get_markup_template("tokens.tpl"); $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("tokens"), + '$permcat' => ['permcat', t('Select a role for this token'), $current_permcat, '', $permcats], '$title' => t('Guest Access Tokens'), '$desc' => $desc, '$desc2' => $desc2, diff --git a/boot.php b/boot.php index 838c93177..91f0b8147 100644 --- a/boot.php +++ b/boot.php @@ -58,7 +58,7 @@ define ( 'PLATFORM_NAME', 'hubzilla' ); define ( 'STD_VERSION', '6.5.10' ); define ( 'ZOT_REVISION', '6.0' ); -define ( 'DB_UPDATE_VERSION', 1249 ); +define ( 'DB_UPDATE_VERSION', 1250 ); define ( 'PROJECT_BASE', __DIR__ ); diff --git a/include/channel.php b/include/channel.php index 00d973738..c80a35385 100644 --- a/include/channel.php +++ b/include/channel.php @@ -21,6 +21,7 @@ require_once('include/crypto.php'); require_once('include/menu.php'); require_once('include/perm_upgrade.php'); require_once('include/photo/photo_driver.php'); +require_once('include/security.php'); /** * @brief Called when creating a new channel. @@ -878,6 +879,14 @@ function identity_basic_export($channel_id, $sections = null, $zap_compat = fals } if(in_array('connections',$sections)) { + $r = q("select * from atoken where atoken_uid = %d", + intval($channel_id) + ); + + if ($r) { + $ret['atoken'] = $r; + } + $xchans = array(); $r = q("select * from abook where abook_channel = %d ", intval($channel_id) @@ -1963,11 +1972,24 @@ function zat_init() { ); if($r) { $xchan = atoken_xchan($r[0]); - atoken_create_xchan($xchan); + //atoken_create_xchan($xchan); atoken_login($xchan); } } +function atoken_delete_and_sync($channel_id, $atoken_guid) { + $r = q("select * from atoken where atoken_guid = '%s' and atoken_uid = %d", + dbesc($atoken_guid), + intval($channel_id) + ); + + if ($r) { + $atok = $r[0]; + $atok['deleted'] = true; + atoken_delete($atok['atoken_id']); + Libsync::build_sync_packet($channel_id, ['atoken' => [ $atok ]]); + } +} /** * @brief Used from within PCSS themes to set theme parameters. diff --git a/include/connections.php b/include/connections.php index 11264e6d8..fbbf59c72 100644 --- a/include/connections.php +++ b/include/connections.php @@ -376,6 +376,19 @@ function contact_remove($channel_id, $abook_id) { if(intval($abook['abook_self'])) return false; + // if this is an atoken, delete the atoken record + + $xchan = q("select * from xchan where xchan_hash = '%s'", + dbesc($abook['abook_xchan']) + ); + + if (strpos($xchan['xchan_addr'],'guest:') === 0 && strpos($abook['abook_xchan'],'.')){ + $atoken_guid = substr($abook['abook_xchan'],strrpos($abook['abook_xchan'],'.') + 1); + if ($atoken_guid) { + atoken_delete_and_sync($channel_id,$atoken_guid); + } + } + $r = q("select id, parent from item where (owner_xchan = '%s' or author_xchan = '%s') and uid = %d and item_retained = 0 and item_starred = 0", dbesc($abook['abook_xchan']), dbesc($abook['abook_xchan']), diff --git a/include/import.php b/include/import.php index 8707a9430..291dd2638 100644 --- a/include/import.php +++ b/include/import.php @@ -162,6 +162,64 @@ function import_config($channel, $configs) { } } +function import_atoken($channel, $atokens) { + if ($channel && $atokens) { + foreach ($atokens as $atoken) { + unset($atoken['atoken_id']); + $atoken['atoken_aid'] = $channel['channel_account_id']; + $atoken['atoken_uid'] = $channel['channel_id']; + create_table_from_array('atoken', $atoken); + } + } +} + +function sync_atoken($channel, $atokens) { + + if ($channel && $atokens) { + foreach ($atokens as $atoken) { + unset($atoken['atoken_id']); + $atoken['atoken_aid'] = $channel['channel_account_id']; + $atoken['atoken_uid'] = $channel['channel_id']; + + if ($atoken['deleted']) { + q("delete from atoken where atoken_uid = %d and atoken_guid = '%s' ", + intval($atoken['atoken_uid']), + dbesc($atoken['atoken_guid']) + ); + continue; + } + + $r = q("select * from atoken where atoken_uid = %d and atoken_guid = '%s' ", + intval($atoken['atoken_uid']), + dbesc($atoken['atoken_guid']) + ); + if (! $r) { + create_table_from_array('atoken', $atoken); + } + else { + $columns = db_columns('atoken'); + foreach ($atoken as $k => $v) { + if (! in_array($k,$columns)) { + continue; + } + + if (in_array($k, ['atoken_guid','atoken_uid','atoken_aid'])) { + continue; + } + + $r = q("UPDATE atoken SET " . TQUOT . "%s" . TQUOT . " = '%s' WHERE atoken_guid = '%s' AND atoken_uid = %d", + dbesc($k), + dbesc($v), + dbesc($atoken['atoken_guid']), + intval($channel['channel_id']) + ); + } + } + } + } +} + + /** * @brief Import profiles. * diff --git a/include/security.php b/include/security.php index b6c0f1511..f02fb8023 100644 --- a/include/security.php +++ b/include/security.php @@ -89,8 +89,20 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa } function atoken_login($atoken) { - if (!$atoken) + if (! $atoken) { return false; + } + + if (App::$cmd === 'channel' && argv(1)) { + $channel = channelx_by_nick(argv(1)); + if (perm_is_allowed($channel['channel_id'],$atoken['xchan_hash'],'delegate')) { + $_SESSION['delegate_channel'] = $channel['channel_id']; + $_SESSION['delegate'] = $atoken['xchan_hash']; + $_SESSION['account_id'] = intval($channel['channel_account_id']); + change_channel($channel['channel_id']); + return; + } + } $_SESSION['authenticated'] = 1; $_SESSION['visitor_id'] = $atoken['xchan_hash']; @@ -113,11 +125,11 @@ function atoken_xchan($atoken) { if ($c) { return [ 'atoken_id' => $atoken['atoken_id'], - 'xchan_hash' => substr($c['channel_hash'], 0, 16) . '.' . $atoken['atoken_name'], + 'xchan_hash' => substr($c['channel_hash'], 0, 16) . '.' . $atoken['atoken_guid'], 'xchan_name' => $atoken['atoken_name'], 'xchan_addr' => 'guest:' . $atoken['atoken_name'] . '@' . App::get_hostname(), 'xchan_network' => 'unknown', - 'xchan_url' => z_root() . '/guest/' . substr($c['channel_hash'], 0, 16) . '.' . $atoken['atoken_name'], + 'xchan_url' => z_root() . '/guest/' . substr($c['channel_hash'], 0, 16) . '.' . $atoken['atoken_guid'], 'xchan_hidden' => 1, 'xchan_photo_mimetype' => 'image/png', 'xchan_photo_l' => z_root() . '/' . get_default_profile_photo(300), @@ -143,11 +155,17 @@ function atoken_delete($atoken_id) { if (!$c) return; - $atoken_xchan = substr($c[0]['channel_hash'], 0, 16) . '.' . $r[0]['atoken_name']; + $atoken_xchan = substr($c[0]['channel_hash'], 0, 16) . '.' . $r[0]['atoken_guid']; q("delete from atoken where atoken_id = %d", intval($atoken_id) ); + + q("delete from abook where abook_channel = %d and abook_xchan = '%s'", + intval($c[0]['channel_id']), + dbesc($atoken_xchan) + ); + q("delete from abconfig where chan = %d and xchan = '%s'", intval($c[0]['channel_id']), dbesc($atoken_xchan) @@ -198,7 +216,7 @@ function atoken_abook($uid, $xchan_hash) { if (!$r) return false; - $x = q("select * from atoken where atoken_uid = %d and atoken_name = '%s'", + $x = q("select * from atoken where atoken_uid = %d and atoken_guid = '%s'", intval($uid), dbesc(substr($xchan_hash, 17)) ); diff --git a/include/text.php b/include/text.php index b2b3fce6e..84f112802 100644 --- a/include/text.php +++ b/include/text.php @@ -1715,7 +1715,7 @@ function prepare_body(&$item,$attach = false,$opts = false) { if ($is_photo) { $object = json_decode($item['obj'],true); $ptr = null; - if (array_key_exists('url',$object) && is_array($object['url'])) { + if (is_array($object) && array_key_exists('url',$object) && is_array($object['url'])) { if (array_key_exists(0,$object['url'])) { foreach ($object['url'] as $link) { if(array_key_exists('width',$link) && $link['width'] >= 640 && $link['width'] <= 1024) { diff --git a/install/schema_mysql.sql b/install/schema_mysql.sql index 054c1da03..80ae20d7b 100644 --- a/install/schema_mysql.sql +++ b/install/schema_mysql.sql @@ -148,12 +148,14 @@ CREATE TABLE IF NOT EXISTS `app` ( CREATE TABLE IF NOT EXISTS `atoken` ( `atoken_id` int(11) NOT NULL AUTO_INCREMENT, + `atoken_guid` char(191) NOT NULL DEFAULT '', `atoken_aid` int(11) NOT NULL DEFAULT 0 , `atoken_uid` int(11) NOT NULL DEFAULT 0 , `atoken_name` char(191) NOT NULL DEFAULT '', `atoken_token` char(191) NOT NULL DEFAULT '', `atoken_expires` datetime NOT NULL DEFAULT '0001-01-01 00:00:00', PRIMARY KEY (`atoken_id`), + KEY `atoken_guid` (`atoken_guid`), KEY `atoken_aid` (`atoken_aid`), KEY `atoken_uid` (`atoken_uid`), KEY `atoken_uid_2` (`atoken_uid`), diff --git a/install/schema_postgres.sql b/install/schema_postgres.sql index c1cbc832b..96d0cc33c 100644 --- a/install/schema_postgres.sql +++ b/install/schema_postgres.sql @@ -147,12 +147,14 @@ create index "app_system" on app ("app_system"); CREATE TABLE "atoken" ( "atoken_id" serial NOT NULL, + "atoken_guid" varchar(255) NOT NULL DEFAULT '', "atoken_aid" bigint NOT NULL DEFAULT 0, "atoken_uid" bigint NOT NULL DEFAULT 0, "atoken_name" varchar(255) NOT NULL DEFAULT '', "atoken_token" varchar(255) NOT NULL DEFAULT '', "atoken_expires" timestamp NOT NULL DEFAULT '0001-01-01 00:00:00', PRIMARY KEY ("atoken_id")); +create index atoken_guid on atoken (atoken_guid); create index atoken_aid on atoken (atoken_aid); create index atoken_uid on atoken (atoken_uid); create index atoken_name on atoken (atoken_name); diff --git a/view/tpl/tokens.tpl b/view/tpl/tokens.tpl index 6bf8dc202..51cd4a6cf 100644 --- a/view/tpl/tokens.tpl +++ b/view/tpl/tokens.tpl @@ -14,12 +14,14 @@ {{include file="field_input.tpl" field=$name}} {{include file="field_input.tpl" field=$token}} {{include file="field_input.tpl" field=$expires}} + {{include file="field_select.tpl" field=$permcat}} +
-
+
{{$desc2}}