<?php

declare(strict_types = 1);

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

/**
 * Wrapper around Symfony's Security service, for static access
 *
 * @package GNUsocial
 * @category Security
 *
 * @author    Hugo Sales <hugo@hsal.es>
 * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Core;

use App\Entity\LocalUser;
use App\Util\Formatting;
use BadMethodCallException;
use Functional as F;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Security as SymfonySecurity;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;

/**
 * Forwards method calls to either Symfony\Component\Security\Core\Security or
 * HtmlSanitizer\SanitizerInterface, calling the first existing method, in that order
 *
 * @codeCoverageIgnore
 *
 * @mixin SymfonySecurity
 *
 * @method static LocalUser getUser()
 */
class Security implements EventSubscriberInterface //implements AuthenticatorInterface
{
    private static ?SymfonySecurity $security;
    public static function setHelper(SymfonySecurity $sec): void
    {
        self::$security = $sec;
    }

    public function loginSucess(LoginSuccessEvent $event): LoginSuccessEvent
    {
        Event::handle('LoginSuccess', [$event]);
        return $event;
    }

    public function loginFailure(LoginFailureEvent $event): LoginFailureEvent
    {
        Event::handle('LoginFailure', [$event]);
        return $event;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            LoginSuccessEvent::class => 'loginSucess',
            LoginFailureEvent::class => 'loginFailure',
        ];
    }

    /**
     * Harden running instance. Called once from `index.php`
     */
    public static function harden(): void
    {
        // Remove sensitive information from the
        [$_ENV, $to_remove] = F\partition(
            $_ENV,
            fn ($_, string $key) => Formatting::startsWith($key, ['HTTP', 'APP', 'CONFIG']) && $key !== 'APP_SECRET',
        );
        F\each($to_remove, fn (mixed $value, string $key) => putenv($key)); // Unset
        // Disable stream wrappers, that could be used in things like
        // `file_get_contents('https://gnu.org')`. This is done
        // because this is a unexpected feature for most developers,
        // and some wrappers can be abused. For instance, `phar://`
        // can be used to essentially override any class when such a
        // file is opened and thus provide code execution to an
        // attacker. Not a complete solution, since `file://`,
        // `php://` and `glob://`, 'compress.zlib' (symfony profiler) get used _somewhere_, so we can't
        // disable them
        F\each(
            ['http', 'https', 'ftp', 'ftps', 'data', 'phar'], // Making this configurable might be a nice feature, but it's tricky because this happens before general initialization
            fn (string $protocol) => stream_wrapper_unregister($protocol),
        );
    }

    public static function __callStatic(string $name, array $args)
    {
        if (method_exists(self::$security, $name)) {
            return self::$security->{$name}(...$args);
        } else {
            throw new BadMethodCallException("Method Security::{$name} doesn't exist");
        }
    }
}
