diff --git a/Dockerfile b/Dockerfile index 0e031b7..839de9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -146,6 +146,10 @@ RUN apk add openssh \ # Add themes ADD plugins/themes /var/www/html/plugins/themes +# Add SSL cert option for DB Connection (replace files in pkp lib) +COPY ssl/PKPContainer.php /var/www/html/lib/pkp/classes/core +COPY ssl/PKPInstall.php /var/www/html/lib/pkp/classes/install + ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so COPY root/ / diff --git a/ssl/DigiCertGlobalRootCA.crt.pem b/ssl/DigiCertGlobalRootCA.crt.pem new file mode 100644 index 0000000..fd4341d --- /dev/null +++ b/ssl/DigiCertGlobalRootCA.crt.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/ssl/PKPContainer.php b/ssl/PKPContainer.php new file mode 100644 index 0000000..3f4e91c --- /dev/null +++ b/ssl/PKPContainer.php @@ -0,0 +1,477 @@ +basePath = BASE_SYS_DIR; + $this->settingProxyForStreamContext(); + $this->registerBaseBindings(); + $this->registerCoreContainerAliases(); + } + + /** + * @brief Bind the current container and set it globally + * let helpers, facades and services know to which container refer to + */ + protected function registerBaseBindings() + { + static::setInstance($this); + $this->instance('app', $this); + $this->instance(Container::class, $this); + $this->instance('path', $this->basePath); + $this->singleton(ExceptionHandler::class, function () { + return new class () implements ExceptionHandler { + public function shouldReport(Throwable $exception) + { + return true; + } + + public function report(Throwable $exception) + { + error_log($exception->__toString()); + } + + public function render($request, Throwable $exception) + { + $pkpRouter = Application::get()->getRequest()->getRouter(); + + if($pkpRouter instanceof APIRouter && app('router')->getRoutes()->count()) { + return response()->json( + [ + 'error' => $exception->getMessage() + ], + in_array($exception->getCode(), array_keys(Response::$statusTexts)) + ? $exception->getCode() + : Response::HTTP_INTERNAL_SERVER_ERROR + )->send(); + } + + return null; + } + + public function renderForConsole($output, Throwable $exception) + { + echo (string) $exception; + } + }; + }); + $this->singleton( + KernelContract::class, + Kernel::class + ); + + $this->singleton('pkpJobQueue', function ($app) { + return new PKPQueueProvider($app); + }); + + $this->singleton( + 'queue.failer', + function ($app) { + return new DatabaseFailedJobProvider( + $app['db'], + config('queue.failed.database'), + config('queue.failed.table') + ); + } + ); + + Facade::setFacadeApplication($this); + } + + /** + * @brief Register used service providers within the container + */ + public function registerConfiguredProviders() + { + // Load main settings, this should be done before registering services, e.g., it's used by Database Service + $this->loadConfiguration(); + $this->register(new \Illuminate\Cache\CacheServiceProvider($this)); + $this->register(new \Illuminate\Filesystem\FilesystemServiceProvider($this)); + $this->register(new \ElcoBvg\Opcache\ServiceProvider($this)); + $this->register(new LaravelEventServiceProvider($this)); + $this->register(new EventServiceProvider($this)); + $this->register(new LogServiceProvider($this)); + $this->register(new \Illuminate\Database\DatabaseServiceProvider($this)); + $this->register(new \Illuminate\Bus\BusServiceProvider($this)); + $this->register(new PKPQueueProvider($this)); + $this->register(new MailServiceProvider($this)); + $this->register(new AppServiceProvider($this)); + $this->register(new LocaleServiceProvider($this)); + $this->register(new PKPRoutingProvider($this)); + } + + /** + * @param \Illuminate\Support\ServiceProvider $provider + * + * @brief Simplified service registration + */ + public function register($provider) + { + $provider->register(); + $provider->callBootingCallbacks(); + if (method_exists($provider, 'boot')) { + $this->call([$provider, 'boot']); + } + + // If there are bindings / singletons set as properties on the provider we + // will spin through them and register them with the application, which + // serves as a convenience layer while registering a lot of bindings. + if (property_exists($provider, 'bindings')) { + foreach ($provider->bindings as $key => $value) { + $this->bind($key, $value); + } + } + + if (property_exists($provider, 'singletons')) { + foreach ($provider->singletons as $key => $value) { + $key = is_int($key) ? $value : $key; + $this->singleton($key, $value); + } + } + + $provider->callBootedCallbacks(); + + $this->app->bind('request', fn () => \Illuminate\Http\Request::capture()); + } + + /** + * @brief Bind aliases with contracts + */ + public function registerCoreContainerAliases() + { + foreach ([ + 'app' => [ + self::class, + \Illuminate\Contracts\Container\Container::class, + \Psr\Container\ContainerInterface::class + ], + 'config' => [ + \Illuminate\Config\Repository::class, + \Illuminate\Contracts\Config\Repository::class + ], + 'cache' => [ + \Illuminate\Cache\CacheManager::class, + \Illuminate\Contracts\Cache\Factory::class + ], + 'cache.store' => [ + \Illuminate\Cache\Repository::class, + \Illuminate\Contracts\Cache\Repository::class, + \Psr\SimpleCache\CacheInterface::class + ], + 'cache.psr6' => [ + \Psr\Cache\CacheItemPoolInterface::class + ], + 'db' => [ + \Illuminate\Database\DatabaseManager::class, + \Illuminate\Database\ConnectionResolverInterface::class + ], + 'db.connection' => [ + \Illuminate\Database\Connection::class, + \Illuminate\Database\ConnectionInterface::class + ], + 'files' => [ + \Illuminate\Filesystem\Filesystem::class + ], + 'filesystem' => [ + \Illuminate\Filesystem\FilesystemManager::class, + \Illuminate\Contracts\Filesystem\Factory::class + ], + 'filesystem.disk' => [ + \Illuminate\Contracts\Filesystem\Filesystem::class + ], + 'filesystem.cloud' => [ + \Illuminate\Contracts\Filesystem\Cloud::class + ], + 'maps' => [ + MapContainer::class, + MapContainer::class + ], + 'events' => [ + \Illuminate\Events\Dispatcher::class, + \Illuminate\Contracts\Events\Dispatcher::class + ], + 'queue' => [ + \Illuminate\Queue\QueueManager::class, + \Illuminate\Contracts\Queue\Factory::class, + \Illuminate\Contracts\Queue\Monitor::class + ], + 'queue.connection' => [ + \Illuminate\Contracts\Queue\Queue::class + ], + 'queue.failer' => [ + \Illuminate\Queue\Failed\FailedJobProviderInterface::class + ], + 'log' => [ + \Illuminate\Log\LogManager::class, + \Psr\Log\LoggerInterface::class + ], + 'router' => [ + \Illuminate\Routing\Router::class, + \Illuminate\Contracts\Routing\Registrar::class, + \Illuminate\Contracts\Routing\BindingRegistrar::class + ], + 'url' => [ + \Illuminate\Routing\UrlGenerator::class, + \Illuminate\Contracts\Routing\UrlGenerator::class + ], + 'validator' => [ + \Illuminate\Validation\Factory::class, + \Illuminate\Contracts\Validation\Factory::class + ], + 'Request' => [ + \Illuminate\Support\Facades\Request::class + ], + 'Response' => [ + \Illuminate\Support\Facades\Response::class + ], + 'Route' => [ + \Illuminate\Support\Facades\Route::class + ], + ] as $key => $aliases) { + foreach ($aliases as $alias) { + $this->alias($key, $alias); + } + } + } + + /** + * @brief Bind and load container configurations + * usage from Facade, see Illuminate\Support\Facades\Config + */ + protected function loadConfiguration() + { + $items = []; + + // Database connection + $driver = 'mysql'; + + if (substr(strtolower(Config::getVar('database', 'driver')), 0, 8) === 'postgres') { + $driver = 'pgsql'; + } + + $items['database']['default'] = $driver; + $items['database']['connections'][$driver] = [ + 'driver' => $driver, + 'host' => Config::getVar('database', 'host'), + 'database' => Config::getVar('database', 'name'), + 'username' => Config::getVar('database', 'username'), + 'port' => Config::getVar('database', 'port'), + 'unix_socket' => Config::getVar('database', 'unix_socket'), + 'password' => Config::getVar('database', 'password'), + 'charset' => Config::getVar('i18n', 'connection_charset', 'utf8'), + 'collation' => Config::getVar('database', 'collation', 'utf8_general_ci'), + 'options' => include("ssl/options.php"), + ]; + + // Queue connection + $items['queue']['default'] = 'database'; + $items['queue']['connections']['sync']['driver'] = 'sync'; + $items['queue']['connections']['database'] = [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + 'retry_after' => 240, + 'after_commit' => true, + ]; + $items['queue']['failed'] = [ + 'driver' => 'database', + 'database' => $driver, + 'table' => 'failed_jobs', + ]; + + // Logging + $items['logging']['channels']['errorlog'] = [ + 'driver' => 'errorlog', + 'level' => 'debug', + ]; + + // Mail Service + $items['mail']['mailers']['sendmail'] = [ + 'transport' => 'sendmail', + 'path' => Config::getVar('email', 'sendmail_path'), + ]; + $items['mail']['mailers']['smtp'] = [ + 'transport' => 'smtp', + 'host' => Config::getVar('email', 'smtp_server'), + 'port' => Config::getVar('email', 'smtp_port'), + 'encryption' => Config::getVar('email', 'smtp_auth'), + 'username' => Config::getVar('email', 'smtp_username'), + 'password' => Config::getVar('email', 'smtp_password'), + 'verify_peer' => !Config::getVar('email', 'smtp_suppress_cert_check'), + ]; + $items['mail']['mailers']['log'] = [ + 'transport' => 'log', + 'channel' => 'errorlog', + ]; + $items['mail']['mailers']['phpmailer'] = [ + 'transport' => 'phpmailer', + ]; + + $items['mail']['default'] = static::getDefaultMailer(); + + // Cache configuration + $items['cache'] = [ + 'default' => 'opcache', + 'stores' => [ + 'opcache' => [ + 'driver' => 'opcache', + 'path' => Core::getBaseDir() . '/cache/opcache' + ] + ] + ]; + + // Create instance and bind to use globally + $this->instance('config', new Repository($items)); + } + + /** + * @param string $path appended to the base path + * + * @brief see Illuminate\Foundation\Application::basePath + */ + public function basePath($path = '') + { + return $this->basePath . ($path ? "/{$path}" : $path); + } + + /** + * @param string $path appended to the path + * + * @brief alias of basePath(), Laravel app path differs from installation path + */ + public function path($path = '') + { + return $this->basePath($path); + } + + /** + * Retrieves default mailer driver depending on the configuration + * + * @throws Exception + */ + protected static function getDefaultMailer(): string + { + $default = Config::getVar('general', 'sandbox', false) + ? 'log' + : Config::getVar('email', 'default'); + + if (!$default) { + throw new Exception('Mailer driver isn\'t specified in the application\'s config'); + } + + return $default; + } + + /** + * Setting a proxy on the stream_context_set_default when configuration [proxy] is filled + */ + protected function settingProxyForStreamContext(): void + { + $proxy = new ProxyParser(); + + if ($httpProxy = Config::getVar('proxy', 'http_proxy')) { + $proxy->parseFQDN($httpProxy); + } + + if ($httpsProxy = Config::getVar('proxy', 'https_proxy')) { + $proxy->parseFQDN($httpsProxy); + } + + if ($proxy->isEmpty()) { + return; + } + + /** + * `Connection close` here its to avoid slowness. More info at https://www.php.net/manual/en/context.http.php#114867 + * `request_fulluri` its related to avoid proxy errors. More info at https://www.php.net/manual/en/context.http.php#110449 + */ + $opts = [ + 'http' => [ + 'protocol_version' => 1.1, + 'header' => [ + 'Connection: close', + ], + 'proxy' => $proxy->getProxy(), + 'request_fulluri' => true, + ], + ]; + + if ($proxy->getAuth()) { + $opts['http']['header'][] = 'Proxy-Authorization: Basic ' . $proxy->getAuth(); + } + + $context = stream_context_create($opts); + stream_context_set_default($opts); + libxml_set_streams_context($context); + } + + /** + * Override Laravel method; always false. + * Prevents the undefined method error when the Log Manager tries to determine the driver + * + * @return bool + */ + public function runningUnitTests() + { + return false; + } + + /** + * Determine if the application is currently down for maintenance. + * + * @return bool + */ + public function isDownForMaintenance() + { + return PKPApplication::isUnderMaintenance(); + } +} + +if (!PKP_STRICT_MODE) { + class_alias('\PKP\core\PKPContainer', '\PKPContainer'); +} diff --git a/ssl/PKPInstall.php b/ssl/PKPInstall.php new file mode 100644 index 0000000..38b8e79 --- /dev/null +++ b/ssl/PKPInstall.php @@ -0,0 +1,275 @@ +currentVersion)) { + $this->currentVersion = Version::fromString(''); + } + + $this->locale = $this->getParam('locale'); + $this->installedLocales = $this->getParam('additionalLocales'); + if (!isset($this->installedLocales) || !is_array($this->installedLocales)) { + $this->installedLocales = []; + } + if (!in_array($this->locale, $this->installedLocales) && Locale::isLocaleValid($this->locale)) { + array_push($this->installedLocales, $this->locale); + } + + // Map valid config options to Illuminate database drivers + $driver = strtolower($this->getParam('databaseDriver')); + if (substr($driver, 0, 8) === 'postgres') { + $driver = 'pgsql'; + } else { + $driver = 'mysql'; + } + + $config = FacadesConfig::get('database'); + $config['default'] = $driver; + $config['connections'][$driver] = [ + 'driver' => $driver, + 'host' => $this->getParam('databaseHost'), + 'port' => $this->getParam('databasePort'), + 'unix_socket' => $this->getParam('unixSocket'), + 'database' => $this->getParam('databaseName'), + 'username' => $this->getParam('databaseUsername'), + 'password' => $this->getParam('databasePassword'), + 'charset' => 'utf8', + 'collation' => 'utf8_general_ci', + 'options' => include("ssl/options.php"), + ]; + FacadesConfig::set('database', $config); + + return parent::preInstall(); + } + + + // + // Installer actions + // + + /** + * Get the names of the directories to create. + * + * @return array + */ + public function getCreateDirectories() + { + return ['site']; + } + + /** + * Create required files directories + * FIXME No longer needed since FileManager will auto-create? + * + * @return bool + */ + public function createDirectories() + { + // Check if files directory exists and is writeable + if (!(file_exists($this->getParam('filesDir')) && is_writeable($this->getParam('filesDir')))) { + // Files upload directory unusable + $this->setError(Installer::INSTALLER_ERROR_GENERAL, 'installer.installFilesDirError'); + return false; + } else { + // Create required subdirectories + $dirsToCreate = $this->getCreateDirectories(); + $dirsToCreate[] = 'usageStats'; + $fileManager = new FileManager(); + foreach ($dirsToCreate as $dirName) { + $dirToCreate = $this->getParam('filesDir') . '/' . $dirName; + if (!file_exists($dirToCreate)) { + if (!$fileManager->mkdir($dirToCreate)) { + $this->setError(Installer::INSTALLER_ERROR_GENERAL, 'installer.installFilesDirError'); + return false; + } + } + } + } + + // Check if public files directory exists and is writeable + $publicFilesDir = Config::getVar('files', 'public_files_dir'); + if (!(file_exists($publicFilesDir) && is_writeable($publicFilesDir))) { + // Public files upload directory unusable + $this->setError(Installer::INSTALLER_ERROR_GENERAL, 'installer.publicFilesDirError'); + return false; + } else { + // Create required subdirectories + $dirsToCreate = $this->getCreateDirectories(); + $fileManager = new FileManager(); + foreach ($dirsToCreate as $dirName) { + $dirToCreate = $publicFilesDir . '/' . $dirName; + if (!file_exists($dirToCreate)) { + if (!$fileManager->mkdir($dirToCreate)) { + $this->setError(Installer::INSTALLER_ERROR_GENERAL, 'installer.publicFilesDirError'); + return false; + } + } + } + } + + return true; + } + + /** + * Write the configuration file. + * + * @return bool + */ + public function createConfig() + { + $request = Application::get()->getRequest(); + return $this->updateConfig( + [ + 'general' => [ + 'installed' => 'On', + 'base_url' => $request->getBaseUrl(), + 'enable_beacon' => $this->getParam('enableBeacon') ? 'On' : 'Off', + 'allowed_hosts' => json_encode([$request->getServerHost(null, false)]), + 'time_zone' => $this->getParam('timeZone') + ], + 'database' => [ + 'driver' => $this->getParam('databaseDriver'), + 'host' => $this->getParam('databaseHost'), + 'username' => $this->getParam('databaseUsername'), + 'password' => $this->getParam('databasePassword'), + 'name' => $this->getParam('databaseName') + ], + 'i18n' => [ + 'locale' => $this->getParam('locale'), + 'connection_charset' => 'utf8', + ], + 'files' => [ + 'files_dir' => $this->getParam('filesDir') + ], + 'oai' => [ + 'repository_id' => $this->getParam('oaiRepositoryId') + ] + ] + ); + } + + /** + * Create initial required data. + * + * @return bool + */ + public function createData() + { + $siteLocale = $this->getParam('locale'); + + // Add initial site administrator user + $user = Repo::user()->newDataObject(); + $user->setUsername($this->getParam('adminUsername')); + $user->setPassword(Validation::encryptCredentials($this->getParam('adminUsername'), $this->getParam('adminPassword'), $this->getParam('encryption'))); + $user->setGivenName($user->getUsername(), $siteLocale); + $user->setFamilyName($user->getUsername(), $siteLocale); + $user->setEmail($this->getParam('adminEmail')); + $user->setDateRegistered(Core::getCurrentDate()); + $user->setInlineHelp(1); + Repo::user()->add($user); + + // Create an admin user group + $adminUserGroup = Repo::userGroup()->newDataObject(); + $adminUserGroup->setRoleId(Role::ROLE_ID_SITE_ADMIN); + $adminUserGroup->setContextId(\PKP\core\PKPApplication::CONTEXT_ID_NONE); + $adminUserGroup->setDefault(true); + foreach ($this->installedLocales as $locale) { + $name = __('default.groups.name.siteAdmin', [], $locale); + $namePlural = __('default.groups.plural.siteAdmin', [], $locale); + $adminUserGroup->setData('name', $name, $locale); + $adminUserGroup->setData('namePlural', $namePlural, $locale); + } + Repo::userGroup()->add($adminUserGroup); + + // Put the installer into this user group + Repo::userGroup()->assignUserToGroup($user->getId(), $adminUserGroup->getId()); + + // Add initial site data + /** @var SiteDAO */ + $siteDao = DAORegistry::getDAO('SiteDAO'); + $site = $siteDao->newDataObject(); + $site->setRedirect(0); + $site->setMinPasswordLength(static::MIN_PASSWORD_LENGTH); + $site->setPrimaryLocale($siteLocale); + $site->setInstalledLocales($this->installedLocales); + $site->setSupportedLocales($this->installedLocales); + $site->setUniqueSiteID(PKPString::generateUUID()); + $siteDao->insertSite($site); + + Repo::emailTemplate()->dao->installEmailTemplates(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), $this->installedLocales); + + // Install default site settings + $schemaService = Services::get('schema'); + $site = $schemaService->setDefaults(PKPSchemaService::SCHEMA_SITE, $site, $site->getSupportedLocales(), $site->getPrimaryLocale()); + $site->setData('contactEmail', $this->getParam('adminEmail'), $site->getPrimaryLocale()); + $siteDao->updateObject($site); + + return true; + } +} + +if (!PKP_STRICT_MODE) { + class_alias('\PKP\install\PKPInstall', '\PKPInstall'); +} diff --git a/ssl/options.php b/ssl/options.php new file mode 100644 index 0000000..7ee84b6 --- /dev/null +++ b/ssl/options.php @@ -0,0 +1,8 @@ + 'ssl/DigiCertGlobalRootCA.pem', + PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false +); + +?> \ No newline at end of file