source of /sprout/tests/securityHelperTest.php<?php /* * Copyright (C) 2017 Karmabunny Pty Ltd. * * This file is a part of SproutCMS. * * SproutCMS is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Foundation, either * version 2 of the License, or (at your option) any later version. * * For more information, visit <http://getsproutcms.com>. */ use Sprout\Helpers\Security; /** * Test suite **/ class securityHelperTest extends PHPUnit_Framework_TestCase { public function testRandBytes() { $bytes = Security::randBytes(16); $this->assertTrue(strlen($bytes) === 16, 'Return value length'); } public function testRandByte() { $byte = Security::randByte(); $this->assertTrue(strlen($byte) === 1, 'Return value length'); } public function testRandStr() { $string = Security::randStr(16); $this->assertTrue(strlen($string) === 16, 'Return value length'); } /** * Data for testing random distributions */ public function dataRandDistribution() { return [ [ 'Sprout\Helpers\Security::randBytes', [4096 * 512], 256 ], [ 'Sprout\Helpers\Security::randStr', [4096 * 512, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'], 26 ], [ 'Sprout\Helpers\Security::randStr', [4096 * 512, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'], 52 ], [ 'Sprout\Helpers\Security::randStr', [4096 * 512, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'], 62 ], ]; } /** * @dataProvider dataRandDistribution * * @param callable $func Function to generate random strings * @param array $args Function arguments * @param int $num_unique Expected number of unique values returned from $func */ public function testRandDistribution ($func, array $args, $num_unique) { $dists = []; foreach ($bytes as $b) { $dists[$b]++; } else { $dists[$b] = 1; } } $this->assertCount($num_unique, $dists); $avg = count($bytes) / $num_unique; $thresh = $avg * 0.1; foreach ($dists as $b => $count) { $diff = abs($count - $avg); $this->assertLessThan($thresh, $diff, "Byte {$b} count {$count} expected {$avg} (+/- {$thresh})"); } } public function testCompareStrings() { $this->assertTrue(Security::compareStrings('aaa', 'aaa')); $this->assertFalse(Security::compareStrings('aaa', 'bbb')); } public function testCompareStringsTimingSafe() { $this->markTestSkipped('Timing not stable in Travis CI'); } $matches = [0.0, 0.0, 0.0]; // When using hash_equals its much faster than the fallback // and this makes the timing unstable so more iterations are required $iter = 5000; } else { $iter = 500; } // Test one - both strings matching for ($i = 0; $i < $iter; ++$i) { Security::compareStrings($xxx, $xxx); $matches[0] += (microtime(true) - $start) * 1000; } // Test two - matching except last character for ($i = 0; $i < $iter; ++$i) { Security::compareStrings($xxx, $yyy); $matches[1] += (microtime(true) - $start) * 1000; } // Test three - matching except first character for ($i = 0; $i < $iter; ++$i) { Security::compareStrings($xxx, $zzz); $matches[2] += (microtime(true) - $start) * 1000; } // Calculate the average time across all three tests // Compare each test against the average, as a percentage // Require to be within 10% or better foreach ($matches as $idx => $val) { $diff = abs($val - $average); $perc = $diff / $average * 100.0; $this->assertLessThan(10, $perc); } } public function dataPasswordComplexityLength() { return [ ['abcdefg', 8, 'Too short, minimum length 8 characters'], ['abcdefgh', 8, null], ['abcdefghi', 8, null], ['abcdefghi', 10, 'Too short, minimum length 10 characters'], ['abcdefghij', 10, null], ['abcdefghijk', 10, null], ]; } /** * @dataProvider dataPasswordComplexityLength */ public function testPasswordComplexityLength($string, $length, $errmsg) { $errs = Security::passwordComplexity($string, $length, 0, false); $this->assertEquals($errmsg?[$errmsg]:[], $errs); } public function dataPasswordComplexityClasses() { return [ ['password', 1, null], ['password', 2, 'Need 2 character types (lowercase, uppercase, numbers, symbols)'], ['password', 3, 'Need 3 character types (lowercase, uppercase, numbers, symbols)'], ['password', 4, 'Need 4 character types (lowercase, uppercase, numbers, symbols)'], ['passWORD', 1, null], ['passWORD', 2, null], ['passWORD', 3, 'Need 3 character types (lowercase, uppercase, numbers, symbols)'], ['passWORD', 4, 'Need 4 character types (lowercase, uppercase, numbers, symbols)'], ['passW0RD', 1, null], ['passW0RD', 2, null], ['passW0RD', 3, null], ['passW0RD', 4, 'Need 4 character types (lowercase, uppercase, numbers, symbols)'], ['pa!sW0RD', 1, null], ['pa!sW0RD', 2, null], ['pa!sW0RD', 3, null], ['pa!sW0RD', 4, null], ]; } /** * @dataProvider dataPasswordComplexityClasses */ public function testPasswordComplexityClasses($string, $classes, $errmsg) { $errs = Security::passwordComplexity($string, 0, $classes, false); $this->assertEquals($errmsg?[$errmsg]:[], $errs); } public function dataPasswordComplexityBadlist() { return [ ['password', 'Matches a very common password'], ['dsbfb83s', null], ]; } /** * @dataProvider dataPasswordComplexityBadlist */ public function testPasswordComplexityBadlist($string, $errmsg) { $errs = Security::passwordComplexity($string, 0, 0, true); $this->assertEquals($errmsg?[$errmsg]:[], $errs); } }
|