Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
version: '2'
services:
db:
platform: linux/amd64
image: mysql:5.7
restart: always
environment:
Expand All @@ -10,6 +11,7 @@ services:
MYSQL_PASSWORD: wordpress

wordpress:
platform: linux/amd64
depends_on:
- db
image: wordpress:latest
Expand Down
144 changes: 144 additions & 0 deletions includes/class-admin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Webfinger;

use WP_User_Query;

class Admin {

/**
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
add_action( 'show_user_profile', array( static::class, 'add_profile' ) );

// Add the save action to user's own profile editing screen update.
add_action(
'personal_options_update',
array( static::class, 'update_user_meta' )
);

// Add the save action to user profile editing screen update.
add_action(
'edit_user_profile_update',
array( static::class, 'update_user_meta' )
);

add_filter(
'user_profile_update_errors',
array( static::class, 'maybe_show_errors' ),
10,
3
);
}

/**
* Load settings template
*
* @param stdClass $user The WordPress user
*
* @return void
*/
public static function add_profile( $user ) {
load_template( dirname( __FILE__ ) . '/../templates/profile-settings.php', true, array( 'user' => $user ) );
}

/**
* The save action.
*
* @param int $user_id the ID of the current user.
*
* @return bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
public static function update_user_meta( $user_id ) {
// check that the current user have the capability to edit the $user_id
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return false;
}

// Verify nonce to prevent CSRF
if (
! isset( $_POST['webfinger_profile_nonce'] ) ||
! wp_verify_nonce( $_POST['webfinger_profile_nonce'], 'webfinger_profile_settings' )
) {
return false;
}

if ( ! isset( $_POST['webfinger_resource'] ) ) {
return false;
}
if ( empty( $_POST['webfinger_resource'] ) ) {
delete_user_meta( $user_id, 'webfinger_resource' );
return false;
}

$valid = self::is_valid_webfinger_resource( $_POST['webfinger_resource'], $user_id );

if ( ! $valid ) {
return;
}

$webfinger = sanitize_title( $_POST['webfinger_resource'], true );
Copy link

Copilot AI Aug 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct access to $_POST without nonce verification creates a CSRF vulnerability. Add nonce verification before processing the form data.

Copilot uses AI. Check for mistakes.

// create/update user meta for the $user_id
update_user_meta(
$user_id,
'webfinger_resource',
$webfinger
);

return $webfinger;
}

/**
* Check if an error should be shown
*
* @param WP_Error $errors WP_Error object (passed by reference).
* @param bool $update Whether this is a user update.
* @param stdClass $user User object (passed by reference).
*
* @return array Updated list of errors
*/
public static function maybe_show_errors( $errors, $update, $user ) {
// Verify nonce for CSRF protection
if ( ! isset( $_POST['webfinger_profile_nonce'] ) || ! wp_verify_nonce( $_POST['webfinger_profile_nonce'], 'webfinger_profile_settings' ) ) {
return $errors;
}
if ( ! isset( $_POST ) || ! isset( $_POST['webfinger_resource'] ) ) {
return $errors;
}

$valid = self::is_valid_webfinger_resource( $_POST['webfinger_resource'], $user->ID );

if ( ! $valid ) {
$errors->add( 'webfinger_resource', __( 'WebFinger resource is already in use by a different user', 'webfinger' ) );
}

return $errors;
}

/**
* Check if the WebFinger resource is valid
*
* @param string $resource The WebFinger resource
* @param int $user_id The user ID
*
* @return boolean
*/
public static function is_valid_webfinger_resource( $resource, $user_id ) {
$webfinger = sanitize_title( $resource, true );

$args = array(
'meta_key' => 'webfinger_resource',
'meta_value' => $webfinger,
'meta_compare' => '=',
'exclude' => $user_id,
);

// check if already exists
$user_query = new WP_User_Query( $args );
$results = $user_query->get_results();

return empty( $results );
}
}
43 changes: 32 additions & 11 deletions includes/class-webfinger-legacy.php → includes/class-legacy.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
<?php

namespace Webfinger;

/**
* WebFinger Legacy
* WebFinger Legacy.
*
* @author Matthias Pfefferle
*/
class Webfinger_Legacy {
class Legacy {

/**
* add query vars
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
add_action( 'query_vars', array( static::class, 'query_vars' ) );
add_filter( 'host_meta', array( static::class, 'host_meta_discovery' ) );

// host-meta recource
add_action( 'host_meta_render', array( static::class, 'render_host_meta' ), -1, 3 );

// XRD output
add_action( 'webfinger_render', array( static::class, 'render_xrd' ), 5 );

// support plugins pre 3.0.0
add_filter( 'webfinger_user_data', array( static::class, 'legacy_filter' ), 10, 3 );
}

/**
* Add query vars.
*
* @param array $vars
*
Expand All @@ -21,9 +42,9 @@ public static function query_vars( $vars ) {
}

/**
* render the XRD representation of the WordPress resource.
* Render the XRD representation of the WordPress resource.
*
* @param array $webfinger the WordPress data-array
* @param array $webfinger The WordPress data-array.
*/
public static function render_xrd( $webfinger ) {
global $wp;
Expand Down Expand Up @@ -70,8 +91,8 @@ public static function render_xrd( $webfinger ) {
exit;
}

/*
* host-meta resource feature
/**
* host-meta resource feature.
*
* @param array $query
*/
Expand All @@ -89,7 +110,7 @@ public static function render_host_meta( $format, $host_meta, $query ) {
if ( empty( $webfinger ) ) {
status_header( 404 );
header( 'Content-Type: text/plain; charset=' . get_bloginfo( 'charset' ), true );
echo 'no data for resource "' . $query['resource'] . '" found';
echo 'no data for resource "' . esc_html( $query['resource'] ) . '" found';
exit;
}

Expand All @@ -103,7 +124,7 @@ public static function render_host_meta( $format, $host_meta, $query ) {
}

/**
* add the host meta information
* Add the host-meta information.
*/
public static function host_meta_discovery( $array ) {
$array['links'][] = array(
Expand Down Expand Up @@ -132,7 +153,7 @@ public static function host_meta_discovery( $array ) {
}

/**
* recursive helper to generade the xrd-xml from the jrd array
* Recursive helper to generade the xrd-xml from the jrd array.
*
* @param string $host_meta
*
Expand Down Expand Up @@ -199,7 +220,7 @@ public static function jrd_to_xrd( $webfinger ) {
}
if ( $cascaded ) {
$xrd .= '>';
$xrd .= Webfinger_Legacy::jrd_to_xrd( $temp );
$xrd .= Legacy::jrd_to_xrd( $temp );
$xrd .= '</Link>';
} else {
$xrd .= ' />';
Expand Down
Loading