/** * @since 1.0.0 */ public function verify_configuration_callback() { if (isset($_REQUEST['nonce']) && $this->wp_facade->wp_verify_nonce($_REQUEST['nonce'], static::VERIFIER_NONCE_KEY)) { $user = $this->wp_facade->wp_get_current_user(); $response = array('nonce' => $this->wp_facade->wp_create_nonce(static::VERIFIER_NONCE_KEY)); if (stripos($_SERVER['REQUEST_METHOD'], 'POST') !== false && isset($_POST['verify_action']) && 'pair' === $_POST['verify_action']) { try { $white_label_user = $this->launchkey_client->whiteLabel()->createUser($user->user_login); $response['qrcode_url'] = $white_label_user->getQrCodeUrl(); $response['manual_code'] = $white_label_user->getCode(); } catch (Exception $e) { $response['error'] = $e->getCode(); } } elseif (stripos($_SERVER['REQUEST_METHOD'], 'POST') !== false) { $response['completed'] = false; try { $username = empty($_POST['username']) ? $user->user_login : $_POST['username']; $auth_request = $this->launchkey_client->auth()->authorize($username); $this->wp_facade->update_user_meta($user->ID, 'launchkey_username', $username); $this->wp_facade->update_user_meta($user->ID, 'launchkey_auth', $auth_request->getAuthRequestId()); $this->wp_facade->update_user_meta($user->ID, 'launchkey_authorized', null); } catch (Exception $e) { $response['error'] = $e->getCode(); } } else { $db = $this->wp_facade->get_wpdb(); $value = $db->get_var($db->prepare("SELECT meta_value FROM {$db->usermeta} WHERE user_id = %s AND meta_key = 'launchkey_authorized' LIMIT 1", $user->ID)); $response['completed'] = !empty($value); } $this->wp_facade->wp_send_json($response); } }
/** * Callback handler to process white label pairing via AJAX * * @since 1.0.0 */ public function white_label_pair_callback() { if (isset($_POST['nonce'])) { // If there is no nonce, ignore the request if ($this->wp_facade->wp_verify_nonce($_POST['nonce'], LaunchKey_WP_User_Profile::NONCE_KEY)) { // If there is a valid nonce if ($user = $this->wp_facade->wp_get_current_user()) { // and a current logged in user try { // Create a LaunchKey White Label user with the WordPress username as the unique identifer $pair_response = $this->launchkey_client->whiteLabel()->createUser($user->user_login); // Set the WordPress username as the LaunchKey username for subsequent login attempts $this->wp_facade->update_user_meta($user->ID, 'launchkey_username', $user->user_login); // Set up the response with the QR Code URL and manual pairing codes $response = array('qrcode' => $pair_response->getQrCodeUrl(), 'code' => $pair_response->getCode()); } catch (\LaunchKey\SDK\Service\Exception\CommunicationError $e) { // Communication error response $response = array('error' => 'There was a communication error encountered during the pairing process. Please try again later'); } catch (\LaunchKey\SDK\Service\Exception\InvalidCredentialsError $e) { // Invalid credentials response $response = array('error' => 'There was an error encountered during the pairing process caused by a misconfiguration. Please contact the administrator.'); } catch (\Exception $e) { // General error response $response = array('error' => 'There was an error encountered during the pairing process. Please contact the administrator.'); } // Add a new nonce to the response to allow another request $response['nonce'] = $this->wp_facade->wp_create_nonce(LaunchKey_WP_User_Profile::NONCE_KEY); // Set the headers for the AJAX response $this->wp_facade->wp_send_json($response); } } } }
/** * @param $user_id * @param $launchkey_username * * @return null|WP_Error */ private function authenticate_user($user_id, $launchkey_username) { // reset user authentication $this->reset_auth($user_id); // Get the auth client from the SDK $auth = $this->launchkey_client->auth(); try { // Authenticate and get the request ID $auth_request = $auth->authenticate($launchkey_username)->getAuthRequestId(); // Set the auth request ID in the user metadata to be available to the server side event $this->wp_facade->update_user_meta($user_id, 'launchkey_auth', $auth_request); // Loop until a response has been recorded by the SSE callback do { // Sleep before checking for the response to not kill the server sleep(1); // See if the user has authorized $auth = $this->get_user_authorized($user_id); } while (null === $auth); // If the response is null, continue the loop if ($auth) { // If the user accepted, return true $response = true; } else { // Otherwise, return an error $response = new WP_Error('launchkey_authentication_denied', $this->wp_facade->__('Authentication denied!', $this->language_domain)); } } catch (Exception $e) { // Process exceptions appropriately $response = new WP_Error(); if ($e instanceof \LaunchKey\SDK\Service\Exception\NoPairedDevicesError) { $response->add('launchkey_authentication_denied', $this->wp_facade->__('No Paired Devices!', $this->language_domain)); } elseif ($e instanceof \LaunchKey\SDK\Service\Exception\NoSuchUserError) { $response->add('launchkey_authentication_denied', $this->wp_facade->__('Authentication denied!', $this->language_domain)); } elseif ($e instanceof \LaunchKey\SDK\Service\Exception\RateLimitExceededError) { $response->add('launchkey_authentication_denied', $this->wp_facade->__('Authentication denied!', $this->language_domain)); } elseif ($e instanceof \LaunchKey\SDK\Service\Exception\ExpiredAuthRequestError) { $response->add('launchkey_authentication_timeout', $this->wp_facade->__('Authentication denied!', $this->language_domain)); } else { if ($this->wp_facade->is_debug_log()) { $this->wp_facade->error_log('Error authenticating user with Launchkey: ' . $e->getMessage()); } $response->add('launchkey_authentication_error', $this->wp_facade->__('Authentication error! Please try again later', $this->language_domain)); } } return $response; }
/** * Initialize LaunchKey WordPress Plugin * * This function will perform the entire initialization for the plugin. The initialization is encapsulated into * a funciton to protect against global variable collision. * * @since 1.0.0 * Enclose plug-in initialization to protect against global variable corruption */ function launchkey_plugin_init() { global $wpdb; /** * Register activation hooks for the plugin * @since 1.1.0 */ register_activation_hook(__FILE__, 'launchkey_create_tables'); /** * Remove the scheduled cron * @since 1.1.0 */ register_deactivation_hook(__FILE__, 'launchkey_cron_remove'); /** * @since 1.1.0 * Add the cron hook and schedule if not scheduled */ add_action('launchkey_cron_hook', 'launchkey_cron'); if (!wp_next_scheduled('launchkey_cron_hook')) { wp_schedule_event(time(), 'hourly', 'launchkey_cron_hook'); } /** * Language domain for the plugin */ $language_domain = 'launchkey'; /** * Register plugin text domain with language files * * @see load_plugin_textdomain * @link https://developer.wordpress.org/reference/hooks/plugins_loaded/ */ add_action('plugins_loaded', function () use($language_domain) { load_plugin_textdomain($language_domain, false, plugin_basename(__FILE__) . '/languages/'); }); /** * Create an AES encryption class for encryption/decryption of the secret options * @link https://docs.launchkey.com/glossary.html#term-aes */ $crypt_aes = new \phpseclib\Crypt\AES(); /** * Use an MD5 hash of the auth key as the crypto key. The crypto key is used as it would normally affect all auth * procedures as it is used as a salt for passwords. An md5 hash is used as it will be a constant value based on * the AUTH_KEY but guaranteed to be exactly thirty-two (32) characters as is needed by AES encryption. */ $crypt_aes->setKey(md5(AUTH_KEY)); // Create an options handler that will encrypt and decrypt the plugin options as necessary $options_handler = new LaunchKey_WP_Options($crypt_aes); /** * The pre_update_option_launchkey filter will process the "launchkey" option directly * before updating the data in the database. * * @since 1.0.0 * @link https://developer.wordpress.org/reference/hooks/pre_update_option_option/ * @see LaunchKey_WP_Options::pre_update_option_filter */ add_filter('pre_update_option_launchkey', array($options_handler, 'pre_update_option_filter')); add_filter('pre_update_site_option_launchkey', array($options_handler, 'pre_update_option_filter')); /** * The pre_update_option_filter filter will process the "launchkey" option directly * before adding the data in the database. * * @since 1.0.0 * @link https://developer.wordpress.org/reference/hooks/pre_update_option_option/ * @see LaunchKey_WP_Options::pre_update_option_filter */ add_filter('pre_add_option_launchkey', array($options_handler, 'pre_update_option_filter')); add_filter('pre_add_site_option_launchkey', array($options_handler, 'pre_update_option_filter')); /** * The option_launchkey filter will process the "launchkey" option directly * after retrieving the data from the database. * * @since 1.0.0 * @link https://developer.wordpress.org/reference/hooks/option_option/ * @see LaunchKey_WP_Options::post_get_option_filter */ add_filter('option_launchkey', array($options_handler, 'post_get_option_filter')); add_filter('site_option_launchkey', array($options_handler, 'post_get_option_filter')); $is_multi_site = is_multisite() && is_plugin_active_for_network(plugin_basename(__FILE__)); $options = $is_multi_site ? get_site_option(LaunchKey_WP_Admin::OPTION_KEY) : get_option(LaunchKey_WP_Admin::OPTION_KEY); /** * Handle upgrades if in the admin and not the latest version */ if (is_admin() && launchkey_is_activated() && $options && $options[LaunchKey_WP_Options::OPTION_VERSION] < 1.1) { launchkey_create_tables(); } /** * If the pre-1.0.0 option style was already used, create a 1.0.0 option and remove the old options. They are * removed as the secret_key was stored plain text in the database. * * @since 1.0.0 */ if (get_option('launchkey_app_key') || get_option('launchkey_secret_key')) { $launchkey_options[LaunchKey_WP_Options::OPTION_ROCKET_KEY] = get_option('launchkey_app_key'); $launchkey_options[LaunchKey_WP_Options::OPTION_SECRET_KEY] = get_option('launchkey_secret_key'); $launchkey_options[LaunchKey_WP_Options::OPTION_SSL_VERIFY] = defined('LAUNCHKEY_SSLVERIFY') && LAUNCHKEY_SSLVERIFY || true; $launchkey_options[LaunchKey_WP_Options::OPTION_IMPLEMENTATION_TYPE] = LaunchKey_WP_Implementation_Type::OAUTH; $launchkey_options[LaunchKey_WP_Options::OPTION_LEGACY_OAUTH] = true; $updated = $is_multi_site ? update_network_option(LaunchKey_WP_Admin::OPTION_KEY, $launchkey_options) : update_option(LaunchKey_WP_Admin::OPTION_KEY, $launchkey_options); if ($updated) { delete_option('launchkey_app_key'); delete_option('launchkey_secret_key'); } else { throw new RuntimeException('Unable to upgrade LaunchKey meta-data. Failed to save setting ' . LaunchKey_WP_Admin::OPTION_KEY); } } elseif (!$options) { $is_multi_site ? add_site_option(LaunchKey_WP_Admin::OPTION_KEY, array()) : add_option(LaunchKey_WP_Admin::OPTION_KEY, array()); $options = $is_multi_site ? get_site_option(LaunchKey_WP_Admin::OPTION_KEY) : get_option(LaunchKey_WP_Admin::OPTION_KEY); } /** * Get the WP global facade * @see LaunchKey_WP_Global_Facade */ $facade = new LaunchKey_WP_Global_Facade(); /** * Create a templating object and point it at the correct directory for template files. * * @see LaunchKey_WP_Template */ $template = new LaunchKey_WP_Template(__DIR__ . '/templates', $facade, $language_domain); // Prevent XXE Processing Vulnerability libxml_disable_entity_loader(true); // Get the plugin options to determine which authentication implementation should be utilized $logger = new LaunchKey_WP_Logger($facade); $launchkey_client = null; $client = null; // Only register the pieces that need to interact with LaunchKey if it's been configured if (LaunchKey_WP_Implementation_Type::SSO === $options[LaunchKey_WP_Options::OPTION_IMPLEMENTATION_TYPE] && !empty($options[LaunchKey_WP_Options::OPTION_SSO_ENTITY_ID])) { $container = new LaunchKey_WP_SAML2_Container($logger); SAML2_Compat_ContainerSingleton::setContainer($container); $securityKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $securityKey->loadKey($options[LaunchKey_WP_Options::OPTION_SSO_CERTIFICATE], false, true); $saml_response_service = new LaunchKey_WP_SAML2_Response_Service($securityKey, $facade); $saml_request_service = new LaunchKey_WP_SAML2_Request_Service($securityKey); $client = new LaunchKey_WP_SSO_Client($facade, $template, $options[LaunchKey_WP_Options::OPTION_SSO_ENTITY_ID], $saml_response_service, $saml_request_service, $wpdb, $options[LaunchKey_WP_Options::OPTION_SSO_LOGIN_URL], $options[LaunchKey_WP_Options::OPTION_SSO_LOGOUT_URL], $options[LaunchKey_WP_Options::OPTION_SSO_ERROR_URL], $is_multi_site); } elseif (LaunchKey_WP_Implementation_Type::OAUTH === $options[LaunchKey_WP_Options::OPTION_IMPLEMENTATION_TYPE] && !empty($options[LaunchKey_WP_Options::OPTION_SECRET_KEY])) { /** * If the implementation type is OAuth, use the OAuth client * @see LaunchKey_WP_OAuth_Client */ $client = new LaunchKey_WP_OAuth_Client($facade, $template, $is_multi_site); } elseif (!empty($options[LaunchKey_WP_Options::OPTION_SECRET_KEY])) { $launchkey_client = \LaunchKey\SDK\Client::wpFactory($options[LaunchKey_WP_Options::OPTION_ROCKET_KEY], $options[LaunchKey_WP_Options::OPTION_SECRET_KEY], $options[LaunchKey_WP_Options::OPTION_PRIVATE_KEY], $options[LaunchKey_WP_Options::OPTION_SSL_VERIFY]); $client = new LaunchKey_WP_Native_Client($launchkey_client, $facade, $template, $language_domain, $is_multi_site); add_filter('init', function () use($facade) { wp_enqueue_script('launchkey-script', plugins_url('/public/launchkey-login.js', __FILE__), array('jquery'), '1.1.1', true); }); } if ($client) { /** * Register the non-admin actions for authentication client. These actions will handle all of the * authentication work for the plugin. * * @see LaunchKey_WP_Client::register_actions * @see LaunchKey_WP_OAuth_Client::register_actions * @see LaunchKey_WP_Native_Client::register_actions */ $client->register_actions(); /** * Create the a user profile object and register its actions. These actions will handle all functionality * related to a user customizing their authentication related options. * * @see LaunchKey_WP_User_Profile */ $profile = new LaunchKey_WP_User_Profile($facade, $template, $language_domain, $is_multi_site); $profile->register_actions(); /** * Hideous workaround for the wp-login.php page not printing styles in the header like it should. * * @since 1.0.0 */ if (!has_action('login_enqueue_scripts', 'wp_print_styles')) { add_action('login_enqueue_scripts', 'wp_print_styles', 11); } } if (is_admin() || $is_multi_site && is_network_admin()) { /** * If we are in the admin, create an admin object and register its actions. These actions * will manage setting of options and user management for the plugin. * * @see is_admin * @see LaunchKey_WP_Admin */ $launchkey_admin = new LaunchKey_WP_Admin($facade, $template, $language_domain, $is_multi_site); $launchkey_admin->register_actions(); $config_wizard = new LaunchKey_WP_Configuration_Wizard($facade, $launchkey_admin, $is_multi_site, $launchkey_client); $config_wizard->register_actions(); } /** * Add a filter to enqueue styles for the plugin * * @since 1.0.0 * * @see add_filter * @see wp_enqueue_style * @link https://developer.wordpress.org/reference/functions/add_filter/ * @link https://developer.wordpress.org/reference/functions/wp_enqueue_style/ */ add_filter('init', function () use($facade) { wp_enqueue_style('launchkey-style', plugins_url('/public/launchkey.css', __FILE__), array(), '1.0.1', false); }); /** * Handle activation when a "must use" plugin */ if (launchkey_is_mu_plugin()) { $mu_activated_option = "launchkey_activated"; if (!get_option($mu_activated_option)) { do_action("activate_" . plugin_basename(__FILE__)); add_option($mu_activated_option, true); } } }
public function wizard_easy_setup_callback() { $headers = array(); array_walk($_SERVER, function ($value, $key) use(&$headers) { if (preg_match('/^HTTP\\_(.+)$/', $key, $matches)) { $headers[str_replace('_', '-', $matches[1])] = $value; } }); preg_match('/^[^\\/]+\\/(.*)$/', $_SERVER['SERVER_PROTOCOL'], $matches); $protocol_version = $matches ? $matches[1] : null; $request = new Request($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $headers, $this->wp_facade->fopen('php://input', 'rb'), $protocol_version); $http_response = new Response(); if ($request->hasHeader('signature')) { try { // Have the SDK client handle the callback $response = $this->launchkey_client->serverSentEvent()->handleEvent($request, $http_response); if ($response instanceof \LaunchKey\SDK\Domain\RocketCreated) { $config = $this->get_option(LaunchKey_WP_Configuration_Wizard::EASY_SETUP_OPTION); if (empty($config['nonce']) || !$config['nonce'] instanceof \LaunchKey\SDK\Domain\NonceResponse) { throw new \LaunchKey\SDK\Service\Exception\InvalidRequestError(sprintf('Easy config request with no valid "nonce" in option "%s"', LaunchKey_WP_Configuration_Wizard::EASY_SETUP_OPTION)); } // Delete the option, valid or not. $this->wp_facade->delete_option(LaunchKey_WP_Configuration_Wizard::EASY_SETUP_OPTION); // Check for expiration of the nonce $expires = $config['nonce']->getExpiration(); if ($expires <= new DateTime("now", new DateTimeZone("UTC"))) { throw new \LaunchKey\SDK\Service\Exception\InvalidRequestError('Easy config "nonce" has expired'); } $rocketConfig = $response->getRocketConfig($this->crypt_service, $config['nonce']->getNonce()); $expected_callback_url = $this->wp_facade->admin_url('admin-ajax.php?action=' . LaunchKey_WP_Native_Client::CALLBACK_AJAX_ACTION); // Verify the callback URL before attempting to decrypt the data $actual_callback_url = $rocketConfig->getCallbackURL(); if ($actual_callback_url !== $expected_callback_url) { throw new \LaunchKey\SDK\Service\Exception\InvalidRequestError(sprintf('Easy config is not for this site based on callback. Expected: %s, Actual: %s.', $expected_callback_url, $actual_callback_url)); } $options = $this->get_option(LaunchKey_WP_Admin::OPTION_KEY); $rocket_type = $rocketConfig->isWhiteLabel() ? LaunchKey_WP_Implementation_Type::WHITE_LABEL : LaunchKey_WP_Implementation_Type::NATIVE; // Update options from server sent event service response $options[LaunchKey_WP_Options::OPTION_IMPLEMENTATION_TYPE] = $rocket_type; $options[LaunchKey_WP_Options::OPTION_ROCKET_KEY] = $rocketConfig->getKey(); $options[LaunchKey_WP_Options::OPTION_SECRET_KEY] = $rocketConfig->getSecret(); $options[LaunchKey_WP_Options::OPTION_PRIVATE_KEY] = $rocketConfig->getPrivateKey(); $this->update_option(LaunchKey_WP_Admin::OPTION_KEY, $options); $response_string = ""; $body = $http_response->getBody(); $body->rewind(); while ($segment = $body->read(256)) { $response_string .= $segment; } $this->wp_facade->header("Content-Type: text/plain", true, $http_response->getStatusCode()); $this->wp_facade->wp_die($response_string); } } catch (\Exception $e) { if ($this->wp_facade->is_debug_log()) { $this->wp_facade->error_log('Callback Exception: ' . $e->getMessage()); } if ($e instanceof \LaunchKey\SDK\Service\Exception\InvalidRequestError) { $this->wp_facade->http_response_code(400); $this->wp_facade->wp_die('Invalid Request'); } else { $this->wp_facade->http_response_code(500); $this->wp_facade->wp_die('Server Error'); } } } }
protected function setUp() { $this->client = Client::factory("APP_KEY", "SECRET_KEY", $this->getPrivateKey()); $this->wpClient = Client::wpFactory("APP_KEY", "SECRET_KEY", $this->getPrivateKey()); }