-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding X-Frame-Options/Content-Security-Policy headers #68
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3f2b5c6
Adding prevent framing feature to output X-Frame-Options
srtfisher 156da92
Adding prevent_framing feature readme
srtfisher 1510e22
Adding optional Content-Security-Policy header
srtfisher 37331bc
Add readme note about CSP
srtfisher 79f9ba7
Typo fix
srtfisher efb15cd
Throw a _doing_it_wrong() when returning an invalid value
srtfisher 1251d49
Fixing for phpcs
srtfisher 9c19931
Test that the REST API is applied too
srtfisher 4906ef2
Merge branch 'main' into feature/66-x-frame
srtfisher File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/alley/wp/alleyvate/features/class-prevent-framing.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<?php | ||
/** | ||
* Class file for Prevent_Framing | ||
* | ||
* (c) Alley <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* | ||
* @package wp-alleyvate | ||
*/ | ||
|
||
namespace Alley\WP\Alleyvate\Features; | ||
|
||
use Alley\WP\Alleyvate\Feature; | ||
|
||
/** | ||
* Headers to prevent iframe-ing of the site. | ||
* | ||
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options | ||
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy | ||
*/ | ||
final class Prevent_Framing implements Feature { | ||
/** | ||
* Boot the feature. | ||
*/ | ||
public function boot(): void { | ||
add_filter( 'wp_headers', [ self::class, 'filter__wp_headers' ] ); | ||
} | ||
|
||
/** | ||
* Output the X-Frame-Options header to prevent sites from being able to be iframe'd. | ||
* | ||
* @param array $headers The headers to be sent. | ||
* @return array The headers to be sent. | ||
*/ | ||
public static function filter__wp_headers( $headers ): array { | ||
if ( ! \is_array( $headers ) ) { | ||
$headers = []; | ||
} | ||
|
||
/** | ||
* Optionally allow the Content-Security-Policy header to be used | ||
* instead of X-Frame-Options. | ||
* | ||
* The Content-Security-Policy header obsoletes the X-Frame-Options | ||
* header when used. | ||
*/ | ||
if ( apply_filters( 'alleyvate_prevent_framing_csp', false ) ) { | ||
if ( isset( $headers['Content-Security-Policy'] ) ) { | ||
return $headers; | ||
} | ||
|
||
$headers['Content-Security-Policy'] = self::get_content_security_policy_header(); | ||
|
||
return $headers; | ||
} | ||
|
||
if ( isset( $headers['X-Frame-Options'] ) ) { | ||
return $headers; | ||
} | ||
|
||
/** | ||
* Allow the X-Frame-Options header to be disabled. | ||
* | ||
* @param bool $prevent_framing Whether to prevent framing. Default false. | ||
*/ | ||
if ( apply_filters( 'alleyvate_prevent_framing_disable', false ) ) { | ||
return $headers; | ||
} | ||
|
||
/** | ||
* Filter the X-Frame-Options header value. | ||
* | ||
* The header can return DENY, SAMEORIGIN, or ALLOW-FROM uri. | ||
* | ||
* @param string $value The value of the X-Frame-Options header. Default SAMEORIGIN. | ||
*/ | ||
$headers['X-Frame-Options'] = apply_filters( 'alleyvate_prevent_framing_x_frame_options', 'SAMEORIGIN' ); | ||
|
||
if ( ! \in_array( $headers['X-Frame-Options'], [ 'DENY', 'SAMEORIGIN' ], true ) && 0 !== strpos( $headers['X-Frame-Options'], 'ALLOW-FROM' ) ) { | ||
_doing_it_wrong( | ||
__METHOD__, | ||
sprintf( | ||
/* translators: %s: The value of the X-Frame-Options header. */ | ||
esc_html__( 'Invalid value for %s. Must be DENY, SAMEORIGIN, or ALLOW-FROM uri.', 'alley' ), | ||
'X-Frame-Options' | ||
), | ||
'2.4.0' | ||
); | ||
} | ||
|
||
return $headers; | ||
} | ||
|
||
/** | ||
* Get the Content-Security-Policy header value. | ||
* | ||
* @return string | ||
*/ | ||
protected static function get_content_security_policy_header(): string { | ||
/** | ||
* Filter the Content-Security-Policy header ancestors. | ||
* | ||
* @param array<string> $frame_ancestors The frame ancestors. Default ['\'self\'']. | ||
*/ | ||
$frame_ancestors = apply_filters( | ||
'alleyvate_prevent_framing_csp_frame_ancestors', | ||
[ | ||
'\'self\'', | ||
] | ||
); | ||
|
||
/** | ||
* Filter the value of the Content-Security-Policy header. | ||
* | ||
* @param string $value The value of the Content-Security-Policy header. Defaults to 'frame-ancestors \'self\'' | ||
*/ | ||
return apply_filters( | ||
'alleyvate_prevent_framing_csp_header', | ||
'frame-ancestors ' . implode( ' ', $frame_ancestors ) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
168 changes: 168 additions & 0 deletions
168
tests/alley/wp/alleyvate/features/test-prevent-framing.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
<?php | ||
/** | ||
* Class file for Test_Prevent_Framing | ||
* | ||
* (c) Alley <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* | ||
* @package wp-alleyvate | ||
*/ | ||
|
||
namespace Alley\WP\Alleyvate\Features; | ||
|
||
use Alley\WP\Alleyvate\Feature; | ||
use Mantle\Testkit\Test_Case; | ||
|
||
/** | ||
* Tests for the preventing the iframing of a site. | ||
*/ | ||
final class Test_Prevent_Framing extends Test_Case { | ||
use \Mantle\Testing\Concerns\Refresh_Database; | ||
|
||
/** | ||
* Feature instance. | ||
* | ||
* @var Feature | ||
*/ | ||
private Feature $feature; | ||
|
||
/** | ||
* Set up. | ||
*/ | ||
protected function setUp(): void { | ||
parent::setUp(); | ||
|
||
$this->feature = new Prevent_Framing(); | ||
} | ||
|
||
/** | ||
* Test that the X-Frame-Options header is output. | ||
*/ | ||
public function test_x_frame_options_header(): void { | ||
$this->expectApplied( 'alleyvate_prevent_framing_x_frame_options' )->andReturnString(); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'X-Frame-Options', 'SAMEORIGIN' ); | ||
$this->get( '/wp-json/wp/v2/posts' )->assertHeader( 'X-Frame-Options', 'SAMEORIGIN' ); | ||
} | ||
|
||
/** | ||
* Test that the X-Frame-Options header can be filtered. | ||
*/ | ||
public function test_filter_x_frame_options_header(): void { | ||
add_filter( 'alleyvate_prevent_framing_x_frame_options', fn () => 'DENY' ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'X-Frame-Options', 'DENY' ); | ||
} | ||
|
||
/** | ||
* Test that the X-Frame-Options header can be filtered to an invalid value | ||
* while throwing a _doing_it_wrong() notice. | ||
*/ | ||
public function test_filter_x_frame_options_invalid_header(): void { | ||
$this->setExpectedIncorrectUsage( Prevent_Framing::class . '::filter__wp_headers' ); | ||
|
||
add_filter( 'alleyvate_prevent_framing_x_frame_options', fn () => 'INVALID' ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'X-Frame-Options', 'INVALID' ); | ||
} | ||
|
||
/** | ||
* Test that the X-Frame-Options header is not output if it already exists. | ||
*/ | ||
public function test_x_frame_options_header_already_exists(): void { | ||
add_filter( 'wp_headers', fn () => [ 'X-Frame-Options' => 'CUSTOM' ] ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'X-Frame-Options', 'CUSTOM' ); | ||
} | ||
|
||
/** | ||
* Test that the X-Frame-Options header is not output if the feature is disabled. | ||
*/ | ||
public function test_x_frame_options_header_disabled(): void { | ||
add_filter( 'alleyvate_prevent_framing_disable', fn () => true ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeaderMissing( 'X-Frame-Options' ); | ||
} | ||
|
||
/** | ||
* Test that the CSP header is not output unless enabled. | ||
*/ | ||
public function test_csp_header_default(): void { | ||
$this->expectApplied( 'alleyvate_prevent_framing_csp' )->andReturnFalse(); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeaderMissing( 'Content-Security-Policy' ); | ||
} | ||
|
||
/** | ||
* Test the default value of the CSP header when enabled. | ||
*/ | ||
public function test_csp_header_enabled(): void { | ||
$this->expectApplied( 'alleyvate_prevent_framing_csp' )->andReturnTrue(); | ||
|
||
add_filter( 'alleyvate_prevent_framing_csp', fn () => true ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'Content-Security-Policy', "frame-ancestors 'self'" ); | ||
} | ||
|
||
/** | ||
* Test that the CSP header is not output if it already exists. | ||
*/ | ||
public function test_csp_header_already_exists(): void { | ||
add_filter( 'wp_headers', fn () => [ 'Content-Security-Policy' => 'CUSTOM' ] ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'Content-Security-Policy', 'CUSTOM' ); | ||
} | ||
|
||
/** | ||
* Test the CSP header with custom frame ancestors. | ||
*/ | ||
public function test_csp_header_custom_frame_ancestors(): void { | ||
$this->expectApplied( 'alleyvate_prevent_framing_csp_frame_ancestors' )->andReturnArray(); | ||
|
||
add_filter( 'alleyvate_prevent_framing_csp', fn () => true ); | ||
|
||
add_filter( | ||
'alleyvate_prevent_framing_csp_frame_ancestors', | ||
fn () => [ | ||
'example.com', | ||
'example.org', | ||
] | ||
); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'Content-Security-Policy', 'frame-ancestors example.com example.org' ); | ||
} | ||
|
||
/** | ||
* Test the CSP header being overridden completely. | ||
*/ | ||
public function test_csp_header_override(): void { | ||
$this->expectApplied( 'alleyvate_prevent_framing_csp_header' )->andReturnString(); | ||
|
||
add_filter( 'alleyvate_prevent_framing_csp', fn () => true ); | ||
add_filter( 'alleyvate_prevent_framing_csp_header', fn () => 'custom-value' ); | ||
|
||
$this->feature->boot(); | ||
|
||
$this->get( '/' )->assertHeader( 'Content-Security-Policy', 'custom-value' ); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you need it even though you are creating no post?