forked from grumpydevelop/federator
		
	- fixed how To and CC field (recipients) are handled in general - fixed posts in database - improved some error exceptions and prevented early breaks through try-catch blocks - we now support CN-articles on our newcontent endpoint, with create and update calls
		
			
				
	
	
		
			242 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
 | 
						|
 * SPDX-License-Identifier: GPL-3.0-or-later
 | 
						|
 *
 | 
						|
 * @author Yannis Vogel (vogeldevelopment)
 | 
						|
 **/
 | 
						|
 | 
						|
namespace Federator\DIO;
 | 
						|
 | 
						|
/**
 | 
						|
 * IO functions related to fedUsers
 | 
						|
 */
 | 
						|
class FedUser
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * add local user based on given user object received from remote service
 | 
						|
     * @param \mysqli $dbh database handle
 | 
						|
     * @param \Federator\Data\FedUser $user user object to use
 | 
						|
     * @param string $_user user/profile name
 | 
						|
     * @return void
 | 
						|
     */
 | 
						|
    protected static function addLocalUser($dbh, $user, $_user)
 | 
						|
    {
 | 
						|
        // check if it is timed out user
 | 
						|
        $sql = 'select unix_timestamp(`validuntil`) from fedusers where id=?';
 | 
						|
        $stmt = $dbh->prepare($sql);
 | 
						|
        if ($stmt === false) {
 | 
						|
            throw new \Federator\Exceptions\ServerError("FedUser::addLocalUser Failed to prepare statement");
 | 
						|
        }
 | 
						|
        $stmt->bind_param("s", $_user);
 | 
						|
        $validuntil = 0;
 | 
						|
        $ret = $stmt->bind_result($validuntil);
 | 
						|
        $stmt->execute();
 | 
						|
        if ($ret) {
 | 
						|
            $stmt->fetch();
 | 
						|
        }
 | 
						|
        $stmt->close();
 | 
						|
        if ($validuntil == 0) {
 | 
						|
            $sql = 'insert into fedusers (id, url, name, publickey, summary, type, inboxurl, sharedinboxurl,';
 | 
						|
            $sql .= ' followersurl, followingurl, publickeyid, outboxurl, validuntil)';
 | 
						|
            $sql .= ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now() + interval 1 day)';
 | 
						|
            $stmt = $dbh->prepare($sql);
 | 
						|
            if ($stmt === false) {
 | 
						|
                throw new \Federator\Exceptions\ServerError("FedUser::addLocalUser Failed to prepare create statement");
 | 
						|
            }
 | 
						|
            $stmt->bind_param(
 | 
						|
                "ssssssssssss",
 | 
						|
                $_user,
 | 
						|
                $user->actorURL,
 | 
						|
                $user->name,
 | 
						|
                $user->publicKey,
 | 
						|
                $user->summary,
 | 
						|
                $user->type,
 | 
						|
                $user->inboxURL,
 | 
						|
                $user->sharedInboxURL,
 | 
						|
                $user->followersURL,
 | 
						|
                $user->followingURL,
 | 
						|
                $user->publicKeyId,
 | 
						|
                $user->outboxURL
 | 
						|
            );
 | 
						|
        } else {
 | 
						|
            // update to existing user
 | 
						|
            $sql = 'update fedusers set validuntil=now() + interval 1 day, url=?, name=?, publickey=?, summary=?,';
 | 
						|
            $sql .= ' type=?, inboxurl=?, sharedinboxurl=?, followersurl=?, followingurl=?, publickeyid=?, outboxurl=?';
 | 
						|
            $sql .= ' where id=?';
 | 
						|
            $stmt = $dbh->prepare($sql);
 | 
						|
            if ($stmt === false) {
 | 
						|
                throw new \Federator\Exceptions\ServerError("FedUser::extendUser Failed to prepare update statement");
 | 
						|
            }
 | 
						|
            $stmt->bind_param(
 | 
						|
                "ssssssssssss",
 | 
						|
                $user->actorURL,
 | 
						|
                $user->name,
 | 
						|
                $user->publicKey,
 | 
						|
                $user->summary,
 | 
						|
                $user->type,
 | 
						|
                $user->inboxURL,
 | 
						|
                $user->sharedInboxURL,
 | 
						|
                $user->followersURL,
 | 
						|
                $user->followingURL,
 | 
						|
                $user->publicKeyId,
 | 
						|
                $user->outboxURL,
 | 
						|
                $_user
 | 
						|
            );
 | 
						|
        }
 | 
						|
        try {
 | 
						|
            $stmt->execute();
 | 
						|
            $stmt->close();
 | 
						|
            $user->id = $_user;
 | 
						|
        } catch (\mysqli_sql_exception $e) {
 | 
						|
            error_log($sql);
 | 
						|
            error_log(print_r($user, true));
 | 
						|
            error_log($e->getMessage());
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * extend the given user with internal data
 | 
						|
     * @param \mysqli $dbh database  handle
 | 
						|
     * @param \Federator\Data\FedUser $user user to extend
 | 
						|
     * @param string $_user user/profile name
 | 
						|
     */
 | 
						|
    protected static function extendUser(\mysqli $dbh, \Federator\Data\FedUser $user, $_user): void
 | 
						|
    {
 | 
						|
        $sql = 'select id,unix_timestamp(`validuntil`) from fedusers where id=?';
 | 
						|
        $stmt = $dbh->prepare($sql);
 | 
						|
        if ($stmt === false) {
 | 
						|
            throw new \Federator\Exceptions\ServerError("FedUser::extendUser Failed to prepare statement");
 | 
						|
        }
 | 
						|
        $stmt->bind_param("s", $_user);
 | 
						|
        $validuntil = 0;
 | 
						|
        $ret = $stmt->bind_result($user->id, $validuntil);
 | 
						|
        $stmt->execute();
 | 
						|
        if ($ret) {
 | 
						|
            $stmt->fetch();
 | 
						|
        }
 | 
						|
        $stmt->close();
 | 
						|
        // if a new user, create own database entry with additionally needed info
 | 
						|
        if ($user->id === null || $validuntil < time()) {
 | 
						|
            self::addLocalUser($dbh, $user, $_user);
 | 
						|
        }
 | 
						|
 | 
						|
        // no further processing for now
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * get user by name
 | 
						|
     *
 | 
						|
     * @param \mysqli $dbh
 | 
						|
     *          database handle
 | 
						|
     * @param string $_name
 | 
						|
     *          user name
 | 
						|
     * @param \Federator\Cache\Cache|null $cache
 | 
						|
     *          optional caching service
 | 
						|
     * @return \Federator\Data\FedUser
 | 
						|
     */
 | 
						|
    public static function getUserByName($dbh, $_name, $cache)
 | 
						|
    {
 | 
						|
        $user = false;
 | 
						|
 | 
						|
        // ask cache
 | 
						|
        if ($cache !== null) {
 | 
						|
            $user = $cache->getRemoteFedUserByName($_name);
 | 
						|
        }
 | 
						|
        if ($user !== false) {
 | 
						|
            return $user;
 | 
						|
        }
 | 
						|
        // check our db
 | 
						|
        $sql = 'select `id`, `url`, `name`, `publickey`, `summary`, `type`, `inboxurl`, `sharedinboxurl`, `followersurl`,';
 | 
						|
        $sql .= ' `followingurl`, `publickeyid`, `outboxurl`';
 | 
						|
        $sql .= ' from fedusers where `id`=? and `validuntil`>=now()';
 | 
						|
        $stmt = $dbh->prepare($sql);
 | 
						|
        if ($stmt === false) {
 | 
						|
            throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to prepare statement");
 | 
						|
        }
 | 
						|
        $stmt->bind_param("s", $_name);
 | 
						|
        $user = new \Federator\Data\FedUser();
 | 
						|
        $ret = $stmt->bind_result(
 | 
						|
            $user->id,
 | 
						|
            $user->actorURL,
 | 
						|
            $user->name,
 | 
						|
            $user->publicKey,
 | 
						|
            $user->summary,
 | 
						|
            $user->type,
 | 
						|
            $user->inboxURL,
 | 
						|
            $user->sharedInboxURL,
 | 
						|
            $user->followersURL,
 | 
						|
            $user->followingURL,
 | 
						|
            $user->publicKeyId,
 | 
						|
            $user->outboxURL
 | 
						|
        );
 | 
						|
        $stmt->execute();
 | 
						|
        if ($ret) {
 | 
						|
            $stmt->fetch();
 | 
						|
        }
 | 
						|
        $stmt->close();
 | 
						|
 | 
						|
        if ($user->id === null) {
 | 
						|
            // check if its a federated user with username@domain.ending
 | 
						|
            if (preg_match("/^([^@]+)@(.*)$/", $_name, $matches) == 1) {
 | 
						|
                // make webfinger request
 | 
						|
                $remoteURL = 'https://' . $matches[2] . '/.well-known/webfinger?resource=acct:' . urlencode($_name);
 | 
						|
                $headers = ['Accept: application/activity+json'];
 | 
						|
                [$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
 | 
						|
                if ($info['http_code'] != 200) {
 | 
						|
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to fetch webfinger for " . $_name);
 | 
						|
                }
 | 
						|
                $r = json_decode($response, true);
 | 
						|
                if ($r === false || $r === null || !is_array($r)) {
 | 
						|
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to decode webfinger for " . $_name);
 | 
						|
                }
 | 
						|
                // get the webwinger user url and fetch the user
 | 
						|
                if (isset($r['links'])) {
 | 
						|
                    foreach ($r['links'] as $link) {
 | 
						|
                        if (isset($link['rel']) && $link['rel'] === 'self') {
 | 
						|
                            $remoteURL = $link['href'];
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (!isset($remoteURL)) {
 | 
						|
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to find self link in webfinger for " . $_name);
 | 
						|
                }
 | 
						|
                // fetch the user
 | 
						|
                $headers = ['Accept: application/activity+json'];
 | 
						|
                [$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
 | 
						|
                if ($info['http_code'] != 200) {
 | 
						|
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to fetch user from remoteUrl for " . $_name);
 | 
						|
                }
 | 
						|
                $r = json_decode($response, true);
 | 
						|
                if ($r === false || $r === null || !is_array($r)) {
 | 
						|
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to decode user for " . $_name);
 | 
						|
                }
 | 
						|
                $r['publicKeyId'] = $r['publicKey']['id'];
 | 
						|
                $r['publicKey'] = $r['publicKey']['publicKeyPem'];
 | 
						|
                if (isset($r['endpoints'])) {
 | 
						|
                    if (isset($r['endpoints']['sharedInbox'])) {
 | 
						|
                        $r['sharedInbox'] = $r['endpoints']['sharedInbox'];
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                $r['actorURL'] = $remoteURL;
 | 
						|
                $data = json_encode($r);
 | 
						|
                if ($data === false) {
 | 
						|
                    throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to encode userdata " . $_name);
 | 
						|
                }
 | 
						|
                $user = \Federator\Data\FedUser::createFromJson($data);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if ($cache !== null && $user !== false) {
 | 
						|
            if ($user->id !== null && $user->actorURL !== null) {
 | 
						|
                self::addLocalUser($dbh, $user, $_name);
 | 
						|
            }
 | 
						|
            $cache->saveRemoteFedUserByName($_name, $user);
 | 
						|
        }
 | 
						|
        if ($user === false) {
 | 
						|
            throw new \Federator\Exceptions\ServerError("FedUser::getUserByName User not found");
 | 
						|
        }
 | 
						|
        return $user;
 | 
						|
    }
 | 
						|
}
 |