Skip to content
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

CDPT-2368 Fix sentry errors #298

Merged
merged 17 commits into from
Dec 13, 2024
Merged
5 changes: 5 additions & 0 deletions public/app/mu-plugins/moj-auth/verify.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
http_response_code(401) && exit();
}

if (isset($_ENV['MOJ_AUTH_ENABLED']) && $_ENV['MOJ_AUTH_ENABLED'] === 'false') {
error_log('MOJ_AUTH_ENABLED is false - skipping auth check.');
http_response_code(200) && exit();
}

require_once $autoload;
require_once 'traits/jwt.php';
require_once 'traits/utils.php';
Expand Down
98 changes: 79 additions & 19 deletions public/app/themes/justice/inc/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,28 @@ public function __construct()
public function addHooks()
{
// Add a rewrite rule to handle an empty search.
add_action('init', fn () => add_rewrite_rule('search/?$', 'index.php?s=', 'bottom'));
add_action('init', fn() => add_rewrite_rule('search/?$', 'index.php?s=', 'bottom'));
// Add a rewrite rule to handle the old search urls.
add_action('template_redirect', [$this, 'redirectOldSearchUrls']);
// Add a rewrite rule to handle the search string.
add_filter('posts_search', [$this, 'handleEmptySearch'], 10, 2);
// Add a query var for the parent page. This will be handled in relevanssiParentFilter.
add_filter('query_vars', fn ($qv) => array_merge($qv, array('parent')));
add_filter('query_vars', fn($qv) => array_merge($qv, array('parent')));
// Update the search query.
add_action('pre_get_posts', [$this, 'searchFilter']);

// Relevanssi - prevent sending documents to the Relevanssi API.
add_filter('option_relevanssi_do_not_call_home', fn () => 'on');
add_filter('default_option_relevanssi_do_not_call_home', fn () => 'on');
add_filter('option_relevanssi_do_not_call_home', fn() => 'on');
add_filter('default_option_relevanssi_do_not_call_home', fn() => 'on');

// Relevanssi - prevent click tracking. We don't need it and it makes the search results url messy.
add_filter('option_relevanssi_click_tracking', fn () => 'off');
add_filter('default_option_relevanssi_click_tracking', fn () => 'off');
add_filter('option_relevanssi_click_tracking', fn() => 'off');
add_filter('default_option_relevanssi_click_tracking', fn() => 'off');

// Relevanssi - filters the did you mean url, to use /search instead of s=.
add_filter('relevanssi_didyoumean_url', [$this, 'didYouMeanUrl'], 10, 3);
// Relevanssi - add numbers to the did you mean alphabet.
add_filter('relevanssi_didyoumean_alphabet', fn ($alphabet) => $alphabet . '0123456789');
add_filter('relevanssi_didyoumean_alphabet', fn($alphabet) => $alphabet . '0123456789');

// Relevanssi - filters the search results to only include the descendants.
add_filter('relevanssi_hits_filter', [$this, 'relevanssiParentFilter']);
Expand All @@ -47,14 +47,19 @@ public function addHooks()

// Relevanssi - remove searches submenus for non-admins.
add_filter('admin_menu', [$this, 'removeSearchesSubMenus'], 999);

// Redirect the user to the search page if the URI contains multiple pages.
add_action('init', [$this, 'redirectMultiplePageInURI'], 1);

// Redirect the user to the search page if there are arrays in the the query string.
add_action('init', [$this, 'redirectIfQueryStringHasArrays'], 1);
}

/**
* Check if the search query is empty.
*
* @return bool True if the search query is empty, false otherwise.
*/

public function hasEmptyQuery(): bool
{
return empty(get_search_query());
Expand All @@ -65,7 +70,6 @@ public function hasEmptyQuery(): bool
*
* @return int|null The number of search results.
*/

public function getResultCount(): ?int
{
if (empty(get_search_query())) {
Expand All @@ -83,7 +87,6 @@ public function getResultCount(): ?int
* @param array $args An array of query parameters to add or modify.
* @return string The URL for the search results.
*/

public function getSearchUrl($search, $args = [])
{
$url_append = '';
Expand Down Expand Up @@ -121,7 +124,6 @@ public function getSearchUrl($search, $args = [])
*
* @return array An array of sort options.
*/

public function getSortOptions(): array
{
$orderby = get_query_var('orderby');
Expand All @@ -145,7 +147,6 @@ public function getSortOptions(): array
*
* @return void
*/

public function redirectOldSearchUrls()
{
// Don't redirect if we're in the admin.
Expand Down Expand Up @@ -178,7 +179,6 @@ public function redirectOldSearchUrls()
* @param \WP_Query $q The main WordPress query.
* @return string The modified search query.
*/

public function handleEmptySearch($search, \WP_Query $q)
{
if (!is_admin() && empty($search) && $q->is_search() && $q->is_main_query()) {
Expand All @@ -197,7 +197,6 @@ public function handleEmptySearch($search, \WP_Query $q)
* @param \WP_Query $query The main WordPress query.
* @return void
*/

public function searchFilter($query)
{
if (!is_admin() && $query->is_main_query() && $query->is_search) {
Expand All @@ -211,7 +210,6 @@ public function searchFilter($query)
* @param string $url The URL to format.
* @return string The formatted URL.
*/

public function formattedUrl(string $url): string
{
$split_length = 80;
Expand Down Expand Up @@ -241,7 +239,6 @@ public function formattedUrl(string $url): string
* @param string $suggestion The suggested search query.
* @return string The filtered URL.
*/

public function didYouMeanUrl($url, $query, $suggestion): string
{
return empty($suggestion) ? $url : $this->getSearchUrl($suggestion);
Expand All @@ -257,7 +254,6 @@ public function didYouMeanUrl($url, $query, $suggestion): string
* @param array $hits The search results.
* @return array The filtered search results.
*/

public function relevanssiParentFilter(array $hits): array
{
global $wp_query;
Expand Down Expand Up @@ -300,7 +296,6 @@ public function relevanssiParentFilter(array $hits): array
* @param array $columns The columns for the admin screen.
* @return array The columns after removing any un-necessary ones.
*/

public function removeColumns(array $columns): array
{
if (!current_user_can('manage_options')) {
Expand All @@ -319,12 +314,77 @@ public function removeColumns(array $columns): array
*
* @return void
*/

public function removeSearchesSubMenus()
{
if (!current_user_can('manage_options')) {
remove_submenu_page('index.php', 'relevanssi-premium/relevanssi.php');
remove_submenu_page('index.php', 'relevanssi_admin_search');
}
}

/**
* Handle malformed search URLs where the path has multiple pages.
*
* e.g /search/the/page/page/11
*
* @return void
*/
public function redirectMultiplePageInURI(): void
{
// Trim the first and last slash
$uri = trim($_SERVER['REQUEST_URI'], '/');
// Split the URI by '/'
$uri_parts = explode('/', $uri);

// Check if the URI has at least 4 parts and the first part is 'search'
if (sizeof($uri_parts) < 4 || $uri_parts[0] !== 'search') {
return;
}

// Remove the first 2 from the array
$uri_parts = array_slice($uri_parts, 2);

// Count the number of times 'page' appears in the $uri_parts
$pages_count = array_count_values($uri_parts)['page'];

if ($pages_count > 1) {
// Redirect to the search page
$url = home_url('/search');
wp_redirect($url);
exit;
}
}


/**
* Handle malformed search URLs with arrays in the query string.
*
* This function will redirect the user to the search page if the query string contains arrays.
* e.g. /search?audience[$testing]=1 or /search?audience%5B%24testing%5D=1
*
EmilyHazlehurst marked this conversation as resolved.
Show resolved Hide resolved
* @return void
*/
public function redirectIfQueryStringHasArrays(): void
{
// Are we on a search page? The URI starts with /search
if (strpos($_SERVER['REQUEST_URI'], '/search') === false) {
return;
}

$query_string = explode('&', $_SERVER['QUERY_STRING'] ?? '');

foreach ($query_string as $query) {
error_log($query);
// Get key and value
[$key] = explode('=', $query);

// Use regex to see if the key contains any of the invalid strings
if (preg_match('/(%5B|%5D|\[|\])/', $key)) {
// Redirect to the search page
$url = home_url('/search');
wp_redirect($url);
exit;
}
}
}
}
81 changes: 79 additions & 2 deletions public/app/themes/justice/inc/security.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,63 @@

namespace MOJ\Justice;

use WP_Error;

/**
* Add a little security for WordPress
*/
class Security
{

private $wp_version;
private $hashed_wp_version;

/**
* Loads up actions that are called when WordPress initialises
* Set properties and run actions.
*/
public function __construct()
{
// Get the WordPress version.
$this->wp_version = get_bloginfo('version');
// Hash the WP version number with a salt - let's borrow AUTH_SALT for this.
// This way a we get a unique hash per WP version but it's not reversible.
$this->hashed_wp_version = substr(hash('sha256', $this->wp_version . AUTH_SALT), 0, 6);

$this->actions();
}

/**
* Loads up actions that are called when WordPress initialises
*
* @return void
*/
public function actions(): void
{
// no generator meta tag in the head
// No generator meta tag in the head
remove_action('wp_head', 'wp_generator');

add_filter('redirect_canonical', [$this, 'noRedirect404']);
add_filter('xmlrpc_enabled', '__return_false');
add_filter('wp_headers', [$this, 'headerMods']);
add_filter('auth_cookie_expiration', [$this, 'setLoginPeriod'], 10, 0);

// Handle malformed URLs with arrays in the query string.
add_filter('login_init', [$this, 'validateLoginRequest'], 10, 0);

// Remove emoji support.
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');

// Strip the WP version number from enqueued asset URLs.
add_filter('style_loader_tag', [$this, 'filterAssetQueryString'], 10, 1);
// change the url with script_loader_tag
add_filter('script_loader_tag', [$this, 'filterAssetQueryString'], 10, 1);

// Hide the WP version number from the feeds.
add_filter('the_generator', '__return_empty_string');

// Disable REST API for non-logged in users.
add_filter('rest_authentication_errors', [$this, 'restAuth']);
}

/**
Expand Down Expand Up @@ -69,4 +102,48 @@ public function setLoginPeriod(): float|int
{
return 7 * DAY_IN_SECONDS; // Cookies set to expire in 7 days.
}

/**
* Change the URL of the script or style tags.
*
* @see https://developer.wordpress.org/reference/hooks/style_loader_tag/
*
* @param $tag string The HTML string of a link or script tag.
* @return string The modified HTML string.
*/
public function filterAssetQueryString(string $tag): string
{
return str_replace('ver=' . $this->wp_version, 'ver=' . $this->hashed_wp_version, $tag);
}

/**
* Disable REST API for non-logged in users.
*
* @see https://developer.wordpress.org/reference/hooks/rest_authentication_errors/
*
* @param WP_Error|null|true $result
* @return WP_Error|null|true
*/
EarthlingDavey marked this conversation as resolved.
Show resolved Hide resolved
public function restAuth(WP_Error|null|true $result): WP_Error|null|true
{
// If a previous authentication check was applied,
// pass that result along without modification.
if (true === $result || is_wp_error($result)) {
return $result;
}

// No authentication has been performed yet.
// Return an error if user is not logged in.
if (! is_user_logged_in()) {
return new WP_Error(
'rest_not_logged_in',
__('You are not currently logged in.'),
array('status' => 401)
);
}

// Our custom authentication check should have no effect
// on logged-in requests
return $result;
}
}
Loading