updated plugin OpenID Connect Generic version 3.11.3

This commit is contained in:
2026-06-03 21:29:12 +00:00
committed by Gitium
parent 44cba94bcb
commit 766faf9ed9
151 changed files with 8558 additions and 1213 deletions

View File

@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -eu
# Activate the plugin.
cd "/app"
echo "Activating plugin..."
if ! wp plugin is-active daggerhart-openid-connect-generic 2>/dev/null; then
wp plugin activate daggerhart-openid-connect-generic --quiet
fi
echo "Done!"

View File

@ -1,57 +0,0 @@
// For format details, https://containers.dev/implementors/json_reference/.
{
"name": "WordPress Development Environment",
"dockerComposeFile": "../docker-compose.yml",
"service": "app",
"mounts": ["source=dind-var-lib-docker,target=/var/lib/docker,type=volume"],
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"customizations": {
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["ms-azuretools.vscode-docker"]
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"./local-features/welcome-message": "latest"
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [8080, 8081, 8026, 3306],
// Maps a port number, "host:port" value, range, or regular expression to a set of default options. See port attributes for available options
"portsAttributes": {
"8080": {
"label": "WordPress Development/Testing Site"
},
"8081": {
"label": "phpMyAdmin"
},
"8026": {
"label": "MailHog"
},
"3306": {
"label": "MariaDB"
}
},
// Use `onCreateCommand` to run commands as part of the container creation.
//"onCreateCommand": "chmod +x .devcontainer/install.sh && .devcontainer/install.sh",
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "chmod +x .devcontainer/setup.sh && .devcontainer/setup.sh",
// Use 'postStartCommand' to run commands after the container has started.
"postStartCommand": "chmod +x .devcontainer/activate.sh && .devcontainer/activate.sh",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "wp_php",
// A set of name-value pairs that sets or overrides environment variables for the devcontainer.json supporting service / tool (or sub-processes like terminals) but not the container as a whole.
"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }
}

View File

@ -1,8 +0,0 @@
{
"id": "welcome-message",
"name": "Install the First Start Welcome Message",
"install": {
"app": "",
"file": "install.sh"
}
}

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -eux
export DEBIAN_FRONTEND=noninteractive
# Copy the welcome message
if [ ! -f /usr/local/etc/vscode-dev-containers/first-run-notice.txt ]; then
echo "Installing First Run Notice..."
echo -e "👋 Welcome to \"OpenID Connect for WP Development\" in Dev Containers!\n\n🛠 Your environment is fully setup with all the required software.\n\n🚀 To get started, wait for the \"postCreateCommand\" to finish setting things up, then open the portforwarded URL and append '/wp/wp-admin'. Login to the WordPress Dashboard using \`admin/password\` for the credentials.\n" | sudo tee /usr/local/etc/vscode-dev-containers/first-run-notice.txt
fi
echo "Done!"

View File

@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -eu
# true is shell command and always return 0
# false always return 1
if [ -z "${CODESPACES}" ] ; then
SITE_HOST="http://localhost:8080"
else
SITE_HOST="https://${CODESPACE_NAME}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
fi
PLUGIN_DIR=/workspaces/openid-connect-generic
# Attempt to make ipv4 traffic have a higher priority than ipv6.
sudo sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf"
# Install Composer dependencies.
cd "${PLUGIN_DIR}"
echo "Installing Composer dependencies..."
COMPOSER_NO_INTERACTION=1 COMPOSER_ALLOW_XDEBUG=0 COMPOSER_MEMORY_LIMIT=-1 composer install --no-progress --quiet
# Install NPM dependencies.
cd "${PLUGIN_DIR}"
if [ ! -d "node_modules" ]; then
echo "Installing NPM dependencies..."
npm ci
fi
# Setup the WordPress environment.
cd "/app"
if ! wp core is-installed 2>/dev/null; then
echo "Setting up WordPress at $SITE_HOST"
wp core install --url="$SITE_HOST" --title="OpenID Connect Development" --admin_user="admin" --admin_email="admin@example.com" --admin_password="password" --skip-email --quiet
fi
echo "Done!"

View File

@ -1,17 +0,0 @@
# List the start up tasks. Learn more https://www.gitpod.io/docs/config-start-tasks/
tasks:
- name: WordPress Development Environment
init: npm run setup # runs during prebuild
command: |
npm start -- --update
npm stop
npm start -- --update
# List the ports to expose. Learn more https://www.gitpod.io/docs/config-ports/
ports:
- port: 8888
onOpen: notify
visibility: public
- port: 8889
onOpen: notify
visibility: public

View File

@ -1,16 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"label": "npm: build",
"detail": "npm run grunt build"
}
]
}

View File

@ -1,203 +0,0 @@
# OpenId Connect Generic Changelog
**3.10.0**
- Chore: @timnolte - Dependency updates.
- Fix: @drzraf - Prevents running the auth url filter twice.
- Fix: @timnolte - Updates the log cleanup handling to properly retain the configured number of log entries.
- Fix: @timnolte - Updates the log display output to reflect the log retention policy.
- Chore: @timnolte - Adds Unit Testing & New Local Development Environment.
- Feature: @timnolte - Updates logging to allow for tracking processing time.
- Feature: @menno-ll - Adds a remember me feature via a new filter.
- Improvement: @menno-ll - Updates WP Cookie Expiration to Same as Session Length.
**3.9.1**
- Improvement: @timnolte - Refactors Composer setup and GitHub Actions.
- Improvement: @timnolte - Bumps WordPress tested version compatibility.
**3.9.0**
- Feature: @matchaxnb - Added support for additional configuration constants.
- Feature: @schanzen - Added support for agregated claims.
- Fix: @rkcreation - Fixed access token not updating user metadata after login.
- Fix: @danc1248 - Fixed user creation issue on Multisite Networks.
- Feature: @RobjS - Added plugin singleton to support for more developer customization.
- Feature: @jkouris - Added action hook to allow custom handling of session expiration.
- Fix: @tommcc - Fixed admin CSS loading only on the plugin settings screen.
- Feature: @rkcreation - Added method to refresh the user claim.
- Feature: @Glowsome - Added acr_values support & verification checks that it when defined in options is honored.
- Fix: @timnolte - Fixed regression which caused improper fallback on missing claims.
- Fix: @slykar - Fixed missing query string handling in redirect URL.
- Fix: @timnolte - Fixed issue with some user linking and user creation handling.
- Improvement: @timnolte - Fixed plugin settings typos and screen formatting.
- Security: @timnolte - Updated build tooling security vulnerabilities.
- Improvement: @timnolte - Changed build tooling scripts.
**3.8.5**
- Fix: @timnolte - Fixed missing URL request validation before use & ensure proper current page URL is setup for Redirect Back.
- Fix: @timnolte - Fixed Redirect URL Logic to Handle Sub-directory Installs.
- Fix: @timnolte - Fixed issue with redirecting user back when the openid_connect_generic_auth_url shortcode is used.
**3.8.4**
- Fix: @timnolte - Fixed invalid State object access for redirection handling.
- Improvement: @timnolte - Fixed local wp-env Docker development environment.
- Improvement: @timnolte - Fixed Composer scripts for linting and static analysis.
**3.8.3**
- Fix: @timnolte - Fixed problems with proper redirect handling.
- Improvement: @timnolte - Changes redirect handling to use State instead of cookies.
- Improvement: @timnolte - Refactored additional code to meet coding standards.
**3.8.2**
- Fix: @timnolte - Fixed reported XSS vulnerability on WordPress login screen.
**3.8.1**
- Fix: @timnolte - Prevent SSO redirect on password protected posts.
- Fix: @timnolte - CI/CD build issues.
- Fix: @timnolte - Invalid redirect handling on logout for Auto Login setting.
**3.8.0**
- Feature: @timnolte - Ability to use 6 new constants for setting client configuration instead of storing in the DB.
- Improvement: @timnolte - NPM version requirements for development.
- Improvement: @timnolte - Travis CI build fixes.
- Improvement: @timnolte - GrumPHP configuration updates for code contributions.
- Improvement: @timnolte - Refactored to meet WordPress coding standards.
- Improvement: @timnolte - Refactored to provide localization.
- Improvement: @timnolte - Refactored to provide a Docker-based local development environment.
**3.7.1**
- Fix: Release Version Number.
**3.7.0**
- Feature: @timnolte - Ability to enable/disable token refresh. Useful for IDPs that don't support token refresh.
- Feature: @timnolte - Support custom redirect URL(`redirect_to`) with the authentication URL & login button shortcodes.
- Supports additional attribute overrides including login `button_text`, `endpoint_login`, `scope`, `redirect_uri`.
**3.6.0**
- Improvement: @RobjS - Improved error messages during login state failure.
- Improvement: @RobjS - New developer filter for login form button URL.
- Fix: @cs1m0n - Only increment username during new user creation if the "Link existing user" setting is enabled.
- Fix: @xRy-42 - Allow periods and spaces in usernames to match what WordPress core allows.
- Feature: @benochen - New setting named "Create user if does not exist" determines whether new users are created during login attempts.
- Improvement: @flat235 - Username transliteration and normalization.
**3.5.1**
- Fix: @daggerhart - New approach to state management using transients.
**3.5.0**
- Readme fix: @thijskh - Fix syntax error in example openid-connect-generic-login-button-text
- Feature: @slavicd - Allow override of the plugin by posting credentials to wp-login.php
- Feature: @gassan - New action on use login
- Fix: @daggerhart - Avoid double question marks in auth url query string
- Fix: @drzraf - wp-cli bootstrap must not inhibit custom rewrite rules
- Syntax change: @mullikine - Change PHP keywords to comply with PSR2
**3.4.1**
- Minor documentation update and additional error checking.
**3.4.0**
- Feature: @drzraf - New filter hook: ability to filter claim and derived user data before user creation.
- Feature: @anttileppa - State time limit can now be changed on the settings page.
- Fix: @drzraf - Fix PHP notice when using traditional login, $token_response may be empty.
- Fix: @drzraf - Fixed a notice when cookie does not contain expected redirect_url
**3.3.1**
- Prefixing classes for more efficient autoloading.
- Avoid altering global wp_remote_post() parameters.
- Minor metadata updates for wp.org
**3.3.0**
- Fix: @pjeby - Handle multiple user sessions better by using the `WP_Session_Tokens` object. Predecessor to fixes for multiple other issues: #49, #50, #51
**3.2.1**
- Bug fix: @svenvanhal - Exit after issuing redirect. Fixes #46
**3.2.0**
- Feature: @robbiepaul - trigger core action `wp_login` when user is logged in through this plugin
- Feature: @moriyoshi - Determine the WP_User display name with replacement tokens on the settings page. Tokens can be any property of the user_claim.
- Feature: New setting to set redirect URL when session expires.
- Feature: @robbiepaul - New filter for modifying authentication URL
- Fix: @cedrox - Adding id_token_hint to logout URL according to spec
- Bug fix: Provide port to the request header when requesting the user_claim
**3.1.0**
- Feature: @rwasef1830 - Refresh tokens
- Feature: @rwasef1830 - Integrated logout support with end_session endpoint
- Feature: May use an alternate redirect_uri that doesn't rely on admin-ajax
- Feature: @ahatherly - Support for IDP behind reverse proxy
- Bug fix: @robertstaddon - case insensitive check for Bearer token
- Bug fix: @rwasef1830 - "redirect to origin when auto-sso" cookie issue
- Bug fix: @rwasef1830 - PHP Warnings headers already sent due to attempts to redirect and set cookies during login form message
- Bug fix: @rwasef1830 - expire session when access_token expires if no refresh token found
- UX fix: @rwasef1830 - Show login button on error redirect when using auto-sso
**3.0.8**
- Feature: @wgengarelly - Added `openid-connect-generic-update-user-using-current-claim` action hook allowing other plugins/themes
to take action using the fresh claims received when an existing user logs in.
**3.0.7**
- Bug fix: @wgengarelly - When requesting userinfo, send the access token using the Authorization header field as recommended in
section 5.3.1 of the specs.
**3.0.6**
- Bug fix: @robertstaddon - If "Link Existing Users" is enabled, allow users who login with OpenID Connect to also log in with WordPress credentials
**3.0.5**
- Feature: @robertstaddon - Added `[openid_connect_generic_login_button]` shortcode to allow the login button to be placed anywhere
- Feature: @robertstaddon - Added setting to "Redirect Back to Origin Page" after a successful login instead of redirecting to the home page.
**3.0.4**
- Feature: @robertstaddon - Added setting to allow linking existing WordPress user accounts with newly-authenticated OpenID Connect login
**3.0.3**
- Using WordPresss's is_ssl() for setcookie()'s "secure" parameter
- Bug fix: Incrementing username in case of collision.
- Bug fix: Wrong error sent when missing token body
**3.0.2**
- Added http_request_timeout setting
**3.0.1**
- Finalizing 3.0.x api
**3.0**
- Complete rewrite to separate concerns
- Changed settings keys for clarity (requires updating settings if upgrading from another version)
- Error logging
**2.1**
- Working my way closer to spec. Possible breaking change. Now checking for preferred_username as priority.
- New username determination to avoid collisions
**2.0**
Complete rewrite

View File

@ -1,383 +0,0 @@
# OpenID Connect Generic Client
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
A simple client that provides SSO or opt-in authentication against a generic OAuth2 Server implementation.
## Description
This plugin allows to authenticate users against OpenID Connect OAuth2 API with Authorization Code Flow.
Once installed, it can be configured to automatically authenticate users (SSO), or provide a "Login with OpenID Connect"
button on the login form. After consent has been obtained, an existing user is automatically logged into WordPress, while
new users are created in WordPress database.
Much of the documentation can be found on the Settings > OpenID Connect Generic dashboard page.
## Table of Contents
- [Installation](#installation)
- [Composer](#composer)
- [Frequently Asked Questions](#frequently-asked-questions)
- [What is the client's Redirect URI?](#what-is-the-clients-redirect-uri)
- [Can I change the client's Redirect URI?](#can-i-change-the-clients-redirect-uri)
- [Configuration Environment Variables/Constants](#configuration-environment-variables-constants)
- [Hooks](#hooks)
- [Filters](#filters)
- [openid-connect-generic-alter-request](#openid-connect-generic-alter-request)
- [openid-connect-generic-login-button-text](#openid-connect-generic-login-button-text)
- [openid-connect-generic-auth-url](#openid-connect-generic-auth-url)
- [openid-connect-generic-user-login-test](#openid-connect-generic-user-login-test)
- [openid-connect-generic-user-creation-test](#openid-connect-generic-user-creation-test)
- <del>[openid-connect-generic-alter-user-claim](#openid-connect-generic-alter-user-claim)</del>
- [openid-connect-generic-alter-user-data](#openid-connect-generic-alter-user-data)
- [openid-connect-generic-settings-fields](#openid-connect-generic-settings-fields)
- [Actions](#actions)
- [openid-connect-generic-user-create](#openid-connect-generic-user-create)
- [openid-connect-generic-user-update](#openid-connect-generic-user-update)
- [openid-connect-generic-update-user-using-current-claim](#openid-connect-generic-update-user-using-current-claim)
- [openid-connect-generic-redirect-user-back](#openid-connect-generic-redirect-user-back)
## Installation
1. Upload to the `/wp-content/plugins/` directory
1. Activate the plugin
1. Visit Settings > OpenID Connect and configure to meet your needs
### Composer
[OpenID Connect Generic on packagist](https://packagist.org/packages/daggerhart/openid-connect-generic)
Installation:
`composer require daggerhart/openid-connect-generic`
## Frequently Asked Questions
### What is the client's Redirect URI?
Most OAuth2 servers should require a whitelist of redirect URIs for security purposes. The Redirect URI provided
by this client is like so: `https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize`
Replace `example.com` with your domain name and path to WordPress.
### Can I change the client's Redirect URI?
Some OAuth2 servers do not allow for a client redirect URI to contain a query string. The default URI provided by
this module leverages WordPress's `admin-ajax.php` endpoint as an easy way to provide a route that does not include
HTML, but this will naturally involve a query string. Fortunately, this plugin provides a setting that will make use of
an alternate redirect URI that does not include a query string.
On the settings page for this plugin (Dashboard > Settings > OpenID Connect Generic) there is a checkbox for
**Alternate Redirect URI**. When checked, the plugin will use the Redirect URI
`https://example.com/openid-connect-authorize`.
## Configuration Environment Variables/Constants
- Client ID: `OIDC_CLIENT_ID`
- Client Secret Key: `OIDC_CLIENT_SECRET`
- Login Endpoint URL: `OIDC_ENDPOINT_LOGIN_URL`
- Userinfo Endpoint URL: `OIDC_ENDPOINT_USERINFO_URL`
- Token Validation Endpoint URL: `OIDC_ENDPOINT_TOKEN_URL`
- End Session Endpoint URL: `OIDC_ENDPOINT_LOGOUT_URL`
- OpenID scope: `OIDC_CLIENT_SCOPE` (space separated)
- OpenID login type: `OIDC_LOGIN_TYPE` ('button' or 'auto')
- Enforce privacy: `OIDC_ENFORCE_PRIVACY` (boolean)
- Create user if they do not exist: `OIDC_CREATE_IF_DOES_NOT_EXIST` (boolean)
- Link existing user: `OIDC_LINK_EXISTING_USERS` (boolean)
- Redirect user back to origin page: `OIDC_REDIRECT_USER_BACK` (boolean)
- Redirect on logout: `OIDC_REDIRECT_ON_LOGOUT` (boolean)
## Hooks
This plugin provides a number of hooks to allow for a significant amount of customization of the plugin operations from
elsewhere in the WordPress system.
### Filters
Filters are WordPress hooks that are used to modify data. The first argument in a filter hook is always expected to be
returned at the end of the hook.
WordPress filters API - [`add_filter()`](https://developer.wordpress.org/reference/functions/add_filter/) and
[`apply_filters()`](https://developer.wordpress.org/reference/functions/apply_filters/).
Most often you'll only need to use `add_filter()` to hook into this plugin's code.
#### `openid-connect-generic-alter-request`
Hooks directly into client before requests are sent to the OpenID Server.
Provides 2 arguments: the request array being sent to the server, and the operation currently being executed by this
plugin.
Possible operations:
- get-authentication-token
- refresh-token
- get-userinfo
```
add_filter('openid-connect-generic-alter-request', function( $request, $operation ) {
if ( $operation == 'get-authentication-token' ) {
$request['some_key'] = 'modified value';
}
return $request;
}, 10, 2);
```
#### `openid-connect-generic-login-button-text`
Modify the login button text. Default value is `__( 'Login with OpenID Connect' )`.
Provides 1 argument: the current login button text.
```
add_filter('openid-connect-generic-login-button-text', function( $text ) {
$text = __('Login to my super cool IDP server');
return $text;
});
```
#### `openid-connect-generic-auth-url`
Modify the authentication URL before presented to the user. This is the URL that will send the user to the IDP server
for login.
Provides 1 argument: the plugin generated URL.
```
add_filter('openid-connect-generic-auth-url', function( $url ) {
// Add some custom data to the url.
$url.= '&my_custom_data=123abc';
return $url;
});
```
#### `openid-connect-generic-user-login-test`
Determine whether or not the user should be logged into WordPress.
Provides 2 arguments: the boolean result of the test (default `TRUE`), and the `$user_claim` array from the server.
```
add_filter('openid-connect-generic-user-login-test', function( $result, $user_claim ) {
// Don't let Terry login.
if ( $user_claim['email'] == 'terry@example.com' ) {
$result = FALSE;
}
return $result;
}, 10, 2);
```
#### `openid-connect-generic-user-creation-test`
Determine whether or not the user should be created. This filter is called when a new user is trying to login and they
do not currently exist within WordPress.
Provides 2 arguments: the boolean result of the test (default `TRUE`), and the `$user_claim` array from the server.
```
add_filter('', function( $result, $user_claim ) {
// Don't let anyone from example.com create an account.
$email_array = explode( '@', $user_claim['email'] );
if ( $email_array[1] == 'example.com' ) {
$result = FALSE;
}
return $result;
}, 10, 2)
```
#### <del>`openid-connect-generic-alter-user-claim`</del>
Modify the `$user_claim` before the plugin builds the `$user_data` array for new user created.
**Deprecated** - This filter is not very useful due to some changes that were added later. Recommend not using this
filter, and using the `openid-connect-generic-alter-user-data` filter instead. Practically, you can only change the
user's `first_name` and `last_name` values with this filter, but you could easily do that in
`openid-connect-generic-alter-user-data` as well.
Provides 1 argument: the `$user_claim` from the server.
```
// Not a great example because the hook isn't very useful.
add_filter('openid-connect-generic-alter-user-claim', function( $user_claim ) {
// Use the beginning of the user's email address as the user's first name.
if ( empty( $user_claim['given_name'] ) ) {
$email_array = explode( '@', $user_claim['email'] );
$user_claim['given_name'] = $email_array[0];
}
return $user_claim;
});
```
#### `openid-connect-generic-alter-user-data`
Modify a new user's data immediately before the user is created.
Provides 2 arguments: the `$user_data` array that will be sent to `wp_insert_user()`, and the `$user_claim` from the
server.
```
add_filter('openid-connect-generic-alter-user-data', function( $user_data, $user_claim ) {
// Don't register any user with their real email address. Create a fake internal address.
if ( !empty( $user_data['user_email'] ) ) {
$email_array = explode( '@', $user_data['user_email'] );
$email_array[1] = 'my-fake-domain.co';
$user_data['user_email'] = implode( '@', $email_array );
}
return $user_data;
}, 10, 2);
```
#### `openid-connect-generic-settings-fields`
For extending the plugin with a new setting field (found on Dashboard > Settings > OpenID Connect Generic) that the site
administrator can modify. Also useful to alter the existing settings fields.
See `/includes/openid-connect-generic-settings-page.php` for how fields are constructed.
New settings fields will be automatically saved into the wp_option for this plugin's settings, and will be available in
the `\OpenID_Connect_Generic_Option_Settings` object this plugin uses.
**Note:** It can be difficult to get a copy of the settings from within other hooks. The easiest way to make use of
settings in your custom hooks is to call
`$settings = get_option('openid_connect_generic_settings', array());`.
Provides 1 argument: the existing fields array.
```
add_filter('openid-connect-generic-settings-fields', function( $fields ) {
// Modify an existing field's title.
$fields['endpoint_userinfo']['title'] = __('User information endpoint url');
// Add a new field that is a simple checkbox.
$fields['block_terry'] = array(
'title' => __('Block Terry'),
'description' => __('Prevent Terry from logging in'),
'type' => 'checkbox',
'section' => 'authorization_settings',
);
// A select field that provides options.
$fields['deal_with_terry'] = array(
'title' => __('Manage Terry'),
'description' => __('How to deal with Terry when he tries to log in.'),
'type' => 'select',
'options' => array(
'allow' => __('Allow login'),
'block' => __('Block'),
'redirect' => __('Redirect'),
),
'section' => 'authorization_settings',
);
return $fields;
});
```
"Sections" are where your setting appears on the admin settings page. Keys for settings sections:
- client_settings
- user_settings
- authorization_settings
- log_settings
Field types:
- text
- checkbox
- select (requires an array of "options")
### Actions
WordPress actions are generic events that other plugins can react to.
Actions API: [`add_action`](https://developer.wordpress.org/reference/functions/add_action/) and [`do_actions`](https://developer.wordpress.org/reference/functions/do_action/)
You'll probably only ever want to use `add_action` when hooking into this plugin.
#### `openid-connect-generic-user-create`
React to a new user being created by this plugin.
Provides 2 arguments: the `\WP_User` object that was created, and the `$user_claim` from the IDP server.
```
add_action('openid-connect-generic-user-create', function( $user, $user_claim ) {
// Send the user an email when their account is first created.
wp_mail(
$user->user_email,
__('Welcome to my web zone'),
"Hi {$user->first_name},\n\nYour account has been created at my cool website.\n\n Enjoy!"
);
}, 10, 2);
```
#### `openid-connect-generic-user-update`
React to the user being updated after login. This is the event that happens when a user logins and they already exist as
a user in WordPress, as opposed to a new WordPress user being created.
Provides 1 argument: the user's WordPress user ID.
```
add_action('openid-connect-generic-user-update', function( $uid ) {
// Keep track of the number of times the user has logged into the site.
$login_count = get_user_meta( $uid, 'my-user-login-count', TRUE);
$login_count += 1;
add_user_meta( $uid, 'my-user-login-count', $login_count, TRUE);
});
```
#### `openid-connect-generic-update-user-using-current-claim`
React to an existing user logging in (after authentication and authorization).
Provides 2 arguments: the `WP_User` object, and the `$user_claim` provided by the IDP server.
```
add_action('openid-connect-generic-update-user-using-current-claim', function( $user, $user_claim) {
// Based on some data in the user_claim, modify the user.
if ( !empty( $user_claim['wp_user_role'] ) ) {
if ( $user_claim['wp_user_role'] == 'should-be-editor' ) {
$user->set_role( 'editor' );
}
}
}, 10, 2);
```
#### `openid-connect-generic-redirect-user-back`
React to a user being redirected after a successful login. This hook is the last hook that will fire when a user logs
in. It will only fire if the plugin setting "Redirect Back to Origin Page" is enabled at Dashboard > Settings >
OpenID Connect Generic. It will fire for both new and existing users.
Provides 2 arguments: the url where the user will be redirected, and the `WP_User` object.
```
add_action('openid-connect-generic-redirect-user-back', function( $redirect_url, $user ) {
// Take over the redirection complete. Send users somewhere special based on their capabilities.
if ( $user->has_cap( 'edit_users' ) ) {
wp_redirect( admin_url( 'users.php' ) );
exit();
}
}, 10, 2);
```
### User Meta Data
This plugin stores meta data about the user for both practical and debugging purposes.
* `openid-connect-generic-subject-identity` - The identity of the user provided by the IDP server.
* `openid-connect-generic-last-id-token-claim` - The user's most recent `id_token` claim, decoded and stored as an array.
* `openid-connect-generic-last-user-claim` - The user's most recent `user_claim`, stored as an array.
* `openid-connect-generic-last-token-response` - The user's most recent `token_response`, stored as an array.

View File

@ -1,133 +0,0 @@
# OpenID Connect Generic Client #
**Contributors:** [daggerhart](https://profiles.wordpress.org/daggerhart/), [tnolte](https://profiles.wordpress.org/tnolte/)
**Tags:** security, login, oauth2, openidconnect, apps, authentication, autologin, sso
**Requires at least:** 5.0
**Tested up to:** 6.4.3
**Stable tag:** 3.10.1
**Requires PHP:** 7.4
**License:** GPLv2 or later
**License URI:** http://www.gnu.org/licenses/gpl-2.0.html
A simple client that provides SSO or opt-in authentication against a generic OAuth2 Server implementation.
## Description ##
This plugin allows to authenticate users against OpenID Connect OAuth2 API with Authorization Code Flow.
Once installed, it can be configured to automatically authenticate users (SSO), or provide a "Login with OpenID Connect"
button on the login form. After consent has been obtained, an existing user is automatically logged into WordPress, while
new users are created in WordPress database.
Much of the documentation can be found on the Settings > OpenID Connect Generic dashboard page.
Please submit issues to the Github repo: https://github.com/oidc-wp/openid-connect-generic
## Installation ##
1. Upload to the `/wp-content/plugins/` directory
1. Activate the plugin
1. Visit Settings > OpenID Connect and configure to meet your needs
## Frequently Asked Questions ##
### What is the client's Redirect URI? ###
Most OAuth2 servers will require whitelisting a set of redirect URIs for security purposes. The Redirect URI provided
by this client is like so: https://example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize
Replace `example.com` with your domain name and path to WordPress.
### Can I change the client's Redirect URI? ###
Some OAuth2 servers do not allow for a client redirect URI to contain a query string. The default URI provided by
this module leverages WordPress's `admin-ajax.php` endpoint as an easy way to provide a route that does not include
HTML, but this will naturally involve a query string. Fortunately, this plugin provides a setting that will make use of
an alternate redirect URI that does not include a query string.
On the settings page for this plugin (Dashboard > Settings > OpenID Connect Generic) there is a checkbox for
**Alternate Redirect URI**. When checked, the plugin will use the Redirect URI
`https://example.com/openid-connect-authorize`.
## Changelog ##
### 3.10.1 ###
* Chore: @daggerhart - Readme updates and clarifications.
* Chore: @daggerhart - Release workflow updates.
* Improved error handling for malformed urls.
* Fix: @JUVOJustin - Change request for userinfo to GET.
* Feature: @JUVOJustin - New filter for settings values `openid-connect-generic-settings`.
* Feature: @JUVOJustin - New filter for state values `openid-connect-generic-new-state-value`.
### 3.10.0 ###
* Chore: @timnolte - Dependency updates.
* Fix: @drzraf - Prevents running the auth url filter twice.
* Fix: @timnolte - Updates the log cleanup handling to properly retain the configured number of log entries.
* Fix: @timnolte - Updates the log display output to reflect the log retention policy.
* Chore: @timnolte - Adds Unit Testing & New Local Development Environment.
* Feature: @timnolte - Updates logging to allow for tracking processing time.
* Feature: @menno-ll - Adds a remember me feature via a new filter.
* Improvement: @menno-ll - Updates WP Cookie Expiration to Same as Session Length.
### 3.9.1 ###
* Improvement: @timnolte - Refactors Composer setup and GitHub Actions.
* Improvement: @timnolte - Bumps WordPress tested version compatibility.
### 3.9.0 ###
* Feature: @matchaxnb - Added support for additional configuration constants.
* Feature: @schanzen - Added support for agregated claims.
* Fix: @rkcreation - Fixed access token not updating user metadata after login.
* Fix: @danc1248 - Fixed user creation issue on Multisite Networks.
* Feature: @RobjS - Added plugin singleton to support for more developer customization.
* Feature: @jkouris - Added action hook to allow custom handling of session expiration.
* Fix: @tommcc - Fixed admin CSS loading only on the plugin settings screen.
* Feature: @rkcreation - Added method to refresh the user claim.
* Feature: @Glowsome - Added acr_values support & verification checks that it when defined in options is honored.
* Fix: @timnolte - Fixed regression which caused improper fallback on missing claims.
* Fix: @slykar - Fixed missing query string handling in redirect URL.
* Fix: @timnolte - Fixed issue with some user linking and user creation handling.
* Improvement: @timnolte - Fixed plugin settings typos and screen formatting.
* Security: @timnolte - Updated build tooling security vulnerabilities.
* Improvement: @timnolte - Changed build tooling scripts.
### 3.8.5 ###
* Fix: @timnolte - Fixed missing URL request validation before use & ensure proper current page URL is setup for Redirect Back.
* Fix: @timnolte - Fixed Redirect URL Logic to Handle Sub-directory Installs.
* Fix: @timnolte - Fixed issue with redirecting user back when the openid_connect_generic_auth_url shortcode is used.
### 3.8.4 ###
* Fix: @timnolte - Fixed invalid State object access for redirection handling.
* Improvement: @timnolte - Fixed local wp-env Docker development environment.
* Improvement: @timnolte - Fixed Composer scripts for linting and static analysis.
### 3.8.3 ###
* Fix: @timnolte - Fixed problems with proper redirect handling.
* Improvement: @timnolte - Changes redirect handling to use State instead of cookies.
* Improvement: @timnolte - Refactored additional code to meet coding standards.
### 3.8.2 ###
* Fix: @timnolte - Fixed reported XSS vulnerability on WordPress login screen.
### 3.8.1 ###
* Fix: @timnolte - Prevent SSO redirect on password protected posts.
* Fix: @timnolte - CI/CD build issues.
* Fix: @timnolte - Invalid redirect handling on logout for Auto Login setting.
### 3.8.0 ###
* Feature: @timnolte - Ability to use 6 new constants for setting client configuration instead of storing in the DB.
* Improvement: @timnolte - Plugin development & contribution updates.
* Improvement: @timnolte - Refactored to meet WordPress coding standards.
* Improvement: @timnolte - Refactored to provide localization.
--------
[See the previous changelogs here](https://github.com/oidc-wp/openid-connect-generic/blob/main/CHANGELOG.md#changelog)

View File

@ -1,17 +0,0 @@
# Security Policy
## Supported Versions
We follow the [WordPress Core style of versioning](https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/) rather than traditional [SemVer](https://semver.org/). This means that a move from version 3.9 to 4.0 is no different from a move from version 3.8 to 3.9. When a **PATCH** version is released it represents a bug fix, or non-code, only change.
The latest version released is the only version that will receive security updates, generally as a **PATCH** release unless a security issue requires a functionality change in which requires a minor/major version bump.
## Reporting a Vulnerability
For security reasons, the following are acceptable options for reporting all security issues.
1. Via Keybase secure message to [timnolte](https://keybase.io/timnolte/chat) or [daggerhart](https://keybase.io/daggerhart/chat).
2. Send a DM via the [WordPress Slack](https://make.wordpress.org/chat/) to `tnolte`.
3. Via a private [security advisory](https://github.com/oidc-wp/openid-connect-generic/security/advisories) notice.
Please disclose responsibly and not via public GitHub Issues (which allows for exploiting issues in the wild before the patch is released).

View File

@ -1,10 +0,0 @@
coverage:
status:
project:
default:
target: auto
threshold: 0.5%
patch: off
comment:
require_changes: true

View File

@ -30,3 +30,43 @@
#logger-table .col-details label {
font-weight: bold;
}
/* Discovery Document Import Form */
.oidc-discovery-section {
margin: 20px 0;
}
.oidc-discovery-summary {
cursor: pointer;
font-size: 1.1em;
font-weight: 600;
padding: 10px;
background: #f0f0f1;
border: 1px solid #c3c4c7;
border-radius: 4px;
}
.oidc-discovery-summary:hover {
background: #e8e8e9;
}
.oidc-discovery-content {
margin: 15px 0 0 0;
padding: 15px;
border-left: 4px solid #72aee6;
}
.oidc-discovery-url-input {
width: 500px;
max-width: 100%;
}
.oidc-discovery-separator {
margin: 30px 0;
}
/* Warning text styling */
.oidc-warning {
color: #d63638;
font-weight: bold;
}

View File

@ -1,95 +0,0 @@
# This is the Compose file for command-line services.
# Anything that doesn't need to be run as part of the main `docker-compose up'
# command should reside in here and be invoked by a helper script.
version: "3.7"
services:
app:
image: ghcr.io/ndigitals/wp-dev-container:php-8.0-node-16
restart: always
depends_on:
- db
- phpmyadmin
- web
- mailhog
working_dir: /workspaces/openid-connect-generic
environment: &env
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_TEST_DB_NAME: wordpress_test
CODESPACES: "${CODESPACES}"
CODESPACE_NAME: "${CODESPACE_NAME}"
GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: "${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
volumes:
- .:/workspaces/openid-connect-generic:cached
- ./tools/local-env:/app:cached
- ./tools/php/php-cli.ini:/usr/local/etc/php/php-cli.ini:ro,cached
- .:/app/wp-content/plugins/daggerhart-openid-connect-generic:ro,cached
- ~/.composer:/root/.composer:cached
- ~/.npm:/root/.npm:cached
networks:
- oidcwp-net
web:
image: httpd
restart: unless-stopped
depends_on:
- db
ports:
- 8080:80
environment:
<<: *env
volumes:
- ./tools/local-env:/app:cached
- .:/app/wp-content/plugins/daggerhart-openid-connect-generic:ro,cached
- ./tools/apache/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro,cached
networks:
- oidcwp-net
db:
image: mariadb
restart: unless-stopped
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
volumes:
- db:/var/lib/mysql
- ./tests/db-wordpress_test.sql:/docker-entrypoint-initdb.d/db-wordpress_test.sql
networks:
- oidcwp-net
phpmyadmin:
image: phpmyadmin
restart: unless-stopped
depends_on:
- db
ports:
- 8081:8081
environment:
PMA_HOST: db
APACHE_PORT: 8081
networks:
- oidcwp-net
## SMTP Server + Web Interface for viewing and testing emails during development.
mailhog:
image: mailhog/mailhog
restart: unless-stopped
ports:
- 1025:1025 # smtp server
- 8026:8025 # web ui
networks:
- oidcwp-net
volumes:
db:
networks:
oidcwp-net:

View File

@ -99,6 +99,11 @@ class OpenID_Connect_Generic_Client_Wrapper {
// Alter the requests according to settings.
add_filter( 'openid-connect-generic-alter-request', array( $client_wrapper, 'alter_request' ), 10, 2 );
// Ensure tokens are refreshed before they expire.
if ( $settings->token_refresh_enable ) {
add_action( 'init', array( $client_wrapper, 'ensure_tokens_still_fresh' ) );
}
if ( is_admin() ) {
/*
* Use the ajax url to handle processing authorization without any html output
@ -252,7 +257,15 @@ class OpenID_Connect_Generic_Client_Wrapper {
}
$user_id = wp_get_current_user()->ID;
$last_token_response = get_user_meta( $user_id, 'openid-connect-generic-last-token-response', true );
$last_token_response = get_user_option( 'openid-connect-generic-last-token-response', $user_id );
if ( false === $last_token_response ) {
$last_token_response = get_user_meta(
$user_id,
'openid-connect-generic-last-token-response',
true
);
}
if ( ! empty( $last_token_response['expires_in'] ) && ! empty( $last_token_response['time'] ) ) {
/*
@ -296,9 +309,9 @@ class OpenID_Connect_Generic_Client_Wrapper {
}
// Capture the time so that access token expiration can be calculated later.
$token_response[] = time();
$token_response['time'] = time();
update_user_meta( $user_id, 'openid-connect-generic-last-token-response', $token_response );
update_user_option( $user_id, 'openid-connect-generic-last-token-response', $token_response );
$this->save_refresh_token( $manager, $token, $token_response );
}
@ -368,7 +381,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
$redirect_url = home_url();
}
$token_response = $user->get( 'openid-connect-generic-last-token-response' );
$token_response = get_user_option( 'openid-connect-generic-last-token-response', $user->ID );
if ( ! $token_response ) {
// Happens if non-openid login was used.
return $redirect_url;
@ -377,7 +390,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
$redirect_url = site_url( $redirect_url );
}
$claim = $user->get( 'openid-connect-generic-last-id-token-claim' );
$claim = get_user_option( 'openid-connect-generic-last-id-token-claim', $user->ID );
if ( isset( $claim['iss'] ) && 'https://accounts.google.com' == $claim['iss'] ) {
/*
@ -407,8 +420,20 @@ class OpenID_Connect_Generic_Client_Wrapper {
$request['timeout'] = intval( $this->settings->http_request_timeout );
}
if ( $this->settings->no_sslverify ) {
// Only allow SSL bypass in local development environments.
if (
$this->settings->no_sslverify &&
defined( 'WP_DEBUG' ) && WP_DEBUG === true &&
( ! defined( 'WP_ENVIRONMENT_TYPE' ) || WP_ENVIRONMENT_TYPE === 'local' )
) {
$request['sslverify'] = false;
// Log warning every time this is used.
$this->logger->log(
'SSL verification disabled - ONLY for development. NEVER use in production!',
'ssl-bypass-warning'
);
}
return $request;
@ -427,6 +452,32 @@ class OpenID_Connect_Generic_Client_Wrapper {
$authentication_request = $client->validate_authentication_request( $_GET );
if ( is_wp_error( $authentication_request ) ) {
// Check if this is a retryable IDP error (e.g. Safari ITP causing
// Keycloak session cookies to be blocked on cross-site navigation).
$retryable_idp_errors = array(
'temporarily_unavailable',
'authentication_expired',
'login_required',
);
$error_code = $authentication_request->get_error_code();
$is_retryable = in_array( $error_code, $retryable_idp_errors, true );
$already_retried = isset( $_GET['openid-connect-generic-retry'] );
if ( $is_retryable && ! $already_retried ) {
// Log the original error before retrying.
$this->logger->log( $authentication_request, 'retry' );
$this->logger->log( "Retrying authentication due to IDP error: {$error_code}", 'retry' );
// Build a fresh authentication URL and append a retry flag
// to prevent infinite redirect loops (max 1 retry).
$auth_url = $this->get_authentication_url();
$auth_url = add_query_arg( 'openid-connect-generic-retry', '1', $auth_url );
wp_redirect( $auth_url );
exit;
}
$this->error_redirect( $authentication_request );
}
@ -559,7 +610,10 @@ class OpenID_Connect_Generic_Client_Wrapper {
// Provide backwards compatibility for customization using the deprecated cookie method.
if ( ! empty( $_COOKIE[ self::COOKIE_REDIRECT_KEY ] ) ) {
$redirect_url = esc_url_raw( wp_unslash( $_COOKIE[ self::COOKIE_REDIRECT_KEY ] ) );
$redirect_url = wp_validate_redirect(
esc_url_raw( wp_unslash( $_COOKIE[ self::COOKIE_REDIRECT_KEY ] ) ),
home_url()
);
}
// Only do redirect-user-back action hook when the plugin is configured for it.
@ -640,9 +694,9 @@ class OpenID_Connect_Generic_Client_Wrapper {
}
// Store the tokens for future reference.
update_user_meta( $user->ID, 'openid-connect-generic-last-token-response', $token_response );
update_user_meta( $user->ID, 'openid-connect-generic-last-id-token-claim', $id_token_claim );
update_user_meta( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
update_user_option( $user->ID, 'openid-connect-generic-last-token-response', $token_response );
update_user_option( $user->ID, 'openid-connect-generic-last-id-token-claim', $id_token_claim );
update_user_option( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
return $user_claim;
}
@ -660,9 +714,9 @@ class OpenID_Connect_Generic_Client_Wrapper {
*/
public function login_user( $user, $token_response, $id_token_claim, $user_claim, $subject_identity ): void {
// Store the tokens for future reference.
update_user_meta( $user->ID, 'openid-connect-generic-last-token-response', $token_response );
update_user_meta( $user->ID, 'openid-connect-generic-last-id-token-claim', $id_token_claim );
update_user_meta( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
update_user_option( $user->ID, 'openid-connect-generic-last-token-response', $token_response );
update_user_option( $user->ID, 'openid-connect-generic-last-id-token-claim', $id_token_claim );
update_user_option( $user->ID, 'openid-connect-generic-last-user-claim', $user_claim );
// Allow plugins / themes to take action using current claims on existing user (e.g. update role).
do_action( 'openid-connect-generic-update-user-using-current-claim', $user, $user_claim );
@ -713,14 +767,21 @@ class OpenID_Connect_Generic_Client_Wrapper {
* @return false|WP_User
*/
public function get_user_by_identity( $subject_identity ) {
global $wpdb;
// Look for user by their openid-connect-generic-subject-identity value.
$user_query = new WP_User_Query(
array(
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'openid-connect-generic-subject-identity',
'value' => $subject_identity,
),
array(
'key' => $wpdb->get_blog_prefix() . 'openid-connect-generic-subject-identity',
'value' => $subject_identity,
),
),
// Override the default blog_id (get_current_blog_id) to find users on different sites of a multisite install.
'blog_id' => 0,
@ -847,13 +908,48 @@ class OpenID_Connect_Generic_Client_Wrapper {
return false;
}
/**
* Extract claim from JWT.
* FIXME: We probably want to verify the JWT signature/issuer here.
* For example, using JWKS if applicable. For symmetrically signed
* JWTs (HMAC), we need a way to specify the acceptable secrets
* and each possible issuer in the config.
* Extract claim from JWT with signature verification.
*/
$jwt = $src['JWT'];
// Check if JWKS endpoint is configured for JWT signature verification.
if ( ! empty( $this->settings->endpoint_jwks ) ) {
// Use configured issuer if provided, otherwise derive from endpoint_login.
$issuer = ! empty( $this->settings->issuer ) ?
$this->settings->issuer :
( ! empty( $this->settings->endpoint_login ) ? $this->client->get_issuer_from_endpoint( $this->settings->endpoint_login ) : '' );
// Use JWT validator for secure signature verification.
$jwt_validator = new OpenID_Connect_Generic_JWT_Validator(
$this->settings->endpoint_jwks,
$this->settings->client_id,
$issuer,
$this->settings->jwks_cache_ttl,
$this->settings->allow_internal_idp,
$this->logger
);
// Validate JWT signature and claims.
$body_json = $jwt_validator->validate_id_token( $jwt );
if ( is_wp_error( $body_json ) ) {
$this->logger->log( $body_json, 'aggregated-claim-jwt-validation-failed' );
return false;
}
if ( ! array_key_exists( $claimname, $body_json ) ) {
return false;
}
$claimvalue = $body_json[ $claimname ];
return true;
}
$this->logger->log(
'SECURITY WARNING: JWKS endpoint not configured. Aggregated claim JWT signatures are NOT being verified. This is a security vulnerability. Configure the JWKS endpoint to secure aggregated claims.',
'aggregated-jwt-not-verified'
);
// Legacy JWT decoding without signature verification (INSECURE).
list ( $header, $body, $rest ) = explode( '.', $jwt, 3 );
$body_str = base64_decode( $body, false );
if ( ! $body_str ) {
@ -1098,7 +1194,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
$user = get_user_by( 'id', $uid );
// Save some meta data about this new user for the future.
add_user_meta( $user->ID, 'openid-connect-generic-subject-identity', (string) $subject_identity, true );
update_user_option( $user->ID, 'openid-connect-generic-subject-identity', (string) $subject_identity, true );
// Log the results.
$end_time = microtime( true );
@ -1120,7 +1216,7 @@ class OpenID_Connect_Generic_Client_Wrapper {
*/
public function update_existing_user( $uid, $subject_identity ) {
// Add the OpenID Connect meta data.
update_user_meta( $uid, 'openid-connect-generic-subject-identity', strval( $subject_identity ) );
update_user_option( $uid, 'openid-connect-generic-subject-identity', strval( $subject_identity ), true );
// Allow plugins / themes to take action on user update.
do_action( 'openid-connect-generic-user-update', $uid );

View File

@ -91,6 +91,33 @@ class OpenID_Connect_Generic_Client {
*/
private $acr_values;
/**
* The JWKS endpoint URL for JWT signature verification.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_jwks
*
* @var string
*/
private $endpoint_jwks;
/**
* The issuer URL for JWT validation.
*
* @see OpenID_Connect_Generic_Option_Settings::issuer
*
* @var string
*/
private $issuer;
/**
* The JWKS cache TTL in seconds.
*
* @see OpenID_Connect_Generic_Option_Settings::jwks_cache_ttl
*
* @var int
*/
private $jwks_cache_ttl;
/**
* The state time limit. States are only valid for 3 minutes.
*
@ -100,6 +127,15 @@ class OpenID_Connect_Generic_Client {
*/
private $state_time_limit = 180;
/**
* Allow HTTP requests to internal/private network endpoints.
*
* @see OpenID_Connect_Generic_Option_Settings::allow_internal_idp
*
* @var bool
*/
private $allow_internal_idp;
/**
* The logger object instance.
*
@ -110,18 +146,22 @@ class OpenID_Connect_Generic_Client {
/**
* Client constructor.
*
* @param string $client_id @see OpenID_Connect_Generic_Option_Settings::client_id for description.
* @param string $client_secret @see OpenID_Connect_Generic_Option_Settings::client_secret for description.
* @param string $scope @see OpenID_Connect_Generic_Option_Settings::scope for description.
* @param string $endpoint_login @see OpenID_Connect_Generic_Option_Settings::endpoint_login for description.
* @param string $endpoint_userinfo @see OpenID_Connect_Generic_Option_Settings::endpoint_userinfo for description.
* @param string $endpoint_token @see OpenID_Connect_Generic_Option_Settings::endpoint_token for description.
* @param string $redirect_uri @see OpenID_Connect_Generic_Option_Settings::redirect_uri for description.
* @param string $acr_values @see OpenID_Connect_Generic_Option_Settings::acr_values for description.
* @param int $state_time_limit @see OpenID_Connect_Generic_Option_Settings::state_time_limit for description.
* @param OpenID_Connect_Generic_Option_Logger $logger The plugin logging object instance.
* @param string $client_id @see OpenID_Connect_Generic_Option_Settings::client_id for description.
* @param string $client_secret @see OpenID_Connect_Generic_Option_Settings::client_secret for description.
* @param string $scope @see OpenID_Connect_Generic_Option_Settings::scope for description.
* @param string $endpoint_login @see OpenID_Connect_Generic_Option_Settings::endpoint_login for description.
* @param string $endpoint_userinfo @see OpenID_Connect_Generic_Option_Settings::endpoint_userinfo for description.
* @param string $endpoint_token @see OpenID_Connect_Generic_Option_Settings::endpoint_token for description.
* @param string $redirect_uri @see OpenID_Connect_Generic_Option_Settings::redirect_uri for description.
* @param string $acr_values @see OpenID_Connect_Generic_Option_Settings::acr_values for description.
* @param string $endpoint_jwks @see OpenID_Connect_Generic_Option_Settings::endpoint_jwks for description.
* @param string $issuer @see OpenID_Connect_Generic_Option_Settings::issuer for description.
* @param int $jwks_cache_ttl @see OpenID_Connect_Generic_Option_Settings::jwks_cache_ttl for description.
* @param int $state_time_limit @see OpenID_Connect_Generic_Option_Settings::state_time_limit for description.
* @param bool $allow_internal_idp @see OpenID_Connect_Generic_Option_Settings::allow_internal_idp for description.
* @param OpenID_Connect_Generic_Option_Logger $logger The plugin logging object instance.
*/
public function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $acr_values, $state_time_limit, $logger ) {
public function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $acr_values, $endpoint_jwks, $issuer, $jwks_cache_ttl, $state_time_limit, $allow_internal_idp, $logger ) {
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->scope = $scope;
@ -130,10 +170,52 @@ class OpenID_Connect_Generic_Client {
$this->endpoint_token = $endpoint_token;
$this->redirect_uri = $redirect_uri;
$this->acr_values = $acr_values;
$this->endpoint_jwks = $endpoint_jwks;
$this->issuer = $issuer;
$this->jwks_cache_ttl = $jwks_cache_ttl;
$this->state_time_limit = $state_time_limit;
$this->allow_internal_idp = $allow_internal_idp;
$this->logger = $logger;
}
/**
* Make a safe HTTP GET request with optional internal endpoint support.
*
* By default, uses wp_safe_remote_get() which blocks requests to internal/private
* networks (SSRF protection). If allow_internal_idp is enabled, uses wp_remote_get()
* to allow connections to localhost and private network identity providers.
*
* @param string $url The URL to request.
* @param array $args Optional. Request arguments.
*
* @return array|WP_Error Response array or WP_Error on failure.
*/
private function http_get( $url, $args = array() ) {
if ( $this->allow_internal_idp ) {
return wp_remote_get( $url, $args );
}
return wp_safe_remote_get( $url, $args );
}
/**
* Make a safe HTTP POST request with optional internal endpoint support.
*
* By default, uses wp_safe_remote_post() which blocks requests to internal/private
* networks (SSRF protection). If allow_internal_idp is enabled, uses wp_remote_post()
* to allow connections to localhost and private network identity providers.
*
* @param string $url The URL to request.
* @param array $args Optional. Request arguments.
*
* @return array|WP_Error Response array or WP_Error on failure.
*/
private function http_post( $url, $args = array() ) {
if ( $this->allow_internal_idp ) {
return wp_remote_post( $url, $args );
}
return wp_safe_remote_post( $url, $args );
}
/**
* Provides the configured Redirect URI supplied to the IDP.
*
@ -162,7 +244,19 @@ class OpenID_Connect_Generic_Client {
public function validate_authentication_request( $request ) {
// Look for an existing error of some kind.
if ( isset( $request['error'] ) ) {
return new WP_Error( 'unknown-error', 'An unknown error occurred.', $request );
$error_code = sanitize_text_field( $request['error'] );
$error_message = 'An unknown error occurred.';
// Use the IDP's error description if available for better diagnostics.
if ( ! empty( $request['error_description'] ) ) {
$error_message = sprintf(
'IDP error %s: %s',
$error_code,
sanitize_text_field( $request['error_description'] )
);
}
return new WP_Error( $error_code, $error_message, $request );
}
// Make sure we have a legitimate authentication code and valid state.
@ -232,7 +326,7 @@ class OpenID_Connect_Generic_Client {
// Call the server and ask for a token.
$start_time = microtime( true );
$response = wp_remote_post( $this->endpoint_token, $request );
$response = $this->http_post( $this->endpoint_token, $request );
$end_time = microtime( true );
$this->logger->log( $this->endpoint_token, 'request_authentication_token', $end_time - $start_time );
@ -265,7 +359,7 @@ class OpenID_Connect_Generic_Client {
// Call the server and ask for new tokens.
$start_time = microtime( true );
$response = wp_remote_post( $this->endpoint_token, $request );
$response = $this->http_post( $this->endpoint_token, $request );
$end_time = microtime( true );
$this->logger->log( $this->endpoint_token, 'request_new_tokens', $end_time - $start_time );
@ -341,8 +435,16 @@ class OpenID_Connect_Generic_Client {
// Attempt the request including the access token in the query string for backwards compatibility.
$start_time = microtime( true );
$response = wp_remote_get( $this->endpoint_userinfo, $request );
$end_time = microtime( true );
$response = $this->http_get( $this->endpoint_userinfo, $request );
// This endpoint can support GET or POST requests according to spec, but some IDPs only allow one.
// If the GET request failed to produce valid json, attempt a POST request.
// Spec: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest.
if ( ! is_wp_error( $response ) && json_decode( $response['body'] ) === null ) {
$response = $this->http_post( $this->endpoint_userinfo, $request );
}
$end_time = microtime( true );
$this->logger->log( $this->endpoint_userinfo, 'request_userinfo', $end_time - $start_time );
if ( is_wp_error( $response ) ) {
@ -360,8 +462,8 @@ class OpenID_Connect_Generic_Client {
* @return string
*/
public function new_state( $redirect_to ) {
// New state w/ timestamp.
$state = md5( mt_rand() . microtime( true ) );
// New state with cryptographically secure random bytes.
$state = bin2hex( random_bytes( 16 ) );
$state_value = array(
$state => array(
'redirect_to' => $redirect_to,
@ -438,7 +540,7 @@ class OpenID_Connect_Generic_Client {
}
/**
* Extract the id_token_claim from the token_response.
* Extract and validate the id_token_claim from the token_response.
*
* @param array $token_response The token response.
*
@ -450,14 +552,47 @@ class OpenID_Connect_Generic_Client {
return new WP_Error( 'no-identity-token', __( 'No identity token.', 'daggerhart-openid-connect-generic' ), $token_response );
}
// Break apart the id_token in the response for decoding.
// Check if JWKS endpoint is configured for JWT signature verification.
if ( ! empty( $this->endpoint_jwks ) ) {
// Use configured issuer if provided, otherwise derive from endpoint_login.
$issuer = ! empty( $this->issuer )
? $this->issuer
: $this->get_issuer_from_endpoint( $this->endpoint_login );
// Use JWT validator for secure signature verification.
$jwt_validator = new OpenID_Connect_Generic_JWT_Validator(
$this->endpoint_jwks,
$this->client_id,
$issuer,
$this->jwks_cache_ttl,
$this->allow_internal_idp,
$this->logger
);
// Validate JWT signature and claims.
$id_token_claim = $jwt_validator->validate_id_token( $token_response['id_token'] );
if ( is_wp_error( $id_token_claim ) ) {
$this->logger->log( $id_token_claim, 'jwt-validation-failed' );
return $id_token_claim;
}
return $id_token_claim;
}
$this->logger->log(
'SECURITY WARNING: JWKS endpoint not configured. JWT signatures are NOT being verified. This is a critical security vulnerability. Configure the JWKS endpoint immediately in Settings > OpenID Connect Client to secure authentication.',
'jwks-not-configured-insecure'
);
// Legacy JWT decoding without signature verification (INSECURE).
$tmp = explode( '.', $token_response['id_token'] );
if ( ! isset( $tmp[1] ) ) {
return new WP_Error( 'missing-identity-token', __( 'Missing identity token.', 'daggerhart-openid-connect-generic' ), $token_response );
}
// Extract the id_token's claims from the token.
// Extract the id_token's claims from the token (no signature verification).
$id_token_claim = json_decode(
base64_decode(
str_replace( // Because token is encoded in base64 URL (and not just base64).
@ -472,6 +607,38 @@ class OpenID_Connect_Generic_Client {
return $id_token_claim;
}
/**
* Extract issuer URL from endpoint URL.
*
* The issuer is typically the base URL (scheme + host + trailing slash).
*
* @param string $endpoint_url The full endpoint URL.
*
* @return string The issuer URL.
*/
public function get_issuer_from_endpoint( $endpoint_url ) {
$parsed = wp_parse_url( $endpoint_url );
if ( ! $parsed || ! isset( $parsed['scheme'] ) || ! isset( $parsed['host'] ) ) {
return $endpoint_url;
}
$issuer = $parsed['scheme'] . '://' . $parsed['host'];
// Add port if non-standard.
if ( isset( $parsed['port'] ) ) {
$default_ports = array(
'http' => 80,
'https' => 443,
);
if ( ! isset( $default_ports[ $parsed['scheme'] ] ) || $parsed['port'] != $default_ports[ $parsed['scheme'] ] ) {
$issuer .= ':' . $parsed['port'];
}
}
return $issuer;
}
/**
* Ensure the id_token_claim contains the required values.
*
@ -484,11 +651,71 @@ class OpenID_Connect_Generic_Client {
return new WP_Error( 'bad-id-token-claim', __( 'Bad ID token claim.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate the identification data and it's value.
// Validate the identification data and its value.
if ( ! isset( $id_token_claim['sub'] ) || empty( $id_token_claim['sub'] ) ) {
return new WP_Error( 'no-subject-identity', __( 'No subject identity.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate expiration claim.
if ( ! isset( $id_token_claim['exp'] ) ) {
return new WP_Error( 'missing-exp', __( 'Token missing expiration claim.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
if ( time() >= $id_token_claim['exp'] ) {
return new WP_Error( 'token-expired', __( 'Token has expired.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate issued at claim.
if ( ! isset( $id_token_claim['iat'] ) ) {
return new WP_Error( 'missing-iat', __( 'Token missing issued at claim.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate audience claim matches client_id (can be string or array).
if ( ! isset( $id_token_claim['aud'] ) ) {
return new WP_Error( 'missing-aud', __( 'Token missing audience claim.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
$aud = $id_token_claim['aud'];
$audience_valid = false;
if ( is_array( $aud ) ) {
$audience_valid = in_array( $this->client_id, $aud, true );
} elseif ( is_string( $aud ) ) {
$audience_valid = ( $aud === $this->client_id );
}
if ( ! $audience_valid ) {
return new WP_Error( 'invalid-aud', __( 'Token audience does not match client.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
// Validate issuer claim if configured or endpoint_login is available.
$expected_issuer = ! empty( $this->issuer ) ?
$this->issuer :
( ! empty( $this->endpoint_login ) ? $this->get_issuer_from_endpoint( $this->endpoint_login ) : '' );
if ( ! empty( $expected_issuer ) ) {
if ( ! isset( $id_token_claim['iss'] ) ) {
return new WP_Error( 'missing-iss', __( 'Token missing issuer claim.', 'daggerhart-openid-connect-generic' ), $id_token_claim );
}
if ( rtrim( $id_token_claim['iss'], '/' ) !== rtrim( $expected_issuer, '/' ) ) {
$this->logger->log(
sprintf(
'Issuer mismatch - Expected: "%s", Received: "%s". Configure the correct issuer in Settings > OpenID Connect Client > Issuer field, or via the OIDC_ISSUER constant.',
$expected_issuer,
$id_token_claim['iss']
),
'issuer-mismatch'
);
return new WP_Error(
'invalid-iss',
sprintf(
__( 'Token issuer does not match expected issuer.', 'daggerhart-openid-connect-generic' ),
),
$id_token_claim
);
}
}
// Validate acr values when the option is set in the configuration.
if ( ! empty( $this->acr_values ) && isset( $id_token_claim['acr'] ) ) {
if ( $this->acr_values != $id_token_claim['acr'] ) {

View File

@ -0,0 +1,364 @@
<?php
/**
* JWT validation and verification class.
*
* @package OpenID_Connect_Generic
* @category Authentication
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Firebase\JWT\Key;
/**
* OpenID_Connect_Generic_JWT_Validator class.
*
* Handles JWT signature verification and claim validation using JWKS.
*
* @package OpenID_Connect_Generic
* @category Authentication
*/
class OpenID_Connect_Generic_JWT_Validator {
/**
* The JWKS endpoint URL.
*
* @var string
*/
private $jwks_uri;
/**
* The expected client ID (audience).
*
* @var string
*/
private $client_id;
/**
* The expected issuer.
*
* @var string
*/
private $issuer;
/**
* JWKS cache TTL in seconds.
*
* @var int
*/
private $cache_ttl;
/**
* Allow HTTP requests to internal/private network endpoints.
*
* @var bool
*/
private $allow_internal_idp;
/**
* Logger instance.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
/**
* Constructor.
*
* @param string $jwks_uri The JWKS endpoint URL.
* @param string $client_id The client ID for audience validation.
* @param string $issuer The expected issuer.
* @param int $cache_ttl JWKS cache TTL in seconds.
* @param bool $allow_internal_idp Allow internal/private network endpoints.
* @param OpenID_Connect_Generic_Option_Logger $logger Logger instance.
*/
public function __construct( $jwks_uri, $client_id, $issuer, $cache_ttl, $allow_internal_idp, $logger ) {
$this->jwks_uri = $jwks_uri;
$this->client_id = $client_id;
$this->issuer = $issuer;
$this->cache_ttl = $cache_ttl;
$this->allow_internal_idp = $allow_internal_idp;
$this->logger = $logger;
}
/**
* Make a safe HTTP GET request with optional internal endpoint support.
*
* By default, uses wp_safe_remote_get() which blocks requests to internal/private
* networks (SSRF protection). If allow_internal_idp is enabled, uses wp_remote_get()
* to allow connections to localhost and private network identity providers.
*
* @param string $url The URL to request.
* @param array $args Optional. Request arguments.
*
* @return array|WP_Error Response array or WP_Error on failure.
*/
private function http_get( $url, $args = array() ) {
if ( $this->allow_internal_idp ) {
return wp_remote_get( $url, $args );
}
return wp_safe_remote_get( $url, $args );
}
/**
* Fetch JWKS from the IDP endpoint with caching.
*
* @return array|WP_Error Array of keys or WP_Error on failure.
*/
private function fetch_jwks() {
// Check cache first.
$cache_key = 'openid_connect_jwks_' . md5( $this->jwks_uri );
$cached_jwks = get_transient( $cache_key );
if ( false !== $cached_jwks ) {
return $cached_jwks;
}
// Fetch JWKS from IDP.
$response = $this->http_get( $this->jwks_uri, array( 'timeout' => 10 ) );
if ( is_wp_error( $response ) ) {
$this->logger->log( $response, 'jwks-fetch-failed' );
return new WP_Error(
'jwks-fetch-failed',
__( 'Failed to fetch JWKS from identity provider.', 'daggerhart-openid-connect-generic' ),
$response
);
}
$response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $response_code ) {
$error = new WP_Error(
'jwks-fetch-failed',
sprintf(
/* translators: %d is the HTTP response code */
__( 'JWKS endpoint returned HTTP %d', 'daggerhart-openid-connect-generic' ),
$response_code
)
);
$this->logger->log( $error, 'jwks-fetch-failed' );
return $error;
}
$body = wp_remote_retrieve_body( $response );
$jwks = json_decode( $body, true );
if ( ! $jwks || ! isset( $jwks['keys'] ) ) {
$error = new WP_Error(
'jwks-invalid-format',
__( 'Invalid JWKS format received from identity provider.', 'daggerhart-openid-connect-generic' )
);
$this->logger->log( $error, 'jwks-invalid-format' );
return $error;
}
// Cache the JWKS.
set_transient( $cache_key, $jwks, $this->cache_ttl );
return $jwks;
}
/**
* Validate JWT claims.
*
* @param object $decoded_jwt The decoded JWT payload.
*
* @return true|WP_Error True if valid, WP_Error on failure.
*/
private function validate_jwt_claims( $decoded_jwt ) {
// Validate subject (sub) claim.
if ( ! isset( $decoded_jwt->sub ) || empty( $decoded_jwt->sub ) ) {
return new WP_Error(
'missing-sub',
__( 'Token missing subject claim.', 'daggerhart-openid-connect-generic' )
);
}
// Validate expiration (exp) claim - JWT library already validates this, but double-check.
if ( ! isset( $decoded_jwt->exp ) ) {
return new WP_Error(
'missing-exp',
__( 'Token missing expiration claim.', 'daggerhart-openid-connect-generic' )
);
}
// Validate issued at (iat) claim.
if ( ! isset( $decoded_jwt->iat ) ) {
return new WP_Error(
'missing-iat',
__( 'Token missing issued at claim.', 'daggerhart-openid-connect-generic' )
);
}
// Validate audience (aud) claim.
if ( ! isset( $decoded_jwt->aud ) ) {
return new WP_Error(
'missing-aud',
__( 'Token missing audience claim.', 'daggerhart-openid-connect-generic' )
);
}
// Audience can be string or array.
$aud = $decoded_jwt->aud;
$audience_valid = false;
if ( is_array( $aud ) ) {
$audience_valid = in_array( $this->client_id, $aud, true );
} elseif ( is_string( $aud ) ) {
$audience_valid = ( $aud === $this->client_id );
}
if ( ! $audience_valid ) {
return new WP_Error(
'invalid-aud',
__( 'Token audience does not match client.', 'daggerhart-openid-connect-generic' )
);
}
// Validate issuer (iss) claim if configured.
if ( ! empty( $this->issuer ) ) {
if ( ! isset( $decoded_jwt->iss ) ) {
return new WP_Error(
'missing-iss',
__( 'Token missing issuer claim.', 'daggerhart-openid-connect-generic' )
);
}
if ( rtrim( $decoded_jwt->iss, '/' ) !== rtrim( $this->issuer, '/' ) ) {
$this->logger->log(
sprintf(
'Issuer mismatch - Expected: "%s", Received: "%s". Configure the correct issuer in Settings > OpenID Connect Client > Issuer field, or via the OIDC_ISSUER constant.',
$this->issuer,
$decoded_jwt->iss
),
'issuer-mismatch'
);
return new WP_Error(
'invalid-iss',
__( 'Token issuer does not match expected issuer.', 'daggerhart-openid-connect-generic' )
);
}
}
return true;
}
/**
* Extract the algorithm from JWT header.
*
* @param string $id_token The JWT ID token.
*
* @return string|null The algorithm from JWT header or null if not found.
*/
private function get_jwt_header_alg( $id_token ) {
$token_parts = explode( '.', $id_token );
if ( count( $token_parts ) < 2 ) {
return null;
}
$header_base64 = $token_parts[0];
$header_json = JWT::urlsafeB64Decode( $header_base64 );
$header = json_decode( $header_json, true );
return isset( $header['alg'] ) ? $header['alg'] : null;
}
/**
* Enrich JWKS with algorithm from JWT header if missing.
*
* Some identity providers (like Microsoft Entra ID) return JWKs without
* the "alg" parameter. This method adds the algorithm from the JWT header
* to each key that's missing it, ensuring compatibility with the Firebase
* JWT library which requires "alg" to be present.
*
* @param array $jwks The JWKS array with keys.
* @param string $id_token The JWT ID token.
*
* @return array The enriched JWKS array.
*/
private function enrich_jwks_with_alg( $jwks, $id_token ) {
// Extract algorithm from JWT header.
$jwt_alg = $this->get_jwt_header_alg( $id_token );
// If we couldn't extract the algorithm, default to RS256 (most common for OIDC).
if ( empty( $jwt_alg ) ) {
$jwt_alg = 'RS256';
}
// Add algorithm to keys that are missing it.
if ( isset( $jwks['keys'] ) && is_array( $jwks['keys'] ) ) {
foreach ( $jwks['keys'] as &$key ) {
if ( ! isset( $key['alg'] ) ) {
$key['alg'] = $jwt_alg;
}
}
}
return $jwks;
}
/**
* Validate and verify an ID token.
*
* @param string $id_token The JWT ID token to validate.
*
* @return array|WP_Error Array of claims if valid, WP_Error on failure.
*/
public function validate_id_token( $id_token ) {
// Check if JWKS URI is configured.
if ( empty( $this->jwks_uri ) ) {
$error = new WP_Error(
'jwks-not-configured',
__( 'JWKS URI not configured. JWT signature verification requires JWKS endpoint.', 'daggerhart-openid-connect-generic' )
);
$this->logger->log( $error, 'jwks-not-configured' );
return $error;
}
// Fetch JWKS.
$jwks = $this->fetch_jwks();
if ( is_wp_error( $jwks ) ) {
return $jwks;
}
// Enrich JWKS with algorithm from JWT header if keys are missing "alg".
// This ensures compatibility with providers like Microsoft Entra ID that
// don't include "alg" in their JWKS.
$jwks = $this->enrich_jwks_with_alg( $jwks, $id_token );
// Verify JWT signature and decode.
try {
// Parse JWKS into Key objects.
$keys = JWK::parseKeySet( $jwks );
// Decode and verify JWT signature.
// The JWT library will automatically validate exp, nbf, and signature.
$decoded_jwt = JWT::decode( $id_token, $keys );
} catch ( Exception $e ) {
$error = new WP_Error(
'jwt-verification-failed',
sprintf(
/* translators: %s is the error message */
__( 'JWT verification failed: %s', 'daggerhart-openid-connect-generic' ),
$e->getMessage()
)
);
$this->logger->log( $error, 'jwt-verification-failed' );
return $error;
}
// Validate additional claims.
$claims_valid = $this->validate_jwt_claims( $decoded_jwt );
if ( is_wp_error( $claims_valid ) ) {
$this->logger->log( $claims_valid, 'jwt-claims-invalid' );
return $claims_valid;
}
// Convert stdClass to array for consistency with existing code.
return json_decode( json_encode( $decoded_jwt ), true );
}
}

View File

@ -33,15 +33,24 @@ class OpenID_Connect_Generic_Login_Form {
*/
private $client_wrapper;
/**
* The client object instance.
*
* @var OpenID_Connect_Generic_Client
*/
private $client;
/**
* The class constructor.
*
* @param OpenID_Connect_Generic_Option_Settings $settings A plugin settings object instance.
* @param OpenID_Connect_Generic_Client_Wrapper $client_wrapper A plugin client wrapper object instance.
* @param OpenID_Connect_Generic_Client $client A plugin client object instance.
*/
public function __construct( $settings, $client_wrapper ) {
public function __construct( $settings, $client_wrapper, $client ) {
$this->settings = $settings;
$this->client_wrapper = $client_wrapper;
$this->client = $client;
}
/**
@ -49,11 +58,12 @@ class OpenID_Connect_Generic_Login_Form {
*
* @param OpenID_Connect_Generic_Option_Settings $settings A plugin settings object instance.
* @param OpenID_Connect_Generic_Client_Wrapper $client_wrapper A plugin client wrapper object instance.
* @param OpenID_Connect_Generic_Client $client A plugin client object instance.
*
* @return void
*/
public static function register( $settings, $client_wrapper ) {
$login_form = new self( $settings, $client_wrapper );
public static function register( $settings, $client_wrapper, $client ) {
$login_form = new self( $settings, $client_wrapper, $client );
// Alter the login form as dictated by settings.
add_filter( 'login_message', array( $login_form, 'handle_login_page' ), 99 );
@ -136,9 +146,20 @@ class OpenID_Connect_Generic_Login_Form {
*/
public function make_login_button( $atts = array() ) {
// Use admin-configured button text, or fall back to default.
$default_button_text = ! empty( trim( $this->settings->login_button_text ?? '' ) )
? $this->settings->login_button_text
: __( 'Login with OpenID Connect', 'daggerhart-openid-connect-generic' );
$atts = shortcode_atts(
array(
'button_text' => __( 'Login with OpenID Connect', 'daggerhart-openid-connect-generic' ),
'button_text' => $default_button_text,
'endpoint_login' => $this->settings->endpoint_login,
'scope' => $this->settings->scope,
'client_id' => $this->settings->client_id,
'redirect_uri' => $this->client->get_redirect_uri(),
'redirect_to' => $this->client_wrapper->get_redirect_to(),
'acr_values' => $this->settings->acr_values,
),
$atts,
'openid_connect_generic_login_button'
@ -147,7 +168,16 @@ class OpenID_Connect_Generic_Login_Form {
$text = apply_filters( 'openid-connect-generic-login-button-text', $atts['button_text'] );
$text = esc_html( $text );
$href = $this->client_wrapper->get_authentication_url( $atts );
$href = $this->client_wrapper->get_authentication_url(
array(
'endpoint_login' => $atts['endpoint_login'],
'scope' => $atts['scope'],
'client_id' => $atts['client_id'],
'redirect_uri' => $atts['redirect_uri'],
'redirect_to' => $atts['redirect_to'],
'acr_values' => $atts['acr_values'],
)
);
$href = esc_url_raw( $href );
$login_button = <<<HTML

View File

@ -174,7 +174,9 @@ class OpenID_Connect_Generic_Option_Logger {
* @return array
*/
private function upkeep_logs( $logs ) {
$items_to_remove = count( $logs ) - $this->log_limit;
$items_to_remove = is_array( $logs ) ?
count( $logs ) - $this->log_limit :
0;
if ( $items_to_remove > 0 ) {
// Only keep the last $log_limit messages from the end.
@ -250,10 +252,13 @@ class OpenID_Connect_Generic_Option_Logger {
</div>
<div>
<label><?php esc_html_e( 'Response&nbsp;Time&nbsp;(sec)', 'daggerhart-openid-connect-generic' ); ?></label>
<?php print esc_html( ! empty( $log['response_time'] ) ? $log['response_time'] : '' ); ?>
<?php print esc_html( ! empty( $log['processing_time'] ) ? $log['processing_time'] : 0 ); ?>
</div>
</td>
<td class="col-data"><pre><?php var_dump( ! empty( $log['data'] ) ? $log['data'] : '' ); ?></pre></td>
<td class="col-data">
<?php $log_data = ! empty( $log['data'] ) ? print_r( $log['data'], true ) : ''; ?>
<pre><?php print esc_html( $log_data ); ?></pre>
</td>
</tr>
<?php } ?>
</tbody>

View File

@ -26,6 +26,7 @@
* OAuth Client Settings:
*
* @property string $login_type How the client (login form) should provide login options.
* @property string $login_button_text Customizable text for the OpenID Connect login button.
* @property string $client_id The ID the client will be recognized as when connecting the to Identity provider server.
* @property string $client_secret The secret key the IDP server expects from the client.
* @property string $scope The list of scopes this client should access.
@ -33,12 +34,16 @@
* @property string $endpoint_userinfo The IDP User information endpoint URL.
* @property string $endpoint_token The IDP token validation endpoint URL.
* @property string $endpoint_end_session The IDP logout endpoint URL.
* @property string $endpoint_jwks The IDP JWKS endpoint URL for JWT signature verification.
* @property string $issuer The IDP issuer URL for JWT validation (optional - derived from endpoint_login if not set).
* @property int $jwks_cache_ttl The JWKS cache TTL in seconds.
* @property string $acr_values The Authentication contract as defined on the IDP.
*
* Non-standard Settings:
*
* @property bool $no_sslverify The flag to enable/disable SSL verification during authorization.
* @property int $http_request_timeout The timeout for requests made to the IDP. Default value is 5.
* @property bool $allow_internal_idp The flag to allow HTTP requests to internal/private network endpoints. Default is false.
* @property string $identity_key The key in the user claim array to find the user's identification data.
* @property string $nickname_key The key in the user claim array to find the user's nickname.
* @property string $email_format The key(s) in the user claim array to formulate the user's email address.
@ -93,6 +98,8 @@ class OpenID_Connect_Generic_Option_Settings {
'endpoint_login' => 'OIDC_ENDPOINT_LOGIN_URL',
'endpoint_token' => 'OIDC_ENDPOINT_TOKEN_URL',
'endpoint_userinfo' => 'OIDC_ENDPOINT_USERINFO_URL',
'endpoint_jwks' => 'OIDC_ENDPOINT_JWKS_URL',
'issuer' => 'OIDC_ISSUER',
'login_type' => 'OIDC_LOGIN_TYPE',
'scope' => 'OIDC_CLIENT_SCOPE',
'create_if_does_not_exist' => 'OIDC_CREATE_IF_DOES_NOT_EXIST',

View File

@ -79,6 +79,25 @@ class OpenID_Connect_Generic_Settings_Page {
$this->settings_fields = $fields;
}
/**
* Make a safe HTTP GET request with optional internal endpoint support.
*
* By default, uses wp_safe_remote_get() which blocks requests to internal/private
* networks (SSRF protection). If allow_internal_idp is enabled, uses wp_remote_get()
* to allow connections to localhost and private network identity providers.
*
* @param string $url The URL to request.
* @param array $args Optional. Request arguments.
*
* @return array|WP_Error Response array or WP_Error on failure.
*/
private function http_get( $url, $args = array() ) {
if ( $this->settings->allow_internal_idp ) {
return wp_remote_get( $url, $args );
}
return wp_safe_remote_get( $url, $args );
}
/**
* Hook the settings page into WordPress.
*
@ -219,6 +238,13 @@ class OpenID_Connect_Generic_Settings_Page {
'disabled' => defined( 'OIDC_LOGIN_TYPE' ),
'section' => 'client_settings',
),
'login_button_text' => array(
'title' => __( 'Login Button Text', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Customize the text shown on the OpenID Connect login button. Leave empty to use the default text.', 'daggerhart-openid-connect-generic' ),
'example' => 'Login with Single Sign-On',
'type' => 'text',
'section' => 'client_settings',
),
'client_id' => array(
'title' => __( 'Client ID', 'daggerhart-openid-connect-generic' ),
'description' => __( 'The ID this client will be recognized as when connecting the to Identity provider server.', 'daggerhart-openid-connect-generic' ),
@ -274,6 +300,29 @@ class OpenID_Connect_Generic_Settings_Page {
'disabled' => defined( 'OIDC_ENDPOINT_LOGOUT_URL' ),
'section' => 'client_settings',
),
'endpoint_jwks' => array(
'title' => __( 'JWKS URI', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identity provider JWKS (JSON Web Key Set) endpoint for JWT signature verification. Usually found at /.well-known/jwks.json', 'daggerhart-openid-connect-generic' ),
'example' => 'https://example.com/.well-known/jwks.json',
'type' => 'text',
'disabled' => defined( 'OIDC_ENDPOINT_JWKS_URL' ),
'section' => 'client_settings',
),
'issuer' => array(
'title' => __( 'Issuer', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Identity provider issuer URL for JWT validation. If not set, the issuer will be automatically derived from the Login Endpoint URL. Only configure this if your IDP uses a different issuer than the base URL of the login endpoint.', 'daggerhart-openid-connect-generic' ),
'example' => 'https://example.com',
'type' => 'text',
'disabled' => defined( 'OIDC_ISSUER' ),
'section' => 'client_settings',
),
'jwks_cache_ttl' => array(
'title' => __( 'JWKS Cache TTL (seconds)', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Time in seconds to cache JWKS keys. Default: 3600 (1 hour)', 'daggerhart-openid-connect-generic' ),
'example' => 3600,
'type' => 'number',
'section' => 'client_settings',
),
'acr_values' => array(
'title' => __( 'ACR values', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Use a specific defined authentication contract from the IDP - optional.', 'daggerhart-openid-connect-generic' ),
@ -288,13 +337,6 @@ class OpenID_Connect_Generic_Settings_Page {
'type' => 'text',
'section' => 'client_settings',
),
'no_sslverify' => array(
'title' => __( 'Disable SSL Verify', 'daggerhart-openid-connect-generic' ),
// translators: %1$s HTML tags for layout/styles, %2$s closing HTML tag for styles.
'description' => sprintf( __( 'Do not require SSL verification during authorization. The OAuth extension uses curl to make the request. By default CURL will generally verify the SSL certificate to see if its valid an issued by an accepted CA. This setting disabled that verification.%1$sNot recommended for production sites.%2$s', 'daggerhart-openid-connect-generic' ), '<br><strong>', '</strong>' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'http_request_timeout' => array(
'title' => __( 'HTTP Request Timeout', 'daggerhart-openid-connect-generic' ),
'description' => __( 'Set the timeout for requests made to the IDP. Default value is 5.', 'daggerhart-openid-connect-generic' ),
@ -354,6 +396,20 @@ class OpenID_Connect_Generic_Settings_Page {
'type' => 'checkbox',
'section' => 'client_settings',
),
'no_sslverify' => array(
'title' => __( 'Disable SSL Verify', 'daggerhart-openid-connect-generic' ),
// translators: %1$s HTML tags for layout/styles (strong tag start with warning class), %2$s closing HTML tag for styles.
'description' => sprintf( __( 'Do not require SSL verification during authorization. %1$sOnly works in local development (WP_DEBUG=true, WP_ENVIRONMENT_TYPE=local).%2$s This setting is automatically disabled in production. If you need this in production, fix your SSL certificates instead.', 'daggerhart-openid-connect-generic' ), '<br><strong class="oidc-warning">', '</strong>' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'allow_internal_idp' => array(
'title' => __( 'Allow Internal IDP', 'daggerhart-openid-connect-generic' ),
// translators: %1$s HTML tags for layout/styles (strong tag start with warning class), %2$s closing HTML tag for styles.
'description' => sprintf( __( 'Allow HTTP requests to internal/private network endpoints (localhost, 127.0.0.1, 10.x.x.x, 192.168.x.x, 172.16-31.x.x). %1$sOnly enable this for local development or corporate internal identity providers. Disabling SSRF protection can expose your server to security risks.%2$s', 'daggerhart-openid-connect-generic' ), '<br><strong class="oidc-warning">', '</strong>' ),
'type' => 'checkbox',
'section' => 'client_settings',
),
'link_existing_users' => array(
'title' => __( 'Link Existing Users', 'daggerhart-openid-connect-generic' ),
'description' => __( 'If a WordPress account already exists with the same identity as a newly-authenticated user over OpenID Connect, login as that user instead of generating an error.', 'daggerhart-openid-connect-generic' ),
@ -429,6 +485,9 @@ class OpenID_Connect_Generic_Settings_Page {
* @return void
*/
public function settings_page() {
// Handle discovery form submission before any output.
$this->handle_discovery_import();
wp_enqueue_style( 'daggerhart-openid-connect-generic-admin', plugin_dir_url( __DIR__ ) . 'css/styles-admin.css', array(), OpenID_Connect_Generic::VERSION, 'all' );
$redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' );
@ -440,16 +499,16 @@ class OpenID_Connect_Generic_Settings_Page {
<div class="wrap">
<h2><?php print esc_html( get_admin_page_title() ); ?></h2>
<?php
// Render discovery document import form.
$this->render_discovery_form();
?>
<form method="post" action="options.php">
<?php
settings_fields( $this->settings_field_group );
do_settings_sections( $this->options_page_name );
submit_button();
// Simple debug to view settings array.
if ( isset( $_GET['debug'] ) ) {
var_dump( $this->settings->get_values() );
}
?>
</form>
@ -600,4 +659,281 @@ class OpenID_Connect_Generic_Settings_Page {
public function log_settings_description() {
esc_html_e( 'Log information about login attempts through OpenID Connect Generic.', 'daggerhart-openid-connect-generic' );
}
/**
* Fetch OpenID Connect discovery document from provider.
*
* @param string $discovery_url The discovery document URL (.well-known/openid-configuration).
*
* @return array|WP_Error Array of discovery data on success, WP_Error on failure.
*/
private function fetch_discovery_document( $discovery_url ) {
// Validate URL is provided.
if ( empty( $discovery_url ) ) {
return new WP_Error(
'empty-discovery-url',
__( 'Please enter a discovery URL.', 'daggerhart-openid-connect-generic' )
);
}
// Validate HTTPS in production.
$parsed_url = wp_parse_url( $discovery_url );
if ( ! $parsed_url || ! isset( $parsed_url['scheme'] ) ) {
return new WP_Error(
'invalid-discovery-url',
__( 'Invalid discovery URL format.', 'daggerhart-openid-connect-generic' )
);
}
// Require HTTPS except in local development.
$is_local_dev = defined( 'WP_DEBUG' ) && WP_DEBUG === true &&
( ! defined( 'WP_ENVIRONMENT_TYPE' ) || WP_ENVIRONMENT_TYPE === 'local' );
if ( 'https' !== $parsed_url['scheme'] && ! $is_local_dev ) {
return new WP_Error(
'discovery-url-not-https',
__( 'Discovery URL must use HTTPS in production environments.', 'daggerhart-openid-connect-generic' )
);
}
// Fetch discovery document.
$response = $this->http_get(
$discovery_url,
array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/json',
),
)
);
if ( is_wp_error( $response ) ) {
return new WP_Error(
'discovery-fetch-failed',
sprintf(
/* translators: %s: error message */
__( 'Failed to fetch discovery document: %s', 'daggerhart-openid-connect-generic' ),
$response->get_error_message()
)
);
}
$response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $response_code ) {
return new WP_Error(
'discovery-fetch-failed',
sprintf(
/* translators: %d: HTTP status code */
__( 'Discovery document request returned HTTP %d.', 'daggerhart-openid-connect-generic' ),
$response_code
)
);
}
// Parse JSON response.
$body = wp_remote_retrieve_body( $response );
$discovery = json_decode( $body, true );
if ( null === $discovery || ! is_array( $discovery ) ) {
return new WP_Error(
'discovery-invalid-json',
__( 'Discovery document is not valid JSON.', 'daggerhart-openid-connect-generic' )
);
}
// Validate required fields are present.
$required_fields = array( 'authorization_endpoint', 'token_endpoint', 'jwks_uri' );
$missing_fields = array();
foreach ( $required_fields as $field ) {
if ( ! isset( $discovery[ $field ] ) || empty( $discovery[ $field ] ) ) {
$missing_fields[] = $field;
}
}
if ( ! empty( $missing_fields ) ) {
return new WP_Error(
'discovery-missing-fields',
sprintf(
/* translators: %s: comma-separated list of missing fields */
__( 'Discovery document is missing required fields: %s', 'daggerhart-openid-connect-generic' ),
implode( ', ', $missing_fields )
)
);
}
return $discovery;
}
/**
* Populate plugin settings from discovery document.
*
* Maps discovery document fields to plugin setting keys.
* Does not save to database - only updates in-memory values.
*
* @param array $discovery The discovery document data.
*
* @return array Array of setting keys that were populated.
*/
private function populate_settings_from_discovery( $discovery ) {
$populated_fields = array();
// Map discovery fields to plugin settings.
$field_mapping = array(
'authorization_endpoint' => 'endpoint_login',
'token_endpoint' => 'endpoint_token',
'userinfo_endpoint' => 'endpoint_userinfo',
'jwks_uri' => 'endpoint_jwks',
'issuer' => 'issuer',
'end_session_endpoint' => 'endpoint_end_session',
);
foreach ( $field_mapping as $discovery_key => $setting_key ) {
if ( isset( $discovery[ $discovery_key ] ) && ! empty( $discovery[ $discovery_key ] ) ) {
// Update the setting value (not saved yet).
$this->settings->{ $setting_key } = $discovery[ $discovery_key ];
$populated_fields[] = $setting_key;
}
}
return $populated_fields;
}
/**
* Handle discovery document import form submission.
*
* Checks if the discovery form was submitted, validates it,
* fetches the discovery document, and populates settings.
*
* @return void
*/
private function handle_discovery_import() {
// Check if discovery form was submitted.
if ( ! isset( $_POST['oidc_discovery_submit'] ) ) {
return;
}
// Verify nonce.
if (
! isset( $_POST['oidc_discovery_nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['oidc_discovery_nonce'] ) ), 'oidc_discovery_import' )
) {
add_settings_error(
'openid-connect-generic',
'invalid-nonce',
__( 'Security check failed. Please try again.', 'daggerhart-openid-connect-generic' ),
'error'
);
return;
}
// Get discovery URL from form.
$discovery_url = isset( $_POST['oidc_discovery_url'] )
? esc_url_raw( wp_unslash( $_POST['oidc_discovery_url'] ) )
: '';
// Fetch discovery document.
$discovery = $this->fetch_discovery_document( $discovery_url );
if ( is_wp_error( $discovery ) ) {
add_settings_error(
'openid-connect-generic',
$discovery->get_error_code(),
$discovery->get_error_message(),
'error'
);
return;
}
// Populate settings from discovery document.
$populated_fields = $this->populate_settings_from_discovery( $discovery );
// Log the import.
$this->logger->log(
sprintf(
'Configuration loaded from discovery URL: %s. Populated fields: %s',
$discovery_url,
implode( ', ', $populated_fields )
),
'discovery-import'
);
// Show success message.
$field_count = count( $populated_fields );
add_settings_error(
'openid-connect-generic',
'discovery-success',
sprintf(
/* translators: %d: number of fields populated */
_n(
'Configuration loaded successfully! %d field was populated. Review the settings below and click "Save Changes" to apply.',
'Configuration loaded successfully! %d fields were populated. Review the settings below and click "Save Changes" to apply.',
$field_count,
'daggerhart-openid-connect-generic'
),
$field_count
),
'success'
);
}
/**
* Render the discovery document import form.
*
* Outputs HTML form for importing configuration from discovery document.
* Collapsed by default if endpoint_login is already configured.
*
* @return void
*/
private function render_discovery_form() {
// Auto-expand if plugin is not yet configured.
$is_configured = ! empty( $this->settings->endpoint_login );
$open_attribute = $is_configured ? '' : ' open';
?>
<details<?php echo esc_attr( $open_attribute ); ?> class="oidc-discovery-section">
<summary class="oidc-discovery-summary">
⚡ <?php esc_html_e( 'Quick Setup: Import from Discovery Document', 'daggerhart-openid-connect-generic' ); ?>
</summary>
<div class="notice notice-info inline oidc-discovery-content">
<p>
<?php esc_html_e( 'Auto-populate endpoint settings from your identity provider\'s OpenID Connect discovery document. After loading, review the populated fields below and click "Save Changes" to apply.', 'daggerhart-openid-connect-generic' ); ?>
</p>
<form method="post" action="">
<?php wp_nonce_field( 'oidc_discovery_import', 'oidc_discovery_nonce' ); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="oidc_discovery_url">
<?php esc_html_e( 'Discovery URL', 'daggerhart-openid-connect-generic' ); ?>
</label>
</th>
<td>
<input
type="url"
id="oidc_discovery_url"
name="oidc_discovery_url"
class="regular-text oidc-discovery-url-input"
placeholder="https://your-idp.com/.well-known/openid-configuration"
/>
<p class="description">
<?php esc_html_e( 'Enter your identity provider\'s OpenID Connect discovery endpoint URL.', 'daggerhart-openid-connect-generic' ); ?>
<br>
<strong><?php esc_html_e( 'Examples:', 'daggerhart-openid-connect-generic' ); ?></strong>
<br>
• Auth0: <code>https://{tenant}.{region}.auth0.com/.well-known/openid-configuration</code>
<br>
• Keycloak: <code>https://{domain}/realms/{realm}/.well-known/openid-configuration</code>
<br>
• Okta: <code>https://{domain}/.well-known/openid-configuration</code>
</p>
</td>
</tr>
</table>
<?php submit_button( __( 'Load Configuration', 'daggerhart-openid-connect-generic' ), 'secondary', 'oidc_discovery_submit', false ); ?>
</form>
</div>
</details>
<hr class="oidc-discovery-separator">
<?php
}
}

View File

@ -1,15 +1,15 @@
# Copyright (C) 2024 daggerhart
# Copyright (C) 2026 daggerhart
# This file is distributed under the GPL-2.0+.
msgid ""
msgstr ""
"Project-Id-Version: OpenID Connect Generic 3.10.1\n"
"Project-Id-Version: OpenID Connect Generic 3.11.3\n"
"Report-Msgid-Bugs-To: "
"https://github.com/daggerhart/openid-connect-generic/issues\n"
"POT-Creation-Date: 2024-04-09 01:24:09+00:00\n"
"https://github.com/oidc-wp/openid-connect-generic/issues\n"
"POT-Creation-Date: 2026-02-13 04:19:17+00:00\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2024-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2026-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: en\n"
@ -25,316 +25,397 @@ msgstr ""
"X-Textdomain-Support: yes\n"
"X-Generator: grunt-wp-i18n 1.0.3\n"
#: includes/openid-connect-generic-client-wrapper.php:293
msgid "Session expired. Please login again."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:540
#: includes/openid-connect-generic-client-wrapper.php:578
msgid "User identity is not linked to an existing WordPress user."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:598
#: includes/openid-connect-generic-client-wrapper.php:639
msgid "Invalid user."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:816
#: includes/openid-connect-generic-client-wrapper.php:828
msgid "No appropriate username found."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:826
#: includes/openid-connect-generic-client-wrapper.php:838
#. translators: %1$s is the santitized version of the username from the IDP.
msgid "Username %1$s could not be sanitized."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:848
#: includes/openid-connect-generic-client-wrapper.php:860
#. translators: %1$s is the configured User Claim nickname key.
msgid "No nickname found in user claim using key: %1$s."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:945
#: includes/openid-connect-generic-client-wrapper.php:992
msgid "User claim incomplete."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:1048
#: includes/openid-connect-generic-client-wrapper.php:1095
msgid "Bad user claim result."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:1114
#: includes/openid-connect-generic-client-wrapper.php:1161
msgid "Can not authorize."
msgstr ""
#: includes/openid-connect-generic-client-wrapper.php:1143
#: includes/openid-connect-generic-client-wrapper.php:1190
msgid "Failed user creation."
msgstr ""
#: includes/openid-connect-generic-client.php:176
#: includes/openid-connect-generic-client.php:270
msgid "Missing state."
msgstr ""
#: includes/openid-connect-generic-client.php:180
#: includes/openid-connect-generic-client.php:274
msgid "Invalid state."
msgstr ""
#: includes/openid-connect-generic-client.php:195
#: includes/openid-connect-generic-client.php:289
msgid "Missing authentication code."
msgstr ""
#: includes/openid-connect-generic-client.php:240
#: includes/openid-connect-generic-client.php:334
msgid "Request for authentication token failed."
msgstr ""
#: includes/openid-connect-generic-client.php:273
#: includes/openid-connect-generic-client.php:367
msgid "Refresh token failed."
msgstr ""
#: includes/openid-connect-generic-client.php:288
#: includes/openid-connect-generic-client.php:382
msgid "Missing token body."
msgstr ""
#: includes/openid-connect-generic-client.php:296
#: includes/openid-connect-generic-client.php:390
msgid "Invalid token."
msgstr ""
#: includes/openid-connect-generic-client.php:349
#: includes/openid-connect-generic-client.php:451
msgid "Request for userinfo failed."
msgstr ""
#: includes/openid-connect-generic-client.php:409
#: includes/openid-connect-generic-client.php:515
msgid "Missing authentication state."
msgstr ""
#: includes/openid-connect-generic-client.php:446
#: includes/openid-connect-generic-client.php:552
msgid "No identity token."
msgstr ""
#: includes/openid-connect-generic-client.php:453
#: includes/openid-connect-generic-client.php:592
msgid "Missing identity token."
msgstr ""
#: includes/openid-connect-generic-client.php:480
#: includes/openid-connect-generic-client.php:651
msgid "Bad ID token claim."
msgstr ""
#: includes/openid-connect-generic-client.php:485
#: includes/openid-connect-generic-client.php:656
msgid "No subject identity."
msgstr ""
#: includes/openid-connect-generic-client.php:491
#: includes/openid-connect-generic-client.php:661
#: includes/openid-connect-generic-jwt-validator.php:184
msgid "Token missing expiration claim."
msgstr ""
#: includes/openid-connect-generic-client.php:664
msgid "Token has expired."
msgstr ""
#: includes/openid-connect-generic-client.php:669
#: includes/openid-connect-generic-jwt-validator.php:192
msgid "Token missing issued at claim."
msgstr ""
#: includes/openid-connect-generic-client.php:674
#: includes/openid-connect-generic-jwt-validator.php:200
msgid "Token missing audience claim."
msgstr ""
#: includes/openid-connect-generic-client.php:687
#: includes/openid-connect-generic-jwt-validator.php:217
msgid "Token audience does not match client."
msgstr ""
#: includes/openid-connect-generic-client.php:697
#: includes/openid-connect-generic-jwt-validator.php:226
msgid "Token missing issuer claim."
msgstr ""
#: includes/openid-connect-generic-client.php:712
#: includes/openid-connect-generic-jwt-validator.php:241
msgid "Token issuer does not match expected issuer."
msgstr ""
#: includes/openid-connect-generic-client.php:722
msgid "No matching acr values."
msgstr ""
#: includes/openid-connect-generic-client.php:511
#: includes/openid-connect-generic-client.php:742
msgid "Bad user claim."
msgstr ""
#: includes/openid-connect-generic-client.php:531
#: includes/openid-connect-generic-client.php:762
msgid "Invalid user claim."
msgstr ""
#: includes/openid-connect-generic-client.php:536
#: includes/openid-connect-generic-client.php:767
msgid "Error from the IDP."
msgstr ""
#: includes/openid-connect-generic-client.php:545
#: includes/openid-connect-generic-client.php:776
msgid "Incorrect user claim."
msgstr ""
#: includes/openid-connect-generic-client.php:552
#: includes/openid-connect-generic-client.php:783
msgid "Unauthorized access."
msgstr ""
#: includes/openid-connect-generic-login-form.php:122
#: includes/openid-connect-generic-jwt-validator.php:127
msgid "Failed to fetch JWKS from identity provider."
msgstr ""
#: includes/openid-connect-generic-jwt-validator.php:138
#. translators: %d is the HTTP response code
msgid "JWKS endpoint returned HTTP %d"
msgstr ""
#: includes/openid-connect-generic-jwt-validator.php:152
msgid "Invalid JWKS format received from identity provider."
msgstr ""
#: includes/openid-connect-generic-jwt-validator.php:176
msgid "Token missing subject claim."
msgstr ""
#: includes/openid-connect-generic-jwt-validator.php:315
msgid "JWKS URI not configured. JWT signature verification requires JWKS endpoint."
msgstr ""
#: includes/openid-connect-generic-jwt-validator.php:346
#. translators: %s is the error message
msgid "JWT verification failed: %s"
msgstr ""
#: includes/openid-connect-generic-login-form.php:132
#. translators: %1$s is the error code from the IDP.
msgid "ERROR (%1$s)"
msgstr ""
#: includes/openid-connect-generic-login-form.php:141
#: includes/openid-connect-generic-login-form.php:152
msgid "Login with OpenID Connect"
msgstr ""
#: includes/openid-connect-generic-option-logger.php:228
#: includes/openid-connect-generic-option-logger.php:230
msgid "Details"
msgstr ""
#: includes/openid-connect-generic-option-logger.php:229
#: includes/openid-connect-generic-option-logger.php:231
msgid "Data"
msgstr ""
#: includes/openid-connect-generic-option-logger.php:236
#: includes/openid-connect-generic-option-logger.php:238
msgid "Date"
msgstr ""
#: includes/openid-connect-generic-option-logger.php:240
#: includes/openid-connect-generic-option-logger.php:242
msgid "Type"
msgstr ""
#: includes/openid-connect-generic-option-logger.php:244
#: includes/openid-connect-generic-option-logger.php:246
msgid "User"
msgstr ""
#: includes/openid-connect-generic-option-logger.php:248
#: includes/openid-connect-generic-option-logger.php:250
msgid "URI "
msgstr ""
#: includes/openid-connect-generic-option-logger.php:252
#: includes/openid-connect-generic-option-logger.php:254
msgid "Response&nbsp;Time&nbsp;(sec)"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:108
#: includes/openid-connect-generic-settings-page.php:127
msgid "OpenID Connect - Generic Client"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:109
#: includes/openid-connect-generic-settings-page.php:128
msgid "OpenID Connect Client"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:133
#: includes/openid-connect-generic-settings-page.php:152
msgid "Client Settings"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:140
#: includes/openid-connect-generic-settings-page.php:159
msgid "WordPress User Settings"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:147
#: includes/openid-connect-generic-settings-page.php:166
msgid "Authorization Settings"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:154
#: includes/openid-connect-generic-settings-page.php:173
msgid "Log Settings"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:212
#: includes/openid-connect-generic-settings-page.php:231
msgid "Login Type"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:213
#: includes/openid-connect-generic-settings-page.php:232
msgid "Select how the client (login form) should provide login options."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:216
#: includes/openid-connect-generic-settings-page.php:235
msgid "OpenID Connect button on login form"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:217
#: includes/openid-connect-generic-settings-page.php:236
msgid "Auto Login - SSO"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:223
#: includes/openid-connect-generic-settings-page.php:242
msgid "Login Button Text"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:243
msgid ""
"Customize the text shown on the OpenID Connect login button. Leave empty to "
"use the default text."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:249
msgid "Client ID"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:224
#: includes/openid-connect-generic-settings-page.php:250
msgid ""
"The ID this client will be recognized as when connecting the to Identity "
"provider server."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:231
#: includes/openid-connect-generic-settings-page.php:257
msgid "Client Secret Key"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:232
#: includes/openid-connect-generic-settings-page.php:258
msgid ""
"Arbitrary secret key the server expects from this client. Can be anything, "
"but should be very unique."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:238
#: includes/openid-connect-generic-settings-page.php:264
msgid "OpenID Scope"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:239
#: includes/openid-connect-generic-settings-page.php:265
msgid "Space separated list of scopes this client should access."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:246
#: includes/openid-connect-generic-settings-page.php:272
msgid "Login Endpoint URL"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:247
#: includes/openid-connect-generic-settings-page.php:273
msgid "Identify provider authorization endpoint."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:254
#: includes/openid-connect-generic-settings-page.php:280
msgid "Userinfo Endpoint URL"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:255
#: includes/openid-connect-generic-settings-page.php:281
msgid "Identify provider User information endpoint."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:262
#: includes/openid-connect-generic-settings-page.php:288
msgid "Token Validation Endpoint URL"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:263
#: includes/openid-connect-generic-settings-page.php:289
msgid "Identify provider token endpoint."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:270
#: includes/openid-connect-generic-settings-page.php:296
msgid "End Session Endpoint URL"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:271
#: includes/openid-connect-generic-settings-page.php:297
msgid "Identify provider logout endpoint."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:278
#: includes/openid-connect-generic-settings-page.php:304
msgid "JWKS URI"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:305
msgid ""
"Identity provider JWKS (JSON Web Key Set) endpoint for JWT signature "
"verification. Usually found at /.well-known/jwks.json"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:312
msgid "Issuer"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:313
msgid ""
"Identity provider issuer URL for JWT validation. If not set, the issuer "
"will be automatically derived from the Login Endpoint URL. Only configure "
"this if your IDP uses a different issuer than the base URL of the login "
"endpoint."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:320
msgid "JWKS Cache TTL (seconds)"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:321
msgid "Time in seconds to cache JWKS keys. Default: 3600 (1 hour)"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:327
msgid "ACR values"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:279
#: includes/openid-connect-generic-settings-page.php:328
msgid "Use a specific defined authentication contract from the IDP - optional."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:285
#: includes/openid-connect-generic-settings-page.php:334
msgid "Identity Key"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:286
#: includes/openid-connect-generic-settings-page.php:335
msgid ""
"Where in the user claim array to find the user's identification data. "
"Possible standard values: preferred_username, name, or sub. If you're "
"having trouble, use \"sub\"."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:292
msgid "Disable SSL Verify"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:294
#. translators: %1$s HTML tags for layout/styles, %2$s closing HTML tag for
#. styles.
msgid ""
"Do not require SSL verification during authorization. The OAuth extension "
"uses curl to make the request. By default CURL will generally verify the "
"SSL certificate to see if its valid an issued by an accepted CA. This "
"setting disabled that verification.%1$sNot recommended for production "
"sites.%2$s"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:299
#: includes/openid-connect-generic-settings-page.php:341
msgid "HTTP Request Timeout"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:300
#: includes/openid-connect-generic-settings-page.php:342
msgid "Set the timeout for requests made to the IDP. Default value is 5."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:306
#: includes/openid-connect-generic-settings-page.php:348
msgid "Enforce Privacy"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:307
#: includes/openid-connect-generic-settings-page.php:349
msgid "Require users be logged in to see the site."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:313
#: includes/openid-connect-generic-settings-page.php:355
msgid "Alternate Redirect URI"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:314
#: includes/openid-connect-generic-settings-page.php:356
msgid ""
"Provide an alternative redirect route. Useful if your server is causing "
"issues with the default admin-ajax method. You must flush rewrite rules "
@ -342,78 +423,106 @@ msgid ""
"settings page."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:319
#: includes/openid-connect-generic-settings-page.php:361
msgid "Nickname Key"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:320
#: includes/openid-connect-generic-settings-page.php:362
msgid ""
"Where in the user claim array to find the user's nickname. Possible "
"standard values: preferred_username, name, or sub."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:326
#: includes/openid-connect-generic-settings-page.php:368
msgid "Email Formatting"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:327
#: includes/openid-connect-generic-settings-page.php:369
msgid ""
"String from which the user's email address is built. Specify \"{email}\" as "
"long as the user claim contains an email claim."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:333
#: includes/openid-connect-generic-settings-page.php:375
msgid "Display Name Formatting"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:334
#: includes/openid-connect-generic-settings-page.php:376
msgid "String from which the user's display name is built."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:340
#: includes/openid-connect-generic-settings-page.php:382
msgid "Identify with User Name"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:341
#: includes/openid-connect-generic-settings-page.php:383
msgid ""
"If checked, the user's identity will be determined by the user name instead "
"of the email address."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:346
#: includes/openid-connect-generic-settings-page.php:388
msgid "State time limit"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:347
#: includes/openid-connect-generic-settings-page.php:389
msgid "State valid time in seconds. Defaults to 180"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:352
#: includes/openid-connect-generic-settings-page.php:394
msgid "Enable Refresh Token"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:353
#: includes/openid-connect-generic-settings-page.php:395
msgid ""
"If checked, support refresh tokens used to obtain access tokens from "
"supported IDPs."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:358
#: includes/openid-connect-generic-settings-page.php:400
msgid "Disable SSL Verify"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:402
#. translators: %1$s HTML tags for layout/styles (strong tag start with warning
#. class), %2$s closing HTML tag for styles.
msgid ""
"Do not require SSL verification during authorization. %1$sOnly works in "
"local development (WP_DEBUG=true, WP_ENVIRONMENT_TYPE=local).%2$s This "
"setting is automatically disabled in production. If you need this in "
"production, fix your SSL certificates instead."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:407
msgid "Allow Internal IDP"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:409
#. translators: %1$s HTML tags for layout/styles (strong tag start with warning
#. class), %2$s closing HTML tag for styles.
msgid ""
"Allow HTTP requests to internal/private network endpoints (localhost, "
"127.0.0.1, 10.x.x.x, 192.168.x.x, 172.16-31.x.x). %1$sOnly enable this for "
"local development or corporate internal identity providers. Disabling SSRF "
"protection can expose your server to security risks.%2$s"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:414
msgid "Link Existing Users"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:359
#: includes/openid-connect-generic-settings-page.php:415
msgid ""
"If a WordPress account already exists with the same identity as a "
"newly-authenticated user over OpenID Connect, login as that user instead of "
"generating an error."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:365
#: includes/openid-connect-generic-settings-page.php:421
msgid "Create user if does not exist"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:366
#: includes/openid-connect-generic-settings-page.php:422
msgid ""
"If the user identity is not linked to an existing WordPress user, it is "
"created. If this setting is not enabled, and if the user authenticates with "
@ -421,11 +530,11 @@ msgid ""
"authentication will fail."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:372
#: includes/openid-connect-generic-settings-page.php:428
msgid "Redirect Back to Origin Page"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:373
#: includes/openid-connect-generic-settings-page.php:429
msgid ""
"After a successful OpenID Connect authentication, this will redirect the "
"user back to the page on which they clicked the OpenID Connect login "
@ -436,84 +545,179 @@ msgid ""
"account page."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:379
#: includes/openid-connect-generic-settings-page.php:435
msgid "Redirect to the login screen when session is expired"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:380
#: includes/openid-connect-generic-settings-page.php:436
msgid ""
"When enabled, this will automatically redirect the user back to the "
"WordPress login page if their access token has expired."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:386
#: includes/openid-connect-generic-settings-page.php:442
msgid "Enable Logging"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:387
#: includes/openid-connect-generic-settings-page.php:443
msgid "Very simple log messages for debugging purposes."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:393
#: includes/openid-connect-generic-settings-page.php:449
msgid "Log Limit"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:394
#: includes/openid-connect-generic-settings-page.php:450
msgid ""
"Number of items to keep in the log. These logs are stored as an option in "
"the database, so space is limited."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:456
#: includes/openid-connect-generic-settings-page.php:515
msgid "Notes"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:459
#: includes/openid-connect-generic-settings-page.php:518
msgid "Redirect URI"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:463
#: includes/openid-connect-generic-settings-page.php:522
msgid "Login Button Shortcode"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:467
#: includes/openid-connect-generic-settings-page.php:526
msgid "Authentication URL Shortcode"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:472
#: includes/openid-connect-generic-settings-page.php:531
msgid "Logs"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:561
#: includes/openid-connect-generic-settings-page.php:620
msgid "Example"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:574
#: includes/openid-connect-generic-settings-page.php:633
msgid "Enter your OpenID Connect identity provider settings."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:583
#: includes/openid-connect-generic-settings-page.php:642
msgid "Modify the interaction between OpenID Connect and WordPress users."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:592
#: includes/openid-connect-generic-settings-page.php:651
msgid "Control the authorization mechanics of the site."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:601
#: includes/openid-connect-generic-settings-page.php:660
msgid "Log information about login attempts through OpenID Connect Generic."
msgstr ""
#: openid-connect-generic.php:242
#: includes/openid-connect-generic-settings-page.php:675
msgid "Please enter a discovery URL."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:684
msgid "Invalid discovery URL format."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:695
msgid "Discovery URL must use HTTPS in production environments."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:715
#. translators: %s: error message
msgid "Failed to fetch discovery document: %s"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:727
#. translators: %d: HTTP status code
msgid "Discovery document request returned HTTP %d."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:740
msgid "Discovery document is not valid JSON."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:759
#. translators: %s: comma-separated list of missing fields
msgid "Discovery document is missing required fields: %s"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:824
msgid "Security check failed. Please try again."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:868
#. translators: %d: number of fields populated
msgid ""
"Configuration loaded successfully! %d field was populated. Review the "
"settings below and click \"Save Changes\" to apply."
msgid_plural ""
"Configuration loaded successfully! %d fields were populated. Review the "
"settings below and click \"Save Changes\" to apply."
msgstr[0] ""
msgstr[1] ""
#: includes/openid-connect-generic-settings-page.php:895
msgid "Quick Setup: Import from Discovery Document"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:899
msgid ""
"Auto-populate endpoint settings from your identity provider's OpenID "
"Connect discovery document. After loading, review the populated fields "
"below and click \"Save Changes\" to apply."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:907
msgid "Discovery URL"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:919
msgid "Enter your identity provider's OpenID Connect discovery endpoint URL."
msgstr ""
#: includes/openid-connect-generic-settings-page.php:921
msgid "Examples:"
msgstr ""
#: includes/openid-connect-generic-settings-page.php:932
msgid "Load Configuration"
msgstr ""
#: openid-connect-generic.php:252
msgid "Private site"
msgstr ""
#: openid-connect-generic.php:282
msgid "OpenID Connect Generic - Security Configuration Required"
msgstr ""
#: openid-connect-generic.php:289
#. translators: %s is a link to the settings page
msgid ""
"Your OpenID Connect authentication is using an insecure fallback method. "
"You must configure the <strong>JWKS endpoint</strong> in <a "
"href=\"%s\">plugin settings</a> as soon as possible."
msgstr ""
#: openid-connect-generic.php:296
msgid ""
"The current insecure fallback will be removed in version 3.12.0. After that "
"update, authentication will fail until the JWKS endpoint is configured."
msgstr ""
#: openid-connect-generic.php:299
msgid "Common JWKS endpoints:"
msgstr ""
#. Plugin Name of the plugin/theme
msgid "OpenID Connect Generic"
msgstr ""
#. Plugin URI of the plugin/theme
msgid "https://github.com/daggerhart/openid-connect-generic"
msgid "https://github.com/oidc-wp/openid-connect-generic"
msgstr ""
#. Description of the plugin/theme
@ -527,5 +731,5 @@ msgid "daggerhart"
msgstr ""
#. Author URI of the plugin/theme
msgid "http://www.daggerhart.com"
msgstr ""
msgid "https://www.daggerhartlab.com"
msgstr ""

View File

@ -16,7 +16,7 @@
* Plugin Name: OpenID Connect Generic
* Plugin URI: https://github.com/oidc-wp/openid-connect-generic
* Description: Connect to an OpenID Connect identity provider using Authorization Code Flow.
* Version: 3.10.2
* Version: 3.11.3
* Requires at least: 5.0
* Requires PHP: 7.4
* Author: daggerhart
@ -59,11 +59,11 @@ Notes
Callable actions
User Meta
- openid-connect-generic-subject-identity - the identity of the user provided by the idp
- openid-connect-generic-last-id-token-claim - the user's most recent id_token claim, decoded
- openid-connect-generic-last-user-claim - the user's most recent user_claim
- openid-connect-generic-last-token-response - the user's most recent token response
User Meta (since v3.10.4 prefixed with the blog database prefix, for example wp_2_openid-connect-generic-subject-identity)
- [[BLOG_DB_PREFIX]]openid-connect-generic-subject-identity - the identity of the user provided by the idp
- [[BLOG_DB_PREFIX]]openid-connect-generic-last-id-token-claim - the user's most recent id_token claim, decoded
- [[BLOG_DB_PREFIX]]openid-connect-generic-last-user-claim - the user's most recent user_claim
- [[BLOG_DB_PREFIX]]openid-connect-generic-last-token-response - the user's most recent token response
Options
- openid_connect_generic_settings - plugin settings
@ -93,7 +93,7 @@ class OpenID_Connect_Generic {
*
* @var string
*/
const VERSION = '3.10.2';
const VERSION = '3.11.3';
/**
* Plugin settings.
@ -158,7 +158,11 @@ class OpenID_Connect_Generic {
$this->settings->endpoint_token,
$this->get_redirect_uri( $this->settings ),
$this->settings->acr_values,
$this->settings->endpoint_jwks,
$this->settings->issuer ?? '',
$this->settings->jwks_cache_ttl,
$this->get_state_time_limit( $this->settings ),
$this->settings->allow_internal_idp,
$this->logger
);
@ -167,7 +171,7 @@ class OpenID_Connect_Generic {
return;
}
OpenID_Connect_Generic_Login_Form::register( $this->settings, $this->client_wrapper );
OpenID_Connect_Generic_Login_Form::register( $this->settings, $this->client_wrapper, $this->client );
// Add a shortcode to get the auth URL.
add_shortcode( 'openid_connect_generic_auth_url', array( $this->client_wrapper, 'get_authentication_url' ) );
@ -179,6 +183,7 @@ class OpenID_Connect_Generic {
if ( is_admin() ) {
OpenID_Connect_Generic_Settings_Page::register( $this->settings, $this->logger );
add_action( 'admin_notices', array( $this, 'admin_notice_jwks_required' ) );
}
}
@ -249,6 +254,59 @@ class OpenID_Connect_Generic {
return $content;
}
/**
* Display admin notice when JWKS endpoint is not configured.
*
* @return void
*/
public function admin_notice_jwks_required() {
// Only show to users who can manage options.
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Check if JWKS endpoint is configured.
if ( ! empty( $this->settings->endpoint_jwks ) ) {
return;
}
// Check if any OIDC endpoints are configured (plugin is actually being used).
if ( empty( $this->settings->endpoint_login ) ) {
return;
}
$settings_url = admin_url( 'options-general.php?page=openid-connect-generic-settings' );
?>
<div class="notice notice-error">
<p>
<strong><?php esc_html_e( 'OpenID Connect Generic - Security Configuration Required', 'daggerhart-openid-connect-generic' ); ?></strong>
</p>
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: %s is a link to the settings page */
__( 'Your OpenID Connect authentication is using an insecure fallback method. You must configure the <strong>JWKS endpoint</strong> in <a href="%s">plugin settings</a> as soon as possible.', 'daggerhart-openid-connect-generic' ),
esc_url( $settings_url )
)
);
?>
</p>
<p>
<?php esc_html_e( 'The current insecure fallback will be removed in version 3.12.0. After that update, authentication will fail until the JWKS endpoint is configured.', 'daggerhart-openid-connect-generic' ); ?>
</p>
<p>
<strong><?php esc_html_e( 'Common JWKS endpoints:', 'daggerhart-openid-connect-generic' ); ?></strong><br>
• Keycloak: <code>https://your-domain/realms/your-realm/protocol/openid-connect/certs</code><br>
• Auth0: <code>https://your-domain.auth0.com/.well-known/jwks.json</code><br>
• Okta: <code>https://your-domain.okta.com/oauth2/default/v1/keys</code><br>
• Azure AD: <code>https://login.microsoftonline.com/your-tenant/discovery/v2.0/keys</code><br>
• Google: <code>https://www.googleapis.com/oauth2/v3/certs</code>
</p>
</div>
<?php
}
/**
* Handle plugin upgrades
*
@ -360,18 +418,14 @@ class OpenID_Connect_Generic {
* @return void
*/
public static function bootstrap() {
/**
* This is a documented valid call for spl_autoload_register.
*
* @link https://www.php.net/manual/en/function.spl-autoload-register.php#71155
*/
spl_autoload_register( array( 'OpenID_Connect_Generic', 'autoload' ) );
require_once __DIR__ . '/vendor/autoload.php';
$settings = new OpenID_Connect_Generic_Option_Settings(
// Default settings values.
array(
// OAuth client settings.
'login_type' => defined( 'OIDC_LOGIN_TYPE' ) ? OIDC_LOGIN_TYPE : 'button',
'login_button_text' => '',
'client_id' => defined( 'OIDC_CLIENT_ID' ) ? OIDC_CLIENT_ID : '',
'client_secret' => defined( 'OIDC_CLIENT_SECRET' ) ? OIDC_CLIENT_SECRET : '',
'scope' => defined( 'OIDC_CLIENT_SCOPE' ) ? OIDC_CLIENT_SCOPE : '',
@ -379,11 +433,14 @@ class OpenID_Connect_Generic {
'endpoint_userinfo' => defined( 'OIDC_ENDPOINT_USERINFO_URL' ) ? OIDC_ENDPOINT_USERINFO_URL : '',
'endpoint_token' => defined( 'OIDC_ENDPOINT_TOKEN_URL' ) ? OIDC_ENDPOINT_TOKEN_URL : '',
'endpoint_end_session' => defined( 'OIDC_ENDPOINT_LOGOUT_URL' ) ? OIDC_ENDPOINT_LOGOUT_URL : '',
'endpoint_jwks' => defined( 'OIDC_ENDPOINT_JWKS_URL' ) ? OIDC_ENDPOINT_JWKS_URL : '',
'jwks_cache_ttl' => 3600,
'acr_values' => defined( 'OIDC_ACR_VALUES' ) ? OIDC_ACR_VALUES : '',
// Non-standard settings.
'no_sslverify' => 0,
'http_request_timeout' => 5,
'allow_internal_idp' => 0,
'identity_key' => 'preferred_username',
'nickname_key' => 'preferred_username',
'email_format' => '{email}',

View File

@ -3,7 +3,7 @@ Contributors: daggerhart, tnolte
Tags: security, login, oauth2, openidconnect, apps, authentication, autologin, sso
Requires at least: 5.0
Tested up to: 6.9.0
Stable tag: 3.10.2
Stable tag: 3.11.3
Requires PHP: 7.4
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -19,7 +19,7 @@ new users are created in WordPress database.
Much of the documentation can be found on the Settings > OpenID Connect Generic dashboard page.
Please submit issues to the Github repo: https://github.com/daggerhart/openid-connect-generic
Please submit issues to the Github repo: https://github.com/oidc-wp/openid-connect-generic
== Installation ==
@ -47,9 +47,56 @@ On the settings page for this plugin (Dashboard > Settings > OpenID Connect Gene
**Alternate Redirect URI**. When checked, the plugin will use the Redirect URI
`https://example.com/openid-connect-authorize`.
== Upgrade Notice ==
= 3.11.3 =
SECURITY UPDATE: 3.11.x branch - Fixes authentication vulnerabilities including JWT signature bypass and SSRF protection. Update immediately and configure JWKS endpoint in settings.
== Changelog ==
= 3.11.3 =
* Feature/improvement: Added configurable issuer setting for JWT validation.
= 3.11.2 =
* Improvement: Support identity providers that omit algorithm parameter in JWKS (Microsoft Entra ID).
= 3.11.1 =
* Fix bug created in 3.11.0 release when comparing issuer to derived expected value.
= 3.11.0 =
**SECURITY RELEASE**
* Security: Added JWT signature verification using JWKS to prevent token forgery
* Security: Enhanced token claim validation (exp, aud, iss, iat, nonce)
* Security: Replaced weak state generation with cryptographically secure random_bytes()
* Security: Fixed open redirect vulnerability in authentication flow
* Security: Restricted SSL verification bypass to local development environments only
* Security: Added nonce protection to debug mode to prevent information disclosure
* Security: Added SSRF protection by default through use of wp_safe_remote_* functions
* Feature: Added JWKS endpoint configuration setting
* Feature: Added OpenID Connect discovery document support
* Feature: Added customizable login button text setting
* Improvement: Migrated to Composer-managed dependencies
* Fix: Corrected issuer validation to properly extract base URL from endpoints
* Fix: Identity token timestamp tracking
= 3.10.4 =
* Fix issue with finding users on multisite after switch to user options in place of user meta.
* Improvement: Retry logins for some IDP errors to bypass issue with Safari ITP. Also improves display of error messages that come from the IDP.
= 3.10.3 =
* Fix issue with log corruption causing fatal error.
* Fix: Fallback to a POST request for userinfo when GET fails.
* Fix: Improves multisite compatibility by switching to *_user_options() functions.
* Fix: Fix for WordPress user session length being very short when refresh tokens are enabled.
= 3.10.2 =
* Fix: @socialmedialabs - Regression affecting SSO Auto Login with url handling improvement changes.

View File

@ -0,0 +1,22 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitdf50dab69727c9acc15d5a5590cd963a::getLoader();

View File

@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@ -0,0 +1,396 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}

View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,125 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Composer\\Installers\\AglInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AglInstaller.php',
'Composer\\Installers\\AkauntingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AkauntingInstaller.php',
'Composer\\Installers\\AnnotateCmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
'Composer\\Installers\\AsgardInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AsgardInstaller.php',
'Composer\\Installers\\AttogramInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AttogramInstaller.php',
'Composer\\Installers\\BaseInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BaseInstaller.php',
'Composer\\Installers\\BitrixInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BitrixInstaller.php',
'Composer\\Installers\\BonefishInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BonefishInstaller.php',
'Composer\\Installers\\BotbleInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BotbleInstaller.php',
'Composer\\Installers\\CakePHPInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CakePHPInstaller.php',
'Composer\\Installers\\ChefInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ChefInstaller.php',
'Composer\\Installers\\CiviCrmInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CiviCrmInstaller.php',
'Composer\\Installers\\ClanCatsFrameworkInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php',
'Composer\\Installers\\CockpitInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CockpitInstaller.php',
'Composer\\Installers\\CodeIgniterInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php',
'Composer\\Installers\\Concrete5Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Concrete5Installer.php',
'Composer\\Installers\\ConcreteCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ConcreteCMSInstaller.php',
'Composer\\Installers\\CroogoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CroogoInstaller.php',
'Composer\\Installers\\DecibelInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DecibelInstaller.php',
'Composer\\Installers\\DframeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DframeInstaller.php',
'Composer\\Installers\\DokuWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DokuWikiInstaller.php',
'Composer\\Installers\\DolibarrInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DolibarrInstaller.php',
'Composer\\Installers\\DrupalInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DrupalInstaller.php',
'Composer\\Installers\\ElggInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ElggInstaller.php',
'Composer\\Installers\\EliasisInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/EliasisInstaller.php',
'Composer\\Installers\\ExpressionEngineInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php',
'Composer\\Installers\\EzPlatformInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/EzPlatformInstaller.php',
'Composer\\Installers\\ForkCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ForkCMSInstaller.php',
'Composer\\Installers\\FuelInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/FuelInstaller.php',
'Composer\\Installers\\FuelphpInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/FuelphpInstaller.php',
'Composer\\Installers\\GravInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/GravInstaller.php',
'Composer\\Installers\\HuradInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/HuradInstaller.php',
'Composer\\Installers\\ImageCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ImageCMSInstaller.php',
'Composer\\Installers\\Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Installer.php',
'Composer\\Installers\\ItopInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ItopInstaller.php',
'Composer\\Installers\\KanboardInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KanboardInstaller.php',
'Composer\\Installers\\KnownInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KnownInstaller.php',
'Composer\\Installers\\KodiCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KodiCMSInstaller.php',
'Composer\\Installers\\KohanaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KohanaInstaller.php',
'Composer\\Installers\\LanManagementSystemInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php',
'Composer\\Installers\\LaravelInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LaravelInstaller.php',
'Composer\\Installers\\LavaLiteInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LavaLiteInstaller.php',
'Composer\\Installers\\LithiumInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LithiumInstaller.php',
'Composer\\Installers\\MODULEWorkInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php',
'Composer\\Installers\\MODXEvoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MODXEvoInstaller.php',
'Composer\\Installers\\MagentoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MagentoInstaller.php',
'Composer\\Installers\\MajimaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MajimaInstaller.php',
'Composer\\Installers\\MakoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MakoInstaller.php',
'Composer\\Installers\\MantisBTInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MantisBTInstaller.php',
'Composer\\Installers\\MatomoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MatomoInstaller.php',
'Composer\\Installers\\MauticInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
'Composer\\Installers\\MayaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
'Composer\\Installers\\MediaWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
'Composer\\Installers\\MiaoxingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MiaoxingInstaller.php',
'Composer\\Installers\\MicroweberInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
'Composer\\Installers\\ModxInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
'Composer\\Installers\\MoodleInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
'Composer\\Installers\\OctoberInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OctoberInstaller.php',
'Composer\\Installers\\OntoWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OntoWikiInstaller.php',
'Composer\\Installers\\OsclassInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OsclassInstaller.php',
'Composer\\Installers\\OxidInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OxidInstaller.php',
'Composer\\Installers\\PPIInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PPIInstaller.php',
'Composer\\Installers\\PantheonInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PantheonInstaller.php',
'Composer\\Installers\\PhiftyInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PhiftyInstaller.php',
'Composer\\Installers\\PhpBBInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PhpBBInstaller.php',
'Composer\\Installers\\PiwikInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PiwikInstaller.php',
'Composer\\Installers\\PlentymarketsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php',
'Composer\\Installers\\Plugin' => $vendorDir . '/composer/installers/src/Composer/Installers/Plugin.php',
'Composer\\Installers\\PortoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
'Composer\\Installers\\PrestashopInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
'Composer\\Installers\\ProcessWireInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ProcessWireInstaller.php',
'Composer\\Installers\\PuppetInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
'Composer\\Installers\\PxcmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
'Composer\\Installers\\RadPHPInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
'Composer\\Installers\\ReIndexInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ReIndexInstaller.php',
'Composer\\Installers\\Redaxo5Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Redaxo5Installer.php',
'Composer\\Installers\\RedaxoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RedaxoInstaller.php',
'Composer\\Installers\\RoundcubeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RoundcubeInstaller.php',
'Composer\\Installers\\SMFInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SMFInstaller.php',
'Composer\\Installers\\ShopwareInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
'Composer\\Installers\\SilverStripeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
'Composer\\Installers\\SiteDirectInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
'Composer\\Installers\\StarbugInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/StarbugInstaller.php',
'Composer\\Installers\\SyDESInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
'Composer\\Installers\\SyliusInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
'Composer\\Installers\\TaoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
'Composer\\Installers\\TastyIgniterInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php',
'Composer\\Installers\\TheliaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
'Composer\\Installers\\TuskInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
'Composer\\Installers\\UserFrostingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
'Composer\\Installers\\VanillaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
'Composer\\Installers\\VgmcpInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
'Composer\\Installers\\WHMCSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
'Composer\\Installers\\WinterInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WinterInstaller.php',
'Composer\\Installers\\WolfCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
'Composer\\Installers\\WordPressInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
'Composer\\Installers\\YawikInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
'Composer\\Installers\\ZendInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ZendInstaller.php',
'Composer\\Installers\\ZikulaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
'Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php',
'Firebase\\JWT\\CachedKeySet' => $vendorDir . '/firebase/php-jwt/src/CachedKeySet.php',
'Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php',
'Firebase\\JWT\\JWK' => $vendorDir . '/firebase/php-jwt/src/JWK.php',
'Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php',
'Firebase\\JWT\\JWTExceptionWithPayloadInterface' => $vendorDir . '/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php',
'Firebase\\JWT\\Key' => $vendorDir . '/firebase/php-jwt/src/Key.php',
'Firebase\\JWT\\SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/src/SignatureInvalidException.php',
'OpenID_Connect_Generic' => $baseDir . '/openid-connect-generic.php',
'OpenID_Connect_Generic_Client' => $baseDir . '/includes/openid-connect-generic-client.php',
'OpenID_Connect_Generic_Client_Wrapper' => $baseDir . '/includes/openid-connect-generic-client-wrapper.php',
'OpenID_Connect_Generic_JWT_Validator' => $baseDir . '/includes/openid-connect-generic-jwt-validator.php',
'OpenID_Connect_Generic_Login_Form' => $baseDir . '/includes/openid-connect-generic-login-form.php',
'OpenID_Connect_Generic_Option_Logger' => $baseDir . '/includes/openid-connect-generic-option-logger.php',
'OpenID_Connect_Generic_Option_Settings' => $baseDir . '/includes/openid-connect-generic-option-settings.php',
'OpenID_Connect_Generic_Settings_Page' => $baseDir . '/includes/openid-connect-generic-settings-page.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,11 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
);

View File

@ -0,0 +1,38 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitdf50dab69727c9acc15d5a5590cd963a
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitdf50dab69727c9acc15d5a5590cd963a', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitdf50dab69727c9acc15d5a5590cd963a', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitdf50dab69727c9acc15d5a5590cd963a::getInitializer($loader));
$loader->register(true);
return $loader;
}
}

View File

@ -0,0 +1,159 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitdf50dab69727c9acc15d5a5590cd963a
{
public static $prefixLengthsPsr4 = array (
'F' =>
array (
'Firebase\\JWT\\' => 13,
),
'C' =>
array (
'Composer\\Installers\\' => 20,
),
);
public static $prefixDirsPsr4 = array (
'Firebase\\JWT\\' =>
array (
0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
),
'Composer\\Installers\\' =>
array (
0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Composer\\Installers\\AglInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AglInstaller.php',
'Composer\\Installers\\AkauntingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AkauntingInstaller.php',
'Composer\\Installers\\AnnotateCmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
'Composer\\Installers\\AsgardInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AsgardInstaller.php',
'Composer\\Installers\\AttogramInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AttogramInstaller.php',
'Composer\\Installers\\BaseInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BaseInstaller.php',
'Composer\\Installers\\BitrixInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BitrixInstaller.php',
'Composer\\Installers\\BonefishInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BonefishInstaller.php',
'Composer\\Installers\\BotbleInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BotbleInstaller.php',
'Composer\\Installers\\CakePHPInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CakePHPInstaller.php',
'Composer\\Installers\\ChefInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ChefInstaller.php',
'Composer\\Installers\\CiviCrmInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CiviCrmInstaller.php',
'Composer\\Installers\\ClanCatsFrameworkInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php',
'Composer\\Installers\\CockpitInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CockpitInstaller.php',
'Composer\\Installers\\CodeIgniterInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php',
'Composer\\Installers\\Concrete5Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Concrete5Installer.php',
'Composer\\Installers\\ConcreteCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ConcreteCMSInstaller.php',
'Composer\\Installers\\CroogoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CroogoInstaller.php',
'Composer\\Installers\\DecibelInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DecibelInstaller.php',
'Composer\\Installers\\DframeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DframeInstaller.php',
'Composer\\Installers\\DokuWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DokuWikiInstaller.php',
'Composer\\Installers\\DolibarrInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DolibarrInstaller.php',
'Composer\\Installers\\DrupalInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DrupalInstaller.php',
'Composer\\Installers\\ElggInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ElggInstaller.php',
'Composer\\Installers\\EliasisInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/EliasisInstaller.php',
'Composer\\Installers\\ExpressionEngineInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php',
'Composer\\Installers\\EzPlatformInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/EzPlatformInstaller.php',
'Composer\\Installers\\ForkCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ForkCMSInstaller.php',
'Composer\\Installers\\FuelInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/FuelInstaller.php',
'Composer\\Installers\\FuelphpInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/FuelphpInstaller.php',
'Composer\\Installers\\GravInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/GravInstaller.php',
'Composer\\Installers\\HuradInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/HuradInstaller.php',
'Composer\\Installers\\ImageCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ImageCMSInstaller.php',
'Composer\\Installers\\Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Installer.php',
'Composer\\Installers\\ItopInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ItopInstaller.php',
'Composer\\Installers\\KanboardInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KanboardInstaller.php',
'Composer\\Installers\\KnownInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KnownInstaller.php',
'Composer\\Installers\\KodiCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KodiCMSInstaller.php',
'Composer\\Installers\\KohanaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KohanaInstaller.php',
'Composer\\Installers\\LanManagementSystemInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php',
'Composer\\Installers\\LaravelInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LaravelInstaller.php',
'Composer\\Installers\\LavaLiteInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LavaLiteInstaller.php',
'Composer\\Installers\\LithiumInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LithiumInstaller.php',
'Composer\\Installers\\MODULEWorkInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php',
'Composer\\Installers\\MODXEvoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MODXEvoInstaller.php',
'Composer\\Installers\\MagentoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MagentoInstaller.php',
'Composer\\Installers\\MajimaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MajimaInstaller.php',
'Composer\\Installers\\MakoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MakoInstaller.php',
'Composer\\Installers\\MantisBTInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MantisBTInstaller.php',
'Composer\\Installers\\MatomoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MatomoInstaller.php',
'Composer\\Installers\\MauticInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
'Composer\\Installers\\MayaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
'Composer\\Installers\\MediaWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
'Composer\\Installers\\MiaoxingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MiaoxingInstaller.php',
'Composer\\Installers\\MicroweberInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
'Composer\\Installers\\ModxInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
'Composer\\Installers\\MoodleInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
'Composer\\Installers\\OctoberInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OctoberInstaller.php',
'Composer\\Installers\\OntoWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OntoWikiInstaller.php',
'Composer\\Installers\\OsclassInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OsclassInstaller.php',
'Composer\\Installers\\OxidInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OxidInstaller.php',
'Composer\\Installers\\PPIInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PPIInstaller.php',
'Composer\\Installers\\PantheonInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PantheonInstaller.php',
'Composer\\Installers\\PhiftyInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PhiftyInstaller.php',
'Composer\\Installers\\PhpBBInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PhpBBInstaller.php',
'Composer\\Installers\\PiwikInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PiwikInstaller.php',
'Composer\\Installers\\PlentymarketsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php',
'Composer\\Installers\\Plugin' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Plugin.php',
'Composer\\Installers\\PortoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
'Composer\\Installers\\PrestashopInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
'Composer\\Installers\\ProcessWireInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ProcessWireInstaller.php',
'Composer\\Installers\\PuppetInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
'Composer\\Installers\\PxcmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
'Composer\\Installers\\RadPHPInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
'Composer\\Installers\\ReIndexInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ReIndexInstaller.php',
'Composer\\Installers\\Redaxo5Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Redaxo5Installer.php',
'Composer\\Installers\\RedaxoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RedaxoInstaller.php',
'Composer\\Installers\\RoundcubeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RoundcubeInstaller.php',
'Composer\\Installers\\SMFInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SMFInstaller.php',
'Composer\\Installers\\ShopwareInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
'Composer\\Installers\\SilverStripeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
'Composer\\Installers\\SiteDirectInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
'Composer\\Installers\\StarbugInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/StarbugInstaller.php',
'Composer\\Installers\\SyDESInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
'Composer\\Installers\\SyliusInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
'Composer\\Installers\\TaoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
'Composer\\Installers\\TastyIgniterInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php',
'Composer\\Installers\\TheliaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
'Composer\\Installers\\TuskInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
'Composer\\Installers\\UserFrostingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
'Composer\\Installers\\VanillaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
'Composer\\Installers\\VgmcpInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
'Composer\\Installers\\WHMCSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
'Composer\\Installers\\WinterInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WinterInstaller.php',
'Composer\\Installers\\WolfCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
'Composer\\Installers\\WordPressInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
'Composer\\Installers\\YawikInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
'Composer\\Installers\\ZendInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ZendInstaller.php',
'Composer\\Installers\\ZikulaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
'Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php',
'Firebase\\JWT\\CachedKeySet' => __DIR__ . '/..' . '/firebase/php-jwt/src/CachedKeySet.php',
'Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php',
'Firebase\\JWT\\JWK' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWK.php',
'Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php',
'Firebase\\JWT\\JWTExceptionWithPayloadInterface' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php',
'Firebase\\JWT\\Key' => __DIR__ . '/..' . '/firebase/php-jwt/src/Key.php',
'Firebase\\JWT\\SignatureInvalidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/SignatureInvalidException.php',
'OpenID_Connect_Generic' => __DIR__ . '/../..' . '/openid-connect-generic.php',
'OpenID_Connect_Generic_Client' => __DIR__ . '/../..' . '/includes/openid-connect-generic-client.php',
'OpenID_Connect_Generic_Client_Wrapper' => __DIR__ . '/../..' . '/includes/openid-connect-generic-client-wrapper.php',
'OpenID_Connect_Generic_JWT_Validator' => __DIR__ . '/../..' . '/includes/openid-connect-generic-jwt-validator.php',
'OpenID_Connect_Generic_Login_Form' => __DIR__ . '/../..' . '/includes/openid-connect-generic-login-form.php',
'OpenID_Connect_Generic_Option_Logger' => __DIR__ . '/../..' . '/includes/openid-connect-generic-option-logger.php',
'OpenID_Connect_Generic_Option_Settings' => __DIR__ . '/../..' . '/includes/openid-connect-generic-option-settings.php',
'OpenID_Connect_Generic_Settings_Page' => __DIR__ . '/../..' . '/includes/openid-connect-generic-settings-page.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitdf50dab69727c9acc15d5a5590cd963a::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitdf50dab69727c9acc15d5a5590cd963a::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitdf50dab69727c9acc15d5a5590cd963a::$classMap;
}, null, ClassLoader::class);
}
}

View File

@ -0,0 +1,221 @@
{
"packages": [
{
"name": "composer/installers",
"version": "v2.3.0",
"version_normalized": "2.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e",
"reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"composer/composer": "^1.10.27 || ^2.7",
"composer/semver": "^1.7.2 || ^3.4.0",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-phpunit": "^1",
"symfony/phpunit-bridge": "^7.1.1",
"symfony/process": "^5 || ^6 || ^7"
},
"time": "2024-06-24T20:46:46+00:00",
"type": "composer-plugin",
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-main": "2.x-dev"
},
"plugin-modifies-install-path": true
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Composer\\Installers\\": "src/Composer/Installers"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"description": "A multi-framework Composer library installer",
"homepage": "https://composer.github.io/installers/",
"keywords": [
"Dolibarr",
"Eliasis",
"Hurad",
"ImageCMS",
"Kanboard",
"Lan Management System",
"MODX Evo",
"MantisBT",
"Mautic",
"Maya",
"OXID",
"Plentymarkets",
"Porto",
"RadPHP",
"SMF",
"Starbug",
"Thelia",
"Whmcs",
"WolfCMS",
"agl",
"annotatecms",
"attogram",
"bitrix",
"cakephp",
"chef",
"cockpit",
"codeigniter",
"concrete5",
"concreteCMS",
"croogo",
"dokuwiki",
"drupal",
"eZ Platform",
"elgg",
"expressionengine",
"fuelphp",
"grav",
"installer",
"itop",
"known",
"kohana",
"laravel",
"lavalite",
"lithium",
"magento",
"majima",
"mako",
"matomo",
"mediawiki",
"miaoxing",
"modulework",
"modx",
"moodle",
"osclass",
"pantheon",
"phpbb",
"piwik",
"ppi",
"processwire",
"puppet",
"pxcms",
"reindex",
"roundcube",
"shopware",
"silverstripe",
"sydes",
"sylius",
"tastyigniter",
"wordpress",
"yawik",
"zend",
"zikula"
],
"support": {
"issues": "https://github.com/composer/installers/issues",
"source": "https://github.com/composer/installers/tree/v2.3.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"install-path": "./installers"
},
{
"name": "firebase/php-jwt",
"version": "v6.11.1",
"version_normalized": "6.11.1.0",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"time": "2025-04-09T20:32:01+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
},
"install-path": "../firebase/php-jwt"
}
],
"dev": false,
"dev-package-names": []
}

View File

@ -0,0 +1,41 @@
<?php return array(
'root' => array(
'name' => 'daggerhart/openid-connect-generic',
'pretty_version' => '3.11.3',
'version' => '3.11.3.0',
'reference' => '0ef442c0f61bda837bddacec37b96035176bee27',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => false,
),
'versions' => array(
'composer/installers' => array(
'pretty_version' => 'v2.3.0',
'version' => '2.3.0.0',
'reference' => '12fb2dfe5e16183de69e784a7b84046c43d97e8e',
'type' => 'composer-plugin',
'install_path' => __DIR__ . '/./installers',
'aliases' => array(),
'dev_requirement' => false,
),
'daggerhart/openid-connect-generic' => array(
'pretty_version' => '3.11.3',
'version' => '3.11.3.0',
'reference' => '0ef442c0f61bda837bddacec37b96035176bee27',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'firebase/php-jwt' => array(
'pretty_version' => 'v6.11.1',
'version' => '6.11.1.0',
'reference' => 'd1e91ecf8c598d073d0995afa8cd5c75c6e19e66',
'type' => 'library',
'install_path' => __DIR__ . '/../firebase/php-jwt',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@ -0,0 +1,19 @@
Copyright (c) 2012 Kyle Robinson Young
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,117 @@
{
"name": "composer/installers",
"type": "composer-plugin",
"license": "MIT",
"description": "A multi-framework Composer library installer",
"keywords": [
"installer",
"AGL",
"AnnotateCms",
"Attogram",
"Bitrix",
"CakePHP",
"Chef",
"Cockpit",
"CodeIgniter",
"concrete5",
"ConcreteCMS",
"Croogo",
"DokuWiki",
"Dolibarr",
"Drupal",
"Elgg",
"Eliasis",
"ExpressionEngine",
"eZ Platform",
"FuelPHP",
"Grav",
"Hurad",
"ImageCMS",
"iTop",
"Kanboard",
"Known",
"Kohana",
"Lan Management System",
"Laravel",
"Lavalite",
"Lithium",
"Magento",
"majima",
"Mako",
"MantisBT",
"Matomo",
"Mautic",
"Maya",
"MODX",
"MODX Evo",
"MediaWiki",
"Miaoxing",
"OXID",
"osclass",
"MODULEWork",
"Moodle",
"Pantheon",
"Piwik",
"pxcms",
"phpBB",
"Plentymarkets",
"PPI",
"Puppet",
"Porto",
"ProcessWire",
"RadPHP",
"ReIndex",
"Roundcube",
"shopware",
"SilverStripe",
"SMF",
"Starbug",
"SyDES",
"Sylius",
"TastyIgniter",
"Thelia",
"WHMCS",
"WolfCMS",
"WordPress",
"YAWIK",
"Zend",
"Zikula"
],
"homepage": "https://composer.github.io/installers/",
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"autoload": {
"psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
},
"autoload-dev": {
"psr-4": { "Composer\\Installers\\Test\\": "tests/Composer/Installers/Test" }
},
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-main": "2.x-dev"
},
"plugin-modifies-install-path": true
},
"require": {
"php": "^7.2 || ^8.0",
"composer-plugin-api": "^1.0 || ^2.0"
},
"require-dev": {
"composer/composer": "^1.10.27 || ^2.7",
"composer/semver": "^1.7.2 || ^3.4.0",
"symfony/phpunit-bridge": "^7.1.1",
"phpstan/phpstan": "^1.11",
"symfony/process": "^5 || ^6 || ^7",
"phpstan/phpstan-phpunit": "^1"
},
"scripts": {
"test": "@php vendor/bin/simple-phpunit",
"phpstan": "@php vendor/bin/phpstan analyse"
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Composer\Installers;
class AglInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'More/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$name = preg_replace_callback('/(?:^|_|-)(.?)/', function ($matches) {
return strtoupper($matches[1]);
}, $vars['name']);
if (null === $name) {
throw new \RuntimeException('Failed to run preg_replace_callback: '.preg_last_error());
}
$vars['name'] = $name;
return $vars;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Composer\Installers;
class AkauntingInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class AnnotateCmsInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'addons/modules/{$name}/',
'component' => 'addons/components/{$name}/',
'service' => 'addons/services/{$name}/',
);
}

View File

@ -0,0 +1,58 @@
<?php
namespace Composer\Installers;
class AsgardInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'Modules/{$name}/',
'theme' => 'Themes/{$name}/'
);
/**
* Format package name.
*
* For package type asgard-module, cut off a trailing '-plugin' if present.
*
* For package type asgard-theme, cut off a trailing '-theme' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] === 'asgard-module') {
return $this->inflectPluginVars($vars);
}
if ($vars['type'] === 'asgard-theme') {
return $this->inflectThemeVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectPluginVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-module$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectThemeVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-theme$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class AttogramInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
);
}

View File

@ -0,0 +1,137 @@
<?php
namespace Composer\Installers;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Package\PackageInterface;
abstract class BaseInstaller
{
/** @var array<string, string> */
protected $locations = array();
/** @var Composer */
protected $composer;
/** @var PackageInterface */
protected $package;
/** @var IOInterface */
protected $io;
/**
* Initializes base installer.
*/
public function __construct(PackageInterface $package, Composer $composer, IOInterface $io)
{
$this->composer = $composer;
$this->package = $package;
$this->io = $io;
}
/**
* Return the install path based on package type.
*/
public function getInstallPath(PackageInterface $package, string $frameworkType = ''): string
{
$type = $this->package->getType();
$prettyName = $this->package->getPrettyName();
if (strpos($prettyName, '/') !== false) {
list($vendor, $name) = explode('/', $prettyName);
} else {
$vendor = '';
$name = $prettyName;
}
$availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type'));
$extra = $package->getExtra();
if (!empty($extra['installer-name'])) {
$availableVars['name'] = $extra['installer-name'];
}
$extra = $this->composer->getPackage()->getExtra();
if (!empty($extra['installer-paths'])) {
$customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor);
if ($customPath !== false) {
return $this->templatePath($customPath, $availableVars);
}
}
$packageType = substr($type, strlen($frameworkType) + 1);
$locations = $this->getLocations($frameworkType);
if (!isset($locations[$packageType])) {
throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type));
}
return $this->templatePath($locations[$packageType], $availableVars);
}
/**
* For an installer to override to modify the vars per installer.
*
* @param array<string, string> $vars This will normally receive array{name: string, vendor: string, type: string}
* @return array<string, string>
*/
public function inflectPackageVars(array $vars): array
{
return $vars;
}
/**
* Gets the installer's locations
*
* @return array<string, string> map of package types => install path
*/
public function getLocations(string $frameworkType)
{
return $this->locations;
}
/**
* Replace vars in a path
*
* @param array<string, string> $vars
*/
protected function templatePath(string $path, array $vars = array()): string
{
if (strpos($path, '{') !== false) {
extract($vars);
preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches);
if (!empty($matches[1])) {
foreach ($matches[1] as $var) {
$path = str_replace('{$' . $var . '}', $$var, $path);
}
}
}
return $path;
}
/**
* Search through a passed paths array for a custom install path.
*
* @param array<string, string[]|string> $paths
* @return string|false
*/
protected function mapCustomInstallPaths(array $paths, string $name, string $type, ?string $vendor = null)
{
foreach ($paths as $path => $names) {
$names = (array) $names;
if (in_array($name, $names) || in_array('type:' . $type, $names) || in_array('vendor:' . $vendor, $names)) {
return $path;
}
}
return false;
}
protected function pregReplace(string $pattern, string $replacement, string $subject): string
{
$result = preg_replace($pattern, $replacement, $subject);
if (null === $result) {
throw new \RuntimeException('Failed to run preg_replace with '.$pattern.': '.preg_last_error());
}
return $result;
}
}

View File

@ -0,0 +1,123 @@
<?php
namespace Composer\Installers;
use Composer\Util\Filesystem;
/**
* Installer for Bitrix Framework. Supported types of extensions:
* - `bitrix-d7-module` — copy the module to directory `bitrix/modules/<vendor>.<name>`.
* - `bitrix-d7-component` — copy the component to directory `bitrix/components/<vendor>/<name>`.
* - `bitrix-d7-template` — copy the template to directory `bitrix/templates/<vendor>_<name>`.
*
* You can set custom path to directory with Bitrix kernel in `composer.json`:
*
* ```json
* {
* "extra": {
* "bitrix-dir": "s1/bitrix"
* }
* }
* ```
*
* @author Nik Samokhvalov <nik@samokhvalov.info>
* @author Denis Kulichkin <onexhovia@gmail.com>
*/
class BitrixInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => '{$bitrix_dir}/modules/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
'component' => '{$bitrix_dir}/components/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
'theme' => '{$bitrix_dir}/templates/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
'd7-module' => '{$bitrix_dir}/modules/{$vendor}.{$name}/',
'd7-component' => '{$bitrix_dir}/components/{$vendor}/{$name}/',
'd7-template' => '{$bitrix_dir}/templates/{$vendor}_{$name}/',
);
/**
* @var string[] Storage for informations about duplicates at all the time of installation packages.
*/
private static $checkedDuplicates = array();
public function inflectPackageVars(array $vars): array
{
/** @phpstan-ignore-next-line */
if ($this->composer->getPackage()) {
$extra = $this->composer->getPackage()->getExtra();
if (isset($extra['bitrix-dir'])) {
$vars['bitrix_dir'] = $extra['bitrix-dir'];
}
}
if (!isset($vars['bitrix_dir'])) {
$vars['bitrix_dir'] = 'bitrix';
}
return parent::inflectPackageVars($vars);
}
/**
* {@inheritdoc}
*/
protected function templatePath(string $path, array $vars = array()): string
{
$templatePath = parent::templatePath($path, $vars);
$this->checkDuplicates($templatePath, $vars);
return $templatePath;
}
/**
* Duplicates search packages.
*
* @param array<string, string> $vars
*/
protected function checkDuplicates(string $path, array $vars = array()): void
{
$packageType = substr($vars['type'], strlen('bitrix') + 1);
$localDir = explode('/', $vars['bitrix_dir']);
array_pop($localDir);
$localDir[] = 'local';
$localDir = implode('/', $localDir);
$oldPath = str_replace(
array('{$bitrix_dir}', '{$name}'),
array($localDir, $vars['name']),
$this->locations[$packageType]
);
if (in_array($oldPath, static::$checkedDuplicates)) {
return;
}
if ($oldPath !== $path && file_exists($oldPath) && $this->io->isInteractive()) {
$this->io->writeError(' <error>Duplication of packages:</error>');
$this->io->writeError(' <info>Package ' . $oldPath . ' will be called instead package ' . $path . '</info>');
while (true) {
switch ($this->io->ask(' <info>Delete ' . $oldPath . ' [y,n,?]?</info> ', '?')) {
case 'y':
$fs = new Filesystem();
$fs->removeDirectory($oldPath);
break 2;
case 'n':
break 2;
case '?':
default:
$this->io->writeError(array(
' y - delete package ' . $oldPath . ' and to continue with the installation',
' n - don\'t delete and to continue with the installation',
));
$this->io->writeError(' ? - print help');
break;
}
}
}
static::$checkedDuplicates[] = $oldPath;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class BonefishInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'package' => 'Packages/{$vendor}/{$name}/'
);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class BotbleInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'platform/plugins/{$name}/',
'theme' => 'platform/themes/{$name}/',
);
}

View File

@ -0,0 +1,67 @@
<?php
namespace Composer\Installers;
use Composer\DependencyResolver\Pool;
use Composer\Semver\Constraint\Constraint;
class CakePHPInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'Plugin/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
if ($this->matchesCakeVersion('>=', '3.0.0')) {
return $vars;
}
$nameParts = explode('/', $vars['name']);
foreach ($nameParts as &$value) {
$value = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $value));
$value = str_replace(array('-', '_'), ' ', $value);
$value = str_replace(' ', '', ucwords($value));
}
$vars['name'] = implode('/', $nameParts);
return $vars;
}
/**
* Change the default plugin location when cakephp >= 3.0
*/
public function getLocations(string $frameworkType): array
{
if ($this->matchesCakeVersion('>=', '3.0.0')) {
$this->locations['plugin'] = $this->composer->getConfig()->get('vendor-dir') . '/{$vendor}/{$name}/';
}
return $this->locations;
}
/**
* Check if CakePHP version matches against a version
*
* @phpstan-param '='|'=='|'<'|'<='|'>'|'>='|'<>'|'!=' $matcher
*/
protected function matchesCakeVersion(string $matcher, string $version): bool
{
$repositoryManager = $this->composer->getRepositoryManager();
/** @phpstan-ignore-next-line */
if (!$repositoryManager) {
return false;
}
$repos = $repositoryManager->getLocalRepository();
/** @phpstan-ignore-next-line */
if (!$repos) {
return false;
}
return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class ChefInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'cookbook' => 'Chef/{$vendor}/{$name}/',
'role' => 'Chef/roles/{$name}/',
);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class CiviCrmInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'ext' => 'ext/{$name}/'
);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class ClanCatsFrameworkInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'ship' => 'CCF/orbit/{$name}/',
'theme' => 'CCF/app/themes/{$name}/',
);
}

View File

@ -0,0 +1,36 @@
<?php
namespace Composer\Installers;
class CockpitInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'cockpit/modules/addons/{$name}/',
);
/**
* Format module name.
*
* Strip `module-` prefix from package name.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] == 'cockpit-module') {
return $this->inflectModuleVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
public function inflectModuleVars(array $vars): array
{
$vars['name'] = ucfirst($this->pregReplace('/cockpit-/i', '', $vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class CodeIgniterInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'library' => 'application/libraries/{$name}/',
'third-party' => 'application/third_party/{$name}/',
'module' => 'application/modules/{$name}/',
);
}

View File

@ -0,0 +1,15 @@
<?php
namespace Composer\Installers;
class Concrete5Installer extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'core' => 'concrete/',
'block' => 'application/blocks/{$name}/',
'package' => 'packages/{$name}/',
'theme' => 'application/themes/{$name}/',
'update' => 'updates/{$name}/',
);
}

View File

@ -0,0 +1,15 @@
<?php
namespace Composer\Installers;
class ConcreteCMSInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'core' => 'concrete/',
'block' => 'application/blocks/{$name}/',
'package' => 'packages/{$name}/',
'theme' => 'application/themes/{$name}/',
'update' => 'updates/{$name}/',
);
}

View File

@ -0,0 +1,23 @@
<?php
namespace Composer\Installers;
class CroogoInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'Plugin/{$name}/',
'theme' => 'View/Themed/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$vars['name'] = strtolower(str_replace(array('-', '_'), ' ', $vars['name']));
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class DecibelInstaller extends BaseInstaller
{
/** @var array */
/** @var array<string, string> */
protected $locations = array(
'app' => 'app/{$name}/',
);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class DframeInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$vendor}/{$name}/',
);
}

View File

@ -0,0 +1,57 @@
<?php
namespace Composer\Installers;
class DokuWikiInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'lib/plugins/{$name}/',
'template' => 'lib/tpl/{$name}/',
);
/**
* Format package name.
*
* For package type dokuwiki-plugin, cut off a trailing '-plugin',
* or leading dokuwiki_ if present.
*
* For package type dokuwiki-template, cut off a trailing '-template' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] === 'dokuwiki-plugin') {
return $this->inflectPluginVars($vars);
}
if ($vars['type'] === 'dokuwiki-template') {
return $this->inflectTemplateVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectPluginVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-plugin$/', '', $vars['name']);
$vars['name'] = $this->pregReplace('/^dokuwiki_?-?/', '', $vars['name']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectTemplateVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-template$/', '', $vars['name']);
$vars['name'] = $this->pregReplace('/^dokuwiki_?-?/', '', $vars['name']);
return $vars;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Composer\Installers;
/**
* Class DolibarrInstaller
*
* @package Composer\Installers
* @author Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
*/
class DolibarrInstaller extends BaseInstaller
{
//TODO: Add support for scripts and themes
/** @var array<string, string> */
protected $locations = array(
'module' => 'htdocs/custom/{$name}/',
);
}

View File

@ -0,0 +1,25 @@
<?php
namespace Composer\Installers;
class DrupalInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'core' => 'core/',
'module' => 'modules/{$name}/',
'theme' => 'themes/{$name}/',
'library' => 'libraries/{$name}/',
'profile' => 'profiles/{$name}/',
'database-driver' => 'drivers/lib/Drupal/Driver/Database/{$name}/',
'drush' => 'drush/{$name}/',
'custom-theme' => 'themes/custom/{$name}/',
'custom-module' => 'modules/custom/{$name}/',
'custom-profile' => 'profiles/custom/{$name}/',
'drupal-multisite' => 'sites/{$name}/',
'console' => 'console/{$name}/',
'console-language' => 'console/language/{$name}/',
'config' => 'config/sync/',
'recipe' => 'recipes/{$name}',
);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class ElggInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'mod/{$name}/',
);
}

View File

@ -0,0 +1,14 @@
<?php
namespace Composer\Installers;
class EliasisInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'component' => 'components/{$name}/',
'module' => 'modules/{$name}/',
'plugin' => 'plugins/{$name}/',
'template' => 'templates/{$name}/',
);
}

View File

@ -0,0 +1,31 @@
<?php
namespace Composer\Installers;
use Composer\Package\PackageInterface;
class ExpressionEngineInstaller extends BaseInstaller
{
/** @var array<string, string> */
private $ee2Locations = array(
'addon' => 'system/expressionengine/third_party/{$name}/',
'theme' => 'themes/third_party/{$name}/',
);
/** @var array<string, string> */
private $ee3Locations = array(
'addon' => 'system/user/addons/{$name}/',
'theme' => 'themes/user/{$name}/',
);
public function getLocations(string $frameworkType): array
{
if ($frameworkType === 'ee2') {
$this->locations = $this->ee2Locations;
} else {
$this->locations = $this->ee3Locations;
}
return $this->locations;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class EzPlatformInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'meta-assets' => 'web/assets/ezplatform/',
'assets' => 'web/assets/ezplatform/{$name}/',
);
}

View File

@ -0,0 +1,58 @@
<?php
namespace Composer\Installers;
class ForkCMSInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = [
'module' => 'src/Modules/{$name}/',
'theme' => 'src/Themes/{$name}/'
];
/**
* Format package name.
*
* For package type fork-cms-module, cut off a trailing '-plugin' if present.
*
* For package type fork-cms-theme, cut off a trailing '-theme' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] === 'fork-cms-module') {
return $this->inflectModuleVars($vars);
}
if ($vars['type'] === 'fork-cms-theme') {
return $this->inflectThemeVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectModuleVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/^fork-cms-|-module|ForkCMS|ForkCms|Forkcms|forkcms|Module$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); // replace hyphens with spaces
$vars['name'] = str_replace(' ', '', ucwords($vars['name'])); // make module name camelcased
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectThemeVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/^fork-cms-|-theme|ForkCMS|ForkCms|Forkcms|forkcms|Theme$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); // replace hyphens with spaces
$vars['name'] = str_replace(' ', '', ucwords($vars['name'])); // make theme name camelcased
return $vars;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class FuelInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'fuel/app/modules/{$name}/',
'package' => 'fuel/packages/{$name}/',
'theme' => 'fuel/app/themes/{$name}/',
);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class FuelphpInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'component' => 'components/{$name}/',
);
}

View File

@ -0,0 +1,29 @@
<?php
namespace Composer\Installers;
class GravInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'user/plugins/{$name}/',
'theme' => 'user/themes/{$name}/',
);
/**
* Format package name
*/
public function inflectPackageVars(array $vars): array
{
$restrictedWords = implode('|', array_keys($this->locations));
$vars['name'] = strtolower($vars['name']);
$vars['name'] = $this->pregReplace(
'/^(?:grav-)?(?:(?:'.$restrictedWords.')-)?(.*?)(?:-(?:'.$restrictedWords.'))?$/ui',
'$1',
$vars['name']
);
return $vars;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Composer\Installers;
class HuradInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
'theme' => 'plugins/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$nameParts = explode('/', $vars['name']);
foreach ($nameParts as &$value) {
$value = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $value));
$value = str_replace(array('-', '_'), ' ', $value);
$value = str_replace(' ', '', ucwords($value));
}
$vars['name'] = implode('/', $nameParts);
return $vars;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class ImageCMSInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'template' => 'templates/{$name}/',
'module' => 'application/modules/{$name}/',
'library' => 'application/libraries/{$name}/',
);
}

View File

@ -0,0 +1,288 @@
<?php
namespace Composer\Installers;
use Composer\Composer;
use Composer\Installer\BinaryInstaller;
use Composer\Installer\LibraryInstaller;
use Composer\IO\IOInterface;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
class Installer extends LibraryInstaller
{
/**
* Package types to installer class map
*
* @var array<string, string>
*/
private $supportedTypes = array(
'akaunting' => 'AkauntingInstaller',
'asgard' => 'AsgardInstaller',
'attogram' => 'AttogramInstaller',
'agl' => 'AglInstaller',
'annotatecms' => 'AnnotateCmsInstaller',
'bitrix' => 'BitrixInstaller',
'botble' => 'BotbleInstaller',
'bonefish' => 'BonefishInstaller',
'cakephp' => 'CakePHPInstaller',
'chef' => 'ChefInstaller',
'civicrm' => 'CiviCrmInstaller',
'ccframework' => 'ClanCatsFrameworkInstaller',
'cockpit' => 'CockpitInstaller',
'codeigniter' => 'CodeIgniterInstaller',
'concrete5' => 'Concrete5Installer',
'concretecms' => 'ConcreteCMSInstaller',
'croogo' => 'CroogoInstaller',
'dframe' => 'DframeInstaller',
'dokuwiki' => 'DokuWikiInstaller',
'dolibarr' => 'DolibarrInstaller',
'decibel' => 'DecibelInstaller',
'drupal' => 'DrupalInstaller',
'elgg' => 'ElggInstaller',
'eliasis' => 'EliasisInstaller',
'ee3' => 'ExpressionEngineInstaller',
'ee2' => 'ExpressionEngineInstaller',
'ezplatform' => 'EzPlatformInstaller',
'fork' => 'ForkCMSInstaller',
'fuel' => 'FuelInstaller',
'fuelphp' => 'FuelphpInstaller',
'grav' => 'GravInstaller',
'hurad' => 'HuradInstaller',
'tastyigniter' => 'TastyIgniterInstaller',
'imagecms' => 'ImageCMSInstaller',
'itop' => 'ItopInstaller',
'kanboard' => 'KanboardInstaller',
'known' => 'KnownInstaller',
'kodicms' => 'KodiCMSInstaller',
'kohana' => 'KohanaInstaller',
'lms' => 'LanManagementSystemInstaller',
'laravel' => 'LaravelInstaller',
'lavalite' => 'LavaLiteInstaller',
'lithium' => 'LithiumInstaller',
'magento' => 'MagentoInstaller',
'majima' => 'MajimaInstaller',
'mantisbt' => 'MantisBTInstaller',
'mako' => 'MakoInstaller',
'matomo' => 'MatomoInstaller',
'maya' => 'MayaInstaller',
'mautic' => 'MauticInstaller',
'mediawiki' => 'MediaWikiInstaller',
'miaoxing' => 'MiaoxingInstaller',
'microweber' => 'MicroweberInstaller',
'modulework' => 'MODULEWorkInstaller',
'modx' => 'ModxInstaller',
'modxevo' => 'MODXEvoInstaller',
'moodle' => 'MoodleInstaller',
'october' => 'OctoberInstaller',
'ontowiki' => 'OntoWikiInstaller',
'oxid' => 'OxidInstaller',
'osclass' => 'OsclassInstaller',
'pxcms' => 'PxcmsInstaller',
'phpbb' => 'PhpBBInstaller',
'piwik' => 'PiwikInstaller',
'plentymarkets'=> 'PlentymarketsInstaller',
'ppi' => 'PPIInstaller',
'puppet' => 'PuppetInstaller',
'radphp' => 'RadPHPInstaller',
'phifty' => 'PhiftyInstaller',
'porto' => 'PortoInstaller',
'processwire' => 'ProcessWireInstaller',
'quicksilver' => 'PantheonInstaller',
'redaxo' => 'RedaxoInstaller',
'redaxo5' => 'Redaxo5Installer',
'reindex' => 'ReIndexInstaller',
'roundcube' => 'RoundcubeInstaller',
'shopware' => 'ShopwareInstaller',
'sitedirect' => 'SiteDirectInstaller',
'silverstripe' => 'SilverStripeInstaller',
'smf' => 'SMFInstaller',
'starbug' => 'StarbugInstaller',
'sydes' => 'SyDESInstaller',
'sylius' => 'SyliusInstaller',
'tao' => 'TaoInstaller',
'thelia' => 'TheliaInstaller',
'tusk' => 'TuskInstaller',
'userfrosting' => 'UserFrostingInstaller',
'vanilla' => 'VanillaInstaller',
'whmcs' => 'WHMCSInstaller',
'winter' => 'WinterInstaller',
'wolfcms' => 'WolfCMSInstaller',
'wordpress' => 'WordPressInstaller',
'yawik' => 'YawikInstaller',
'zend' => 'ZendInstaller',
'zikula' => 'ZikulaInstaller',
'prestashop' => 'PrestashopInstaller'
);
/**
* Disables installers specified in main composer extra installer-disable
* list
*/
public function __construct(
IOInterface $io,
Composer $composer,
string $type = 'library',
?Filesystem $filesystem = null,
?BinaryInstaller $binaryInstaller = null
) {
parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller);
$this->removeDisabledInstallers();
}
/**
* {@inheritDoc}
*/
public function getInstallPath(PackageInterface $package)
{
$type = $package->getType();
$frameworkType = $this->findFrameworkType($type);
if ($frameworkType === false) {
throw new \InvalidArgumentException(
'Sorry the package type of this package is not yet supported.'
);
}
$class = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType];
/**
* @var BaseInstaller
*/
$installer = new $class($package, $this->composer, $this->getIO());
$path = $installer->getInstallPath($package, $frameworkType);
if (!$this->filesystem->isAbsolutePath($path)) {
$path = getcwd() . '/' . $path;
}
return $path;
}
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
{
$installPath = $this->getPackageBasePath($package);
$io = $this->io;
$outputStatus = function () use ($io, $installPath) {
$io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
};
$promise = parent::uninstall($repo, $package);
// Composer v2 might return a promise here
if ($promise instanceof PromiseInterface) {
return $promise->then($outputStatus);
}
// If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async)
$outputStatus();
return null;
}
/**
* {@inheritDoc}
*
* @param string $packageType
*/
public function supports($packageType)
{
$frameworkType = $this->findFrameworkType($packageType);
if ($frameworkType === false) {
return false;
}
$locationPattern = $this->getLocationPattern($frameworkType);
return preg_match('#' . $frameworkType . '-' . $locationPattern . '#', $packageType, $matches) === 1;
}
/**
* Finds a supported framework type if it exists and returns it
*
* @return string|false
*/
protected function findFrameworkType(string $type)
{
krsort($this->supportedTypes);
foreach ($this->supportedTypes as $key => $val) {
if ($key === substr($type, 0, strlen($key))) {
return substr($type, 0, strlen($key));
}
}
return false;
}
/**
* Get the second part of the regular expression to check for support of a
* package type
*/
protected function getLocationPattern(string $frameworkType): string
{
$pattern = null;
if (!empty($this->supportedTypes[$frameworkType])) {
$frameworkClass = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType];
/** @var BaseInstaller $framework */
$framework = new $frameworkClass(new Package('dummy/pkg', '1.0.0.0', '1.0.0'), $this->composer, $this->getIO());
$locations = array_keys($framework->getLocations($frameworkType));
if ($locations) {
$pattern = '(' . implode('|', $locations) . ')';
}
}
return $pattern ?: '(\w+)';
}
private function getIO(): IOInterface
{
return $this->io;
}
/**
* Look for installers set to be disabled in composer's extra config and
* remove them from the list of supported installers.
*
* Globals:
* - true, "all", and "*" - disable all installers.
* - false - enable all installers (useful with
* wikimedia/composer-merge-plugin or similar)
*/
protected function removeDisabledInstallers(): void
{
$extra = $this->composer->getPackage()->getExtra();
if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) {
// No installers are disabled
return;
}
// Get installers to disable
$disable = $extra['installer-disable'];
// Ensure $disabled is an array
if (!is_array($disable)) {
$disable = array($disable);
}
// Check which installers should be disabled
$all = array(true, "all", "*");
$intersect = array_intersect($all, $disable);
if (!empty($intersect)) {
// Disable all installers
$this->supportedTypes = array();
return;
}
// Disable specified installers
foreach ($disable as $key => $installer) {
if (is_string($installer) && key_exists($installer, $this->supportedTypes)) {
unset($this->supportedTypes[$installer]);
}
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class ItopInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'extension' => 'extensions/{$name}/',
);
}

View File

@ -0,0 +1,20 @@
<?php
namespace Composer\Installers;
/**
*
* Installer for kanboard plugins
*
* kanboard.net
*
* Class KanboardInstaller
* @package Composer\Installers
*/
class KanboardInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
);
}

View File

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class KnownInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'IdnoPlugins/{$name}/',
'theme' => 'Themes/{$name}/',
'console' => 'ConsolePlugins/{$name}/',
);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class KodiCMSInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'cms/plugins/{$name}/',
'media' => 'cms/media/vendor/{$name}/'
);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class KohanaInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
);
}

View File

@ -0,0 +1,27 @@
<?php
namespace Composer\Installers;
class LanManagementSystemInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
'template' => 'templates/{$name}/',
'document-template' => 'documents/templates/{$name}/',
'userpanel-module' => 'userpanel/modules/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class LaravelInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'library' => 'libraries/{$name}/',
);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class LavaLiteInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'package' => 'packages/{$vendor}/{$name}/',
'theme' => 'public/themes/{$name}/',
);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class LithiumInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'library' => 'libraries/{$name}/',
'source' => 'libraries/_source/{$name}/',
);
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class MODULEWorkInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
);
}

View File

@ -0,0 +1,18 @@
<?php
namespace Composer\Installers;
/**
* An installer to handle MODX Evolution specifics when installing packages.
*/
class MODXEvoInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'snippet' => 'assets/snippets/{$name}/',
'plugin' => 'assets/plugins/{$name}/',
'module' => 'assets/modules/{$name}/',
'template' => 'assets/templates/{$name}/',
'lib' => 'assets/lib/{$name}/'
);
}

View File

@ -0,0 +1,13 @@
<?php
namespace Composer\Installers;
class MagentoInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'theme' => 'app/design/frontend/{$name}/',
'skin' => 'skin/frontend/default/{$name}/',
'library' => 'lib/{$name}/',
);
}

View File

@ -0,0 +1,46 @@
<?php
namespace Composer\Installers;
/**
* Plugin/theme installer for majima
* @author David Neustadt
*/
class MajimaInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
);
/**
* Transforms the names
*
* @param array<string, string> $vars
* @return array<string, string>
*/
public function inflectPackageVars(array $vars): array
{
return $this->correctPluginName($vars);
}
/**
* Change hyphenated names to camelcase
*
* @param array<string, string> $vars
* @return array<string, string>
*/
private function correctPluginName(array $vars): array
{
$camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) {
return strtoupper($matches[0][1]);
}, $vars['name']);
if (null === $camelCasedName) {
throw new \RuntimeException('Failed to run preg_replace_callback: '.preg_last_error());
}
$vars['name'] = ucfirst($camelCasedName);
return $vars;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class MakoInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'package' => 'app/packages/{$name}/',
);
}

View File

@ -0,0 +1,25 @@
<?php
namespace Composer\Installers;
use Composer\DependencyResolver\Pool;
class MantisBTInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Composer\Installers;
/**
* Class MatomoInstaller
*
* @package Composer\Installers
*/
class MatomoInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
);
/**
* Format package name to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
$vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Composer\Installers;
use Composer\Package\PackageInterface;
class MauticInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
'theme' => 'themes/{$name}/',
'core' => 'app/',
);
private function getDirectoryName(): string
{
$extra = $this->package->getExtra();
if (!empty($extra['install-directory-name'])) {
return $extra['install-directory-name'];
}
return $this->toCamelCase($this->package->getPrettyName());
}
private function toCamelCase(string $packageName): string
{
return str_replace(' ', '', ucwords(str_replace('-', ' ', basename($packageName))));
}
/**
* Format package name of mautic-plugins to CamelCase
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] == 'mautic-plugin' || $vars['type'] == 'mautic-theme') {
$directoryName = $this->getDirectoryName();
$vars['name'] = $directoryName;
}
return $vars;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Composer\Installers;
class MayaInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
);
/**
* Format package name.
*
* For package type maya-module, cut off a trailing '-module' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] === 'maya-module') {
return $this->inflectModuleVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectModuleVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-module$/', '', $vars['name']);
$vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Composer\Installers;
class MediaWikiInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'core' => 'core/',
'extension' => 'extensions/{$name}/',
'skin' => 'skins/{$name}/',
);
/**
* Format package name.
*
* For package type mediawiki-extension, cut off a trailing '-extension' if present and transform
* to CamelCase keeping existing uppercase chars.
*
* For package type mediawiki-skin, cut off a trailing '-skin' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] === 'mediawiki-extension') {
return $this->inflectExtensionVars($vars);
}
if ($vars['type'] === 'mediawiki-skin') {
return $this->inflectSkinVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectExtensionVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-extension$/', '', $vars['name']);
$vars['name'] = str_replace('-', ' ', $vars['name']);
$vars['name'] = str_replace(' ', '', ucwords($vars['name']));
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectSkinVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/-skin$/', '', $vars['name']);
return $vars;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class MiaoxingInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'plugins/{$name}/',
);
}

View File

@ -0,0 +1,145 @@
<?php
namespace Composer\Installers;
class MicroweberInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'userfiles/modules/{$install_item_dir}/',
'module-skin' => 'userfiles/modules/{$install_item_dir}/templates/',
'template' => 'userfiles/templates/{$install_item_dir}/',
'element' => 'userfiles/elements/{$install_item_dir}/',
'vendor' => 'vendor/{$install_item_dir}/',
'components' => 'components/{$install_item_dir}/'
);
/**
* Format package name.
*
* For package type microweber-module, cut off a trailing '-module' if present
*
* For package type microweber-template, cut off a trailing '-template' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($this->package->getTargetDir() !== null && $this->package->getTargetDir() !== '') {
$vars['install_item_dir'] = $this->package->getTargetDir();
} else {
$vars['install_item_dir'] = $vars['name'];
if ($vars['type'] === 'microweber-template') {
return $this->inflectTemplateVars($vars);
}
if ($vars['type'] === 'microweber-templates') {
return $this->inflectTemplatesVars($vars);
}
if ($vars['type'] === 'microweber-core') {
return $this->inflectCoreVars($vars);
}
if ($vars['type'] === 'microweber-adapter') {
return $this->inflectCoreVars($vars);
}
if ($vars['type'] === 'microweber-module') {
return $this->inflectModuleVars($vars);
}
if ($vars['type'] === 'microweber-modules') {
return $this->inflectModulesVars($vars);
}
if ($vars['type'] === 'microweber-skin') {
return $this->inflectSkinVars($vars);
}
if ($vars['type'] === 'microweber-element' or $vars['type'] === 'microweber-elements') {
return $this->inflectElementVars($vars);
}
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectTemplateVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-template$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/template-$/', '', $vars['install_item_dir']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectTemplatesVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-templates$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/templates-$/', '', $vars['install_item_dir']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectCoreVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-providers$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/-provider$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/-adapter$/', '', $vars['install_item_dir']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectModuleVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-module$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/module-$/', '', $vars['install_item_dir']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectModulesVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-modules$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/modules-$/', '', $vars['install_item_dir']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectSkinVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-skin$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/skin-$/', '', $vars['install_item_dir']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectElementVars(array $vars): array
{
$vars['install_item_dir'] = $this->pregReplace('/-elements$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/elements-$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/-element$/', '', $vars['install_item_dir']);
$vars['install_item_dir'] = $this->pregReplace('/element-$/', '', $vars['install_item_dir']);
return $vars;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Composer\Installers;
/**
* An installer to handle MODX specifics when installing packages.
*/
class ModxInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'extra' => 'core/packages/{$name}/'
);
}

View File

@ -0,0 +1,73 @@
<?php
namespace Composer\Installers;
class MoodleInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'mod' => 'mod/{$name}/',
'admin_report' => 'admin/report/{$name}/',
'atto' => 'lib/editor/atto/plugins/{$name}/',
'tool' => 'admin/tool/{$name}/',
'assignment' => 'mod/assignment/type/{$name}/',
'assignsubmission' => 'mod/assign/submission/{$name}/',
'assignfeedback' => 'mod/assign/feedback/{$name}/',
'antivirus' => 'lib/antivirus/{$name}/',
'auth' => 'auth/{$name}/',
'availability' => 'availability/condition/{$name}/',
'block' => 'blocks/{$name}/',
'booktool' => 'mod/book/tool/{$name}/',
'cachestore' => 'cache/stores/{$name}/',
'cachelock' => 'cache/locks/{$name}/',
'calendartype' => 'calendar/type/{$name}/',
'communication' => 'communication/provider/{$name}/',
'customfield' => 'customfield/field/{$name}/',
'fileconverter' => 'files/converter/{$name}/',
'format' => 'course/format/{$name}/',
'coursereport' => 'course/report/{$name}/',
'contenttype' => 'contentbank/contenttype/{$name}/',
'customcertelement' => 'mod/customcert/element/{$name}/',
'datafield' => 'mod/data/field/{$name}/',
'dataformat' => 'dataformat/{$name}/',
'datapreset' => 'mod/data/preset/{$name}/',
'editor' => 'lib/editor/{$name}/',
'enrol' => 'enrol/{$name}/',
'filter' => 'filter/{$name}/',
'forumreport' => 'mod/forum/report/{$name}/',
'gradeexport' => 'grade/export/{$name}/',
'gradeimport' => 'grade/import/{$name}/',
'gradereport' => 'grade/report/{$name}/',
'gradingform' => 'grade/grading/form/{$name}/',
'h5plib' => 'h5p/h5plib/{$name}/',
'local' => 'local/{$name}/',
'logstore' => 'admin/tool/log/store/{$name}/',
'ltisource' => 'mod/lti/source/{$name}/',
'ltiservice' => 'mod/lti/service/{$name}/',
'media' => 'media/player/{$name}/',
'message' => 'message/output/{$name}/',
'mlbackend' => 'lib/mlbackend/{$name}/',
'mnetservice' => 'mnet/service/{$name}/',
'paygw' => 'payment/gateway/{$name}/',
'plagiarism' => 'plagiarism/{$name}/',
'portfolio' => 'portfolio/{$name}/',
'qbank' => 'question/bank/{$name}/',
'qbehaviour' => 'question/behaviour/{$name}/',
'qformat' => 'question/format/{$name}/',
'qtype' => 'question/type/{$name}/',
'quizaccess' => 'mod/quiz/accessrule/{$name}/',
'quiz' => 'mod/quiz/report/{$name}/',
'report' => 'report/{$name}/',
'repository' => 'repository/{$name}/',
'scormreport' => 'mod/scorm/report/{$name}/',
'search' => 'search/engine/{$name}/',
'theme' => 'theme/{$name}/',
'tiny' => 'lib/editor/tiny/plugins/{$name}/',
'tinymce' => 'lib/editor/tinymce/plugins/{$name}/',
'profilefield' => 'user/profile/field/{$name}/',
'webservice' => 'webservice/{$name}/',
'workshopallocation' => 'mod/workshop/allocation/{$name}/',
'workshopeval' => 'mod/workshop/eval/{$name}/',
'workshopform' => 'mod/workshop/form/{$name}/'
);
}

View File

@ -0,0 +1,57 @@
<?php
namespace Composer\Installers;
class OctoberInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
'plugin' => 'plugins/{$vendor}/{$name}/',
'theme' => 'themes/{$vendor}-{$name}/'
);
/**
* Format package name.
*
* For package type october-plugin, cut off a trailing '-plugin' if present.
*
* For package type october-theme, cut off a trailing '-theme' if present.
*/
public function inflectPackageVars(array $vars): array
{
if ($vars['type'] === 'october-plugin') {
return $this->inflectPluginVars($vars);
}
if ($vars['type'] === 'october-theme') {
return $this->inflectThemeVars($vars);
}
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectPluginVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/^oc-|-plugin$/', '', $vars['name']);
$vars['vendor'] = $this->pregReplace('/[^a-z0-9_]/i', '', $vars['vendor']);
return $vars;
}
/**
* @param array<string, string> $vars
* @return array<string, string>
*/
protected function inflectThemeVars(array $vars): array
{
$vars['name'] = $this->pregReplace('/^oc-|-theme$/', '', $vars['name']);
$vars['vendor'] = $this->pregReplace('/[^a-z0-9_]/i', '', $vars['vendor']);
return $vars;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Composer\Installers;
class OntoWikiInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'extension' => 'extensions/{$name}/',
'theme' => 'extensions/themes/{$name}/',
'translation' => 'extensions/translations/{$name}/',
);
/**
* Format package name to lower case and remove ".ontowiki" suffix
*/
public function inflectPackageVars(array $vars): array
{
$vars['name'] = strtolower($vars['name']);
$vars['name'] = $this->pregReplace('/.ontowiki$/', '', $vars['name']);
$vars['name'] = $this->pregReplace('/-theme$/', '', $vars['name']);
$vars['name'] = $this->pregReplace('/-translation$/', '', $vars['name']);
return $vars;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Composer\Installers;
class OsclassInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'plugin' => 'oc-content/plugins/{$name}/',
'theme' => 'oc-content/themes/{$name}/',
'language' => 'oc-content/languages/{$name}/',
);
}

View File

@ -0,0 +1,49 @@
<?php
namespace Composer\Installers;
use Composer\Package\PackageInterface;
class OxidInstaller extends BaseInstaller
{
const VENDOR_PATTERN = '/^modules\/(?P<vendor>.+)\/.+/';
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
'theme' => 'application/views/{$name}/',
'out' => 'out/{$name}/',
);
public function getInstallPath(PackageInterface $package, string $frameworkType = ''): string
{
$installPath = parent::getInstallPath($package, $frameworkType);
$type = $this->package->getType();
if ($type === 'oxid-module') {
$this->prepareVendorDirectory($installPath);
}
return $installPath;
}
/**
* Makes sure there is a vendormetadata.php file inside
* the vendor folder if there is a vendor folder.
*/
protected function prepareVendorDirectory(string $installPath): void
{
$matches = '';
$hasVendorDirectory = preg_match(self::VENDOR_PATTERN, $installPath, $matches);
if (!$hasVendorDirectory) {
return;
}
$vendorDirectory = $matches['vendor'];
$vendorPath = getcwd() . '/modules/' . $vendorDirectory;
if (!file_exists($vendorPath)) {
mkdir($vendorPath, 0755, true);
}
$vendorMetaDataPath = $vendorPath . '/vendormetadata.php';
touch($vendorMetaDataPath);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Composer\Installers;
class PPIInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'module' => 'modules/{$name}/',
);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Composer\Installers;
class PantheonInstaller extends BaseInstaller
{
/** @var array<string, string> */
protected $locations = array(
'script' => 'web/private/scripts/quicksilver/{$name}',
'module' => 'web/private/scripts/quicksilver/{$name}',
);
}

Some files were not shown because too many files have changed in this diff Show More