-
Notifications
You must be signed in to change notification settings - Fork 1
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
LICENS-45 Add auth logic #16
base: main
Are you sure you want to change the base?
Changes from 14 commits
25c7909
f97b940
c9da919
d457408
2bf67dc
71c61bc
772ddfb
232bc45
6eaa2f7
e0c3a89
3ab0da2
9427972
6091a4e
90b2bff
0693a93
1f293c6
4298871
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace StellarWP\Uplink\Admin; | ||
|
||
use StellarWP\Uplink\API; | ||
use StellarWP\Uplink\Config; | ||
use StellarWP\Uplink\Resources\Collection; | ||
use StellarWP\Uplink\Site\Data; | ||
use StellarWP\Uplink\Utils\Namespaces; | ||
|
||
class Actions { | ||
|
||
const QUERY_VAR = 'stellarwp_action'; | ||
|
||
/** | ||
* Register handle route for connect/disconnect | ||
* | ||
* @since 1.0.1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @borkweb how do we want to handle /**
* Some really awesome method.
*
* @since TBD
*
* @return void
*/ |
||
* | ||
* @return void | ||
* | ||
* @action init | ||
*/ | ||
public function register_route() { | ||
add_rewrite_endpoint( 'stellarwp', EP_ROOT, self::QUERY_VAR ); | ||
} | ||
|
||
/** | ||
* Handle auth connect and disconnect request | ||
* | ||
* @since 1.0.1 | ||
* | ||
* @param \WP $wp | ||
* | ||
* @return void | ||
* | ||
* @action parse_request | ||
*/ | ||
public function handle_auth_request( $wp ) { | ||
if ( empty( $wp->query_vars[ self::QUERY_VAR ] ) ) { | ||
return; | ||
} | ||
|
||
$args = apply_filters( Namespaces::get_hook_name( 'auth/request_args', '%TEXTDOMAIN%' ) , explode( '/', $wp->query_vars[ self::QUERY_VAR ] ) ); | ||
|
||
if ( ! empty( $args['disconnect'] ) ) { // @phpstan-ignore-line | ||
$this->handle_disconnect(); | ||
} | ||
|
||
$this->handle_connect( $args ); | ||
} | ||
|
||
/** | ||
* Remove auth tokens and redirect back to settings page | ||
* | ||
* @since 1.0.1 | ||
*/ | ||
public function handle_disconnect() { | ||
$license = $this->get_license_object(); | ||
|
||
do_action( Namespaces::get_hook_name( 'disconnect/before/redirect', '%TEXTDOMAIN%' ), $license ); | ||
|
||
delete_option( sprintf( '%s%s_auth_token', Namespaces::get_option_name( 'origin', '%TEXTDOMAIN%' ), $license->origin->slug ?? '' ) ); | ||
|
||
do_action( Namespaces::get_hook_name( 'disconnect/after/redirect', '%TEXTDOMAIN%' ), $license ); | ||
|
||
wp_safe_redirect( wp_get_referer() ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should fire an action before the redirect. |
||
exit(); | ||
} | ||
|
||
/** | ||
* Save auth token and redirect back to referer URL | ||
* | ||
* @since 1.0.1 | ||
* | ||
* @param array $args | ||
*/ | ||
public function handle_connect( $args ) { | ||
if ( empty( $args['token'] ) ) { | ||
$url = $this->get_origin_url(); | ||
if ( empty( $url ) ) { | ||
return; | ||
} | ||
|
||
$query_params = [ | ||
'callback_uri' => urlencode( sprintf( '%s/%s', get_site_url(), Namespaces::get_hook_name( 'connect', '%TEXTDOMAIN%' ) ) ), | ||
'refer' => urlencode( wp_get_referer() ), | ||
]; | ||
$url = sprintf( '%s/%s?%s', $url, Namespaces::get_hook_name( 'oauth_connect/login' ), http_build_query( $query_params ) ); | ||
|
||
wp_safe_redirect( $url ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should fire an action before the redirect. |
||
exit(); | ||
} | ||
|
||
do_action( Namespaces::get_hook_name( 'disconnect/before/save_auth_token', '%TEXTDOMAIN%' ), $args ); | ||
|
||
Config::get_container()->get( Data::class )->save_auth_token( $args['token'] ); | ||
|
||
do_action( Namespaces::get_hook_name( 'disconnect/after/save_auth_token', '%TEXTDOMAIN%' ), $args ); | ||
|
||
wp_safe_redirect( $args['refer'] ); | ||
exit(); | ||
} | ||
|
||
/** | ||
* Retrieve origin URL from server | ||
* | ||
* @since 1.0.1 | ||
* | ||
* @return string | ||
*/ | ||
protected function get_origin_url() { | ||
$license = $this->get_license_object(); | ||
$api = Config::get_container()->get( API\Client::class ); | ||
$origin = $api->post('/origin', [ 'slug' => $license->get_slug() ] ); | ||
|
||
if ( ! empty( $origin ) ) { | ||
return $origin->url . '/stellarwp_connect'; | ||
} | ||
|
||
return ''; | ||
} | ||
|
||
/** | ||
* Retrieve License | ||
* | ||
* @since 1.0.1 | ||
* | ||
* @return mixed | ||
*/ | ||
protected function get_license_object() { | ||
$collection = Config::get_container()->get( Collection::class ); | ||
$plugin = $collection->current(); | ||
|
||
return $plugin->get_license_object(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace StellarWP\Uplink\Admin; | ||
|
||
use StellarWP\Uplink\Config; | ||
use StellarWP\Uplink\Resources\Collection; | ||
use StellarWP\Uplink\Utils\Namespaces; | ||
|
||
class Auth { | ||
|
||
/** | ||
* Return auth button html | ||
* | ||
* @since 1.0.1 | ||
* | ||
* @return mixed | ||
*/ | ||
public static function do_auth_html() { | ||
$collection = Config::get_container()->get( Collection::class ); | ||
$plugin = $collection->current(); | ||
$license = $plugin->get_license_object(); | ||
|
||
$token = get_option( sprintf( '%s%s_auth_token', Namespaces::get_option_name( 'origin', '%TEXTDOMAIN%' ), $license->origin->slug ?? '' ), '' ); | ||
$message = esc_html__( 'Connect to receive updates', '%TEXTDOMAIN%' ); | ||
$classes = [ 'button', 'button-primary' ]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defining the structure here looks too prescriptive for the plugins implementing this. Could we provide a new filter to allow them to override the HTML? |
||
$url = Namespaces::get_hook_name( 'connect', '%TEXTDOMAIN%' ); | ||
|
||
if ( ! empty( $token ) ) { | ||
$message = esc_html__( 'Disconnect', '%TEXTDOMAIN%' ); | ||
$classes = [ 'button', 'button-secondary']; | ||
$url = Namespaces::get_hook_name( 'disconnect', '%TEXTDOMAIN%' ); | ||
} | ||
|
||
$btn_html = sprintf( | ||
'<a href="%s" class="%s">%s</a>', | ||
$url, | ||
implode( ' ', $classes ), | ||
$message | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do an apply_filters on the resulting HTML here. |
||
|
||
return apply_filters( Namespaces::get_hook_name( 'connect/btn/html', '%TEXTDOMAIN%' ), $btn_html, $url, $classes ); | ||
} | ||
|
||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is necessary because it's adding a lot of additional abstraction to hooks (you're having to figure out what the hook is doing by referencing this class). We've dealt with this a bit in the Telemetry library too where plugins can hook into their own implementation without affecting others. Instead of adding a prefix to the hook name, we've adjusted to pass the plugin slug as an argument to the hook: apply_filters( 'your/hook/name', '[plugin-slug]', 'value' );
do_action( 'your/hook/name', '[plugin-slug]' ); Since the plugin slug is passed in the hooks, plugins can validate that the hook is firing for a specific implementation: add_filter( 'the/hook/name', 'filter_something', 10, 2 );
function filter_something( $plugin_slug, $value ) {
if ( 'the-events-calendar' !== $plugin_slug) {
return $value;
}
// Do value adjustment here...
return $value;
} Once all the hooks are updated to pass in the plugin slug, hook names shouldn't have to change and they'll be simpler to understand in the library 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tarecord maybe I understood the idea for #16 (comment) in wrong way #16 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You didn't understand wrong, a namespace is perfect when using them for options and endpoints. I am only referring to the filters/actions you are adding a namespace to. I did notice that you have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace StellarWP\Uplink\Utils; | ||
|
||
class Namespaces { | ||
|
||
public const DEFAULT_NAME = 'stellarwp'; | ||
|
||
public static function get_hook_name( string $entity, string $plugin_slug = '' ): string { | ||
return apply_filters( 'stellarwp/namespace/hook_name', sprintf( '%s/%s', $plugin_slug ?: self::DEFAULT_NAME, $entity ), $entity, $plugin_slug ); | ||
} | ||
|
||
public static function get_option_name( string $entity, string $plugin_slug = '' ): string { | ||
return apply_filters( 'stellarwp/namespace/option_name', sprintf( '%s_%s_', $plugin_slug ?: self::DEFAULT_NAME, $entity ), $entity, $plugin_slug ); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php declare( strict_types=1 ); | ||
|
||
namespace StellarWP\Uplink\Tests\Resources; | ||
|
||
use StellarWP\Uplink\API\Validation_Response; | ||
use StellarWP\Uplink\Register; | ||
use StellarWP\Uplink\Uplink; | ||
|
||
class ResourceTest extends \StellarWP\Uplink\Tests\UplinkTestCase { | ||
|
||
public $resource; | ||
|
||
public function setUp() { | ||
parent::setUp(); | ||
|
||
$root = dirname( __DIR__, 3 ); | ||
$this->resource = Register::plugin( | ||
'sample', | ||
'Lib Sample', | ||
$root . '/plugin.php', | ||
Uplink::class, | ||
'1.0.10', | ||
Uplink::class | ||
); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_should_check_auth_token_valid() { | ||
MlKilderkin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$result = $this->resource->has_valid_auth_token( [ 'slug' => 'sample' ] ); | ||
$this->assertFalse( $result ); | ||
|
||
update_option( sprintf( 'stellarwp_origin_%s_auth_token', 'sample' ), [ | ||
'token' => '11111', | ||
'origin' => '', | ||
] ); | ||
add_filter( 'stellarwp/namespace/option_name', function( $name, $entity, $slug ) { | ||
return 'stellarwp_origin_'; | ||
}, 10, 3); | ||
|
||
update_option( sprintf( 'stellarwp_origin_%s_auth_token', 'sample' ), [ | ||
'token' => '11111', | ||
'origin' => '', | ||
] ); | ||
|
||
$result = $this->resource->has_valid_auth_token( [ 'slug' => 'sample' ] ); | ||
$this->assertTrue( $result ); | ||
} | ||
|
||
} |
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.
This is hard to understand what's going on. I'm not sure I understand why the
Namespaces
class is necessary?