From 8e4e2b53730d13bccd6976279a1c54626edf1e4c Mon Sep 17 00:00:00 2001 From: David Shanske Date: Mon, 17 Jun 2024 21:56:22 +0000 Subject: [PATCH 1/4] Add preliminary support for json metadata client discovery --- includes/class-indieauth-client-discovery.php | 87 +++++++++++-------- indieauth.php | 2 +- languages/indieauth.pot | 15 ++-- readme.md | 4 +- readme.txt | 4 +- 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/includes/class-indieauth-client-discovery.php b/includes/class-indieauth-client-discovery.php index 5fc0e66..1714f61 100644 --- a/includes/class-indieauth-client-discovery.php +++ b/includes/class-indieauth-client-discovery.php @@ -5,6 +5,7 @@ class IndieAuth_Client_Discovery { protected $manifest = array(); protected $html = array(); protected $mf2 = array(); + protected $json = array(); public $client_id = ''; public $client_name = ''; public $client_icon = ''; @@ -74,45 +75,63 @@ private function parse( $url ) { return $response; } - $content = wp_remote_retrieve_body( $response ); - - if ( class_exists( 'Masterminds\\HTML5' ) ) { - $domdocument = new \Masterminds\HTML5( array( 'disable_html_ns' => true ) ); - $domdocument = $domdocument->loadHTML( $content ); - } else { - $domdocument = new DOMDocument(); - libxml_use_internal_errors( true ); - if ( function_exists( 'mb_convert_encoding' ) ) { - $content = mb_convert_encoding( $content, 'HTML-ENTITIES', mb_detect_encoding( $content ) ); + $content_type = wp_remote_retrieve_header( $response, 'content-type' ); + if ( 'application/json' === $content_type ) { + $json = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! is_array( $json ) && empty( $json ) ) { + return; } - $domdocument->loadHTML( $content ); - libxml_use_internal_errors( false ); - } - - $this->get_mf2( $domdocument, $url ); - if ( ! empty( $this->mf2 ) ) { - if ( array_key_exists( 'name', $this->mf2 ) ) { - $this->client_name = $this->mf2['name'][0]; + if ( ! array_key_exists( 'client_id', $json ) ) { + return; + } + $this->client_id = $json['client_id']; + if ( array_key_exists( 'client_name', $json ) ) { + $this->client_name = $json['client_name']; + } + if ( array_key_exists( 'logo_uri', $json ) ) { + $this->client_icon = $json['logo_uri']; } - if ( array_key_exists( 'logo', $this->mf2 ) ) { - if ( is_string( $this->mf2['logo'][0] ) ) { - $this->client_icon = $this->mf2['logo'][0]; - } else { - $this->client_icon = $this->mf2['logo'][0]['value']; + } elseif ( 'text/html' === $content_type ) { + $content = wp_remote_retrieve_body( $response ); + + if ( class_exists( 'Masterminds\\HTML5' ) ) { + $domdocument = new \Masterminds\HTML5( array( 'disable_html_ns' => true ) ); + $domdocument = $domdocument->loadHTML( $content ); + } else { + $domdocument = new DOMDocument(); + libxml_use_internal_errors( true ); + if ( function_exists( 'mb_convert_encoding' ) ) { + $content = mb_convert_encoding( $content, 'HTML-ENTITIES', mb_detect_encoding( $content ) ); } + $domdocument->loadHTML( $content ); + libxml_use_internal_errors( false ); } - } elseif ( isset( $this->rels['manifest'] ) ) { - self::get_manifest( $this->rels['manifest'] ); - $this->client_icon = $this->determine_icon( $this->manifest ); - $this->client_name = $this->manifest['name']; - } else { - $this->client_icon = $this->determine_icon( $this->rels ); - $this->get_html( $domdocument ); - $this->client_name = $this->html['title']; - } - if ( ! empty( $this->client_icon ) ) { - $this->client_icon = WP_Http::make_absolute_url( $this->client_icon, $url ); + $this->get_mf2( $domdocument, $url ); + if ( ! empty( $this->mf2 ) ) { + if ( array_key_exists( 'name', $this->mf2 ) ) { + $this->client_name = $this->mf2['name'][0]; + } + if ( array_key_exists( 'logo', $this->mf2 ) ) { + if ( is_string( $this->mf2['logo'][0] ) ) { + $this->client_icon = $this->mf2['logo'][0]; + } else { + $this->client_icon = $this->mf2['logo'][0]['value']; + } + } + } elseif ( isset( $this->rels['manifest'] ) ) { + self::get_manifest( $this->rels['manifest'] ); + $this->client_icon = $this->determine_icon( $this->manifest ); + $this->client_name = $this->manifest['name']; + } else { + $this->client_icon = $this->determine_icon( $this->rels ); + $this->get_html( $domdocument ); + $this->client_name = $this->html['title']; + } + + if ( ! empty( $this->client_icon ) ) { + $this->client_icon = WP_Http::make_absolute_url( $this->client_icon, $url ); + } } } diff --git a/indieauth.php b/indieauth.php index aa7319a..ae2f6ba 100644 --- a/indieauth.php +++ b/indieauth.php @@ -3,7 +3,7 @@ * Plugin Name: IndieAuth * Plugin URI: https://github.com/indieweb/wordpress-indieauth/ * Description: IndieAuth is a way to allow users to use their own domain to sign into other websites and services - * Version: 4.4.2 + * Version: 4.4.3 * Author: IndieWeb WordPress Outreach Club * Author URI: https://indieweb.org/WordPress_Outreach_Club * License: MIT diff --git a/languages/indieauth.pot b/languages/indieauth.pot index 2ff7a33..7970c68 100644 --- a/languages/indieauth.pot +++ b/languages/indieauth.pot @@ -1,11 +1,10 @@ -# Copyright (C) 2024 IndieWebCamp WordPress Outreach Club +# Copyright (C) 2024 IndieWeb WordPress Outreach Club # This file is distributed under the MIT. msgid "" msgstr "" -"Project-Id-Version: IndieAuth 4.4.2\n" -"Report-Msgid-Bugs-To: " -"https://wordpress.org/support/plugin/wordpress-indieauth\n" -"POT-Creation-Date: 2024-01-12 20:14:40+00:00\n" +"Project-Id-Version: IndieAuth 4.4.3\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/indieauth\n" +"POT-Creation-Date: 2024-06-17 21:55:46+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -376,11 +375,11 @@ msgstr "" msgid "Invalid access token" msgstr "" -#: includes/class-indieauth-client-discovery.php:33 +#: includes/class-indieauth-client-discovery.php:34 msgid "Failed to Retrieve IndieAuth Client Details " msgstr "" -#: includes/class-indieauth-client-discovery.php:63 +#: includes/class-indieauth-client-discovery.php:64 msgid "Failed to Retrieve Client Details" msgstr "" @@ -900,7 +899,7 @@ msgid "" msgstr "" #. Author of the plugin/theme -msgid "IndieWebCamp WordPress Outreach Club" +msgid "IndieWeb WordPress Outreach Club" msgstr "" #. Author URI of the plugin/theme diff --git a/readme.md b/readme.md index c10311a..fb86142 100644 --- a/readme.md +++ b/readme.md @@ -3,8 +3,8 @@ **Tags:** IndieAuth, IndieWeb, IndieWebCamp, login **Requires at least:** 4.9.9 **Requires PHP:** 5.6 -**Tested up to:** 6.4 -**Stable tag:** 4.4.2 +**Tested up to:** 6.5 +**Stable tag:** 4.4.3 **License:** MIT **License URI:** http://opensource.org/licenses/MIT **Donate link:** https://opencollective.com/indieweb diff --git a/readme.txt b/readme.txt index 43c8466..095e1d1 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: indieweb, pfefferle, dshanske Tags: IndieAuth, IndieWeb, IndieWebCamp, login Requires at least: 4.9.9 Requires PHP: 5.6 -Tested up to: 6.4 -Stable tag: 4.4.2 +Tested up to: 6.5 +Stable tag: 4.4.3 License: MIT License URI: http://opensource.org/licenses/MIT Donate link: https://opencollective.com/indieweb From 9b209f721bd297b0971f2b94eede5b217ed9592d Mon Sep 17 00:00:00 2001 From: David Shanske Date: Sat, 29 Jun 2024 05:12:01 +0000 Subject: [PATCH 2/4] Remove manifest support as experimental and clean up discovery code. --- includes/class-indieauth-client-discovery.php | 77 ++++++++----------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/includes/class-indieauth-client-discovery.php b/includes/class-indieauth-client-discovery.php index 1714f61..eaaccca 100644 --- a/includes/class-indieauth-client-discovery.php +++ b/includes/class-indieauth-client-discovery.php @@ -2,7 +2,6 @@ class IndieAuth_Client_Discovery { protected $rels = array(); - protected $manifest = array(); protected $html = array(); protected $mf2 = array(); protected $json = array(); @@ -25,7 +24,11 @@ public function __construct( $client_id ) { ); // If this is an IP address on the donotfetch list then do not fetch. - if ( ( $ip && ! in_array( $ip, $donotfetch, true ) || 'localhost' === wp_parse_url( $client_id, PHP_URL_HOST ) ) ) { + if ( $ip && ! in_array( $ip, $donotfetch, true ) ) { + return; + } + + if ( 'localhost' === wp_parse_url( $client_id, PHP_URL_HOST ) ) { return; } @@ -38,10 +41,10 @@ public function __construct( $client_id ) { public function export() { return array( - 'manifest' => $this->manifest, 'rels' => $this->rels, 'mf2' => $this->mf2, 'html' => $this->html, + 'json' => $this->json, 'client_id' => $this->client_id, 'client_name' => $this->client_name, 'client_icon' => $this->client_icon, @@ -77,36 +80,29 @@ private function parse( $url ) { $content_type = wp_remote_retrieve_header( $response, 'content-type' ); if ( 'application/json' === $content_type ) { - $json = json_decode( wp_remote_retrieve_body( $response ), true ); - if ( ! is_array( $json ) && empty( $json ) ) { - return; + $this->json = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! is_array( $this->json ) || empty( $this->json ) ) { + return new WP_Error( 'empty_json', __( 'Discovery Has Returned an Empty JSON Document', 'indieauth' ) ); } - if ( ! array_key_exists( 'client_id', $json ) ) { - return; + if ( ! array_key_exists( 'client_id', $this->json ) ) { + return new WP_Error( 'missing_client_id', __( 'No Client ID Found in JSON Client Metadata', 'indieauth' ) ); } - $this->client_id = $json['client_id']; - if ( array_key_exists( 'client_name', $json ) ) { - $this->client_name = $json['client_name']; + $this->client_id = $this->json['client_id']; + if ( array_key_exists( 'client_name', $this->json ) ) { + $this->client_name = $this->json['client_name']; } - if ( array_key_exists( 'logo_uri', $json ) ) { - $this->client_icon = $json['logo_uri']; + if ( array_key_exists( 'logo_uri', $this->json ) ) { + $this->client_icon = $this->json['logo_uri']; } } elseif ( 'text/html' === $content_type ) { - $content = wp_remote_retrieve_body( $response ); - - if ( class_exists( 'Masterminds\\HTML5' ) ) { - $domdocument = new \Masterminds\HTML5( array( 'disable_html_ns' => true ) ); - $domdocument = $domdocument->loadHTML( $content ); - } else { - $domdocument = new DOMDocument(); - libxml_use_internal_errors( true ); - if ( function_exists( 'mb_convert_encoding' ) ) { - $content = mb_convert_encoding( $content, 'HTML-ENTITIES', mb_detect_encoding( $content ) ); - } - $domdocument->loadHTML( $content ); - libxml_use_internal_errors( false ); + $content = wp_remote_retrieve_body( $response ); + $domdocument = new DOMDocument(); + libxml_use_internal_errors( true ); + if ( function_exists( 'mb_convert_encoding' ) ) { + $content = mb_convert_encoding( $content, 'HTML-ENTITIES', mb_detect_encoding( $content ) ); } - + $domdocument->loadHTML( $content ); + libxml_use_internal_errors( false ); $this->get_mf2( $domdocument, $url ); if ( ! empty( $this->mf2 ) ) { if ( array_key_exists( 'name', $this->mf2 ) ) { @@ -119,10 +115,6 @@ private function parse( $url ) { $this->client_icon = $this->mf2['logo'][0]['value']; } } - } elseif ( isset( $this->rels['manifest'] ) ) { - self::get_manifest( $this->rels['manifest'] ); - $this->client_icon = $this->determine_icon( $this->manifest ); - $this->client_name = $this->manifest['name']; } else { $this->client_icon = $this->determine_icon( $this->rels ); $this->get_html( $domdocument ); @@ -141,7 +133,7 @@ private function get_mf2( $input, $url ) { } $mf = Mf2\parse( $input, $url ); if ( array_key_exists( 'rels', $mf ) ) { - $this->rels = wp_array_slice_assoc( $mf['rels'], array( 'apple-touch-icon', 'icon', 'mask-icon', 'manifest' ) ); + $this->rels = wp_array_slice_assoc( $mf['rels'], array( 'apple-touch-icon', 'icon', 'mask-icon' ) ); } if ( array_key_exists( 'items', $mf ) ) { foreach ( $mf['items'] as $item ) { @@ -153,23 +145,14 @@ private function get_mf2( $input, $url ) { } } - private function get_manifest( $url ) { - if ( is_array( $url ) ) { - $url = $url[0]; - } - $response = self::fetch( $url ); - if ( is_wp_error( $response ) ) { - return $response; - } - $this->manifest = json_decode( wp_remote_retrieve_body( $response ), true ); - } - private function get_html( $input ) { - if ( ! $input ) { - return; - } $xpath = new DOMXPath( $input ); - $this->html['title'] = $xpath->query( '//title' )->item( 0 )->textContent; + if ( ! empty( $xpath ) ) { + $title = $xpath->query( '//title' ); + if ( ! empty( $title ) ) { + $this->html['title'] = $title->item( 0 )->textContent; + } + } } private function ifset( $array, $key, $default = false ) { From 70ca20e8a546ccf40123b1987f9c2fbaab644211 Mon Sep 17 00:00:00 2001 From: David Shanske Date: Sat, 29 Jun 2024 05:18:01 +0000 Subject: [PATCH 3/4] Add client URI support --- includes/class-indieauth-client-discovery.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/includes/class-indieauth-client-discovery.php b/includes/class-indieauth-client-discovery.php index eaaccca..0743d29 100644 --- a/includes/class-indieauth-client-discovery.php +++ b/includes/class-indieauth-client-discovery.php @@ -8,6 +8,7 @@ class IndieAuth_Client_Discovery { public $client_id = ''; public $client_name = ''; public $client_icon = ''; + public $client_uri = ''; public function __construct( $client_id ) { $this->client_id = $client_id; @@ -48,6 +49,7 @@ public function export() { 'client_id' => $this->client_id, 'client_name' => $this->client_name, 'client_icon' => $this->client_icon, + 'client_uri' => $this->client_uri, ); } @@ -94,6 +96,9 @@ private function parse( $url ) { if ( array_key_exists( 'logo_uri', $this->json ) ) { $this->client_icon = $this->json['logo_uri']; } + if ( array_key_exists( 'client_uri', $this->json ) ) { + $this->client_uri = $this->json['client_uri']; + } } elseif ( 'text/html' === $content_type ) { $content = wp_remote_retrieve_body( $response ); $domdocument = new DOMDocument(); From 15e4f6fdf15d9fed9d3ba2a1a994981f90ef7b26 Mon Sep 17 00:00:00 2001 From: David Shanske Date: Sat, 29 Jun 2024 15:02:42 +0000 Subject: [PATCH 4/4] Add docblock noting JSON elements defined in spec --- includes/class-indieauth-client-discovery.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/includes/class-indieauth-client-discovery.php b/includes/class-indieauth-client-discovery.php index 0743d29..43568e3 100644 --- a/includes/class-indieauth-client-discovery.php +++ b/includes/class-indieauth-client-discovery.php @@ -8,7 +8,7 @@ class IndieAuth_Client_Discovery { public $client_id = ''; public $client_name = ''; public $client_icon = ''; - public $client_uri = ''; + public $client_uri = ''; public function __construct( $client_id ) { $this->client_id = $client_id; @@ -83,6 +83,19 @@ private function parse( $url ) { $content_type = wp_remote_retrieve_header( $response, 'content-type' ); if ( 'application/json' === $content_type ) { $this->json = json_decode( wp_remote_retrieve_body( $response ), true ); + /** + * Expected format is per the IndieAuth standard as revised 2024-06-23 to include a JSON Client Metadata File + * + * @param array $json { + * An array of metadata about a client + * + * @type string $client_uri URL of a webpage providing information about the client. + * @type string $client_id The client identifier. + * @type string $client_name Human Readable Name of the Client. Optional. + * @type string $logo_uri URL that references a logo or icon for the client. Optional. + * @type array $redirect_uris An array of redirect URIs. Optional. + * } + **/ if ( ! is_array( $this->json ) || empty( $this->json ) ) { return new WP_Error( 'empty_json', __( 'Discovery Has Returned an Empty JSON Document', 'indieauth' ) ); } @@ -151,7 +164,7 @@ private function get_mf2( $input, $url ) { } private function get_html( $input ) { - $xpath = new DOMXPath( $input ); + $xpath = new DOMXPath( $input ); if ( ! empty( $xpath ) ) { $title = $xpath->query( '//title' ); if ( ! empty( $title ) ) {