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: | Spreadsheets that are protected the as described above can always be read by | ||||||
|  | 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 | ```php | ||||||
| $spreadsheet->getActiveSheet()->getProtection()->setSheet(true); | $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(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										39
									
								
								tests/PhpSpreadsheetTests/Worksheet/ProtectionTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tests/PhpSpreadsheetTests/Worksheet/ProtectionTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -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
	
	Block a user
	 Adrien Crivelli
						Adrien Crivelli