Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,8 @@ name: CodeQL
on:
push:
branches: [main]
paths:
- '.github/workflows/**'
- '.github/dependabot.yml'
pull_request:
branches: [main]
paths:
- '.github/workflows/**'
- '.github/dependabot.yml'
schedule:
- cron: '27 4 * * 1'
workflow_dispatch:
Expand Down
56 changes: 46 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ WordPress ↔ IPS / Invision Community single sign-on bridge.
[![Download WP Plugin](https://img.shields.io/badge/Download-WP--SSO%20Plugin-blue?logo=wordpress&logoColor=white)](https://github.com/drnecrotix/WP-SSO/releases/latest/download/wp-sso.zip)
[![Download IPS Integration](https://img.shields.io/badge/Download-IPS%20Integration-5C2D91)](https://github.com/drnecrotix/WP-SSO/releases/latest/download/WordPress-SSO-IPS.xml)

WP-SSO lets an IPS / Invision Community installation use an existing WordPress authentication session and account data. The project includes a standard installable WordPress plugin, the original legacy endpoint for migration compatibility, and the IPS plugin definition.
WP-SSO lets an IPS / Invision Community installation use an existing WordPress authentication session and account data. The project includes a standard installable WordPress plugin, an optional generated compatibility API file, the original legacy endpoint for migration compatibility, and the IPS plugin definition.

> **Project status:** active modernization of a legacy integration. Test against your exact WordPress and IPS versions before production deployment.

## ✨ Features

- installable WordPress plugin under `wp-sso/`;
- Settings → **WP-SSO Bridge** configuration page;
- guided setup under Settings → **WP-SSO Bridge**;
- automatically generated API secret on activation;
- optional **Generate & Download API file** tool for legacy IPS integrations;
- generated API compatibility file contains no API secret;
- API secret from WordPress settings, `WP_SSO_API_KEY` environment variable, or PHP constant;
- `X-WP-SSO-Key` authentication;
- `Authorization: Bearer` authentication;
Expand Down Expand Up @@ -87,21 +89,51 @@ define('WP_SSO_API_KEY', 'your-long-random-secret');

Environment/constant values take precedence over the database setting.

### 4. Install the IPS integration
### 4. Choose the endpoint

Import `WordPress-SSO-IPS.xml` from the IPS / Invision Community plugin administration area and configure it with the WordPress endpoint and matching secret.
The recommended native endpoint does not require any additional PHP file:

> The bundled IPS definition is legacy and may still depend on query-string authentication. Header-based authentication is preferred for modern integrations.
```text
https://example.com/?wp_sso_api=1&type=test
```

## 🔌 Plugin endpoint
If the IPS integration expects a physical PHP endpoint file, use **Settings → WP-SSO Bridge → Generate & Download API file**.

The standard plugin endpoint is:
The plugin generates:

```text
https://example.com/?wp_sso_api=1&type=test
wp-sso-api.php
```

The generated file contains **no API secret**. It only loads WordPress and passes the request to the installed WP-SSO plugin.

### 5. Upload the generated API file

Upload `wp-sso-api.php` to the **WordPress root directory**. This is the same directory that contains:

```text
wp-config.php
wp-load.php
wp-admin/
wp-content/
wp-includes/
```

Do **not** place it inside `wp-content/plugins/`, your theme directory, or the uploads directory.

After upload, the compatibility endpoint will look like:

```text
https://example.com/wp-sso-api.php?type=test
```

Supported `type` values:
### 6. Install the IPS integration

Import `WordPress-SSO-IPS.xml` from the IPS / Invision Community plugin administration area and configure it with either the native plugin endpoint or the generated compatibility-file endpoint, plus the matching secret.

> The bundled IPS definition is legacy and may still depend on query-string authentication. Header-based authentication is preferred for modern integrations.

## 🔌 Supported endpoint types

| Type | Purpose |
| --- | --- |
Expand Down Expand Up @@ -140,7 +172,7 @@ The legacy query-string form is deprecated because URLs can appear in access log

`wp_api.php` remains in the repository for existing installations that copied the endpoint into the WordPress root.

New installations should use the standard WordPress plugin. Existing installations can migrate gradually by enabling `wp-sso`, configuring the same secret, changing the IPS endpoint, verifying `type=test`, and then removing the standalone file after successful validation.
New installations should use the standard WordPress plugin. If a physical PHP endpoint is still required, use the new generated `wp-sso-api.php` bootstrap instead of copying a secret-bearing legacy file.

## 🍪 Shared cookie domain

Expand All @@ -159,6 +191,8 @@ The maintained implementation includes:
- constant-time API-secret comparison with `hash_equals()`;
- header/Bearer authentication;
- generated secrets instead of a committed default credential;
- generated compatibility API files with no embedded secret;
- nonce and administrator-capability checks before API-file generation;
- request-type allowlisting;
- HTTP/HTTPS redirect sanitization;
- no-cache API responses;
Expand All @@ -182,6 +216,8 @@ The repository includes:
## ⚠️ Compatibility notes

- The WordPress plugin requires PHP 7.4+.
- The generated API compatibility file requires the plugin to remain installed and active.
- The generated API file must be in the same directory as `wp-load.php`.
- The legacy IPS XML has not yet been fully rewritten around header-only authentication.
- `userinfo` depends on the browser request carrying a valid WordPress logged-in cookie.
- Full integration tests against current WordPress and IPS releases are still planned.
Expand Down
43 changes: 37 additions & 6 deletions wp-sso/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: sso, ips, invision, authentication, wordpress
Requires at least: 5.8
Tested up to: 6.8
Requires PHP: 7.4
Stable tag: 1.0.0
Stable tag: 1.1.0
License: MIT
License URI: https://opensource.org/licenses/MIT

Expand All @@ -16,23 +16,34 @@ WP-SSO Bridge exposes an authenticated WordPress endpoint that can be used by an

The preferred authentication methods are the `X-WP-SSO-Key` request header or an `Authorization: Bearer` token. Legacy `api_key` query-string authentication is temporarily supported for compatibility with older IPS integrations.

Version 1.1.0 adds a guided setup screen and an optional API compatibility-file generator for IPS integrations that require a physical PHP endpoint file.

== Installation ==

1. Upload the `wp-sso` directory to `/wp-content/plugins/`, or upload the packaged ZIP from WordPress Admin > Plugins > Add Plugin > Upload Plugin.
2. Activate **WP-SSO Bridge for IPS**.
3. Open Settings > WP-SSO Bridge.
4. Copy the generated API secret or replace it with your own long random secret.
5. Configure the IPS integration to use the displayed endpoint and matching secret.
6. Prefer `X-WP-SSO-Key` or Bearer authentication when your IPS-side integration supports request headers.
4. Save the generated API secret or replace it with your own long random secret.
5. Use the native WordPress endpoint shown by the plugin whenever possible.
6. If your IPS integration requires a physical PHP endpoint file, click **Generate & Download API file**.
7. Upload the generated `wp-sso-api.php` to the WordPress root directory, next to `wp-config.php` and `wp-load.php`.
8. Configure IPS with the selected endpoint and the same API secret.
9. Prefer `X-WP-SSO-Key` or Bearer authentication when your IPS-side integration supports request headers.

You may also define the secret outside the database with either the `WP_SSO_API_KEY` environment variable or a `WP_SSO_API_KEY` PHP constant. These values take precedence over the saved WordPress option.

== Endpoint ==

The plugin endpoint is based on your WordPress home URL:
The recommended native plugin endpoint is based on your WordPress home URL:

`https://example.com/?wp_sso_api=1&type=test`

If the generated compatibility file is installed in the WordPress root, its endpoint is:

`https://example.com/wp-sso-api.php?type=test`

The generated file contains no API secret. It loads WordPress from `wp-load.php` and forwards the request to the installed WP-SSO Bridge plugin.

Supported `type` values are:

* `userinfo`
Expand All @@ -42,12 +53,32 @@ Supported `type` values are:
* `logout`
* `test`

== API compatibility file ==

The API file is optional. Use it only when your IPS integration expects a physical PHP endpoint.

After generating `wp-sso-api.php`, upload it to the WordPress root directory. This is the directory containing:

* `wp-config.php`
* `wp-load.php`
* `wp-admin/`
* `wp-content/`
* `wp-includes/`

Do not place the generated file inside the plugin directory, a theme directory, or the uploads directory.

== Security ==

Use HTTPS and a long random secret. Do not publish production credentials. Query-string API key authentication is deprecated because URLs can be captured in logs and browser history.
Use HTTPS and a long random secret. The generated compatibility file intentionally contains no secret. Do not publish production credentials. Query-string API key authentication is deprecated because URLs can be captured in logs and browser history.

== Changelog ==

= 1.1.0 =
* Added step-by-step usage instructions to the WordPress settings screen.
* Added an optional `wp-sso-api.php` compatibility-file generator.
* Added explicit instructions showing where the generated API file must be uploaded.
* Kept API credentials out of the generated file.

= 1.0.0 =
* Added installable WordPress plugin packaging.
* Added WordPress Settings page and generated API secret.
Expand Down
119 changes: 111 additions & 8 deletions wp-sso/wp-sso.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* Plugin Name: WP-SSO Bridge for IPS
* Plugin URI: https://github.com/drnecrotix/WP-SSO
* Description: WordPress-side SSO bridge for IPS/Invision Community with authenticated user, role, and login URL endpoints.
* Version: 1.0.0
* Author: Nikola Stoyanov
* Version: 1.1.0
* Author: Dr.Necrotix [NIKO]
* Author URI: https://github.com/drnecrotix
* License: MIT
* License URI: https://opensource.org/licenses/MIT
* Requires PHP: 7.4
Expand All @@ -16,15 +17,17 @@

final class WP_SSO_Bridge
{
const VERSION = '1.0.0';
const VERSION = '1.1.0';
const OPTION_API_KEY = 'wp_sso_api_key';
const ENDPOINT_QUERY_VAR = 'wp_sso_api';
const API_FILE_NAME = 'wp-sso-api.php';

public static function init()
{
add_action('template_redirect', array(__CLASS__, 'maybeHandleRequest'), 0);
add_action('admin_menu', array(__CLASS__, 'registerSettingsPage'));
add_action('admin_init', array(__CLASS__, 'registerSettings'));
add_action('admin_post_wp_sso_download_api_file', array(__CLASS__, 'downloadApiFile'));
}

public static function activate()
Expand Down Expand Up @@ -76,12 +79,16 @@ public static function renderSettingsPage()
}

$endpoint = add_query_arg(self::ENDPOINT_QUERY_VAR, '1', home_url('/'));
$testEndpoint = add_query_arg(array(self::ENDPOINT_QUERY_VAR => '1', 'type' => 'test'), home_url('/'));
$legacyFileUrl = trailingslashit(home_url('/')) . self::API_FILE_NAME;
$apiKey = self::getConfiguredApiKey();
?>
<div class="wrap">
<h1>WP-SSO Bridge</h1>
<p>Use this endpoint in the IPS integration:</p>
<p><code><?php echo esc_html($endpoint); ?></code></p>
<p>Connect an IPS / Invision Community installation to the current WordPress login session.</p>

<h2>1. Configure the API secret</h2>
<p>Use the same secret in WordPress and in the IPS-side integration. Prefer header or Bearer authentication whenever the IPS integration supports it.</p>

<form method="post" action="options.php">
<?php settings_fields('wp_sso_settings'); ?>
Expand All @@ -101,15 +108,111 @@ class="regular-text code"
</td>
</tr>
</table>
<?php submit_button(); ?>
<?php submit_button('Save API secret'); ?>
</form>

<h2>Connectivity example</h2>
<p><code>curl -H "X-WP-SSO-Key: YOUR_SECRET" "<?php echo esc_html(add_query_arg(array(self::ENDPOINT_QUERY_VAR => '1', 'type' => 'test'), home_url('/'))); ?>"</code></p>
<h2>2. Choose the endpoint</h2>
<p><strong>Recommended:</strong> use the native plugin endpoint. No extra PHP file is required:</p>
<p><code><?php echo esc_html($endpoint); ?></code></p>

<p>If your IPS integration specifically requires a physical PHP endpoint file, generate the compatibility file below.</p>

<h2>3. Generate the optional API file</h2>
<p>The generated <code><?php echo esc_html(self::API_FILE_NAME); ?></code> file does <strong>not</strong> contain your API secret. It only loads WordPress and forwards the request to this plugin.</p>

<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<input type="hidden" name="action" value="wp_sso_download_api_file" />
<?php wp_nonce_field('wp_sso_download_api_file'); ?>
<?php submit_button('Generate & Download API file', 'secondary', 'submit', false); ?>
</form>

<h3>Where to upload the generated file</h3>
<p>After downloading <code><?php echo esc_html(self::API_FILE_NAME); ?></code>, upload it to the <strong>WordPress root directory</strong> — the same directory that contains:</p>
<ul style="list-style: disc; margin-left: 2em;">
<li><code>wp-config.php</code></li>
<li><code>wp-load.php</code></li>
<li><code>wp-admin/</code></li>
<li><code>wp-content/</code></li>
<li><code>wp-includes/</code></li>
</ul>
<p>Example file URL after upload:</p>
<p><code><?php echo esc_html($legacyFileUrl); ?></code></p>
<p>Example test URL:</p>
<p><code><?php echo esc_html(add_query_arg('type', 'test', $legacyFileUrl)); ?></code></p>

<h2>4. Test the connection</h2>
<p>Recommended endpoint test:</p>
<p><code>curl -H "X-WP-SSO-Key: YOUR_SECRET" "<?php echo esc_html($testEndpoint); ?>"</code></p>
<p>If the configuration is correct, the endpoint returns <code>OK</code>.</p>

<h2>5. Configure IPS / Invision Community</h2>
<ol>
<li>Install/import the IPS integration file.</li>
<li>Set its WordPress endpoint to either the native plugin endpoint or the generated API file URL.</li>
<li>Use the same API secret configured above.</li>
<li>Prefer <code>X-WP-SSO-Key</code> or <code>Authorization: Bearer</code> over a query-string API key.</li>
<li>Test login, logout, registration, roles, and user information before enabling the integration for all users.</li>
</ol>

<p><strong>Security:</strong> use HTTPS and do not put production secrets inside files that may be downloaded, committed, backed up publicly, or served as plain text.</p>
</div>
<?php
}

public static function downloadApiFile()
{
if (!current_user_can('manage_options')) {
wp_die('You are not allowed to generate the WP-SSO API file.', 'WP-SSO', array('response' => 403));
}

check_admin_referer('wp_sso_download_api_file');

nocache_headers();
header('Content-Type: application/x-httpd-php; charset=utf-8');
header('Content-Disposition: attachment; filename="' . self::API_FILE_NAME . '"');
header('X-Content-Type-Options: nosniff');

echo self::getApiFileContents();
exit;
}

private static function getApiFileContents()
{
return <<<'PHP'
<?php
/**
* WP-SSO compatibility API bootstrap.
*
* Place this file in the WordPress root directory, next to wp-config.php
* and wp-load.php. The WP-SSO Bridge for IPS plugin must be installed
* and active in WordPress.
*
* This file intentionally contains no API secret.
*/

$wpLoad = __DIR__ . '/wp-load.php';

if (!is_file($wpLoad)) {
http_response_code(500);
header('Content-Type: text/plain; charset=utf-8');
echo 'WP-SSO error: wp-load.php was not found. Place this file in the WordPress root directory.';
exit;
}

require_once $wpLoad;

if (!class_exists('WP_SSO_Bridge')) {
http_response_code(500);
header('Content-Type: text/plain; charset=utf-8');
echo 'WP-SSO error: WP-SSO Bridge for IPS is not installed or active.';
exit;
}

$_GET['wp_sso_api'] = '1';
WP_SSO_Bridge::maybeHandleRequest();
PHP;
}

public static function maybeHandleRequest()
{
if (!isset($_GET[self::ENDPOINT_QUERY_VAR]) || (string) $_GET[self::ENDPOINT_QUERY_VAR] !== '1') {
Expand Down
Loading