__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
<?php

/**
 * @package     Joomla.Plugin
 * @subpackage  System.Webauthn
 *
 * @copyright   (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Plugin\System\Webauthn;

use Joomla\CMS\Date\Date;
use Joomla\CMS\Encrypt\Aes;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\User\UserFactoryInterface;
use Joomla\Database\DatabaseAwareInterface;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\DatabaseInterface;
use Joomla\Plugin\System\Webauthn\Extension\Webauthn;
use Joomla\Registry\Registry;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Handles the storage of WebAuthn credentials in the database
 *
 * @since   4.0.0
 */
final class CredentialRepository implements PublicKeyCredentialSourceRepository, DatabaseAwareInterface
{
    use DatabaseAwareTrait;

    /**
     * Public constructor.
     *
     * @param   DatabaseInterface|null  $db  The database driver object to use for persistence.
     *
     * @since   4.2.0
     */
    public function __construct(DatabaseInterface $db = null)
    {
        $this->setDatabase($db);
    }

    /**
     * Returns a PublicKeyCredentialSource object given the public key credential ID
     *
     * @param   string  $publicKeyCredentialId  The identified of the public key credential we're searching for
     *
     * @return  PublicKeyCredentialSource|null
     *
     * @since   4.0.0
     */
    public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
    {
        /** @var DatabaseInterface $db */
        $db           = $this->getDatabase();
        $credentialId = base64_encode($publicKeyCredentialId);
        $query        = $db->getQuery(true)
            ->select($db->quoteName('credential'))
            ->from($db->quoteName('#__webauthn_credentials'))
            ->where($db->quoteName('id') . ' = :credentialId')
            ->bind(':credentialId', $credentialId);

        $encrypted = $db->setQuery($query)->loadResult();

        if (empty($encrypted)) {
            return null;
        }

        $json = $this->decryptCredential($encrypted);

        try {
            return PublicKeyCredentialSource::createFromArray(json_decode($json, true));
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * Returns all PublicKeyCredentialSource objects given a user entity. We only use the `id` property of the user
     * entity, cast to integer, as the Joomla user ID by which records are keyed in the database table.
     *
     * @param   PublicKeyCredentialUserEntity  $publicKeyCredentialUserEntity  Public key credential user entity record
     *
     * @return  PublicKeyCredentialSource[]
     *
     * @since  4.0.0
     */
    public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
    {
        /** @var DatabaseInterface $db */
        $db         = $this->getDatabase();
        $userHandle = $publicKeyCredentialUserEntity->getId();
        $query      = $db->getQuery(true)
            ->select('*')
            ->from($db->quoteName('#__webauthn_credentials'))
            ->where($db->quoteName('user_id') . ' = :user_id')
            ->bind(':user_id', $userHandle);

        try {
            $records = $db->setQuery($query)->loadAssocList();
        } catch (\Exception $e) {
            return [];
        }

        /**
         * Converts invalid credential records to PublicKeyCredentialSource objects, or null if they
         * are invalid.
         *
         * This closure is defined as a variable to prevent PHP-CS from getting a stoke trying to
         * figure out the correct indentation :)
         *
         * @param   array  $record  The record to convert
         *
         * @return  PublicKeyCredentialSource|null
         */
        $recordsMapperClosure = function ($record) {
            try {
                $json = $this->decryptCredential($record['credential']);
                $data = json_decode($json, true);
            } catch (\JsonException $e) {
                return null;
            }

            if (empty($data)) {
                return null;
            }

            try {
                return PublicKeyCredentialSource::createFromArray($data);
            } catch (\InvalidArgumentException $e) {
                return null;
            }
        };

        $records = array_map($recordsMapperClosure, $records);

        /**
         * Filters the list of records to only keep valid entries.
         *
         * Only array members that are PublicKeyCredentialSource objects survive the filter.
         *
         * This closure is defined as a variable to prevent PHP-CS from getting a stoke trying to
         * figure out the correct indentation :)
         *
         * @param  PublicKeyCredentialSource|mixed  $record  The record to filter
         *
         * @return boolean
         */
        $filterClosure = function ($record) {
            return !\is_null($record) && \is_object($record) && ($record instanceof PublicKeyCredentialSource);
        };

        return array_filter($records, $filterClosure);
    }

    /**
     * Add or update an attested credential for a given user.
     *
     * @param   PublicKeyCredentialSource  $publicKeyCredentialSource  The public key credential
     *                                                                 source to store
     *
     * @return  void
     *
     * @throws \Exception
     * @since   4.0.0
     */
    public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
    {
        // Default values for saving a new credential source
        /** @var Webauthn $plugin */
        $plugin              = Factory::getApplication()->bootPlugin('webauthn', 'system');
        $knownAuthenticators = $plugin->getAuthenticationHelper()->getKnownAuthenticators();
        $aaguid              = (string) ($publicKeyCredentialSource->getAaguid() ?? '');
        $defaultName         = ($knownAuthenticators[$aaguid] ?? $knownAuthenticators[''])->description;
        $credentialId        = base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId());
        $user                = Factory::getApplication()->getIdentity();
        $o                   = (object) [
            'id'      => $credentialId,
            'user_id' => $this->getHandleFromUserId($user->id),
            'label'   => Text::sprintf(
                'PLG_SYSTEM_WEBAUTHN_LBL_DEFAULT_AUTHENTICATOR_LABEL',
                $defaultName,
                $this->formatDate('now')
            ),
            'credential' => json_encode($publicKeyCredentialSource),
        ];
        $update              = false;

        /** @var DatabaseInterface $db */
        $db = $this->getDatabase();

        // Try to find an existing record
        try {
            $query     = $db->getQuery(true)
                ->select('*')
                ->from($db->quoteName('#__webauthn_credentials'))
                ->where($db->quoteName('id') . ' = :credentialId')
                ->bind(':credentialId', $credentialId);
            $oldRecord = $db->setQuery($query)->loadObject();

            if (\is_null($oldRecord)) {
                throw new \Exception('This is a new record');
            }

            /**
             * Sanity check. The existing credential source must have the same user handle as the one I am trying to
             * save. Otherwise something fishy is going on.
             */
            if ($oldRecord->user_id != $publicKeyCredentialSource->getUserHandle()) {
                throw new \RuntimeException(Text::_('PLG_SYSTEM_WEBAUTHN_ERR_CREDENTIAL_ID_ALREADY_IN_USE'));
            }

            $o->user_id = $oldRecord->user_id;
            $o->label   = $oldRecord->label;
            $update     = true;
        } catch (\Exception $e) {
        }

        $o->credential = $this->encryptCredential($o->credential);

        if ($update) {
            $db->updateObject('#__webauthn_credentials', $o, ['id']);

            return;
        }

        /**
         * This check is deliberately skipped for updates. When logging in the underlying library will try to save the
         * credential source. This is necessary to update the last known authenticator signature counter which prevents
         * replay attacks. When we are saving a new record, though, we have to make sure we are not a guest user. Hence
         * the check below.
         */
        if ((\is_null($user) || $user->guest)) {
            throw new \RuntimeException(Text::_('PLG_SYSTEM_WEBAUTHN_ERR_CANT_STORE_FOR_GUEST'));
        }

        $db->insertObject('#__webauthn_credentials', $o);
    }

    /**
     * Get all credential information for a given user ID. This is meant to only be used for displaying records.
     *
     * @param   int  $userId  The user ID
     *
     * @return  array
     *
     * @since   4.0.0
     */
    public function getAll(int $userId): array
    {
        /** @var DatabaseInterface $db */
        $db         = $this->getDatabase();
        $userHandle = $this->getHandleFromUserId($userId);
        $query      = $db->getQuery(true)
            ->select('*')
            ->from($db->quoteName('#__webauthn_credentials'))
            ->where($db->quoteName('user_id') . ' = :user_id')
            ->bind(':user_id', $userHandle);

        try {
            $results = $db->setQuery($query)->loadAssocList();
        } catch (\Exception $e) {
            return [];
        }

        if (empty($results)) {
            return [];
        }

        /**
         * Decodes the credentials on each record.
         *
         * @param   array  $record  The record to convert
         *
         * @return  array
         * @since   4.2.0
         */
        $recordsMapperClosure = function ($record) {
            try {
                $json = $this->decryptCredential($record['credential']);
                $data = json_decode($json, true);
            } catch (\JsonException $e) {
                $record['credential'] = null;

                return $record;
            }

            if (empty($data)) {
                $record['credential'] = null;

                return $record;
            }

            try {
                $record['credential'] = PublicKeyCredentialSource::createFromArray($data);

                return $record;
            } catch (\InvalidArgumentException $e) {
                $record['credential'] = null;

                return $record;
            }
        };

        return array_map($recordsMapperClosure, $results);
    }

    /**
     * Do we have stored credentials under the specified Credential ID?
     *
     * @param   string  $credentialId  The ID of the credential to check for existence
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    public function has(string $credentialId): bool
    {
        /** @var DatabaseInterface $db */
        $db           = $this->getDatabase();
        $credentialId = base64_encode($credentialId);
        $query        = $db->getQuery(true)
            ->select('COUNT(*)')
            ->from($db->quoteName('#__webauthn_credentials'))
            ->where($db->quoteName('id') . ' = :credentialId')
            ->bind(':credentialId', $credentialId);

        try {
            $count = $db->setQuery($query)->loadResult();

            return $count > 0;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Update the human readable label of a credential
     *
     * @param   string  $credentialId  The credential ID
     * @param   string  $label         The human readable label to set
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function setLabel(string $credentialId, string $label): void
    {
        /** @var DatabaseInterface $db */
        $db           = $this->getDatabase();
        $credentialId = base64_encode($credentialId);
        $o            = (object) [
            'id'    => $credentialId,
            'label' => $label,
        ];

        $db->updateObject('#__webauthn_credentials', $o, ['id'], false);
    }

    /**
     * Remove stored credentials
     *
     * @param   string  $credentialId  The credentials ID to remove
     *
     * @return  void
     *
     * @since   4.0.0
     */
    public function remove(string $credentialId): void
    {
        if (!$this->has($credentialId)) {
            return;
        }

        /** @var DatabaseInterface $db */
        $db           = $this->getDatabase();
        $credentialId = base64_encode($credentialId);
        $query        = $db->getQuery(true)
            ->delete($db->quoteName('#__webauthn_credentials'))
            ->where($db->quoteName('id') . ' = :credentialId')
            ->bind(':credentialId', $credentialId);

        $db->setQuery($query)->execute();
    }

    /**
     * Return the user handle for the stored credential given its ID.
     *
     * The user handle must not be personally identifiable. Per https://w3c.github.io/webauthn/#user-handle it is
     * acceptable to have a salted hash with a salt private to our server, e.g. Joomla's secret. The only immutable
     * information in Joomla is the user ID so that's what we will be using.
     *
     * @param   string  $credentialId  The credential ID to get the user handle for
     *
     * @return  string
     *
     * @since   4.0.0
     */
    public function getUserHandleFor(string $credentialId): string
    {
        $publicKeyCredentialSource = $this->findOneByCredentialId($credentialId);

        if (empty($publicKeyCredentialSource)) {
            return '';
        }

        return $publicKeyCredentialSource->getUserHandle();
    }

    /**
     * Return a user handle given an integer Joomla user ID. We use the HMAC-SHA-256 of the user ID with the site's
     * secret as the key. Using it instead of SHA-512 is on purpose! WebAuthn only allows user handles up to 64 bytes
     * long.
     *
     * @param   int  $id  The user ID to convert
     *
     * @return  string  The user handle (HMAC-SHA-256 of the user ID)
     *
     * @since   4.0.0
     */
    public function getHandleFromUserId(int $id): string
    {
        $key  = $this->getEncryptionKey();
        $data = sprintf('%010u', $id);

        return hash_hmac('sha256', $data, $key, false);
    }

    /**
     * Get the user ID from the user handle
     *
     * This is a VERY inefficient method. Since the user handle is an HMAC-SHA-256 of the user ID we can't just go
     * directly from a handle back to an ID. We have to iterate all user IDs, calculate their handles and compare them
     * to the given handle.
     *
     * To prevent a lengthy infinite loop in case of an invalid user handle we don't iterate the entire 2+ billion valid
     * 32-bit integer range. We load the user IDs of active users (not blocked, not pending activation) and iterate
     * through them.
     *
     * To avoid memory outage on large sites with thousands of active user records we load up to 10000 users at a time.
     * Each block of 10,000 user IDs takes about 60-80 msec to iterate. On a site with 200,000 active users this method
     * will take less than 1.5 seconds. This is slow but not impractical, even on crowded shared hosts with a quarter of
     * the performance of my test subject (a mid-range, shared hosting server).
     *
     * @param   string|null  $userHandle  The user handle which will be converted to a user ID.
     *
     * @return  integer|null
     * @since   4.2.0
     */
    public function getUserIdFromHandle(?string $userHandle): ?int
    {
        if (empty($userHandle)) {
            return null;
        }

        /** @var DatabaseInterface $db */
        $db = $this->getDatabase();

        // Check that the userHandle does exist in the database
        $query = $db->getQuery(true)
            ->select('COUNT(*)')
            ->from($db->quoteName('#__webauthn_credentials'))
            ->where($db->quoteName('user_id') . ' = ' . $db->q($userHandle));

        try {
            $numRecords = $db->setQuery($query)->loadResult();
        } catch (\Exception $e) {
            return null;
        }

        if (is_null($numRecords) || ($numRecords < 1)) {
            return null;
        }

        // Prepare the query
        $query = $db->getQuery(true)
            ->select([$db->quoteName('id')])
            ->from($db->quoteName('#__users'))
            ->where($db->quoteName('block') . ' = 0')
            ->where(
                '(' .
                $db->quoteName('activation') . ' IS NULL OR ' .
                $db->quoteName('activation') . ' = 0 OR ' .
                $db->quoteName('activation') . ' = ' . $db->q('') .
                ')'
            );

        $key   = $this->getEncryptionKey();
        $start = 0;
        $limit = 10000;

        while (true) {
            try {
                $ids = $db->setQuery($query, $start, $limit)->loadColumn();
            } catch (\Exception $e) {
                return null;
            }

            if (empty($ids)) {
                return null;
            }

            foreach ($ids as $userId) {
                $data       = sprintf('%010u', $userId);
                $thisHandle = hash_hmac('sha256', $data, $key, false);

                if ($thisHandle == $userHandle) {
                    return $userId;
                }
            }

            $start += $limit;
        }
    }

    /**
     * Encrypt the credential source before saving it to the database
     *
     * @param   string   $credential  The unencrypted, JSON-encoded credential source
     *
     * @return  string  The encrypted credential source, base64 encoded
     *
     * @since   4.0.0
     */
    private function encryptCredential(string $credential): string
    {
        $key = $this->getEncryptionKey();

        if (empty($key)) {
            return $credential;
        }

        $aes = new Aes($key, 256);

        return $aes->encryptString($credential);
    }

    /**
     * Decrypt the credential source if it was already encrypted in the database
     *
     * @param   string  $credential  The encrypted credential source, base64 encoded
     *
     * @return  string  The decrypted, JSON-encoded credential source
     *
     * @since   4.0.0
     */
    private function decryptCredential(string $credential): string
    {
        $key = $this->getEncryptionKey();

        if (empty($key)) {
            return $credential;
        }

        // Was the credential stored unencrypted (e.g. the site's secret was empty)?
        if ((strpos($credential, '{') !== false) && (strpos($credential, '"publicKeyCredentialId"') !== false)) {
            return $credential;
        }

        $aes = new Aes($key, 256);

        return $aes->decryptString($credential);
    }

    /**
     * Get the site's secret, used as an encryption key
     *
     * @return  string
     *
     * @since   4.0.0
     */
    private function getEncryptionKey(): string
    {
        try {
            $app = Factory::getApplication();
            /** @var Registry $config */
            $config = $app->getConfig();
            $secret = $config->get('secret', '');
        } catch (\Exception $e) {
            $secret = '';
        }

        return $secret;
    }

    /**
     * Format a date for display.
     *
     * The $tzAware parameter defines whether the formatted date will be timezone-aware. If set to false the formatted
     * date will be rendered in the UTC timezone. If set to true the code will automatically try to use the logged in
     * user's timezone or, if none is set, the site's default timezone (Server Timezone). If set to a positive integer
     * the same thing will happen but for the specified user ID instead of the currently logged in user.
     *
     * @param   string|\DateTime  $date     The date to format
     * @param   string|null       $format   The format string, default is Joomla's DATE_FORMAT_LC6 (usually "Y-m-d
     *                                      H:i:s")
     * @param   bool              $tzAware  Should the format be timezone aware? See notes above.
     *
     * @return  string
     * @since   4.2.0
     */
    private function formatDate($date, ?string $format = null, bool $tzAware = true): string
    {
        $utcTimeZone = new \DateTimeZone('UTC');
        $jDate       = new Date($date, $utcTimeZone);

        // Which timezone should I use?
        $tz = null;

        if ($tzAware !== false) {
            $userId = is_bool($tzAware) ? null : (int) $tzAware;

            try {
                $tzDefault = Factory::getApplication()->get('offset');
            } catch (\Exception $e) {
                $tzDefault = 'GMT';
            }

            $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId ?? 0);
            $tz   = $user->getParam('timezone', $tzDefault);
        }

        if (!empty($tz)) {
            try {
                $userTimeZone = new \DateTimeZone($tz);

                $jDate->setTimezone($userTimeZone);
            } catch (\Exception $e) {
                // Nothing. Fall back to UTC.
            }
        }

        if (empty($format)) {
            $format = Text::_('DATE_FORMAT_LC6');
        }

        return $jDate->format($format, true);
    }
}

Filemanager

Name Type Size Permission Actions
Extension Folder 0775
Field Folder 0775
Hotfix Folder 0775
PluginTraits Folder 0775
Authentication.php File 19.15 KB 0664
CredentialRepository.php File 21.21 KB 0664
MetadataRepository.php File 4.68 KB 0664
Filemanager