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

Add filters for contextualization #559

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

JUVOJustin
Copy link

All Submissions:

Changes proposed in this Pull Request:

We needed a way to dynamically use openid details of multiple companies based on an external identifier. In detail, the new filters allow modifying the settings used to create the login button.

In our setup, we are storing the openid credentials as post meta and identify the post to load by a url parameter. To identify the post used we store it inside the state which is why we implemented the filter.

In general the filters do not intervene with the current functionality, but allow for more usecases.

How to test the changes in this Pull Request:

Here is our full custom implementation:

use OpenID_Connect_Generic_Option_Settings;

/**
 * This class is responsible for dynamically setting OpenID Connect Generic settings
 * It works by first modifying the settings based on a url parameter to identify the post.
 * After that it adds the post id to the state so that the login redirect can identify the post.
 */
class DynamicClient {

	/**
	 * Adds the callbacks to openid filter.
	 */
	public function __construct() {
		add_filter( 'openid-connect-generic-settings', array( $this, 'dynamic_settings' ) );
		add_filter( 'openid-connect-generic-new-state-value', array( $this, 'add_post_id_to_state' ) );
	}

	/**
	 * Whenever the shortcode or the automatic login redirect is called we need to overwrite the settings since they are used
	 * for further logic.
	 *
	 * @param OpenID_Connect_Generic_Option_Settings $settings OpenId Settings built on every request.
	 * @return OpenID_Connect_Generic_Option_Settings
	 */
	public function dynamic_settings( OpenID_Connect_Generic_Option_Settings $settings ): OpenID_Connect_Generic_Option_Settings {

		$post_id = false;

		if ( ! empty( $_GET['state'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$state = wp_kses( wp_unslash( $_GET['state'] ), array() ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$state = get_transient( 'openid-connect-generic-state--' . $state );
			if ( ! empty( $state ) && ! empty( $state['post_id'] ) ) {
				$post_id = $state['post_id'];
			}
		}

		// If post id was not determined from state we try to get it from the url param
		if ( ! $post_id ) {
			$post_id = $this->get_post_by_param();
		}

		// If post id still not determine we return the settings and stop further processing
		if ( ! $post_id ) {
			return $settings;
		}

		$post_settings = $this->get_post_settings( $post_id );
		if ( ! $post_settings ) {
			return $settings;
		}

		// Overwrite settings
		$settings->client_id         = $post_settings['client_id'];
		$settings->client_secret     = $post_settings['client_secret'];
		$settings->endpoint_login    = $post_settings['authorize_endpoint'];
		$settings->endpoint_userinfo = $post_settings['userinfo_endpoint'];
		$settings->endpoint_token    = $post_settings['token_endpoint'];
		return $settings;
	}

	/**
	 * To identify the post we get our settings from throughout the request process we add the post id to the state.
	 *
	 * @param array<string, mixed> $state_value Value that is going to be set as state.
	 * @return array<string, mixed>
	 */
	public function add_post_id_to_state( array $state_value ): array {

		// State is set during the same request the settings are set. We can assume the url param is still available.
		$post_id = $this->get_post_by_param();
		if ( ! $post_id ) {
			return $state_value;
		}

		$state_value['post_id'] = $post_id;

		return $state_value;
	}

	/**
	 * Get the post id from the request. The request is expected to have a partner parameter which is the slug of the post.
	 *
	 * @return int|false
	 */
	private function get_post_by_param(): int|false {
		if ( ! isset( $_GET['partner'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			return false;
		}

		$partner = wp_kses( wp_unslash( $_GET['partner'] ), array() ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		// If we reach this point we are not logged in and have a partner
		$post = get_page_by_path( $partner, OBJECT, 'unternehmen' );
		if ( ! $post ) {
			return false;
		}
		return $post->ID;
	}

	/**
	 * Get the openid settings from the post.
	 *
	 * @param int $post_id Id of the post to get the settings from.
	 * @return array{'client_id': string, 'client_secret': string, 'authorize_endpoint': string, 'userinfo_endpoint': string, 'token_endpoint': string}|false
	 */
	private function get_post_settings( int $post_id ): bool|array {
		// Get our fields
		// phpcs:disable Universal.Operators.DisallowShortTernary.Found
		$client_id          = get_field( 'openid_company_client_id', $post_id ) ?: '';
		$client_secret      = get_field( 'openid_company_client_secret', $post_id ) ?: '';
		$authorize_endpoint = get_field( 'openid_company_authorize', $post_id ) ?: '';
		$userinfo_endpoint  = get_field( 'openid_company_userinfo', $post_id ) ?: '';
		$token_endpoint     = get_field( 'openid_company_token', $post_id ) ?: '';
		// phpcs:enable

		// Check all fields are set
		if ( empty( $client_id ) || empty( $client_secret ) || empty( $authorize_endpoint ) || empty( $userinfo_endpoint ) || empty( $token_endpoint ) ) {
			return false;
		}

		return array(
			'client_id'          => $client_id,
			'client_secret'      => $client_secret,
			'authorize_endpoint' => $authorize_endpoint,
			'userinfo_endpoint'  => $userinfo_endpoint,
			'token_endpoint'     => $token_endpoint,
		);
	}
}

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?

Changelog entry

  • Added new filter that allows modifying settings
  • Added filter to allow modifying state value

This allows additional data to be stored with the state, which can help in identifying user relationships or other custom requirements. The `openid-connect-generic-new-state-value` filter provides flexibility for developers to extend the state value as needed.
Signed-off-by: Justin Vogt <mail@justin-vogt.de>
Signed-off-by: Justin Vogt <mail@justin-vogt.de>
@schrittweiter
Copy link

This looks quite nice, would love to see this feature in an upcoming release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants