1<?php declare(strict_types=1); 2/* Copyright (c) 1998-2016 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once 'Services/Password/classes/encoders/class.ilBcryptPhpPasswordEncoder.php'; 5require_once 'Services/Password/test/ilPasswordBaseTest.php'; 6 7/** 8 * Class ilBcryptPhpPasswordEncoderTest 9 * @author Michael Jansen <mjansen@databay.de> 10 * @package ServicesPassword 11 */ 12class ilBcryptPhpPasswordEncoderTest extends ilPasswordBaseTest 13{ 14 /** @var string */ 15 const VALID_COSTS = '08'; 16 17 /** @var string */ 18 const PASSWORD = 'password'; 19 20 /** @var string */ 21 const WRONG_PASSWORD = 'wrong_password'; 22 23 /** 24 * 25 */ 26 private function skipIfPhpVersionIsNotSupported() : void 27 { 28 if (version_compare(phpversion(), '5.5.0', '<')) { 29 $this->markTestSkipped('Requires PHP >= 5.5.0'); 30 } 31 } 32 33 /** 34 * @return array 35 */ 36 public function costsProvider() : array 37 { 38 $data = []; 39 for ($i = 4; $i <= 31; $i++) { 40 $data[sprintf("Costs: %s", (string) $i)] = [(string) $i]; 41 } 42 43 return $data; 44 } 45 46 /** 47 * @return ilBcryptPhpPasswordEncoder 48 * @throws ilPasswordException 49 */ 50 public function testInstanceCanBeCreated() : ilBcryptPhpPasswordEncoder 51 { 52 $this->skipIfPhpVersionIsNotSupported(); 53 54 $default_costs_encoder = new ilBcryptPhpPasswordEncoder(); 55 $this->assertTrue((int) $default_costs_encoder->getCosts() > 4 && (int) $default_costs_encoder->getCosts() < 32); 56 57 $encoder = new ilBcryptPhpPasswordEncoder([ 58 'cost' => self::VALID_COSTS 59 ]); 60 $this->assertInstanceOf('ilBcryptPhpPasswordEncoder', $encoder); 61 $this->assertEquals(self::VALID_COSTS, $encoder->getCosts()); 62 return $encoder; 63 } 64 65 /** 66 * @depends testInstanceCanBeCreated 67 * @param ilBcryptPhpPasswordEncoder $encoder 68 * @throws ilPasswordException 69 */ 70 public function testCostsCanBeRetrievedWhenCostsAreSet(ilBcryptPhpPasswordEncoder $encoder) : void 71 { 72 $expected = '04'; 73 74 $encoder->setCosts($expected); 75 $this->assertEquals($expected, $encoder->getCosts()); 76 } 77 78 /** 79 * @depends testInstanceCanBeCreated 80 * @param ilBcryptPhpPasswordEncoder $encoder 81 * @throws ilPasswordException 82 */ 83 public function testCostsCannotBeSetAboveRange(ilBcryptPhpPasswordEncoder $encoder) : void 84 { 85 $this->expectException(ilPasswordException::class); 86 $encoder->setCosts('32'); 87 } 88 89 /** 90 * @depends testInstanceCanBeCreated 91 * @param ilBcryptPhpPasswordEncoder $encoder 92 * @throws ilPasswordException 93 */ 94 public function testCostsCannotBeSetBelowRange(ilBcryptPhpPasswordEncoder $encoder) : void 95 { 96 $this->expectException(ilPasswordException::class); 97 $encoder->setCosts('3'); 98 } 99 100 /** 101 * @depends testInstanceCanBeCreated 102 * @dataProvider costsProvider 103 * @doesNotPerformAssertions 104 * @param string $costs 105 * @param ilBcryptPhpPasswordEncoder $encoder 106 * @throws ilPasswordException 107 */ 108 public function testCostsCanBeSetInRange(string $costs, ilBcryptPhpPasswordEncoder $encoder) : void 109 { 110 $encoder->setCosts($costs); 111 } 112 113 /** 114 * @depends testInstanceCanBeCreated 115 * @param ilBcryptPhpPasswordEncoder $encoder 116 * @return ilBcryptPhpPasswordEncoder 117 * @throws ilPasswordException 118 */ 119 public function testPasswordShouldBeCorrectlyEncodedAndVerified( 120 ilBcryptPhpPasswordEncoder $encoder 121 ) : ilBcryptPhpPasswordEncoder { 122 $encoder->setCosts(self::VALID_COSTS); 123 $encoded_password = $encoder->encodePassword(self::PASSWORD, ''); 124 $this->assertTrue($encoder->isPasswordValid($encoded_password, self::PASSWORD, '')); 125 $this->assertFalse($encoder->isPasswordValid($encoded_password, self::WRONG_PASSWORD, '')); 126 return $encoder; 127 } 128 129 /** 130 * @depends testInstanceCanBeCreated 131 * @param ilBcryptPhpPasswordEncoder $encoder 132 * @throws ilPasswordException 133 */ 134 public function testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEncoding( 135 ilBcryptPhpPasswordEncoder $encoder 136 ) : void { 137 $this->expectException(ilPasswordException::class); 138 $encoder->setCosts(self::VALID_COSTS); 139 $encoder->encodePassword(str_repeat('a', 5000), ''); 140 } 141 142 /** 143 * @depends testInstanceCanBeCreated 144 * @param ilBcryptPhpPasswordEncoder $encoder 145 * @throws ilPasswordException 146 */ 147 public function testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupportedLength( 148 ilBcryptPhpPasswordEncoder $encoder 149 ) : void { 150 $encoder->setCosts(self::VALID_COSTS); 151 $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), '')); 152 } 153 154 /** 155 * @depends testInstanceCanBeCreated 156 * @param ilBcryptPhpPasswordEncoder $encoder 157 */ 158 public function testNameShouldBeBcryptPhp(ilBcryptPhpPasswordEncoder $encoder) : void 159 { 160 $this->assertEquals('bcryptphp', $encoder->getName()); 161 } 162 163 /** 164 * @depends testInstanceCanBeCreated 165 * @param ilBcryptPhpPasswordEncoder $encoder 166 * @throws ilPasswordException 167 */ 168 public function testCostsCanBeDeterminedDynamically(ilBcryptPhpPasswordEncoder $encoder) : void 169 { 170 $costs_default = $encoder->benchmarkCost(); 171 $costs_target = $encoder->benchmarkCost(0.5); 172 173 $this->assertTrue($costs_default > 4 && $costs_default < 32); 174 $this->assertTrue($costs_target > 4 && $costs_target < 32); 175 $this->assertIsInt($costs_default); 176 $this->assertIsInt($costs_target); 177 $this->assertNotEquals($costs_default, $costs_target); 178 } 179 180 /** 181 * @depends testInstanceCanBeCreated 182 * @param ilBcryptPhpPasswordEncoder $encoder 183 */ 184 public function testEncoderDoesNotRelyOnSalts(ilBcryptPhpPasswordEncoder $encoder) : void 185 { 186 $this->assertFalse($encoder->requiresSalt()); 187 } 188 189 /** 190 * @depends testInstanceCanBeCreated 191 * @param ilBcryptPhpPasswordEncoder $encoder 192 * @throws ilPasswordException 193 */ 194 public function testReencodingIsDetectedWhenNecessary(ilBcryptPhpPasswordEncoder $encoder) : void 195 { 196 $raw = self::PASSWORD; 197 198 $encoder->setCosts('8'); 199 $encoded = $encoder->encodePassword($raw, ''); 200 $encoder->setCosts('8'); 201 $this->assertFalse($encoder->requiresReencoding($encoded)); 202 203 $encoder->setCosts('9'); 204 $this->assertTrue($encoder->requiresReencoding($encoded)); 205 } 206} 207