# Test-generated files
admin-export.json
specialexport.json
+/data/site-credentials-secret-key.txt
$table->addColumn('user_id', 'integer');
$table->addColumn('host', 'string', ['length' => 255]);
$table->addColumn('username', 'string', ['length' => 255]);
- $table->addColumn('password', 'string', ['length' => 255]);
+ $table->addColumn('password', 'text');
$table->addColumn('createdAt', 'datetime');
$table->addIndex(['user_id'], 'idx_user');
$table->setPrimaryKey(['id']);
fetching_error_message: |
wallabag can't retrieve contents for this article. Please <a href="http://doc.wallabag.org/en/user/errors_during_fetching.html#how-can-i-help-to-fix-that">troubleshoot this issue</a>.
api_limit_mass_actions: 10
+ encryption_key_path: "%kernel.root_dir%/../data/site-credentials-secret-key.txt"
default_internal_settings:
-
name: share_public
"kphoen/rulerz-bundle": "~0.13",
"guzzlehttp/guzzle": "^5.3.1",
"doctrine/doctrine-migrations-bundle": "^1.0",
- "paragonie/random_compat": "~1.0",
+ "paragonie/random_compat": "~2.0",
"craue/config-bundle": "~2.0",
"mnapoli/piwik-twig-extension": "^1.0",
"ocramius/proxy-manager": "1.*",
"javibravo/simpleue": "^1.0",
"symfony/dom-crawler": "^3.1",
"friendsofsymfony/jsrouting-bundle": "^1.6",
- "bdunogier/guzzle-site-authenticator": "^1.0.0@dev"
+ "bdunogier/guzzle-site-authenticator": "^1.0.0@dev",
+ "defuse/php-encryption": "^2.1"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2",
$this
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]);
+
+ return $this;
}
/**
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
+ $credential->setPassword($this->get('wallabag_core.helper.crypto_proxy')->crypt($credential->getPassword()));
+
$em = $this->getDoctrine()->getManager();
$em->persist($credential);
$em->flush($credential);
->end()
->end()
->end()
+ ->scalarNode('encryption_key_path')
+ ->end()
->end()
;
$container->setParameter('wallabag_core.fetching_error_message_title', $config['fetching_error_message_title']);
$container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']);
$container->setParameter('wallabag_core.default_internal_settings', $config['default_internal_settings']);
+ $container->setParameter('wallabag_core.site_credentials.encryption_key_path', $config['encryption_key_path']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
* @var string
*
* @Assert\NotBlank()
- * @Assert\Length(max=255)
- * @ORM\Column(name="password", type="string", length=255)
+ * @ORM\Column(name="password", type="text")
*/
private $password;
--- /dev/null
+<?php
+
+namespace Wallabag\CoreBundle\Helper;
+
+use Psr\Log\LoggerInterface;
+use Defuse\Crypto\Key;
+use Defuse\Crypto\Crypto;
+use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
+
+/**
+ * This is a proxy to crypt and decrypt password used by SiteCredential entity.
+ * BTW, It might be re-use for sth else.
+ */
+class CryptoProxy
+{
+ private $logger;
+ private $encryptionKey;
+
+ public function __construct($encryptionKeyPath, LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+
+ if (!file_exists($encryptionKeyPath)) {
+ $key = Key::createNewRandomKey();
+
+ file_put_contents($encryptionKeyPath, $key->saveToAsciiSafeString());
+ chmod($encryptionKeyPath, 0600);
+ }
+
+ $this->encryptionKey = file_get_contents($encryptionKeyPath);
+ }
+
+ /**
+ * Ensure the given value will be crypted.
+ *
+ * @param string $secretValue Secret valye to crypt
+ *
+ * @return string
+ */
+ public function crypt($secretValue)
+ {
+ $this->logger->debug('Crypto: crypting value: '.$this->mask($secretValue));
+
+ return Crypto::encrypt($secretValue, $this->loadKey());
+ }
+
+ /**
+ * Ensure the given crypted value will be decrypted.
+ *
+ * @param string $cryptedValue The value to be decrypted
+ *
+ * @return string
+ */
+ public function decrypt($cryptedValue)
+ {
+ $this->logger->debug('Crypto: decrypting value: '.$this->mask($cryptedValue));
+
+ try {
+ return Crypto::decrypt($cryptedValue, $this->loadKey());
+ } catch (WrongKeyOrModifiedCiphertextException $e) {
+ throw new \RuntimeException('Decrypt fail: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Load the private key.
+ *
+ * @return string
+ */
+ private function loadKey()
+ {
+ return Key::loadFromAsciiSafeString($this->encryptionKey);
+ }
+
+ /**
+ * Keep first and last character and put some stars in between.
+ *
+ * @param string $value Value to mask
+ *
+ * @return string
+ */
+ private function mask($value)
+ {
+ return $value[0].'*****'.$value[strlen($value) - 1];
+ }
+}
namespace Wallabag\CoreBundle\Repository;
+use Wallabag\CoreBundle\Helper\CryptoProxy;
+
/**
* SiteCredentialRepository.
*/
class SiteCredentialRepository extends \Doctrine\ORM\EntityRepository
{
+ private $cryptoProxy;
+
+ public function setCrypto(CryptoProxy $cryptoProxy)
+ {
+ $this->cryptoProxy = $cryptoProxy;
+ }
+
/**
* Retrieve one username/password for the given host and userId.
*
*/
public function findOneByHostAndUser($host, $userId)
{
- return $this->createQueryBuilder('s')
+ $res = $this->createQueryBuilder('s')
->select('s.username', 's.password')
->where('s.host = :hostname')->setParameter('hostname', $host)
->andWhere('s.user = :userId')->setParameter('userId', $userId)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
+
+ if (null === $res) {
+ return;
+ }
+
+ // decrypt password before returning it
+ $res['password'] = $this->cryptoProxy->decrypt($res['password']);
+
+ return $res;
}
}
factory: [ "@doctrine.orm.default_entity_manager", getRepository ]
arguments:
- WallabagCoreBundle:SiteCredential
+ calls:
+ - [ setCrypto, [ "@wallabag_core.helper.crypto_proxy" ] ]
wallabag_core.helper.entries_export:
class: Wallabag\CoreBundle\Helper\EntriesExport
wallabag_core.entry.download_images.client:
class: GuzzleHttp\Client
+
+ wallabag_core.helper.crypto_proxy:
+ class: Wallabag\CoreBundle\Helper\CryptoProxy
+ arguments:
+ - "%wallabag_core.site_credentials.encryption_key_path%"
+ - "@logger"
$credential = new SiteCredential($user);
$credential->setHost('monde-diplomatique.fr');
$credential->setUsername('foo');
- $credential->setPassword('bar');
+ $credential->setPassword($client->getContainer()->get('wallabag_core.helper.crypto_proxy')->crypt('bar'));
$em->persist($credential);
$em->flush();
use Monolog\Logger;
use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfig;
use Graby\SiteConfig\SiteConfig as GrabySiteConfig;
-use PHPUnit_Framework_TestCase;
use Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
-class GrabySiteConfigBuilderTest extends PHPUnit_Framework_TestCase
+class GrabySiteConfigBuilderTest extends \PHPUnit_Framework_TestCase
{
/** @var \Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder */
protected $builder;
--- /dev/null
+<?php
+
+namespace Tests\Wallabag\CoreBundle\Helper;
+
+use Psr\Log\NullLogger;
+use Monolog\Logger;
+use Monolog\Handler\TestHandler;
+use Wallabag\CoreBundle\Helper\CryptoProxy;
+
+class CryptoProxyTest extends \PHPUnit_Framework_TestCase
+{
+ public function testCrypto()
+ {
+ $logHandler = new TestHandler();
+ $logger = new Logger('test', [$logHandler]);
+
+ $crypto = new CryptoProxy(sys_get_temp_dir().'/'.uniqid('', true).'.txt', $logger);
+ $crypted = $crypto->crypt('test');
+ $decrypted = $crypto->decrypt($crypted);
+
+ $this->assertSame('test', $decrypted);
+
+ $records = $logHandler->getRecords();
+ $this->assertCount(2, $records);
+ $this->assertContains('Crypto: crypting value', $records[0]['message']);
+ $this->assertContains('Crypto: decrypting value', $records[1]['message']);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage Decrypt fail
+ *
+ * @return [type] [description]
+ */
+ public function testDecryptBadValue()
+ {
+ $crypto = new CryptoProxy(sys_get_temp_dir().'/'.uniqid('', true).'.txt', new NullLogger());
+ $crypto->decrypt('badvalue');
+ }
+}