diff --git a/example/security.yml b/example/security.yml index 86c33d8..6731e60 100644 --- a/example/security.yml +++ b/example/security.yml @@ -1,15 +1,17 @@ defaults: target: host: www.somehost.com + scheme: https:// Sections: website: rules: - - HttpHeaderXSSProtection + - HttpHeaderXSSProtectionSecure - HttpHeaderExposePHP - HttpHeaderContentTypeNoSniffing - HttpHeaderCookieWithHttpOnlyFlag - - + - HttpHeaderHSTSPresent + - HttpHeaderHSTSWithSubdomains + - HttpHeaderCookieWithHttpSecureFlag diff --git a/example/seo.yml b/example/seo.yml index a1543b8..4d9eed0 100644 --- a/example/seo.yml +++ b/example/seo.yml @@ -27,7 +27,8 @@ Sections: uri: pics/someimage.png rules: - HttpHeaderResourceFound - - HttpHeaderHasFarFutureExpiresHeader + - HttpHeaderHasFarFutureExpiresHeader: + - ["thresholdInSeconds", 604800] javascript: config: diff --git a/src/Configuration/Configuration.php b/src/Configuration/Configuration.php index 8019e4a..65328e8 100644 --- a/src/Configuration/Configuration.php +++ b/src/Configuration/Configuration.php @@ -1,6 +1,7 @@ buildDefaultTarget( $config['defaults']['target'] ); } - if( isset( $config['Rules'] ) ) { - $this->generateRules( $config['Rules'] ); - } } private function buildDefaultTarget($config) { @@ -86,21 +84,15 @@ private function enrichTarget(TargetUrl $targetUrl, $config) { } } + /** + * @param $config + * + * @return RuleInterface[] + */ private function generateRules($config) { - - foreach($config as $name => $values) { - $className = $values['class']; - $class = new $className(); - if(isset($values['calls']) && is_array($values['calls'])) { - foreach($values['calls'] as $calls) { - $method = $calls[0]; - $params = $calls[1]; - call_user_func_array(array($class, $method), $params); - } - } - $this->rules[$name] = $class; - } - + $ruleBuilder = new RuleBuilder(); + $this->rules = $ruleBuilder->buildRules($config); + return $this->rules; } /** @@ -111,20 +103,29 @@ protected function buildSections($config) { $section = new Section( $name ); $sectionTargetUrl = $this->getSectionTargetUrl( $sectionConfig ); $section->setTargetUrlItem( $sectionTargetUrl ); - $section->setRules( $this->getRulesForSection( $sectionConfig ) ); + $section->setRules( $this->getRulesForSection( $sectionConfig, $config ) ); $this->sections[ $name ] = $section; } } - private function getRulesForSection($config) { - if(empty($config['rules'])) { + private function getRulesForSection($sectionConfig, $mainConfig) { + if(empty($sectionConfig['rules'])) { return array(); } $result = array(); - foreach($config['rules'] as $name) { - $result[$name] = $this->rules[$name]; + foreach( $sectionConfig['rules'] as $name) { + $plainName = $name; + if(is_array($name)) { + list($plainName, $configData) = each($name); + foreach($configData as $variableBlock) { + $rule['configuration'][] = array('set', $variableBlock); + } + } + $rule = $mainConfig['Rules'][$name]; + $result[$plainName] = $rule; } - return $result; + + return $this->generateRules($result); } private function getSectionTargetUrl($config){ diff --git a/src/Configuration/RuleBuilder.php b/src/Configuration/RuleBuilder.php new file mode 100644 index 0000000..bffed7a --- /dev/null +++ b/src/Configuration/RuleBuilder.php @@ -0,0 +1,46 @@ + $values) { + $className = $values['class']; + $class = new $className(); + $this->addMethodCalls( $values, $class ); + $this->addConfiguration( $values, $class ); + $rules[$name] = $class; + } + return $rules; + } + + /** + * @param $values + * @param $class + */ + private function addMethodCalls($config, $class) { + if( isset( $config['calls'] ) && is_array( $config['calls'] ) ) { + foreach( $config['calls'] as $calls ) { + $method = $calls[0]; + $params = $calls[1]; + call_user_func_array( array( $class, $method ), $params ); + } + } + + } + + private function addConfiguration($config, $class) { + if(empty($config['configuration'])) { + return; + } + if(!(isset($config['configuration']) && $class instanceof ConfigurableRuleInterface)) { + throw new RuleNotConfigurableException($config['class'] . ' is not configurable.'); + } + $this->addMethodCalls(array('calls' => $config['configuration']), $class); + } + +} \ No newline at end of file diff --git a/src/Configuration/Section.php b/src/Configuration/Section.php index e0e75ba..b054f47 100644 --- a/src/Configuration/Section.php +++ b/src/Configuration/Section.php @@ -1,7 +1,7 @@ rules = $rules; @@ -43,7 +43,7 @@ public function getName() { } /** - * @return Rule[] + * @return RuleInterface[] */ public function getRules() { return $this->rules; diff --git a/src/Log/Listener/ConsoleOutputListener.php b/src/Log/Listener/ConsoleOutputListener.php index 9a9a9df..c68b02a 100644 --- a/src/Log/Listener/ConsoleOutputListener.php +++ b/src/Log/Listener/ConsoleOutputListener.php @@ -1,7 +1,7 @@ messages[] = $rule->getErrorMessage() . '(' . $sectionName. ':' . $rule->getName() . ')'; diff --git a/src/Log/Listener/JunitLogListener.php b/src/Log/Listener/JunitLogListener.php index 44cd273..2c680a6 100644 --- a/src/Log/Listener/JunitLogListener.php +++ b/src/Log/Listener/JunitLogListener.php @@ -1,7 +1,7 @@ getName(); if(!isset($this->logs[$sectionName])) { $this->logs[$sectionName] = array(); diff --git a/src/Log/Listener/Listener.php b/src/Log/Listener/Listener.php index c9bcc3f..bd0f2e4 100644 --- a/src/Log/Listener/Listener.php +++ b/src/Log/Listener/Listener.php @@ -1,19 +1,19 @@ log($sectionName, $targetUrl, $rule, true); } /** * @param string $sectionName * @param string $targetUrl - * @param Rule $rule + * @param RuleInterface $rule */ - public function logFail($sectionName, $targetUrl, Rule $rule) { + public function logFail($sectionName, $targetUrl, RuleInterface $rule) { $this->log($sectionName, $targetUrl, $rule, false); } /** * @param string $sectionName * @param string $targetUrl - * @param Rule $rule + * @param RuleInterface $rule * @param Boolean $success */ - public function log($sectionName, $targetUrl, Rule $rule, $success) { + public function log($sectionName, $targetUrl, RuleInterface $rule, $success) { foreach($this->listeners as $listener) { $listener->log($sectionName, $targetUrl, $rule, $success); } diff --git a/src/Rules/ConfigurableRuleBase.php b/src/Rules/ConfigurableRuleBase.php new file mode 100644 index 0000000..6d61b39 --- /dev/null +++ b/src/Rules/ConfigurableRuleBase.php @@ -0,0 +1,21 @@ +configurableField)) { + throw new FieldNotConfigurableException("$key is not configurable"); + } + + $this->$key = $value; + + } + + +} \ No newline at end of file diff --git a/src/Rules/ConfigurableRuleInterface.php b/src/Rules/ConfigurableRuleInterface.php new file mode 100644 index 0000000..7250332 --- /dev/null +++ b/src/Rules/ConfigurableRuleInterface.php @@ -0,0 +1,8 @@ +name)) { diff --git a/src/Rules/Rule.php b/src/Rules/RuleInterface.php similarity index 91% rename from src/Rules/Rule.php rename to src/Rules/RuleInterface.php index 63c2025..b2c6684 100644 --- a/src/Rules/Rule.php +++ b/src/Rules/RuleInterface.php @@ -3,7 +3,7 @@ use Frickelbruder\KickOff\Http\HttpResponse; -interface Rule { +interface RuleInterface { public function setHttpResponse(HttpResponse $httpResponse); diff --git a/src/config/Rules.yml b/src/config/Rules.yml index 1c1e154..bd6849f 100644 --- a/src/config/Rules.yml +++ b/src/config/Rules.yml @@ -1,10 +1,17 @@ Rules: - HttpHeaderXSSProtection: + HttpHeaderXSSProtectionPresent: class: \Frickelbruder\KickOff\Rules\HttpHeaderPresent calls: - ["setName", ["HttpHeaderXSSProtection"]] - ["setHeaderToSearchFor", ["X-XSS-Protection"]] + HttpHeaderXSSProtectionSecure: + class: \Frickelbruder\KickOff\Rules\HttpHeaderHasValue + calls: + - ["setName", ["HttpHeaderXSSProtection"]] + - ["setHeaderToSearchFor", ["X-XSS-Protection"]] + - ["setValue", ["1; block"]] + HttpHeaderExposePHP: class: \Frickelbruder\KickOff\Rules\HttpHeaderNotPresent calls: @@ -53,6 +60,29 @@ Rules: - ["setExactMatch", [false]] - ["setValue", ["; HttpOnly"]] + HttpHeaderCookieWithHttpSecureFlag: + class: \Frickelbruder\KickOff\Rules\HttpHeaderHasValue + calls: + - ["setName", ["HttpHeaderCookieWithHttpOnlyFlag"]] + - ["setHeaderToSearchFor", ["Set-Cookie"]] + - ["setExactMatch", [false]] + - ["setValue", ["; Secure"]] + + #Better use HttpHeaderHSTSWithSubdomains + HttpHeaderHSTSPresent: + class: \Frickelbruder\KickOff\Rules\HttpHeaderPresent + calls: + - ["setName", ["HttpHeaderCookieWithHttpOnlyFlag"]] + - ["setHeaderToSearchFor", ["Strict-Transport-Security"]] + + HttpHeaderHSTSWithSubdomains: + class: \Frickelbruder\KickOff\Rules\HttpHeaderHasValue + calls: + - ["setName", ["HttpHeaderCookieWithHttpOnlyFlag"]] + - ["setHeaderToSearchFor", ["Strict-Transport-Security"]] + - ["setExactMatch", [false]] + - ["setValue", ["; includeSubDomains"]] + HttpHeaderCookieWithHttpOnlyFlag: class: \Frickelbruder\KickOff\Rules\HttpHeaderHasValue calls: