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

LICENS-45 Add auth logic #16

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open

LICENS-45 Add auth logic #16

wants to merge 17 commits into from

Conversation

MlKilderkin
Copy link
Contributor

@MlKilderkin MlKilderkin commented Jun 14, 2023

What

Add auth logic
Provide ability connect and disconnect from origin site
Do not allow download link if auth required.

https://github.com/stellarwp/licensing/pull/32
https://github.com/stellarwp/uplink-origin/pull/35

https://moderntribe.atlassian.net/browse/LICENS-45

Copy link
Contributor

@tarecord tarecord left a comment

Choose a reason for hiding this comment

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

Really nice work, just a couple of suggestions below.

I also noticed that methods and properties don't have docblocks or the docblocks aren't filled out completely. Can you go back through and document those so these additions match the rest of the library?

/**
* Method Description.
*
* @since TBD
*
* @param 
*
* @return
*/

if ( empty( $this->response->auth_required ) || $this->resource->has_valid_auth_token( (array) $this->response->origin ) ) {
$info->download_link = isset($this->response->download_url) ? $this->response->download_url . '&pu_get_download=1' : '';
} else {
$url = $this->response->origin->url;
Copy link
Contributor

Choose a reason for hiding this comment

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

Some alignment inconsistencies here

$url = sprintf( '%s/stellarwp/oauth_connect/login?%s', $url, http_build_query( $query_params ) );

$info->api_upgrade = sprintf(
esc_html__( 'Please connect plugin on Setting page in order to receive updates. You can use plugin settings page or follow this <a href="%s">link</a>', '%TEXTDOMAIN%' ),
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels a bit strange to not provide a link to the settings page. Should we just recommend they use the oauth_connect link and avoid referencing the plugin settings since implementations will be unique for each plugin? Here's what that could look like:

Suggested change
esc_html__( 'Please connect plugin on Setting page in order to receive updates. You can use plugin settings page or follow this <a href="%s">link</a>', '%TEXTDOMAIN%' ),
esc_html__( 'Please <a href="%s">authenticate this plugin</a> to receive updates.', '%TEXTDOMAIN%' ),


$args = explode( '/', $wp->query_vars[ self::QUERY_VAR ] );

if ( ! empty( $args['disconnect'] ) ) { // @phpstan-ignore-line
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the error you are ignoring here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is for Offset 'disconnect' on non-empty-array<int, string> in isset() does not exist.

}

/**
* Remove auth token§
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Remove auth token§
* Remove auth tokens

Comment on lines 62 to 63
'callback_uri' => urlencode( sprintf( '%s/stellarwp/connect', get_site_url() ) ),
'refer' => urlencode( wp_get_referer() ),
Copy link
Contributor

Choose a reason for hiding this comment

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

Alignment here


$token = get_option( sprintf( 'stellarwp_origin_%s_auth_token', $license->origin->slug ?? '' ), '' );
$message = esc_html__( 'Connect to origin', '%TEXTDOMAIN%' );
$classes = [ 'button', 'button-primary' ];
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

* @param \WP $wp
*/
public function auth_request( $wp ) {
if ( ! is_user_logged_in() ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of only logged in users, it might be a good idea to also check user capabilities current_user_can( 'manage_options' )

'callback_uri' => urlencode( sprintf( '%s/stellarwp/connect', get_site_url() ) ),
'refer' => urlencode( wp_get_referer() ),
];
$url = sprintf( '%s/stellarwp/oauth_connect/login?%s', $url, http_build_query( $query_params ) );
Copy link
Member

Choose a reason for hiding this comment

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

This endpoint will need to be namespaced with the plugin/plugin suite that is implementing this library. We have to assume that multiple plugins will be installed and active on a given site with this same library in use.

public function handle_disconnect() {
$license = $this->get_license_object();

delete_option( sprintf( 'stellarwp_origin_%s_auth_token', $license->origin->slug ?? '' ) );
Copy link
Member

Choose a reason for hiding this comment

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

The option needs to be implementation-namespaced.

}

$query_params = [
'callback_uri' => urlencode( sprintf( '%s/stellarwp/connect', get_site_url() ) ),
Copy link
Member

Choose a reason for hiding this comment

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

This URL needs to be implementation-namespaced.

$plugin = $collection->current();
$license = $plugin->get_license_object();

$token = get_option( sprintf( 'stellarwp_origin_%s_auth_token', $license->origin->slug ?? '' ), '' );
Copy link
Member

Choose a reason for hiding this comment

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

The option needs to be implementation-namespaced.

$license = $plugin->get_license_object();

$token = get_option( sprintf( 'stellarwp_origin_%s_auth_token', $license->origin->slug ?? '' ), '' );
$message = esc_html__( 'Connect to origin', '%TEXTDOMAIN%' );
Copy link
Member

Choose a reason for hiding this comment

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

The customer won't know what origin means - we should avoid using that terminology for customer-facing text. Perhaps we output the Origin Site's name instead?

tests/wpunit/Site/DataTest.php Show resolved Hide resolved

delete_option( sprintf( 'stellarwp_origin_%s_auth_token', $license->origin->slug ?? '' ) );

wp_safe_redirect( wp_get_referer() );
Copy link
Member

Choose a reason for hiding this comment

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

We should fire an action before the redirect.

];
$url = sprintf( '%s/stellarwp/oauth_connect/login?%s', $url, http_build_query( $query_params ) );

wp_safe_redirect( $url );
Copy link
Member

Choose a reason for hiding this comment

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

We should fire an action before the redirect.


$args = explode( '/', $wp->query_vars[ self::QUERY_VAR ] );

if ( ! empty( $args['disconnect'] ) ) { // @phpstan-ignore-line
Copy link
Member

Choose a reason for hiding this comment

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

Let's do an apply_filters for the args before this if statement.

$url,
implode( ' ', $classes ),
$message
);
Copy link
Member

Choose a reason for hiding this comment

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

Let's do an apply_filters on the resulting HTML here.

@MlKilderkin MlKilderkin requested review from borkweb and tarecord July 17, 2023 13:21
Copy link
Contributor

@tarecord tarecord left a comment

Choose a reason for hiding this comment

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

Couple of notes, especially around hook naming.


class Actions {

const QUERY_VAR = 'stellarwp_action';

/**
* Register handle route for connect/disconnect
*
* @since 1.0.1
Copy link
Contributor

Choose a reason for hiding this comment

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

@borkweb how do we want to handle @since comments if the version hasn't been determined yet? We've used this in other libraries:

/**
 * Some really awesome method.
 * 
 * @since TBD
 * 
 * @return void
 */

$query_params = [
'callback_uri' => urlencode( sprintf( '%s/stellarwp/connect', get_site_url() ) ),
'callback_uri' => urlencode( sprintf( '%s/%s', get_site_url(), Namespaces::get_hook_name( 'connect', '%TEXTDOMAIN%' ) ) ),
Copy link
Contributor

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?

Copy link
Contributor

@tarecord tarecord Jul 17, 2023

Choose a reason for hiding this comment

The 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 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)

Copy link
Contributor

Choose a reason for hiding this comment

The 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 Config::get_hook_prefix() available to you. Would that be good to use instead of this class? I imagine brands will want to define their prefix once and have it used everywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@MlKilderkin MlKilderkin requested a review from tarecord July 21, 2023 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants