Password and hash are exclusive
As specified in https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/85f5567f-2599-41ad-ae26-8cfab23ce754 password and hashValue are exlusive and thus should be treated transparently with a single API in our model.
This commit is contained in:
parent
1eaf40be69
commit
b9a59660d0
|
@ -919,29 +919,53 @@ disallow inserting rows on a specific sheet, disallow sorting, ...
|
||||||
- Cell: offers the option to lock/unlock a cell as well as show/hide
|
- Cell: offers the option to lock/unlock a cell as well as show/hide
|
||||||
the internal formula.
|
the internal formula.
|
||||||
|
|
||||||
|
**Make sure you enable worksheet protection if you need any of the
|
||||||
|
worksheet or cell protection features!** This can be done using the following
|
||||||
|
code:
|
||||||
|
|
||||||
|
``` php
|
||||||
|
$spreadsheet->getActiveSheet()->getProtection()->setSheet(true);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Document
|
||||||
|
|
||||||
An example on setting document security:
|
An example on setting document security:
|
||||||
|
|
||||||
``` php
|
``` php
|
||||||
$spreadsheet->getSecurity()->setLockWindows(true);
|
$security = $spreadsheet->getSecurity();
|
||||||
$spreadsheet->getSecurity()->setLockStructure(true);
|
$security->setLockWindows(true);
|
||||||
$spreadsheet->getSecurity()->setWorkbookPassword("PhpSpreadsheet");
|
$security->setLockStructure(true);
|
||||||
|
$security->setWorkbookPassword("PhpSpreadsheet");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Worksheet
|
||||||
|
|
||||||
An example on setting worksheet security:
|
An example on setting worksheet security:
|
||||||
|
|
||||||
``` php
|
``` php
|
||||||
$spreadsheet->getActiveSheet()
|
$protection = $spreadsheet->getActiveSheet()->getProtection();
|
||||||
->getProtection()->setPassword('PhpSpreadsheet');
|
$protection->setPassword('PhpSpreadsheet');
|
||||||
$spreadsheet->getActiveSheet()
|
$protection->setSheet(true);
|
||||||
->getProtection()->setSheet(true);
|
$protection->setSort(true);
|
||||||
$spreadsheet->getActiveSheet()
|
$protection->setInsertRows(true);
|
||||||
->getProtection()->setSort(true);
|
$protection->setFormatCells(true);
|
||||||
$spreadsheet->getActiveSheet()
|
|
||||||
->getProtection()->setInsertRows(true);
|
|
||||||
$spreadsheet->getActiveSheet()
|
|
||||||
->getProtection()->setFormatCells(true);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If writing Xlsx files you can specify the algorithm used to hash the password
|
||||||
|
before calling `setPassword()` like so:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$protection = $spreadsheet->getActiveSheet()->getProtection();
|
||||||
|
$protection->setAlgorithm(Protection::ALGORITHM_SHA_512);
|
||||||
|
$protection->setSpinCount(20000);
|
||||||
|
$protection->setPassword('PhpSpreadsheet');
|
||||||
|
```
|
||||||
|
|
||||||
|
The salt should **not** be set manually and will be automatically generated
|
||||||
|
when setting a new password.
|
||||||
|
|
||||||
|
### Cell
|
||||||
|
|
||||||
An example on setting cell security:
|
An example on setting cell security:
|
||||||
|
|
||||||
``` php
|
``` php
|
||||||
|
@ -950,14 +974,30 @@ $spreadsheet->getActiveSheet()->getStyle('B1')
|
||||||
->setLocked(\PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED);
|
->setLocked(\PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED);
|
||||||
```
|
```
|
||||||
|
|
||||||
**Make sure you enable worksheet protection if you need any of the
|
## Reading protected spreadsheet
|
||||||
worksheet protection features!** This can be done using the following
|
|
||||||
code:
|
|
||||||
|
|
||||||
``` php
|
Spreadsheets that are protected the as described above can always be read by
|
||||||
$spreadsheet->getActiveSheet()->getProtection()->setSheet(true);
|
PhpSpreadsheet. There is no need to know the password or do anything special in
|
||||||
|
order to read a protected file.
|
||||||
|
|
||||||
|
However if you need to implement a password verification mechanism, you can use the
|
||||||
|
following helper method:
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
$protection = $spreadsheet->getActiveSheet()->getProtection();
|
||||||
|
$allowed = $protection->verify('my password');
|
||||||
|
|
||||||
|
if ($allowed) {
|
||||||
|
doSomething();
|
||||||
|
} else {
|
||||||
|
throw new Exception('Incorrect password');
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you need to completely prevent reading a file by any tool, including PhpSpreadsheet,
|
||||||
|
then you are looking for "encryption", not "protection".
|
||||||
|
|
||||||
## Setting data validation on a cell
|
## Setting data validation on a cell
|
||||||
|
|
||||||
Data validation is a powerful feature of Xlsx. It allows to specify an
|
Data validation is a powerful feature of Xlsx. It allows to specify an
|
||||||
|
|
|
@ -763,17 +763,8 @@ class Xlsx extends BaseReader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
|
if ($xmlSheet) {
|
||||||
$docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true);
|
$this->readSheetProtection($docSheet, $xmlSheet);
|
||||||
$docSheet->getProtection()->setAlgorithmName((string) $xmlSheet->sheetProtection['algorithmName']);
|
|
||||||
$docSheet->getProtection()->setHashValue((string) $xmlSheet->sheetProtection['hashValue']);
|
|
||||||
$docSheet->getProtection()->setSaltValue((string) $xmlSheet->sheetProtection['saltValue']);
|
|
||||||
$docSheet->getProtection()->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
|
|
||||||
if ($xmlSheet->protectedRanges->protectedRange) {
|
|
||||||
foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
|
|
||||||
$docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
|
if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
|
||||||
|
@ -2035,4 +2026,29 @@ class Xlsx extends BaseReader
|
||||||
|
|
||||||
return $workbookBasename;
|
return $workbookBasename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void
|
||||||
|
{
|
||||||
|
if ($this->readDataOnly || !$xmlSheet->sheetProtection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$algorithmName = (string) $xmlSheet->sheetProtection['algorithmName'];
|
||||||
|
$protection = $docSheet->getProtection();
|
||||||
|
$protection->setAlgorithm($algorithmName);
|
||||||
|
|
||||||
|
if ($algorithmName) {
|
||||||
|
$protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true);
|
||||||
|
$protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']);
|
||||||
|
$protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
|
||||||
|
} else {
|
||||||
|
$protection->setPassword((string) $xmlSheet->sheetProtection['password'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xmlSheet->protectedRanges->protectedRange) {
|
||||||
|
foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
|
||||||
|
$docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,51 +2,39 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Shared;
|
namespace PhpOffice\PhpSpreadsheet\Shared;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Exception;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
|
||||||
|
|
||||||
class PasswordHasher
|
class PasswordHasher
|
||||||
{
|
{
|
||||||
const ALGORITHM_MD2 = 'MD2';
|
|
||||||
const ALGORITHM_MD4 = 'MD4';
|
|
||||||
const ALGORITHM_MD5 = 'MD5';
|
|
||||||
const ALGORITHM_SHA_1 = 'SHA-1';
|
|
||||||
const ALGORITHM_SHA_256 = 'SHA-256';
|
|
||||||
const ALGORITHM_SHA_384 = 'SHA-384';
|
|
||||||
const ALGORITHM_SHA_512 = 'SHA-512';
|
|
||||||
const ALGORITHM_RIPEMD_128 = 'RIPEMD-128';
|
|
||||||
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
|
|
||||||
const ALGORITHM_WHIRLPOOL = 'WHIRLPOOL';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping between algorithm name in Excel and algorithm name in PHP.
|
* Get algorithm name for PHP.
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
*/
|
||||||
private static $algorithmArray = [
|
private static function getAlgorithm(string $algorithmName): string
|
||||||
self::ALGORITHM_MD2 => 'md2',
|
|
||||||
self::ALGORITHM_MD4 => 'md4',
|
|
||||||
self::ALGORITHM_MD5 => 'md5',
|
|
||||||
self::ALGORITHM_SHA_1 => 'sha1',
|
|
||||||
self::ALGORITHM_SHA_256 => 'sha256',
|
|
||||||
self::ALGORITHM_SHA_384 => 'sha384',
|
|
||||||
self::ALGORITHM_SHA_512 => 'sha512',
|
|
||||||
self::ALGORITHM_RIPEMD_128 => 'ripemd128',
|
|
||||||
self::ALGORITHM_RIPEMD_160 => 'ripemd160',
|
|
||||||
self::ALGORITHM_WHIRLPOOL => 'whirlpool',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get algorithm from self::$algorithmArray.
|
|
||||||
*
|
|
||||||
* @param string $pAlgorithmName
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private static function getAlgorithm($pAlgorithmName)
|
|
||||||
{
|
{
|
||||||
if (array_key_exists($pAlgorithmName, self::$algorithmArray)) {
|
if (!$algorithmName) {
|
||||||
return self::$algorithmArray[$pAlgorithmName];
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
// Mapping between algorithm name in Excel and algorithm name in PHP
|
||||||
|
$mapping = [
|
||||||
|
Protection::ALGORITHM_MD2 => 'md2',
|
||||||
|
Protection::ALGORITHM_MD4 => 'md4',
|
||||||
|
Protection::ALGORITHM_MD5 => 'md5',
|
||||||
|
Protection::ALGORITHM_SHA_1 => 'sha1',
|
||||||
|
Protection::ALGORITHM_SHA_256 => 'sha256',
|
||||||
|
Protection::ALGORITHM_SHA_384 => 'sha384',
|
||||||
|
Protection::ALGORITHM_SHA_512 => 'sha512',
|
||||||
|
Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
|
||||||
|
Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
|
||||||
|
Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (array_key_exists($algorithmName, $mapping)) {
|
||||||
|
return $mapping[$algorithmName];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Unsupported password algorithm: ' . $algorithmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,10 +45,8 @@ class PasswordHasher
|
||||||
* Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
|
* Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
|
||||||
*
|
*
|
||||||
* @param string $pPassword Password to hash
|
* @param string $pPassword Password to hash
|
||||||
*
|
|
||||||
* @return string Hashed password
|
|
||||||
*/
|
*/
|
||||||
public static function defaultHashPassword($pPassword)
|
private static function defaultHashPassword(string $pPassword): string
|
||||||
{
|
{
|
||||||
$password = 0x0000;
|
$password = 0x0000;
|
||||||
$charPos = 1; // char position
|
$charPos = 1; // char position
|
||||||
|
@ -87,40 +73,28 @@ class PasswordHasher
|
||||||
*
|
*
|
||||||
* @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
|
* @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
|
||||||
*
|
*
|
||||||
* @param string $pPassword Password to hash
|
* @param string $password Password to hash
|
||||||
* @param string $pAlgorithmName Hash algorithm used to compute the password hash value
|
* @param string $algorithm Hash algorithm used to compute the password hash value
|
||||||
* @param string $pSaltValue Pseudorandom string
|
* @param string $salt Pseudorandom string
|
||||||
* @param string $pSpinCount Number of times to iterate on a hash of a password
|
* @param int $spinCount Number of times to iterate on a hash of a password
|
||||||
*
|
*
|
||||||
* @return string Hashed password
|
* @return string Hashed password
|
||||||
*/
|
*/
|
||||||
public static function hashPassword($pPassword, $pAlgorithmName = '', $pSaltValue = '', $pSpinCount = 10000)
|
public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
|
||||||
{
|
{
|
||||||
$algorithmName = self::getAlgorithm($pAlgorithmName);
|
$phpAlgorithm = self::getAlgorithm($algorithm);
|
||||||
if (!$pAlgorithmName) {
|
if (!$phpAlgorithm) {
|
||||||
return self::defaultHashPassword($pPassword);
|
return self::defaultHashPassword($password);
|
||||||
}
|
}
|
||||||
|
|
||||||
$saltValue = base64_decode($pSaltValue);
|
$saltValue = base64_decode($salt);
|
||||||
$password = mb_convert_encoding($pPassword, 'UCS-2LE', 'UTF-8');
|
$encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
|
||||||
|
|
||||||
$hashValue = hash($algorithmName, $saltValue . $password, true);
|
$hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
|
||||||
for ($i = 0; $i < $pSpinCount; ++$i) {
|
for ($i = 0; $i < $spinCount; ++$i) {
|
||||||
$hashValue = hash($algorithmName, $hashValue . pack('L', $i), true);
|
$hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64_encode($hashValue);
|
return base64_encode($hashValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a pseudorandom string.
|
|
||||||
*
|
|
||||||
* @param int $pSize Length of the output string in bytes
|
|
||||||
*
|
|
||||||
* @return string Pseudorandom string
|
|
||||||
*/
|
|
||||||
public static function generateSalt($pSize = 16)
|
|
||||||
{
|
|
||||||
return base64_encode(random_bytes($pSize));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,17 @@ use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher;
|
||||||
|
|
||||||
class Protection
|
class Protection
|
||||||
{
|
{
|
||||||
|
const ALGORITHM_MD2 = 'MD2';
|
||||||
|
const ALGORITHM_MD4 = 'MD4';
|
||||||
|
const ALGORITHM_MD5 = 'MD5';
|
||||||
|
const ALGORITHM_SHA_1 = 'SHA-1';
|
||||||
|
const ALGORITHM_SHA_256 = 'SHA-256';
|
||||||
|
const ALGORITHM_SHA_384 = 'SHA-384';
|
||||||
|
const ALGORITHM_SHA_512 = 'SHA-512';
|
||||||
|
const ALGORITHM_RIPEMD_128 = 'RIPEMD-128';
|
||||||
|
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
|
||||||
|
const ALGORITHM_WHIRLPOOL = 'WHIRLPOOL';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sheet.
|
* Sheet.
|
||||||
*
|
*
|
||||||
|
@ -119,7 +130,7 @@ class Protection
|
||||||
private $selectUnlockedCells = false;
|
private $selectUnlockedCells = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Password.
|
* Hashed password.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
@ -130,28 +141,28 @@ class Protection
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $algorithmName = '';
|
private $algorithm = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash value.
|
* Hash value.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $hashValue = '';
|
private $hash = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Salt value.
|
* Salt value.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $saltValue = '';
|
private $salt = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spin count.
|
* Spin count.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
private $spinCount = '';
|
private $spinCount = 10000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Protection.
|
* Create a new Protection.
|
||||||
|
@ -570,7 +581,7 @@ class Protection
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Password (hashed).
|
* Get hashed password.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -590,107 +601,84 @@ class Protection
|
||||||
public function setPassword($pValue, $pAlreadyHashed = false)
|
public function setPassword($pValue, $pAlreadyHashed = false)
|
||||||
{
|
{
|
||||||
if (!$pAlreadyHashed) {
|
if (!$pAlreadyHashed) {
|
||||||
$pValue = PasswordHasher::hashPassword($pValue);
|
$salt = $this->generateSalt();
|
||||||
|
$this->setSalt($salt);
|
||||||
|
$pValue = PasswordHasher::hashPassword($pValue, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->password = $pValue;
|
$this->password = $pValue;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get AlgorithmName.
|
* Create a pseudorandom string.
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getAlgorithmName()
|
private function generateSalt(): string
|
||||||
{
|
{
|
||||||
return $this->algorithmName;
|
return base64_encode(random_bytes(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set AlgorithmName.
|
* Get algorithm name.
|
||||||
*
|
|
||||||
* @param string $pValue
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
*/
|
||||||
public function setAlgorithmName($pValue)
|
public function getAlgorithm(): string
|
||||||
{
|
{
|
||||||
$this->algorithmName = $pValue;
|
return $this->algorithm;
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get HashValue.
|
* Set algorithm name.
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getHashValue()
|
public function setAlgorithm(string $algorithm): void
|
||||||
{
|
{
|
||||||
return $this->hashValue;
|
$this->algorithm = $algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set HashValue.
|
* Get salt value.
|
||||||
*
|
|
||||||
* @param string $pValue
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
*/
|
||||||
public function setHashValue($pValue)
|
public function getSalt(): string
|
||||||
{
|
{
|
||||||
$this->hashValue = $pValue;
|
return $this->salt;
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get SaltValue.
|
* Set salt value.
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getSaltValue()
|
public function setSalt(string $salt): void
|
||||||
{
|
{
|
||||||
return $this->saltValue;
|
$this->salt = $salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set SaltValue.
|
* Get spin count.
|
||||||
*
|
|
||||||
* @param string $pValue
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
*/
|
||||||
public function setSaltValue($pValue)
|
public function getSpinCount(): int
|
||||||
{
|
|
||||||
$this->saltValue = $pValue;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get SpinCount.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getSpinCount()
|
|
||||||
{
|
{
|
||||||
return $this->spinCount;
|
return $this->spinCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set SpinCount.
|
* Set spin count.
|
||||||
*
|
|
||||||
* @param int $pValue
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
*/
|
||||||
public function setSpinCount($pValue)
|
public function setSpinCount(int $spinCount): void
|
||||||
{
|
{
|
||||||
$this->spinCount = $pValue;
|
$this->spinCount = $spinCount;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
/**
|
||||||
|
* Verify that the given non-hashed password can "unlock" the protection.
|
||||||
|
*/
|
||||||
|
public function verify(string $password): bool
|
||||||
|
{
|
||||||
|
if (!$this->isProtectionEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
|
||||||
|
|
||||||
|
return $this->getPassword() === $hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -420,42 +420,33 @@ class Worksheet extends WriterPart
|
||||||
// sheetProtection
|
// sheetProtection
|
||||||
$objWriter->startElement('sheetProtection');
|
$objWriter->startElement('sheetProtection');
|
||||||
|
|
||||||
if ($pSheet->getProtection()->getPassword() !== '') {
|
$protection = $pSheet->getProtection();
|
||||||
$objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword());
|
|
||||||
|
if ($protection->getAlgorithm()) {
|
||||||
|
$objWriter->writeAttribute('algorithmName', $protection->getAlgorithm());
|
||||||
|
$objWriter->writeAttribute('hashValue', $protection->getPassword());
|
||||||
|
$objWriter->writeAttribute('saltValue', $protection->getSalt());
|
||||||
|
$objWriter->writeAttribute('spinCount', $protection->getSpinCount());
|
||||||
|
} elseif ($protection->getPassword() !== '') {
|
||||||
|
$objWriter->writeAttribute('password', $protection->getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($pSheet->getProtection()->getHashValue() !== '') {
|
$objWriter->writeAttribute('sheet', ($protection->getSheet() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('hashValue', $pSheet->getProtection()->getHashValue());
|
$objWriter->writeAttribute('objects', ($protection->getObjects() ? 'true' : 'false'));
|
||||||
}
|
$objWriter->writeAttribute('scenarios', ($protection->getScenarios() ? 'true' : 'false'));
|
||||||
|
$objWriter->writeAttribute('formatCells', ($protection->getFormatCells() ? 'true' : 'false'));
|
||||||
if ($pSheet->getProtection()->getAlgorithmName() !== '') {
|
$objWriter->writeAttribute('formatColumns', ($protection->getFormatColumns() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('algorithmName', $pSheet->getProtection()->getAlgorithmName());
|
$objWriter->writeAttribute('formatRows', ($protection->getFormatRows() ? 'true' : 'false'));
|
||||||
}
|
$objWriter->writeAttribute('insertColumns', ($protection->getInsertColumns() ? 'true' : 'false'));
|
||||||
|
$objWriter->writeAttribute('insertRows', ($protection->getInsertRows() ? 'true' : 'false'));
|
||||||
if ($pSheet->getProtection()->getSaltValue() !== '') {
|
$objWriter->writeAttribute('insertHyperlinks', ($protection->getInsertHyperlinks() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('saltValue', $pSheet->getProtection()->getSaltValue());
|
$objWriter->writeAttribute('deleteColumns', ($protection->getDeleteColumns() ? 'true' : 'false'));
|
||||||
}
|
$objWriter->writeAttribute('deleteRows', ($protection->getDeleteRows() ? 'true' : 'false'));
|
||||||
|
$objWriter->writeAttribute('selectLockedCells', ($protection->getSelectLockedCells() ? 'true' : 'false'));
|
||||||
if ($pSheet->getProtection()->getSpinCount() !== '') {
|
$objWriter->writeAttribute('sort', ($protection->getSort() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('spinCount', $pSheet->getProtection()->getSpinCount());
|
$objWriter->writeAttribute('autoFilter', ($protection->getAutoFilter() ? 'true' : 'false'));
|
||||||
}
|
$objWriter->writeAttribute('pivotTables', ($protection->getPivotTables() ? 'true' : 'false'));
|
||||||
|
$objWriter->writeAttribute('selectUnlockedCells', ($protection->getSelectUnlockedCells() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('sheet', ($pSheet->getProtection()->getSheet() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('formatCells', ($pSheet->getProtection()->getFormatCells() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('formatColumns', ($pSheet->getProtection()->getFormatColumns() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('formatRows', ($pSheet->getProtection()->getFormatRows() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('insertColumns', ($pSheet->getProtection()->getInsertColumns() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('insertRows', ($pSheet->getProtection()->getInsertRows() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('insertHyperlinks', ($pSheet->getProtection()->getInsertHyperlinks() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('deleteColumns', ($pSheet->getProtection()->getDeleteColumns() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('deleteRows', ($pSheet->getProtection()->getDeleteRows() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('selectLockedCells', ($pSheet->getProtection()->getSelectLockedCells() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('sort', ($pSheet->getProtection()->getSort() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('autoFilter', ($pSheet->getProtection()->getAutoFilter() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('pivotTables', ($pSheet->getProtection()->getPivotTables() ? 'true' : 'false'));
|
|
||||||
$objWriter->writeAttribute('selectUnlockedCells', ($pSheet->getProtection()->getSelectUnlockedCells() ? 'true' : 'false'));
|
|
||||||
$objWriter->endElement();
|
$objWriter->endElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Worksheet;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ProtectionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testVerifyPassword(): void
|
||||||
|
{
|
||||||
|
$protection = new Protection();
|
||||||
|
self::assertTrue($protection->verify('foo'), 'non-protected always pass');
|
||||||
|
|
||||||
|
$protection->setSheet(true);
|
||||||
|
self::assertFalse($protection->verify('foo'), 'protected will fail');
|
||||||
|
|
||||||
|
$protection->setPassword('foo', true);
|
||||||
|
self::assertSame('foo', $protection->getPassword(), 'was not stored as-is, without hashing');
|
||||||
|
self::assertFalse($protection->verify('foo'), 'setting already hashed password will not match');
|
||||||
|
|
||||||
|
$protection->setPassword('foo');
|
||||||
|
self::assertSame('CC40', $protection->getPassword(), 'was hashed');
|
||||||
|
self::assertTrue($protection->verify('foo'), 'setting non-hashed password will hash it and not match');
|
||||||
|
|
||||||
|
$protection->setAlgorithm(Protection::ALGORITHM_MD5);
|
||||||
|
self::assertFalse($protection->verify('foo'), 'changing algorithm will not match anymore');
|
||||||
|
|
||||||
|
$protection->setPassword('foo');
|
||||||
|
$hash1 = $protection->getPassword();
|
||||||
|
$protection->setPassword('foo');
|
||||||
|
$hash2 = $protection->getPassword();
|
||||||
|
|
||||||
|
self::assertSame(24, mb_strlen($hash1));
|
||||||
|
self::assertSame(24, mb_strlen($hash2));
|
||||||
|
self::assertNotSame($hash1, $hash2, 'was hashed with automatic salt');
|
||||||
|
self::assertTrue($protection->verify('foo'), 'setting password again, will hash with proper algorithm and will match');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue