From 03111e510ca28555a3ac51277e0d0d433d96b095 Mon Sep 17 00:00:00 2001
From: Yassine Guedidi
Date: Fri, 2 Feb 2024 21:56:25 +0100
Subject: [PATCH] Integrate bdunogier/guzzle-site-authenticator in core
---
app/AppKernel.php | 1 -
app/config/services.yml | 6 +-
composer.json | 1 -
composer.lock | 59 +-
.../RegisterWallabagGuzzleSubscribersPass.php | 24 +
.../AuthenticatorProvider.php | 96 +
.../Guzzle/AuthenticatorSubscriber.php | 123 +
.../FixupMondeDiplomatiqueUriSubscriber.php | 33 +
.../SiteConfig/ArraySiteConfigBuilder.php | 34 +
.../Authenticator/Authenticator.php | 31 +
.../SiteConfig/Authenticator/Factory.php | 21 +
.../Authenticator/LoginFormAuthenticator.php | 112 +
.../SiteConfig/GrabySiteConfigBuilder.php | 2 -
.../CoreBundle/SiteConfig/SiteConfig.php | 263 +++
.../SiteConfig/SiteConfigBuilder.php | 17 +
.../CoreBundle/WallabagCoreBundle.php | 2 +
.../Guzzle/AuthenticatorSubscriberTest.php | 327 +++
...ixupMondeDiplomatiqueUriSubscriberTest.php | 95 +
.../SiteConfig/ArraySiteConfigBuilderTest.php | 26 +
.../LoginFormAuthenticatorTest.php | 235 ++
.../CoreBundle/SiteConfig/SiteConfigTest.php | 44 +
.../CoreBundle/fixtures/aoc.media.html | 860 +++++++
.../fixtures/nextinpact-article.html | 2083 +++++++++++++++++
.../CoreBundle/fixtures/nextinpact-login.html | 125 +
24 files changed, 4555 insertions(+), 65 deletions(-)
create mode 100644 src/Wallabag/CoreBundle/DependencyInjection/CompilerPass/RegisterWallabagGuzzleSubscribersPass.php
create mode 100644 src/Wallabag/CoreBundle/ExpressionLanguage/AuthenticatorProvider.php
create mode 100644 src/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriber.php
create mode 100644 src/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriber.php
create mode 100644 src/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilder.php
create mode 100644 src/Wallabag/CoreBundle/SiteConfig/Authenticator/Authenticator.php
create mode 100644 src/Wallabag/CoreBundle/SiteConfig/Authenticator/Factory.php
create mode 100644 src/Wallabag/CoreBundle/SiteConfig/Authenticator/LoginFormAuthenticator.php
create mode 100644 src/Wallabag/CoreBundle/SiteConfig/SiteConfig.php
create mode 100644 src/Wallabag/CoreBundle/SiteConfig/SiteConfigBuilder.php
create mode 100644 tests/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriberTest.php
create mode 100644 tests/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriberTest.php
create mode 100644 tests/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilderTest.php
create mode 100644 tests/Wallabag/CoreBundle/SiteConfig/Authenticator/LoginFormAuthenticatorTest.php
create mode 100644 tests/Wallabag/CoreBundle/SiteConfig/SiteConfigTest.php
create mode 100644 tests/Wallabag/CoreBundle/fixtures/aoc.media.html
create mode 100644 tests/Wallabag/CoreBundle/fixtures/nextinpact-article.html
create mode 100644 tests/Wallabag/CoreBundle/fixtures/nextinpact-login.html
diff --git a/app/AppKernel.php b/app/AppKernel.php
index 216cacf29..d161a9b9f 100644
--- a/app/AppKernel.php
+++ b/app/AppKernel.php
@@ -30,7 +30,6 @@ class AppKernel extends Kernel
new Craue\ConfigBundle\CraueConfigBundle(),
new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),
new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
- new BD\GuzzleSiteAuthenticatorBundle\BDGuzzleSiteAuthenticatorBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
new Http\HttplugBundle\HttplugBundle(),
new Sentry\SentryBundle\SentryBundle(),
diff --git a/app/config/services.yml b/app/config/services.yml
index 7dbb7f501..1e8b57dd8 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -34,7 +34,7 @@ services:
Wallabag\CoreBundle\:
resource: '../../src/Wallabag/CoreBundle/*'
- exclude: ['../../src/Wallabag/CoreBundle/{Consumer,Controller,Entity,DataFixtures,Redis}', '../../src/Wallabag/CoreBundle/Event/*Event.php']
+ exclude: ['../../src/Wallabag/CoreBundle/{Consumer,Controller,Entity,ExpressionLanguage,DataFixtures,Redis}', '../../src/Wallabag/CoreBundle/Event/*Event.php']
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
@@ -196,7 +196,7 @@ services:
- { name: monolog.logger, channel: graby }
# service alias override
- bd_guzzle_site_authenticator.site_config_builder:
+ Wallabag\CoreBundle\SiteConfig\SiteConfigBuilder:
alias: Wallabag\CoreBundle\SiteConfig\GrabySiteConfigBuilder
GuzzleHttp\Cookie\CookieJar:
@@ -204,7 +204,7 @@ services:
Wallabag\CoreBundle\Helper\HttpClientFactory:
calls:
- - ["addSubscriber", ["@bd_guzzle_site_authenticator.authenticator_subscriber"]]
+ - ['addSubscriber', ['@Wallabag\CoreBundle\Guzzle\AuthenticatorSubscriber']]
RulerZ\RulerZ:
alias: rulerz
diff --git a/composer.json b/composer.json
index 4df38480c..f1f31dd0d 100644
--- a/composer.json
+++ b/composer.json
@@ -58,7 +58,6 @@
"ext-tokenizer": "*",
"ext-xml": "*",
"babdev/pagerfanta-bundle": "^3.8",
- "bdunogier/guzzle-site-authenticator": "^1.1.0",
"craue/config-bundle": "^2.7.0",
"defuse/php-encryption": "^2.4",
"doctrine/collections": "^1.8",
diff --git a/composer.lock b/composer.lock
index 55be2648d..227a5907b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3676d938a5f11d9556b4c3047f08bf86",
+ "content-hash": "4ade839f1958082269247bc7ef3ac845",
"packages": [
{
"name": "babdev/pagerfanta-bundle",
@@ -143,63 +143,6 @@
},
"time": "2022-12-07T17:46:57+00:00"
},
- {
- "name": "bdunogier/guzzle-site-authenticator",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/wallabag/guzzle-site-authenticator.git",
- "reference": "16a73a973000cde431f45deb6a45b4315fc4d391"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/wallabag/guzzle-site-authenticator/zipball/16a73a973000cde431f45deb6a45b4315fc4d391",
- "reference": "16a73a973000cde431f45deb6a45b4315fc4d391",
- "shasum": ""
- },
- "require": {
- "guzzlehttp/guzzle": "^5.3.1",
- "psr/log": "^1.0.0",
- "symfony/config": "^4.4|^5.4|^6.0",
- "symfony/dependency-injection": "^4.4|^5.4|^6.0",
- "symfony/expression-language": "^4.4|^5.4|^6.0",
- "symfony/http-kernel": "^4.4|^5.4|^6.0"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^3.4.0",
- "monolog/monolog": "^2.3",
- "nyholm/symfony-bundle-test": "^2.0",
- "symfony/phpunit-bridge": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "BD\\GuzzleSiteAuthenticator\\": "lib/",
- "BD\\GuzzleSiteAuthenticatorBundle\\": "bundle/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Bertrand Dunogier",
- "email": "bertrand.dunogier@gmail.com"
- }
- ],
- "description": "A guzzle plugin that adds, if necessary, authentication data to requests. Uses credentials and cookies, with login requests to the sites.",
- "support": {
- "issues": "https://github.com/wallabag/guzzle-site-authenticator/issues",
- "source": "https://github.com/wallabag/guzzle-site-authenticator/tree/1.1.0"
- },
- "time": "2023-08-21T14:33:48+00:00"
- },
{
"name": "beberlei/assert",
"version": "v3.3.2",
diff --git a/src/Wallabag/CoreBundle/DependencyInjection/CompilerPass/RegisterWallabagGuzzleSubscribersPass.php b/src/Wallabag/CoreBundle/DependencyInjection/CompilerPass/RegisterWallabagGuzzleSubscribersPass.php
new file mode 100644
index 000000000..a9c14c9b4
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DependencyInjection/CompilerPass/RegisterWallabagGuzzleSubscribersPass.php
@@ -0,0 +1,24 @@
+getDefinition(HttpClientFactory::class);
+
+ // manually add subscribers for some websites
+ $definition->addMethodCall(
+ 'addSubscriber', [
+ new Reference(FixupMondeDiplomatiqueUriSubscriber::class),
+ ]
+ );
+ }
+}
diff --git a/src/Wallabag/CoreBundle/ExpressionLanguage/AuthenticatorProvider.php b/src/Wallabag/CoreBundle/ExpressionLanguage/AuthenticatorProvider.php
new file mode 100644
index 000000000..8e366b131
--- /dev/null
+++ b/src/Wallabag/CoreBundle/ExpressionLanguage/AuthenticatorProvider.php
@@ -0,0 +1,96 @@
+guzzle = $guzzle;
+ }
+
+ public function getFunctions(): array
+ {
+ $result = [
+ $this->getRequestHtmlFunction(),
+ $this->getXpathFunction(),
+ $this->getPregMatchFunction(),
+ ];
+
+ return $result;
+ }
+
+ private function getRequestHtmlFunction()
+ {
+ return new ExpressionFunction(
+ 'request_html',
+ function () {
+ throw new \Exception('Not supported');
+ },
+ function (array $arguments, $uri, array $options = []) {
+ return $this->guzzle->get($uri, $options)->getBody();
+ }
+ );
+ }
+
+ private function getPregMatchFunction()
+ {
+ return new ExpressionFunction(
+ 'preg_match',
+ function () {
+ throw new \Exception('Not supported');
+ },
+ function (array $arguments, $pattern, $html) {
+ preg_match($pattern, $html, $matches);
+
+ if (2 !== \count($matches)) {
+ return '';
+ }
+
+ return $matches[1];
+ }
+ );
+ }
+
+ private function getXpathFunction()
+ {
+ return new ExpressionFunction(
+ 'xpath',
+ function () {
+ throw new \Exception('Not supported');
+ },
+ function (array $arguments, $xpathQuery, $html) {
+ $useInternalErrors = libxml_use_internal_errors(true);
+
+ $doc = new \DOMDocument();
+ $doc->loadHTML((string) $html, \LIBXML_NOCDATA | \LIBXML_NOWARNING | \LIBXML_NOERROR);
+
+ $xpath = new \DOMXPath($doc);
+ $domNodeList = $xpath->query($xpathQuery);
+
+ if (0 === $domNodeList->length) {
+ return '';
+ }
+
+ $domNode = $domNodeList->item(0);
+
+ libxml_use_internal_errors($useInternalErrors);
+
+ if (null === $domNode || null === $domNode->attributes) {
+ return '';
+ }
+
+ return $domNode->attributes->getNamedItem('value')->nodeValue;
+ }
+ );
+ }
+}
diff --git a/src/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriber.php b/src/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriber.php
new file mode 100644
index 000000000..1c6156049
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriber.php
@@ -0,0 +1,123 @@
+configBuilder = $configBuilder;
+ $this->authenticatorFactory = $authenticatorFactory;
+ $this->logger = new NullLogger();
+ }
+
+ public function setLogger(LoggerInterface $logger): void
+ {
+ $this->logger = $logger;
+ }
+
+ public function getEvents(): array
+ {
+ return [
+ 'before' => ['loginIfRequired'],
+ 'complete' => ['loginIfRequested'],
+ ];
+ }
+
+ public function loginIfRequired(BeforeEvent $event)
+ {
+ $config = $this->buildSiteConfig($event->getRequest());
+ if (false === $config || !$config->requiresLogin()) {
+ $this->logger->debug('loginIfRequired> will not require login');
+
+ return;
+ }
+
+ $client = $event->getClient();
+ $authenticator = $this->authenticatorFactory->buildFromSiteConfig($config);
+
+ if (!$authenticator->isLoggedIn($client)) {
+ $this->logger->debug('loginIfRequired> user is not logged in, attach authenticator');
+
+ $emitter = $client->getEmitter();
+ $emitter->detach($this);
+ $authenticator->login($client);
+ $emitter->attach($this);
+ }
+ }
+
+ public function loginIfRequested(CompleteEvent $event)
+ {
+ $config = $this->buildSiteConfig($event->getRequest());
+ if (false === $config || !$config->requiresLogin()) {
+ $this->logger->debug('loginIfRequested> will not require login');
+
+ return;
+ }
+
+ $body = $event->getResponse()->getBody();
+
+ if (
+ null === $body
+ || '' === $body->getContents()
+ ) {
+ $this->logger->debug('loginIfRequested> empty body, ignoring');
+
+ return;
+ }
+
+ $authenticator = $this->authenticatorFactory->buildFromSiteConfig($config);
+ $isLoginRequired = $authenticator->isLoginRequired($body);
+
+ $this->logger->debug('loginIfRequested> retry #' . self::$retries . ' with login ' . ($isLoginRequired ? '' : 'not ') . 'required');
+
+ if ($isLoginRequired && self::$retries < self::MAX_RETRIES) {
+ $client = $event->getClient();
+
+ $emitter = $client->getEmitter();
+ $emitter->detach($this);
+ $authenticator->login($client);
+ $emitter->attach($this);
+
+ $event->retry();
+
+ ++self::$retries;
+ }
+ }
+
+ /**
+ * @return SiteConfig|false
+ */
+ private function buildSiteConfig(RequestInterface $request)
+ {
+ return $this->configBuilder->buildForHost($request->getHost());
+ }
+}
diff --git a/src/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriber.php b/src/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriber.php
new file mode 100644
index 000000000..5a7358574
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriber.php
@@ -0,0 +1,33 @@
+ [['fixUri', 500]]];
+ }
+
+ public function fixUri(CompleteEvent $event)
+ {
+ $response = $event->getResponse();
+
+ if (!$response->hasHeader('Location')) {
+ return;
+ }
+
+ $uri = $response->getHeader('Location');
+ if (false === ($badParameter = strstr($uri, 'retour=http://'))) {
+ return;
+ }
+
+ $response->setHeader('Location', str_replace($badParameter, urlencode($badParameter), $uri));
+ }
+}
diff --git a/src/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilder.php b/src/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilder.php
new file mode 100644
index 000000000..853ebf395
--- /dev/null
+++ b/src/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilder.php
@@ -0,0 +1,34 @@
+ SiteConfig.
+ */
+ private $configs = [];
+
+ public function __construct(array $hostConfigMap = [])
+ {
+ foreach ($hostConfigMap as $host => $hostConfig) {
+ $hostConfig['host'] = $host;
+ $this->configs[$host] = new SiteConfig($hostConfig);
+ }
+ }
+
+ public function buildForHost($host)
+ {
+ $host = strtolower($host);
+
+ if ('www.' === substr($host, 0, 4)) {
+ $host = substr($host, 4);
+ }
+
+ if (isset($this->configs[$host])) {
+ return $this->configs[$host];
+ }
+
+ return false;
+ }
+}
diff --git a/src/Wallabag/CoreBundle/SiteConfig/Authenticator/Authenticator.php b/src/Wallabag/CoreBundle/SiteConfig/Authenticator/Authenticator.php
new file mode 100644
index 000000000..4481d2b95
--- /dev/null
+++ b/src/Wallabag/CoreBundle/SiteConfig/Authenticator/Authenticator.php
@@ -0,0 +1,31 @@
+siteConfig = $siteConfig;
+ }
+
+ public function login(ClientInterface $guzzle)
+ {
+ $postFields = [
+ $this->siteConfig->getUsernameField() => $this->siteConfig->getUsername(),
+ $this->siteConfig->getPasswordField() => $this->siteConfig->getPassword(),
+ ] + $this->getExtraFields($guzzle);
+
+ $guzzle->post(
+ $this->siteConfig->getLoginUri(),
+ ['body' => $postFields, 'allow_redirects' => true, 'verify' => false]
+ );
+
+ return $this;
+ }
+
+ public function isLoggedIn(ClientInterface $guzzle)
+ {
+ if (($cookieJar = $guzzle->getDefaultOption('cookies')) instanceof CookieJar) {
+ /** @var \GuzzleHttp\Cookie\SetCookie $cookie */
+ foreach ($cookieJar as $cookie) {
+ // check required cookies
+ if ($cookie->getDomain() === $this->siteConfig->getHost()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public function isLoginRequired($html)
+ {
+ $useInternalErrors = libxml_use_internal_errors(true);
+
+ // need to check for the login dom element ($options['not_logged_in_xpath']) in the HTML
+ $doc = new \DOMDocument();
+ $doc->loadHTML($html);
+
+ $xpath = new \DOMXPath($doc);
+ $loggedIn = $xpath->evaluate((string) $this->siteConfig->getNotLoggedInXpath());
+
+ if (false === $loggedIn) {
+ return false;
+ }
+
+ libxml_use_internal_errors($useInternalErrors);
+
+ return $loggedIn->length > 0;
+ }
+
+ /**
+ * Returns extra fields from the configuration.
+ * Evaluates any field value that is an expression language string.
+ *
+ * @return array
+ */
+ private function getExtraFields(ClientInterface $guzzle)
+ {
+ $extraFields = [];
+
+ foreach ($this->siteConfig->getExtraFields() as $fieldName => $fieldValue) {
+ if ('@=' === substr($fieldValue, 0, 2)) {
+ $expressionLanguage = $this->getExpressionLanguage($guzzle);
+ $fieldValue = $expressionLanguage->evaluate(
+ substr($fieldValue, 2),
+ [
+ 'config' => $this->siteConfig,
+ ]
+ );
+ }
+
+ $extraFields[$fieldName] = $fieldValue;
+ }
+
+ return $extraFields;
+ }
+
+ /**
+ * @return ExpressionLanguage
+ */
+ private function getExpressionLanguage(ClientInterface $guzzle)
+ {
+ return new ExpressionLanguage(
+ null,
+ [new AuthenticatorProvider($guzzle)]
+ );
+ }
+}
diff --git a/src/Wallabag/CoreBundle/SiteConfig/GrabySiteConfigBuilder.php b/src/Wallabag/CoreBundle/SiteConfig/GrabySiteConfigBuilder.php
index 5c329081f..276582ed2 100644
--- a/src/Wallabag/CoreBundle/SiteConfig/GrabySiteConfigBuilder.php
+++ b/src/Wallabag/CoreBundle/SiteConfig/GrabySiteConfigBuilder.php
@@ -2,8 +2,6 @@
namespace Wallabag\CoreBundle\SiteConfig;
-use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfig;
-use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfigBuilder;
use Graby\SiteConfig\ConfigBuilder;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
diff --git a/src/Wallabag/CoreBundle/SiteConfig/SiteConfig.php b/src/Wallabag/CoreBundle/SiteConfig/SiteConfig.php
new file mode 100644
index 000000000..7c8436f73
--- /dev/null
+++ b/src/Wallabag/CoreBundle/SiteConfig/SiteConfig.php
@@ -0,0 +1,263 @@
+ $propertyValue) {
+ if (!property_exists($this, $propertyName)) {
+ throw new \InvalidArgumentException('Unknown property: "' . $propertyName . '"');
+ }
+
+ $this->$propertyName = $propertyValue;
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function requiresLogin()
+ {
+ return $this->requiresLogin;
+ }
+
+ /**
+ * @param bool $requiresLogin
+ *
+ * @return SiteConfig
+ */
+ public function setRequiresLogin($requiresLogin)
+ {
+ $this->requiresLogin = $requiresLogin;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNotLoggedInXpath()
+ {
+ return $this->notLoggedInXpath;
+ }
+
+ /**
+ * @param string $notLoggedInXpath
+ *
+ * @return SiteConfig
+ */
+ public function setNotLoggedInXpath($notLoggedInXpath)
+ {
+ $this->notLoggedInXpath = $notLoggedInXpath;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLoginUri()
+ {
+ return $this->loginUri;
+ }
+
+ /**
+ * @param string $loginUri
+ *
+ * @return SiteConfig
+ */
+ public function setLoginUri($loginUri)
+ {
+ $this->loginUri = $loginUri;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsernameField()
+ {
+ return $this->usernameField;
+ }
+
+ /**
+ * @param string $usernameField
+ *
+ * @return SiteConfig
+ */
+ public function setUsernameField($usernameField)
+ {
+ $this->usernameField = $usernameField;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPasswordField()
+ {
+ return $this->passwordField;
+ }
+
+ /**
+ * @param string $passwordField
+ *
+ * @return SiteConfig
+ */
+ public function setPasswordField($passwordField)
+ {
+ $this->passwordField = $passwordField;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExtraFields()
+ {
+ return $this->extraFields;
+ }
+
+ /**
+ * @param array $extraFields
+ *
+ * @return SiteConfig
+ */
+ public function setExtraFields($extraFields)
+ {
+ $this->extraFields = $extraFields;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return SiteConfig
+ */
+ public function setHost($host)
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * @return SiteConfig
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ *
+ * @return SiteConfig
+ */
+ public function setPassword($password)
+ {
+ $this->password = $password;
+
+ return $this;
+ }
+}
diff --git a/src/Wallabag/CoreBundle/SiteConfig/SiteConfigBuilder.php b/src/Wallabag/CoreBundle/SiteConfig/SiteConfigBuilder.php
new file mode 100644
index 000000000..cc5947e0a
--- /dev/null
+++ b/src/Wallabag/CoreBundle/SiteConfig/SiteConfigBuilder.php
@@ -0,0 +1,17 @@
+addCompilerPass(new ImportCompilerPass());
+ $container->addCompilerPass(new RegisterWallabagGuzzleSubscribersPass());
}
}
diff --git a/tests/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriberTest.php b/tests/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriberTest.php
new file mode 100644
index 000000000..e15c46bfc
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/Guzzle/AuthenticatorSubscriberTest.php
@@ -0,0 +1,327 @@
+getEvents();
+
+ $this->assertArrayHasKey('before', $events);
+ $this->assertArrayHasKey('complete', $events);
+ $this->assertSame('loginIfRequired', $events['before'][0]);
+ $this->assertSame('loginIfRequested', $events['complete'][0]);
+ }
+
+ public function testLoginIfRequiredNotRequired()
+ {
+ $builder = new ArraySiteConfigBuilder(['example.com' => []]);
+ $subscriber = new AuthenticatorSubscriber($builder, new Factory());
+
+ $logger = new Logger('foo');
+ $handler = new TestHandler();
+ $logger->pushHandler($handler);
+
+ $subscriber->setLogger($logger);
+
+ $request = new Request('GET', 'http://www.example.com');
+
+ $event = $this->getMockBuilder(BeforeEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($request);
+
+ $subscriber->loginIfRequired($event);
+
+ $records = $handler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('loginIfRequired> will not require login', $records[0]['message']);
+ }
+
+ public function testLoginIfRequiredWithNotLoggedInUser()
+ {
+ $authenticator = $this->getMockBuilder(Authenticator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $authenticator->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(false);
+
+ $authenticator->expects($this->once())
+ ->method('login');
+
+ $factory = $this->getMockBuilder(Factory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $factory->expects($this->once())
+ ->method('buildFromSiteConfig')
+ ->willReturn($authenticator);
+
+ $builder = new ArraySiteConfigBuilder(['example.com' => ['requiresLogin' => true]]);
+ $subscriber = new AuthenticatorSubscriber($builder, $factory);
+
+ $logger = new Logger('foo');
+ $handler = new TestHandler();
+ $logger->pushHandler($handler);
+
+ $subscriber->setLogger($logger);
+
+ $response = new Response(
+ 200,
+ ['content-type' => 'text/html'],
+ Stream::factory('')
+ );
+ $guzzle = new Client();
+ $guzzle->getEmitter()->attach(new Mock([$response]));
+
+ $request = new Request('GET', 'http://www.example.com');
+
+ $event = $this->getMockBuilder(BeforeEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($request);
+
+ $event->expects($this->once())
+ ->method('getClient')
+ ->willReturn($guzzle);
+
+ $subscriber->loginIfRequired($event);
+
+ $records = $handler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('loginIfRequired> user is not logged in, attach authenticator', $records[0]['message']);
+ }
+
+ public function testLoginIfRequestedNotRequired()
+ {
+ $builder = new ArraySiteConfigBuilder(['example.com' => []]);
+ $subscriber = new AuthenticatorSubscriber($builder, new Factory());
+
+ $logger = new Logger('foo');
+ $handler = new TestHandler();
+ $logger->pushHandler($handler);
+
+ $subscriber->setLogger($logger);
+
+ $request = new Request('GET', 'http://www.example.com');
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($request);
+
+ $subscriber->loginIfRequested($event);
+
+ $records = $handler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('loginIfRequested> will not require login', $records[0]['message']);
+ }
+
+ public function testLoginIfRequestedNotRequested()
+ {
+ $authenticator = $this->getMockBuilder(Authenticator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $authenticator->expects($this->once())
+ ->method('isLoginRequired')
+ ->willReturn(false);
+
+ $factory = $this->getMockBuilder(Factory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $factory->expects($this->once())
+ ->method('buildFromSiteConfig')
+ ->willReturn($authenticator);
+
+ $builder = new ArraySiteConfigBuilder(['example.com' => [
+ 'requiresLogin' => true,
+ 'notLoggedInXpath' => '//html',
+ ]]);
+ $subscriber = new AuthenticatorSubscriber($builder, $factory);
+
+ $logger = new Logger('foo');
+ $handler = new TestHandler();
+ $logger->pushHandler($handler);
+
+ $subscriber->setLogger($logger);
+
+ $response = new Response(
+ 200,
+ ['content-type' => 'text/html'],
+ Stream::factory(' ')
+ );
+ $request = new Request('GET', 'http://www.example.com');
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getResponse')
+ ->willReturn($response);
+
+ $event->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($request);
+
+ $subscriber->loginIfRequested($event);
+
+ $records = $handler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('loginIfRequested> retry #0 with login not required', $records[0]['message']);
+ }
+
+ public function testLoginIfRequestedRequested()
+ {
+ $authenticator = $this->getMockBuilder(Authenticator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $authenticator->expects($this->once())
+ ->method('isLoginRequired')
+ ->willReturn(true);
+
+ $authenticator->expects($this->once())
+ ->method('login');
+
+ $factory = $this->getMockBuilder(Factory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $factory->expects($this->once())
+ ->method('buildFromSiteConfig')
+ ->willReturn($authenticator);
+
+ $builder = new ArraySiteConfigBuilder(['example.com' => [
+ 'requiresLogin' => true,
+ 'notLoggedInXpath' => '//html',
+ ]]);
+ $subscriber = new AuthenticatorSubscriber($builder, $factory);
+
+ $logger = new Logger('foo');
+ $handler = new TestHandler();
+ $logger->pushHandler($handler);
+
+ $subscriber->setLogger($logger);
+
+ $response = new Response(
+ 200,
+ ['content-type' => 'text/html'],
+ Stream::factory('
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Aujourd’hui
+
+ 10.12
+
+
+
+
+
+
+
+
+
+
+
publicité
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tout commence, dans le prologue de La Capitale , par un cochon semant la panique dans le centre de Bruxelles. Panique qui relie les personnages par un fil. Fil que l’écrivain autrichien Robert Menasse s’amuse à emmêler, dérouler, amplifier, dès le premier chapitre. Premier chapitre dans lequel retentit un coup de feu.
+AOC continue sa série de Noël avec ces premières pages traduites par Olivier Mannoni, à paraître en janvier chez Verdier, d’un roman burlesque mais noir, exubérant mais savamment construit, incroyablement divers mais tenu par ce fil. Une satire des institutions européennes, mais aussi un hommage à l’Europe et à sa capitale.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ vendredi
+ 07.12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mercredi
+ 05.12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ En poursuivant votre navigation sur ce site, vous acceptez l’utilisation de cookies de mesures d’audience et de partage sur les réseaux sociaux. En savoir plus. OK
+
+
diff --git a/tests/Wallabag/CoreBundle/fixtures/nextinpact-article.html b/tests/Wallabag/CoreBundle/fixtures/nextinpact-article.html
new file mode 100644
index 000000000..f5c3789ca
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/fixtures/nextinpact-article.html
@@ -0,0 +1,2083 @@
+
+
+
+
+
+
+
+ Le Freebox Delta Server et ses révolutions, au-delà du 10 Gb/s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
à ne pas manquer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Wallabag/CoreBundle/fixtures/nextinpact-login.html b/tests/Wallabag/CoreBundle/fixtures/nextinpact-login.html
new file mode 100644
index 000000000..02510050e
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/fixtures/nextinpact-login.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+ Interface de connexion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Connectez-vous via vos identifiants
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
')
+ );
+ $guzzle = new Client();
+ $guzzle->getEmitter()->attach(new Mock([$response]));
+ $request = new Request('GET', 'http://www.example.com');
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getResponse')
+ ->willReturn($response);
+
+ $event->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($request);
+
+ $event->expects($this->any())
+ ->method('getClient')
+ ->willReturn($guzzle);
+
+ $subscriber->loginIfRequested($event);
+
+ $records = $handler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('loginIfRequested> retry #0 with login required', $records[0]['message']);
+ }
+
+ public function testLoginIfRequestedRedirect()
+ {
+ $factory = $this->getMockBuilder(Factory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $builder = new ArraySiteConfigBuilder(['example.com' => [
+ 'requiresLogin' => true,
+ 'notLoggedInXpath' => '//html',
+ ]]);
+ $subscriber = new AuthenticatorSubscriber($builder, $factory);
+
+ $logger = new Logger('foo');
+ $handler = new TestHandler();
+ $logger->pushHandler($handler);
+
+ $subscriber->setLogger($logger);
+
+ $response = new Response(
+ 301,
+ [],
+ Stream::factory('')
+ );
+ $guzzle = new Client();
+ $guzzle->getEmitter()->attach(new Mock([$response]));
+ $request = new Request('GET', 'http://www.example.com');
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getResponse')
+ ->willReturn($response);
+
+ $event->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($request);
+
+ $event->expects($this->any())
+ ->method('getClient')
+ ->willReturn($guzzle);
+
+ $subscriber->loginIfRequested($event);
+
+ $records = $handler->getRecords();
+
+ $this->assertCount(1, $records);
+ $this->assertSame('loginIfRequested> empty body, ignoring', $records[0]['message']);
+ }
+}
diff --git a/tests/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriberTest.php b/tests/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriberTest.php
new file mode 100644
index 000000000..043154295
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/Guzzle/FixupMondeDiplomatiqueUriSubscriberTest.php
@@ -0,0 +1,95 @@
+getEvents();
+
+ $this->assertArrayHasKey('complete', $events);
+ $this->assertCount(2, $events['complete'][0]);
+ }
+
+ public function testGetEventsWithoutHeaderLocation()
+ {
+ $response = new Response(
+ 200,
+ [
+ 'content-type' => 'text/html',
+ ],
+ Stream::factory('')
+ );
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getResponse')
+ ->willReturn($response);
+
+ $subscriber = new FixupMondeDiplomatiqueUriSubscriber();
+ $subscriber->fixUri($event);
+
+ $this->assertFalse($response->hasHeader('Location'));
+ }
+
+ public function testGetEventsWithNotMachingHeaderLocation()
+ {
+ $response = new Response(
+ 200,
+ [
+ 'content-type' => 'text/html',
+ 'Location' => 'http://example.com',
+ ],
+ Stream::factory('')
+ );
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getResponse')
+ ->willReturn($response);
+
+ $subscriber = new FixupMondeDiplomatiqueUriSubscriber();
+ $subscriber->fixUri($event);
+
+ $this->assertSame('http://example.com', $response->getHeader('Location'));
+ }
+
+ public function testGetEventsWithMachingHeaderLocation()
+ {
+ $response = new Response(
+ 200,
+ [
+ 'content-type' => 'text/html',
+ 'Location' => 'http://example.com/?foo=bar&retour=http://example.com',
+ ],
+ Stream::factory('')
+ );
+
+ $event = $this->getMockBuilder(CompleteEvent::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $event->expects($this->once())
+ ->method('getResponse')
+ ->willReturn($response);
+
+ $subscriber = new FixupMondeDiplomatiqueUriSubscriber();
+ $subscriber->fixUri($event);
+
+ $this->assertSame('http://example.com/?foo=bar&retour%3Dhttp%3A%2F%2Fexample.com', $response->getHeader('Location'));
+ }
+}
diff --git a/tests/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilderTest.php b/tests/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilderTest.php
new file mode 100644
index 000000000..8ebb55c3b
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/SiteConfig/ArraySiteConfigBuilderTest.php
@@ -0,0 +1,26 @@
+ []]);
+ $res = $builder->buildForHost('www.example.com');
+
+ $this->assertInstanceOf(SiteConfig::class, $res);
+ }
+
+ public function testItReturnsFalseOnAHostThatDoesNotExist()
+ {
+ $builder = new ArraySiteConfigBuilder(['anotherexample.com' => []]);
+ $res = $builder->buildForHost('example.com');
+
+ $this->assertfalse($res);
+ }
+}
diff --git a/tests/Wallabag/CoreBundle/SiteConfig/Authenticator/LoginFormAuthenticatorTest.php b/tests/Wallabag/CoreBundle/SiteConfig/Authenticator/LoginFormAuthenticatorTest.php
new file mode 100644
index 000000000..d0189c825
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/SiteConfig/Authenticator/LoginFormAuthenticatorTest.php
@@ -0,0 +1,235 @@
+ 'text/html'],
+ Stream::factory('')
+ );
+ $guzzle = new Client();
+ $guzzle->getEmitter()->attach(new Mock([$response]));
+
+ $siteConfig = new SiteConfig([
+ 'host' => 'example.com',
+ 'loginUri' => 'http://example.com/login',
+ 'usernameField' => 'username',
+ 'passwordField' => 'password',
+ 'extraFields' => [
+ 'action' => 'login',
+ 'foo' => 'bar',
+ ],
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ ]);
+
+ $auth = new LoginFormAuthenticator($siteConfig);
+ $res = $auth->login($guzzle);
+
+ $this->assertInstanceOf(LoginFormAuthenticator::class, $res);
+ }
+
+ public function testLoginPostWithExtraFieldsButEmptyHtml()
+ {
+ $response = new Response(
+ 200,
+ ['content-type' => 'text/html'],
+ Stream::factory('')
+ );
+ $guzzle = new Client();
+ $guzzle->getEmitter()->attach(new Mock([$response, $response]));
+
+ $siteConfig = new SiteConfig([
+ 'host' => 'example.com',
+ 'loginUri' => 'http://example.com/login',
+ 'usernameField' => 'username',
+ 'passwordField' => 'password',
+ 'extraFields' => [
+ 'action' => 'login',
+ 'foo' => 'bar',
+ 'security' => '@=xpath(\'substring(//script[contains(text(), "security")]/text(), 112, 10)\', request_html(\'https://aoc.media/\'))',
+ ],
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ ]);
+
+ $auth = new LoginFormAuthenticator($siteConfig);
+ $res = $auth->login($guzzle);
+
+ $this->assertInstanceOf(LoginFormAuthenticator::class, $res);
+ }
+
+ // testing preg_match
+ public function testLoginPostWithExtraFieldsWithRegex()
+ {
+ $response = $this->getMockBuilder(Response::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $response->expects($this->any())
+ ->method('getBody')
+ ->willReturn(file_get_contents(__DIR__ . '/../../fixtures/aoc.media.html'));
+
+ $response->expects($this->any())
+ ->method('getStatusCode')
+ ->willReturn(200);
+
+ $client = $this->getMockBuilder(Client::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $client->expects($this->any())
+ ->method('post')
+ ->with(
+ $this->equalTo('https://aoc.media/wp-admin/admin-ajax.php'),
+ $this->equalTo([
+ 'body' => [
+ 'nom' => 'johndoe',
+ 'password' => 'unkn0wn',
+ 'security' => 'c506c1b8bc',
+ 'action' => 'login_user',
+ ],
+ 'allow_redirects' => true,
+ 'verify' => false,
+ ])
+ )
+ ->willReturn($response);
+
+ $client->expects($this->any())
+ ->method('get')
+ ->with(
+ $this->equalTo('https://aoc.media/'),
+ $this->equalTo([])
+ )
+ ->willReturn($response);
+
+ $siteConfig = new SiteConfig([
+ 'host' => 'aoc.media',
+ 'loginUri' => 'https://aoc.media/wp-admin/admin-ajax.php',
+ 'usernameField' => 'nom',
+ 'passwordField' => 'password',
+ 'extraFields' => [
+ 'action' => 'login_user',
+ 'security' => '@=preg_match(\'/security\\\":\\\"([a-z0-9]+)\\\"/si\', request_html(\'https://aoc.media/\'))',
+ ],
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ ]);
+
+ $auth = new LoginFormAuthenticator($siteConfig);
+ $res = $auth->login($client);
+
+ $this->assertInstanceOf(LoginFormAuthenticator::class, $res);
+ }
+
+ public function testLoginPostWithExtraFieldsWithData()
+ {
+ $response = $this->getMockBuilder(Response::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $response->expects($this->any())
+ ->method('getBody')
+ ->willReturn(file_get_contents(__DIR__ . '/../../fixtures/nextinpact-login.html'));
+
+ $response->expects($this->any())
+ ->method('getStatusCode')
+ ->willReturn(200);
+
+ $client = $this->getMockBuilder(Client::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $client->expects($this->any())
+ ->method('post')
+ ->with(
+ $this->equalTo('https://compte.nextinpact.com/Account/Login'),
+ $this->equalTo([
+ 'body' => [
+ 'UserName' => 'johndoe',
+ 'Password' => 'unkn0wn',
+ '__RequestVerificationToken' => 's6x2QcnQDUL92mkKSi_JuUBXcgUYx_Plf-KyQ2eJypKAjQZIeTvaFHOsfEdTrcSXt3dt2CW39V7r9V16LUtvjszodAU1',
+ 'returnUrl' => 'https://www.nextinpact.com/news/102835-pour-cour-comptes-fonctionnement-actuel-vote-par-internet-nest-pas-satisfaisant.htm',
+ ],
+ 'allow_redirects' => true,
+ 'verify' => false,
+ ])
+ )
+ ->willReturn($response);
+
+ $client->expects($this->any())
+ ->method('get')
+ ->with(
+ $this->equalTo('https://compte.nextinpact.com/Account/Login?http://www.nextinpact.com/'),
+ $this->equalTo([
+ 'headers' => [
+ 'X-Requested-With' => 'XMLHttpRequest',
+ ],
+ ])
+ )
+ ->willReturn($response);
+
+ $siteConfig = new SiteConfig([
+ 'host' => 'nextinpact.com',
+ 'loginUri' => 'https://compte.nextinpact.com/Account/Login',
+ 'usernameField' => 'UserName',
+ 'passwordField' => 'Password',
+ 'extraFields' => [
+ '__RequestVerificationToken' => '@=xpath(\'//form[@action="/Account/Login"]/input[@name="__RequestVerificationToken"]\', request_html(\'https://compte.nextinpact.com/Account/Login?http://www.nextinpact.com/\', {\'headers\': {\'X-Requested-With\':\'XMLHttpRequest\'}}))',
+ 'returnUrl' => 'https://www.nextinpact.com/news/102835-pour-cour-comptes-fonctionnement-actuel-vote-par-internet-nest-pas-satisfaisant.htm',
+ ],
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ ]);
+
+ $auth = new LoginFormAuthenticator($siteConfig);
+ $res = $auth->login($client);
+
+ $this->assertInstanceOf(LoginFormAuthenticator::class, $res);
+ }
+
+ public function testLoginWithBadSiteConfigNotLoggedInData()
+ {
+ $siteConfig = new SiteConfig([
+ 'host' => 'nextinpact.com',
+ 'loginUri' => 'https://compte.nextinpact.com/Account/Login',
+ 'usernameField' => 'UserName',
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ ]);
+
+ $auth = new LoginFormAuthenticator($siteConfig);
+ $loginRequired = $auth->isLoginRequired(file_get_contents(__DIR__ . '/../../fixtures/nextinpact-login.html'));
+
+ $this->assertFalse($loginRequired);
+ }
+
+ public function testLoginWithGoodSiteConfigNotLoggedInData()
+ {
+ $siteConfig = new SiteConfig([
+ 'host' => 'nextinpact.com',
+ 'loginUri' => 'https://compte.nextinpact.com/Account/Login',
+ 'usernameField' => 'UserName',
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ 'notLoggedInXpath' => '//h2[@class="title_reserve_article"]',
+ ]);
+
+ $auth = new LoginFormAuthenticator($siteConfig);
+ $loginRequired = $auth->isLoginRequired(file_get_contents(__DIR__ . '/../../fixtures/nextinpact-article.html'));
+
+ $this->assertTrue($loginRequired);
+ }
+}
diff --git a/tests/Wallabag/CoreBundle/SiteConfig/SiteConfigTest.php b/tests/Wallabag/CoreBundle/SiteConfig/SiteConfigTest.php
new file mode 100644
index 000000000..e8f5fa63b
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/SiteConfig/SiteConfigTest.php
@@ -0,0 +1,44 @@
+assertInstanceOf(SiteConfig::class, $config);
+ }
+
+ public function testUnknownProperty()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unknown property: "bad"');
+
+ new SiteConfig(['bad' => true]);
+ }
+
+ public function testInitSiteConfigWillFullOptions()
+ {
+ $config = new SiteConfig([
+ 'host' => 'example.com',
+ 'requiresLogin' => true,
+ 'notLoggedInXpath' => '//all',
+ 'loginUri' => 'https://example.com/login',
+ 'usernameField' => 'username',
+ 'passwordField' => 'password',
+ 'extraFields' => [
+ 'action' => 'login',
+ 'foo' => 'bar',
+ ],
+ 'username' => 'johndoe',
+ 'password' => 'unkn0wn',
+ ]);
+
+ $this->assertInstanceOf(SiteConfig::class, $config);
+ }
+}
diff --git a/tests/Wallabag/CoreBundle/fixtures/aoc.media.html b/tests/Wallabag/CoreBundle/fixtures/aoc.media.html
new file mode 100644
index 000000000..9b5a3de86
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/fixtures/aoc.media.html
@@ -0,0 +1,860 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AOC media - Analyse Opinion Critique | AOC est un quotidien d'auteurs en ligne, écrit par des chercheurs, des intellectuels, des écrivains et... des journalistes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+-
+
+
+
+ Se connecter / Créer un compte
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+ + +