Merge pull request #209 from timnolte/feature/coding-standards-static-analysis

Feature/coding standards static analysis
isekai
Tim Nolte 4 years ago committed by GitHub
commit 4ad4a6a95b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,24 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
# WordPress Coding Standards
# https://make.wordpress.org/core/handbook/coding-standards/
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
[*.yml,*.yml.dist]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[{*.txt,wp-config-sample.php}]
end_of_line = crlf

54
.gitignore vendored

@ -1,2 +1,52 @@
vendor/**/*
composer.lock
# Numerous always-ignore extensions
*.diff
*.err
*.orig
*.log
*.rej
*.swo
*.swp
*.vi
*~
*.sass-cache
# Local Development files/folders.
.env
phpcs.xml
phpstan.neon
phpunit.xml
# OS or Editor folders
.DS_Store
Thumbs.db
.cache
tags.*
.project
.settings
.tmproj
*.esproj
nbproject
*.sublime-project
*.sublime-workspace
.idea
clover.xml
# Dreamweaver added files
_notes
dwsync.xml
# Komodo
*.komodoproject
.komodotools
# Folders to ignore
.hg
.svn
.CVS
intermediate
.idea
cache
node_modules
vendor
dist
wordpress

@ -0,0 +1,118 @@
# Travis CI Configuration File
# Tell Travis CI which distro to use
dist: trusty
sudo: false
# Tell Travis CI we're using PHP
language: php
# Tell Travis CI which notifications to send
notifications:
email:
on_success: never
on_failure: change
# whitelist branches for the "push" build check
branches:
only:
- main
- /^dev\-release\/.*$/
- /^feature\/.*$/
- /^fix\/.*$/
# Git clone depth
# By default Travis CI clones repositories to a depth of 50 commits
git:
depth: 1
cache:
directories:
- vendor
- $HOME/.composer/cache
# Define a matrix of additional build configurations
# The versions listed above will automatically create our first configuration,
# so it doesn't need to be re-defined below.
matrix:
fast_finish: true
include:
- name: Coding Standards
php: 7.3
env: WP_MODE=single WP_VERSION=5.4.* PHP_LINT=1 COVERAGE=1
- name: Static Code Analysis
php: 7.3
env: WP_MODE=single WP_VERSION=5.4.* PHP_ANALYZE=1
- name: Latest Stable
php: 7.3
env: WP_MODE=single WP_VERSION=5.4.* PHP_UNIT=1
- name: Preferred Minimum requirements
php: 7.2
env: WP_MODE=single WP_VERSION=5.3.* PHP_UNIT=1
- name: Minimum requirements
php: 7.1
env: WP_MODE=single WP_VERSION=5.2.* PHP_UNIT=1
- name: Bleeding Edge
php: 7.4
env: WP_MODE=single WP_VERSION=dev-master PHP_UNIT=1
- name: Multisite Compatibility
php: 7.3
env: WP_MODE=multi WP_VERSION=5.4.* PHP_UNIT=1
allow_failures:
- name: Bleeding Edge
addons:
apt:
packages:
- nodejs
before_install:
- npm install -g npm@6.14
- npm install -g grunt-cli
- composer require "wordpress/wordpress:${WP_VERSION}" --dev --prefer-source --no-update
install:
- composer update --prefer-source --no-interaction --dev --optimize-autoloader
- npm install
before_script:
- export PATH="$HOME/.composer/vendor/bin:$PATH"
# Setup WordPress coding standards
- |
if [[ "$PHP_LINT" == "1" ]]; then
composer global require wp-coding-standards/wpcs
fi
# Setup unit testing environment
- |
if [[ "$PHP_UNIT" == "1" ]]; then
# bash scripts/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
if [[ $TRAVIS_PHP_VERSION == "7.4" ]]; then
composer global require "phpunit/php-code-coverage=dev-master"
composer global require "sebastian/global-state:dev-master"
composer global require "phpunit/phpunit=dev-master"
else
composer global require "phpunit/phpunit=7.*"
fi
fi
script:
- |
if [[ "$PHP_LINT" == "1" ]]; then
if [[ "$WP_MODE" == "single" ]]; then WP_MULTISITE=0 npm run lint; fi
fi
- |
if [[ "$PHP_ANALYZE" == "1" ]]; then
if [[ "$WP_MODE" == "single" ]]; then WP_MULTISITE=0 npm run analyze; fi
fi
- |
if [[ "$PHP_UNIT" == "1" ]]; then
if [[ "$WP_MODE" == "multi" ]]; then WP_MULTISITE=1 npm run test; fi
if [[ "$WP_MODE" == "single" ]]; then WP_MULTISITE=0 npm run test; fi
fi
after_success:
- |
if [[ "$COVERAGE" == "1" ]]; then
bash <(curl -s https://codecov.io/bash)
fi

@ -0,0 +1,17 @@
{
"core": "./wordpress/build",
"mappings": {
"wp-content/mu-plugins": "./tools/local-env/mu-plugins",
"wp-content/plugins/daggerhart-openid-connect-generic": "."
},
"config": {
"PHP_INI_MEMORY_LIMIT": "512M",
"WP_MEMORY_LIMIT": "512M",
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": true,
"SCRIPT_DEBUG": true,
"SMTP_HOST": "mailhog",
"SMTP_PORT": 1025
}
}

@ -0,0 +1,200 @@
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
composerBin: 'vendor/bin',
shell: {
phpcs: {
options: {
stdout: true
},
command: '<%= composerBin %>/phpcs'
},
phpcbf: {
options: {
stdout: true
},
command: '<%= composerBin %>/phpcbf'
},
phpstan: {
options: {
stdout: true
},
command: '<%= composerBin %>/phpstan analyze .'
},
phpunit: {
options: {
stdout: true
},
command: '<%= composerBin %>/phpunit'
},
},
gitinfo: {
commands: {
'local.tag.current.name': ['name-rev', '--tags', '--name-only', 'HEAD'],
'local.tag.current.nameLong': ['describe', '--tags', '--long']
}
},
clean: {
main: ['dist'], //Clean up build folder
i18n: ['languages/*.mo', 'languages/*.pot']
},
copy: {
// Copy the plugin to a versioned release directory
main: {
src: [
'**',
'!*.xml', '!*.log', //any config/log files
'!node_modules/**', '!Gruntfile.js', '!package.json', '!package-lock.json', //npm/Grunt
'!assets/**', //wp-org assets
'!dist/**', //build directory
'!.git/**', //version control
'!tests/**', '!scripts/**', '!phpunit.xml', '!phpunit.xml.dist', //unit testing
'!vendor/**', '!composer.lock', '!composer.phar', '!composer.json', //composer
'!wordpress/**',
'!.*', '!**/*~', //hidden files
'!CONTRIBUTING.md',
'!README.md',
'!phpcs.xml', '!phpcs.xml.dist', '!phpstan.neon.dist', '!grumphp.yml.dist', // CodeSniffer Configuration.
'!docker-compose.override.yml', // Local Docker Development configuration.
'!codecov.yml', // Code coverage configuration.
'!tools/**', // Local Development/Build tools configuration.
],
dest: 'dist/',
options: {
processContentExclude: ['**/*.{png,gif,jpg,ico,mo}'],
processContent: function (content, srcpath) {
if (srcpath == 'readme.txt' || srcpath == 'openid-connect-generic.php') {
if (grunt.config.get('gitinfo').local.tag.current.name !== 'undefined') {
content = content.replace('{{version}}', grunt.config.get('gitinfo').local.tag.current.name);
} else {
content = content.replace('{{version}}', grunt.config.get('gitinfo').local.tag.current.nameLong);
}
}
return content;
},
},
}
},
addtextdomain: {
options: {
textdomain: 'daggerhart-openid-connect-generic', // Project text domain.
},
update_all_domains: {
options: {
updateDomains: true
},
src: ['*.php', '**/*.php', '!node_modules/**', '!tests/**', '!scripts/**', '!wordpress/**']
},
},
wp_readme_to_markdown: {
dest: {
files: {
'README.md': 'readme.txt'
}
},
},
makepot: {
target: {
options: {
domainPath: '/languages', // Where to save the POT file.
exclude: [
'node_modules/.*', //npm
'assets/.*', //wp-org assets
'dist/.*', //build directory
'.git/.*', //version control
'tests/.*', 'scripts/.*', //unit testing
'vendor/.*', //composer
'wordpress/.*',
], // List of files or directories to ignore.
mainFile: 'openid-connect-generic.php', // Main project file.
potFilename: 'openid-connect-generic.pot', // Name of the POT file.
potHeaders: {
poedit: true, // Includes common Poedit headers.
'x-poedit-keywordslist': true // Include a list of all possible gettext functions.
}, // Headers to add to the generated POT file.
type: 'wp-plugin', // Type of project (wp-plugin or wp-theme).
updateTimestamp: true, // Whether the POT-Creation-Date should be updated without other changes.
updatePoFiles: true // Whether to update PO files in the same directory as the POT file.
}
}
},
po2mo: {
plugin: {
src: 'languages/*.po',
expand: true
}
},
checkrepo: {
deploy: {
tagged: true, // Check that the last commit (HEAD) is tagged
clean: true // Check that working directory is clean
}
},
checktextdomain: {
options: {
text_domain: 'daggerhart-openid-connect-generic',
keywords: [
'__:1,2d',
'_e:1,2d',
'_x:1,2c,3d',
'esc_html__:1,2d',
'esc_html_e:1,2d',
'esc_html_x:1,2c,3d',
'esc_attr__:1,2d',
'esc_attr_e:1,2d',
'esc_attr_x:1,2c,3d',
'_ex:1,2c,3d',
'_x:1,2c,3d',
'_n:1,2,4d',
'_nx:1,2,4c,5d',
'_n_noop:1,2,3d',
'_nx_noop:1,2,3c,4d'
],
},
files: {
src: [
'**/*.php',
'!node_modules/**',
'!dist/**',
'!tests/**',
'!vendor/**',
'!wordpress/**',
'!*~',
],
expand: true,
},
},
});
grunt.registerTask('phpcs', ['shell:phpcs']);
grunt.registerTask('phpcbf', ['shell:phpcbf']);
grunt.registerTask('phpstan', ['shell:phpstan']);
grunt.registerTask('phpunit', ['shell:phpunit']);
grunt.registerTask('i18n', ['addtextdomain', 'makepot', 'po2mo']);
grunt.registerTask('readme', ['wp_readme_to_markdown']);
grunt.registerTask('test', ['checktextdomain', 'phpcs']);
grunt.registerTask('build', ['gitinfo', 'test', 'clean', 'i18n', 'readme', 'copy']);
//grunt.registerTask( 'deploy', [ 'checkbranch:master', 'checkrepo', 'build' ] );
grunt.registerTask('deploy', ['checkrepo', 'build']);
};

@ -23,8 +23,80 @@
"support": {
"issues": "https://github.com/daggerhart/openid-connect-generic/issues"
},
"config": {
"platform": {
"php": "7.1"
},
"optimize-autoloader": true
},
"repositories": [
{
"type": "git",
"url": "https://github.com/wordpress/wordpress-develop"
}
],
"require": {
"php": ">=5.6.0",
"php": ">=7.1.0",
"composer/installers": "~1.0"
}
},
"require-dev": {
"php": ">=7.1.0",
"squizlabs/php_codesniffer": "^3.3",
"wp-coding-standards/wpcs": "~2.2.0",
"phpcompatibility/php-compatibility": "^9.0",
"phpcompatibility/phpcompatibility-wp": "^2.1",
"phpmd/phpmd": "^2.6",
"phpunit/phpunit": "^7",
"phpstan/phpstan": "*",
"phpstan/extension-installer": "^1.0",
"szepeviktor/phpstan-wordpress": "*",
"php-stubs/wordpress-stubs": "~5.4.2",
"roave/security-advisories": "dev-master",
"mnsami/composer-custom-directory-installer": "~1.0",
"wordpress/wordpress": "~5.4.2",
"dealerdirect/phpcodesniffer-composer-installer": "^0.6",
"brain/monkey": "^2.4",
"mockery/mockery": "^1.3",
"phpro/grumphp": "^0.16.2",
"sensiolabs/security-checker": "^5.0",
"phpstan/phpstan-deprecation-rules": "^0.12.4"
},
"autoload-dev": {
"classmap": [
"wordpress/src/"
]
},
"autoload": {
"classmap": [
"openid-connect-generic.php",
"includes/openid-connect-generic-client.php",
"includes/openid-connect-generic-client-wrapper.php",
"includes/openid-connect-generic-login-form.php",
"includes/openid-connect-generic-option-logger.php",
"includes/openid-connect-generic-option-settings.php",
"includes/openid-connect-generic-settings-page.php"
]
},
"scripts": {
"install-codestandards": [
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
],
"post-install-cmd": [
"@install-codestandards"
],
"post-update-cmd": [
"@install-codestandards"
],
"phpcs": "vendor/bin/phpcs",
"phpcbf": "vendor/bin/phpcbf",
"phpstan": "\"vendor/bin/phpstan\""
},
"extra": {
"installer-paths": {
"{$name}": [
"wordpress/wordpress"
]
},
"phpcodesniffer-search-depth": 5
}
}

4299
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,75 @@
version: '3.7'
services:
wordpress-develop:
depends_on:
- php
- mailhog
environment:
LOCAL_DIR: ${LOCAL_DIR-src}
SMTP_HOST: ${SMTP_HOST-mailhog}
SMTP_PORT: ${SMTP_PORT-1025}
volumes:
- ../tools/local-env/default.template:/etc/nginx/conf.d/default.template
- ..:/var/www/${LOCAL_DIR-src}/wp-content/plugins/daggerhart-openid-connect-generic
- ../tools/local-env/mu-plugins:/var/www/${LOCAL_DIR-src}/wp-content/mu-plugins
# Load our config file, substituning environment variables into the config.
command: /bin/sh -c "envsubst '$$LOCAL_DIR $$LOCAL_HOSTNAME' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
php:
environment:
LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false}
LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false}
PHP_FPM_UID: ${PHP_FPM_UID-1000}
PHP_FPM_GID: ${PHP_FPM_GID-1000}
SMTP_HOST: ${SMTP_HOST-mailhog}
SMTP_PORT: ${SMTP_PORT-1025}
volumes:
- ..:/var/www/${LOCAL_DIR-src}/wp-content/plugins/daggerhart-openid-connect-generic/
- ../tools/local-env/mu-plugins:/var/www/${LOCAL_DIR-src}/wp-content/mu-plugins
cli:
environment:
LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false}
LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false}
PHP_FPM_UID: ${PHP_FPM_UID-1000}
PHP_FPM_GID: ${PHP_FPM_GID-1000}
SMTP_HOST: ${SMTP_HOST-mailhog}
SMTP_PORT: ${SMTP_PORT-1025}
volumes:
- ..:/var/www/${LOCAL_DIR-src}/wp-content/plugins/daggerhart-openid-connect-generic/
- ../tools/local-env/mu-plugins:/var/www/${LOCAL_DIR-src}/wp-content/mu-plugins
phpunit:
environment:
LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false}
LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false}
LOCAL_DIR: ${LOCAL_DIR-src}
WP_MULTISITE: ${WP_MULTISITE-false}
PHP_FPM_UID: ${PHP_FPM_UID-1000}
PHP_FPM_GID: ${PHP_FPM_GID-1000}
TRAVIS_BRANCH: ${TRAVIS_BRANCH-false}
TRAVIS_PULL_REQUEST: ${TRAVIS_PULL_REQUEST-false}
SMTP_HOST: ${SMTP_HOST-mailhog}
SMTP_PORT: ${SMTP_PORT-1025}
volumes:
- ..:/var/www/${LOCAL_DIR-src}/wp-content/plugins/daggerhart-openid-connect-generic/
- ../tools/local-env/mu-plugins:/var/www/${LOCAL_DIR-src}/wp-content/mu-plugins
## SMTP Server + Web Interface for viewing and testing emails during development.
mailhog:
image: mailhog/mailhog
restart: always
networks:
- wpdevnet
ports:
- "${MAILHOG_PORT:-8025}:8025"
- "${SMTP_PORT:-1025}:1025"

@ -0,0 +1,38 @@
# grumphp.yml
parameters:
git_dir: .
bin_dir: 'vendor/bin'
stop_on_failure: true
process_timeout: 120
parallel:
enabled: true
max_workers: 32
fixer:
enabled: false
fix_by_default: false
tasks:
git_blacklist:
keywords:
- 'wp_die('
- 'die('
git_branch_name:
blacklist:
- 'main'
- 'master'
- 'dev*'
allow_detached_head: false
git_commit_message:
allow_empty_message: false
enforce_capitalized_subject: true
phpcs:
standard: './phpcs.xml.dist'
report: 'summary'
ignore_patterns:
- '/^assets\/(.*)/'
phpstan:
configuration: './phpstan.neon.dist'
level: 5
ignore_patterns:
- '/^assets\/(.*)/'
memory_limit: '-1'
securitychecker: ~

File diff suppressed because it is too large Load Diff

@ -1,36 +1,117 @@
<?php
/**
* Plugin OIDC/oAuth client 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+
*/
/**
* OpenID_Connect_Generic_Client class.
*
* Plugin OIDC/oAuth client class.
*
* @package OpenID_Connect_Generic
* @category Authentication
*/
class OpenID_Connect_Generic_Client {
/**
* The OIDC/oAuth client ID.
*
* @see OpenID_Connect_Generic_Option_Settings::client_id
*
* @var string
*/
private $client_id;
/**
* The OIDC/oAuth client secret.
*
* @see OpenID_Connect_Generic_Option_Settings::client_secret
*
* @var string
*/
private $client_secret;
/**
* The OIDC/oAuth scopes.
*
* @see OpenID_Connect_Generic_Option_Settings::scope
*
* @var string
*/
private $scope;
/**
* The OIDC/oAuth authorization endpoint URL.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_login
*
* @var string
*/
private $endpoint_login;
/**
* The OIDC/oAuth User Information endpoint URL.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_userinfo
*
* @var string
*/
private $endpoint_userinfo;
/**
* The OIDC/oAuth token validation endpoint URL.
*
* @see OpenID_Connect_Generic_Option_Settings::endpoint_token
*
* @var string
*/
private $endpoint_token;
// login flow "ajax" endpoint
/**
* The login flow "ajax" endpoint URI.
*
* @see OpenID_Connect_Generic_Option_Settings::redirect_uri
*
* @var string
*/
private $redirect_uri;
// states are only valid for 3 minutes
/**
* The state time limit. States are only valid for 3 minutes.
*
* @see OpenID_Connect_Generic_Option_Settings::state_time_limit
*
* @var int
*/
private $state_time_limit = 180;
// logger object
/**
* The logger object instance.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
/**
* Client constructor
*
* @param $client_id
* @param $client_secret
* @param $scope
* @param $endpoint_login
* @param $endpoint_userinfo
* @param $endpoint_token
* @param $redirect_uri
* @param $state_time_limit time states are valid in seconds
* 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 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.
*/
function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $state_time_limit, $logger){
function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $state_time_limit, $logger ) {
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->scope = $scope;
@ -55,12 +136,13 @@ class OpenID_Connect_Generic_Client {
$scope = ( ! empty( $atts['scope'] ) ) ? $atts['scope'] : $this->scope;
$client_id = ( ! empty( $atts['client_id'] ) ) ? $atts['client_id'] : $this->client_id;
$redirect_uri = ( ! empty( $atts['redirect_uri'] ) ) ? $atts['redirect_uri'] : $this->redirect_uri;
$separator = '?';
if ( stripos( $this->endpoint_login, '?' ) !== FALSE ) {
if ( stripos( $this->endpoint_login, '?' ) !== false ) {
$separator = '&';
}
$url = sprintf( '%1$s%2$sresponse_type=code&scope=%3$s&client_id=%4$s&state=%5$s&redirect_uri=%6$s',
$url = sprintf(
'%1$s%2$sresponse_type=code&scope=%3$s&client_id=%4$s&state=%5$s&redirect_uri=%6$s',
$endpoint_login,
$separator,
rawurlencode( $scope ),
@ -76,23 +158,23 @@ class OpenID_Connect_Generic_Client {
/**
* Validate the request for login authentication
*
* @param $request
* @param array<string> $request The authentication request results.
*
* @return array|\WP_Error
* @return array<string>|WP_Error
*/
function validate_authentication_request( $request ){
// look for an existing error of some kind
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 );
}
// make sure we have a legitimate authentication code and valid state
// Make sure we have a legitimate authentication code and valid state.
if ( ! isset( $request['code'] ) ) {
return new WP_Error( 'no-code', 'No authentication code present in the request.', $request );
}
// check the client request state
if( ! isset( $request['state']) ) {
// Check the client request state.
if ( ! isset( $request['state'] ) ) {
do_action( 'openid-connect-generic-no-state-provided' );
return new WP_Error( 'missing-state', __( 'Missing state.' ), $request );
}
@ -107,25 +189,29 @@ class OpenID_Connect_Generic_Client {
/**
* Get the authorization code from the request
*
* @param $request array
* @param array<string>|WP_Error $request The authentication request results.
*
* @return string|\WP_Error
* @return string|WP_Error
*/
function get_authentication_code( $request ){
function get_authentication_code( $request ) {
if ( ! isset( $request['code'] ) ) {
return new WP_Error( 'missing-authentication-code', __( 'Missing authentication code.' ), $request );
}
return $request['code'];
}
/**
* Using the authorization_code, request an authentication token from the idp
* Using the authorization_code, request an authentication token from the IDP.
*
* @param $code - authorization_code
* @param string|WP_Error $code The authorization code.
*
* @return array|\WP_Error
* @return array<mixed>|WP_Error
*/
function request_authentication_token( $code ) {
// Add Host header - required for when the openid-connect endpoint is behind a reverse-proxy
$parsed_url = parse_url($this->endpoint_token);
// Add Host header - required for when the openid-connect endpoint is behind a reverse-proxy.
$parsed_url = parse_url( $this->endpoint_token );
$host = $parsed_url['host'];
$request = array(
@ -137,18 +223,18 @@ class OpenID_Connect_Generic_Client {
'grant_type' => 'authorization_code',
'scope' => $this->scope,
),
'headers' => array( 'Host' => $host )
'headers' => array( 'Host' => $host ),
);
// allow modifications to the request
// Allow modifications to the request.
$request = apply_filters( 'openid-connect-generic-alter-request', $request, 'get-authentication-token' );
// call the server and ask for a token
// Call the server and ask for a token.
$this->logger->log( $this->endpoint_token, 'request_authentication_token' );
$response = wp_remote_post( $this->endpoint_token, $request );
if ( is_wp_error( $response ) ){
$response->add( 'request_authentication_token' , __( 'Request for authentication token failed.' ) );
if ( is_wp_error( $response ) ) {
$response->add( 'request_authentication_token', __( 'Request for authentication token failed.' ) );
}
return $response;
@ -157,9 +243,9 @@ class OpenID_Connect_Generic_Client {
/**
* Using the refresh token, request new tokens from the idp
*
* @param $refresh_token - refresh token previously obtained from token response.
* @param string $refresh_token The refresh token previously obtained from token response.
*
* @return array|\WP_Error
* @return array|WP_Error
*/
function request_new_tokens( $refresh_token ) {
$request = array(
@ -167,19 +253,19 @@ class OpenID_Connect_Generic_Client {
'refresh_token' => $refresh_token,
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'grant_type' => 'refresh_token'
)
'grant_type' => 'refresh_token',
),
);
// allow modifications to the request
// Allow modifications to the request.
$request = apply_filters( 'openid-connect-generic-alter-request', $request, 'refresh-token' );
// call the server and ask for new tokens
// Call the server and ask for new tokens.
$this->logger->log( $this->endpoint_token, 'request_new_tokens' );
$response = wp_remote_post( $this->endpoint_token, $request );
if ( is_wp_error( $response ) ) {
$response->add( 'refresh_token' , __( 'Refresh token failed.' ) );
$response->add( 'refresh_token', __( 'Refresh token failed.' ) );
}
return $response;
@ -188,22 +274,28 @@ class OpenID_Connect_Generic_Client {
/**
* Extract and decode the token body of a token response
*
* @param $token_result
* @return array|mixed|object
* @param array<mixed>|WP_Error $token_result The token response.
*
* @return array<mixed>|WP_Error|null
*/
function get_token_response( $token_result ){
if ( ! isset( $token_result['body'] ) ){
function get_token_response( $token_result ) {
if ( ! isset( $token_result['body'] ) ) {
return new WP_Error( 'missing-token-body', __( 'Missing token body.' ), $token_result );
}
// extract token response from token
// Extract the token response from token.
$token_response = json_decode( $token_result['body'], true );
if ( isset( $token_response[ 'error' ] ) ) {
$error = $token_response[ 'error' ];
// Check that the token response body was able to be parsed.
if ( is_null( $token_response ) ) {
return new WP_Error( 'invalid-token', __( 'Invalid token.' ), $token_result );
}
if ( isset( $token_response['error'] ) ) {
$error = $token_response['error'];
$error_description = $error;
if ( isset( $token_response[ 'error_description' ] ) ) {
$error_description = $token_response[ 'error_description' ];
if ( isset( $token_response['error_description'] ) ) {
$error_description = $token_response['error_description'];
}
return new WP_Error( $error, $error_description, $token_result );
}
@ -211,55 +303,55 @@ class OpenID_Connect_Generic_Client {
return $token_response;
}
/**
* Exchange an access_token for a user_claim from the userinfo endpoint
*
* @param $access_token
* @param string $access_token The access token supplied from authentication user claim.
*
* @return array|\WP_Error
* @return array|WP_Error
*/
function request_userinfo( $access_token ) {
// allow modifications to the request
// Allow modifications to the request.
$request = apply_filters( 'openid-connect-generic-alter-request', array(), 'get-userinfo' );
// section 5.3.1 of the spec recommends sending the access token using the authorization header
// a filter may or may not have already added headers - make sure they exist then add the token
if ( !array_key_exists( 'headers', $request ) || !is_array( $request['headers'] ) ) {
/*
* Section 5.3.1 of the spec recommends sending the access token using the authorization header
* a filter may or may not have already added headers - make sure they exist then add the token.
*/
if ( ! array_key_exists( 'headers', $request ) || ! is_array( $request['headers'] ) ) {
$request['headers'] = array();
}
$request['headers']['Authorization'] = 'Bearer '.$access_token;
$request['headers']['Authorization'] = 'Bearer ' . $access_token;
// Add Host header - required for when the openid-connect endpoint is behind a reverse-proxy
$parsed_url = parse_url($this->endpoint_userinfo);
// Add Host header - required for when the openid-connect endpoint is behind a reverse-proxy.
$parsed_url = parse_url( $this->endpoint_userinfo );
$host = $parsed_url['host'];
if ( !empty( $parsed_url['port'] ) ) {
$host.= ":{$parsed_url['port']}";
if ( ! empty( $parsed_url['port'] ) ) {
$host .= ":{$parsed_url['port']}";
}
$request['headers']['Host'] = $host;
// attempt the request including the access token in the query string for backwards compatibility
// Attempt the request including the access token in the query string for backwards compatibility.
$this->logger->log( $this->endpoint_userinfo, 'request_userinfo' );
$response = wp_remote_post( $this->endpoint_userinfo, $request );
if ( is_wp_error( $response ) ){
$response->add( 'request_userinfo' , __( 'Request for userinfo failed.' ) );
if ( is_wp_error( $response ) ) {
$response->add( 'request_userinfo', __( 'Request for userinfo failed.' ) );
}
return $response;
}
/**
* Generate a new state, save it as a transient,
* and return the state hash.
* Generate a new state, save it as a transient, and return the state hash.
*
* @return string
*/
function new_state() {
// new state w/ timestamp
// New state w/ timestamp.
$state = md5( mt_rand() . microtime( true ) );
set_transient( 'openid-connect-generic-state--' . $state, $state, $this->state_time_limit );
@ -269,7 +361,7 @@ class OpenID_Connect_Generic_Client {
/**
* Check the existence of a given state transient.
*
* @param $state
* @param string $state The state hash to validate.
*
* @return bool
*/
@ -288,21 +380,23 @@ class OpenID_Connect_Generic_Client {
do_action( 'openid-connect-generic-state-expired', $state );
}
return !!$valid;
return ! ! $valid;
}
/**
* Ensure that the token meets basic requirements
* Ensure that the token meets basic requirements.
*
* @param $token_response
* @param array $token_response The token response.
*
* @return bool|\WP_Error
* @return bool|WP_Error
*/
function validate_token_response( $token_response ){
// we need to ensure 2 specific items exist with the token response in order
// to proceed with confidence: id_token and token_type == 'Bearer'
function validate_token_response( $token_response ) {
/*
* Ensure 2 specific items exist with the token response in order
* to proceed with confidence: id_token and token_type == 'Bearer'
*/
if ( ! isset( $token_response['id_token'] ) ||
! isset( $token_response['token_type'] ) || strcasecmp( $token_response['token_type'], 'Bearer' )
! isset( $token_response['token_type'] ) || strcasecmp( $token_response['token_type'], 'Bearer' )
) {
return new WP_Error( 'invalid-token-response', 'Invalid token response', $token_response );
}
@ -311,53 +405,53 @@ class OpenID_Connect_Generic_Client {
}
/**
* Extract the id_token_claim from the token_response
* Extract the id_token_claim from the token_response.
*
* @param $token_response
* @param array $token_response The token response.
*
* @return array|\WP_Error
* @return array|WP_Error
*/
function get_id_token_claim( $token_response ){
// name sure we have an id_token
function get_id_token_claim( $token_response ) {
// Validate there is an id_token.
if ( ! isset( $token_response['id_token'] ) ) {
return new WP_Error( 'no-identity-token', __( 'No identity token' ), $token_response );
}
// break apart the id_token in the response for decoding
// Break apart the id_token in the response for decoding.
$tmp = explode( '.', $token_response['id_token'] );
if ( ! isset( $tmp[1] ) ) {
return new WP_Error( 'missing-identity-token', __( 'Missing identity token' ), $token_response );
}
// Extract the id_token's claims from the token
// Extract the id_token's claims from the token.
$id_token_claim = json_decode(
base64_decode(
str_replace( // because token is encoded in base64 URL (and not just base64)
array('-', '_'),
array('+', '/'),
str_replace( // Because token is encoded in base64 URL (and not just base64).
array( '-', '_' ),
array( '+', '/' ),
$tmp[1]
)
)
, true
),
true
);
return $id_token_claim;
}
/**
* Ensure the id_token_claim contains the required values
* Ensure the id_token_claim contains the required values.
*
* @param $id_token_claim
* @param array $id_token_claim The ID token claim.
*
* @return bool|\WP_Error
* @return bool|WP_Error
*/
function validate_id_token_claim( $id_token_claim ){
function validate_id_token_claim( $id_token_claim ) {
if ( ! is_array( $id_token_claim ) ) {
return new WP_Error( 'bad-id-token-claim', __( 'Bad ID token claim' ), $id_token_claim );
}
// make sure we can find our identification data and that it has a value
// Validate the identification data and it's value.
if ( ! isset( $id_token_claim['sub'] ) || empty( $id_token_claim['sub'] ) ) {
return new WP_Error( 'no-subject-identity', __( 'No subject identity' ), $id_token_claim );
}
@ -366,17 +460,17 @@ class OpenID_Connect_Generic_Client {
}
/**
* Attempt to exchange the access_token for a user_claim
* Attempt to exchange the access_token for a user_claim.
*
* @param $token_response
* @param array $token_response The token response.
*
* @return array|mixed|object|\WP_Error
* @return array|WP_Error|null
*/
function get_user_claim( $token_response ){
// send a userinfo request to get user claim
function get_user_claim( $token_response ) {
// Send a userinfo request to get user claim.
$user_claim_result = $this->request_userinfo( $token_response['access_token'] );
// make sure we didn't get an error, and that the response body exists
// Make sure we didn't get an error, and that the response body exists.
if ( is_wp_error( $user_claim_result ) || ! isset( $user_claim_result['body'] ) ) {
return new WP_Error( 'bad-claim', __( 'Bad user claim' ), $user_claim_result );
}
@ -390,32 +484,32 @@ class OpenID_Connect_Generic_Client {
* Make sure the user_claim has all required values, and that the subject
* identity matches of the id_token matches that of the user_claim.
*
* @param $user_claim
* @param $id_token_claim
* @param array $user_claim The authenticated user claim.
* @param array $id_token_claim The ID token claim.
*
* @return \WP_Error
* @return bool|WP_Error
*/
function validate_user_claim( $user_claim, $id_token_claim ) {
// must be an array
if ( ! is_array( $user_claim ) ){
// Validate the user claim.
if ( ! is_array( $user_claim ) ) {
return new WP_Error( 'invalid-user-claim', __( 'Invalid user claim' ), $user_claim );
}
// allow for errors from the IDP
// Allow for errors from the IDP.
if ( isset( $user_claim['error'] ) ) {
$message = __( 'Error from the IDP' );
if ( !empty( $user_claim['error_description'] ) ) {
if ( ! empty( $user_claim['error_description'] ) ) {
$message = $user_claim['error_description'];
}
return new WP_Error( 'invalid-user-claim-' . $user_claim['error'], $message, $user_claim );
}
// make sure the id_token sub === user_claim sub, according to spec
if ( $id_token_claim['sub' ] !== $user_claim['sub'] ) {
// Make sure the id_token sub equals the user_claim sub, according to spec.
if ( $id_token_claim['sub'] !== $user_claim['sub'] ) {
return new WP_Error( 'incorrect-user-claim', __( 'Incorrect user claim' ), func_get_args() );
}
// allow for other plugins to alter the login success
// Allow for other plugins to alter the login success.
$login_user = apply_filters( 'openid-connect-generic-user-login-test', true, $user_claim );
if ( ! $login_user ) {
@ -426,13 +520,14 @@ class OpenID_Connect_Generic_Client {
}
/**
* Retrieve the subject identity from the id_token
* Retrieve the subject identity from the id_token.
*
* @param $id_token_claim array
* @param array $id_token_claim The ID token claim.
*
* @return mixed
*/
function get_subject_identity( $id_token_claim ){
function get_subject_identity( $id_token_claim ) {
return $id_token_claim['sub'];
}
}

@ -1,86 +1,117 @@
<?php
/**
* Login form and login button handlong class.
*
* @package OpenID_Connect_Generic
* @category Login
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenID_Connect_Generic_Login_Form class.
*
* Login form and login button handlong.
*
* @package OpenID_Connect_Generic
* @category Login
*/
class OpenID_Connect_Generic_Login_Form {
/**
* Plugin settings object.
*
* @var OpenID_Connect_Generic_Option_Settings
*/
private $settings;
/**
* Plugin client wrapper instance.
*
* @var OpenID_Connect_Generic_Client_Wrapper
*/
private $client_wrapper;
/**
* @param $settings
* @param $client_wrapper
* 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.
*/
function __construct( $settings, $client_wrapper ){
function __construct( $settings, $client_wrapper ) {
$this->settings = $settings;
$this->client_wrapper = $client_wrapper;
// maybe set redirect cookie on formular page
add_action('login_form_login', [$this, 'handle_redirect_cookie']);
// Handle setting the redirect cookie on a formu page.
add_action( 'login_form_login', array( $this, 'handle_redirect_cookie' ) );
}
/**
* @param $settings
* @param $client_wrapper
* Create an instance of the OpenID_Connect_Generic_Login_Form class.
*
* @return \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.
*
* @return void
*/
static public function register( $settings, $client_wrapper ){
static public function register( $settings, $client_wrapper ) {
$login_form = new self( $settings, $client_wrapper );
// alter the login form as dictated by settings
// Alter the login form as dictated by settings.
add_filter( 'login_message', array( $login_form, 'handle_login_page' ), 99 );
// add a shortcode for the login button
// Add a shortcode for the login button.
add_shortcode( 'openid_connect_generic_login_button', array( $login_form, 'make_login_button' ) );
$login_form->handle_redirect_login_type_auto();
return $login_form;
}
/**
* Auto Login redirect
* Auto Login redirect.
*
* @return void
*/
function handle_redirect_login_type_auto()
{
if ( $GLOBALS['pagenow'] == 'wp-login.php'
&& ( $this->settings->login_type == 'auto' || ! empty( $_GET['force_redirect'] ) )
&& ( ! isset( $_GET[ 'action' ] ) || $_GET[ 'action' ] !== 'logout' )
&& ! isset( $_POST['wp-submit'] ) )
{
if ( ! isset( $_GET['login-error'] ) ) {
$this->handle_redirect_cookie();
function handle_redirect_login_type_auto() {
if ( 'wp-login.php' == $GLOBALS['pagenow']
&& ( 'auto' == $this->settings->login_type || ! empty( $_GET['force_redirect'] ) )
&& ( ! isset( $_GET['action'] ) || 'logout' !== $_GET['action'] )
&& ! isset( $_POST['wp-submit'] ) ) {
if ( ! isset( $_GET['login-error'] ) ) {
$this->handle_redirect_cookie();
wp_redirect( $this->client_wrapper->get_authentication_url() );
exit;
}
else {
} else {
add_action( 'login_footer', array( $this, 'remove_login_form' ), 99 );
}
}
}
/**
* Handle login related redirects
* Handle login related redirects.
*
* @return void
*/
function handle_redirect_cookie()
{
if ( $GLOBALS['pagenow'] == 'wp-login.php' && isset( $_GET[ 'action' ] ) && $_GET[ 'action' ] === 'logout' ) {
function handle_redirect_cookie() {
if ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' == $GLOBALS['pagenow'] && isset( $_GET['action'] ) && 'logout' === $_GET['action'] ) {
return;
}
// record the URL of this page if set to redirect back to origin page
if ( $this->settings->redirect_user_back )
{
$redirect_expiry = current_time('timestamp') + DAY_IN_SECONDS;
// Record the URL of this page if set to redirect back to origin page.
if ( $this->settings->redirect_user_back ) {
$redirect_expiry = current_time( 'timestamp' ) + DAY_IN_SECONDS;
// default redirect to the homepage
// Default redirect to the homepage.
$redirect_url = home_url( esc_url( add_query_arg( null, null ) ) );
if ( $GLOBALS['pagenow'] == 'wp-login.php' ) {
// if using the login form, default redirect to the admin dashboard
if ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' == $GLOBALS['pagenow'] ) {
// If using the login form, default redirect to the admin dashboard.
$redirect_url = admin_url();
if ( isset( $_REQUEST['redirect_to'] ) ) {
$redirect_url = esc_url_raw( $_REQUEST[ 'redirect_to' ] );
$redirect_url = esc_url_raw( $_REQUEST['redirect_to'] );
}
}
@ -91,9 +122,10 @@ class OpenID_Connect_Generic_Login_Form {
}
/**
* Implements filter login_message
* Implements filter login_message.
*
* @param string $message The text message to display on the login page.
*
* @param $message
* @return string
*/
function handle_login_page( $message ) {
@ -102,15 +134,17 @@ class OpenID_Connect_Generic_Login_Form {
$message .= $this->make_error_output( $_GET['login-error'], $_GET['message'] );
}
// login button is appended to existing messages in case of error
// Login button is appended to existing messages in case of error.
$message .= $this->make_login_button();
return $message;
}
/**
* Display an error message to the user
* Display an error message to the user.
*
* @param $error_code
* @param string $error_code The error code.
* @param string $error_message The error message test.
*
* @return string
*/
@ -119,17 +153,17 @@ class OpenID_Connect_Generic_Login_Form {
ob_start();
?>
<div id="login_error">
<strong><?php _e( 'ERROR'); ?>: </strong>
<?php print esc_html($error_message); ?>
<strong><?php _e( 'ERROR' ); ?>: </strong>
<?php print esc_html( $error_message ); ?>
</div>
<?php
return ob_get_clean();
}
/**
* Create a login button (link)
*
* @param array $atts Array of optional attributes to override login buton
* Create a login button (link).
*
* @param array $atts Array of optional attributes to override login buton
* functionality when used by shortcode.
*
* @return string
@ -139,7 +173,7 @@ class OpenID_Connect_Generic_Login_Form {
if ( ! empty( $atts['button_text'] ) ) {
$button_text = $atts['button_text'];
}
$text = apply_filters( 'openid-connect-generic-login-button-text', $button_text );
$href = $this->client_wrapper->get_authentication_url( $atts );
@ -152,8 +186,10 @@ class OpenID_Connect_Generic_Login_Form {
return ob_get_clean();
}
/*
/**
* Removes the login form from the HTML DOM
*
* @return void
*/
function remove_login_form() {
?>
@ -166,4 +202,5 @@ class OpenID_Connect_Generic_Login_Form {
</script>
<?php
}
}

@ -1,90 +1,132 @@
<?php
/**
* Simple class for logging messages to the options table
* Plugin logging class.
*
* @package OpenID_Connect_Generic
* @category Logging
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenID_Connect_Generic_Option_Logger class.
*
* Simple class for logging messages to the options table.
*
* @package OpenID_Connect_Generic
* @category Logging
*/
class OpenID_Connect_Generic_Option_Logger {
// wp option name/key
/**
* Thw WordPress option name/key.
*
* @var string
*/
private $option_name;
// default message type
/**
* The default message type.
*
* @var string
*/
private $default_message_type;
// the number of items to keep in the log
/**
* The number of items to keep in the log.
*
* @var int
*/
private $log_limit;
// whether or not the
/**
* Whether or not logging is enabled.
*
* @var bool
*/
private $logging_enabled;
// internal cache of logs
/**
* Internal cache of logs.
*
* @var array
*/
private $logs;
/**
* Setup the logger according to the needs of the instance
* Setup the logger according to the needs of the instance.
*
* @param string $option_name
* @param string $default_message_type
* @param bool|TRUE $logging_enabled
* @param int $log_limit
* @param string $option_name The plugin log WordPress option name.
* @param string $default_message_type The log message type.
* @param bool|TRUE $logging_enabled Whether logging is enabled.
* @param int $log_limit The log entry limit.
*/
function __construct( $option_name, $default_message_type = 'none', $logging_enabled = true, $log_limit = 1000 ){
function __construct( $option_name, $default_message_type = 'none', $logging_enabled = true, $log_limit = 1000 ) {
$this->option_name = $option_name;
$this->default_message_type = $default_message_type;
$this->logging_enabled = (bool) $logging_enabled;
$this->log_limit = (int) $log_limit;
$this->logging_enabled = boolval( $logging_enabled );
$this->log_limit = intval( $log_limit );
}
/**
* Subscribe logger to a set of filters
* Subscribe logger to a set of filters.
*
* @param $filter_names
* @param int $priority
* @param array|string $filter_names The array, or string, of the name(s) of an filter(s) to hook the logger into.
* @param int $priority The WordPress filter priority level.
*
* @return void
*/
function log_filters( $filter_names, $priority = 10 ){
function log_filters( $filter_names, $priority = 10 ) {
if ( ! is_array( $filter_names ) ) {
$filter_names = array( $filter_names );
}
foreach ( $filter_names as $filter ){
foreach ( $filter_names as $filter ) {
add_filter( $filter, array( $this, 'log_hook' ), $priority );
}
}
/**
* Subscribe logger to a set of actions
* Subscribe logger to a set of actions.
*
* @param array|string $action_names The array, or string, of the name(s) of an action(s) to hook the logger into.
* @param int $priority The WordPress action priority level.
*
* @param $action_names
* @param $priority
* @return void
*/
function log_actions( $action_names, $priority ){
function log_actions( $action_names, $priority ) {
if ( ! is_array( $action_names ) ) {
$action_names = array( $action_names );
}
foreach ( $action_names as $action ){
foreach ( $action_names as $action ) {
add_filter( $action, array( $this, 'log_hook' ), $priority );
}
}
/**
* Log the data
* Log the data.
*
* @param mixed $arg1 The hook argument.
*
* @param null $arg1
* @return null
* @return mixed
*/
function log_hook( $arg1 = null ){
function log_hook( $arg1 = null ) {
$this->log( func_get_args(), current_filter() );
return $arg1;
}
/**
* Save an array of data to the logs
* Save an array of data to the logs.
*
* @param mixed $data The data to be logged.
* @param mixed $type The type of log message.
*
* @param $data mixed
* @return bool
*/
public function log( $data, $type = null ) {
if ( (bool) $this->logging_enabled ) {
if ( boolval( $this->logging_enabled ) ) {
$logs = $this->get_logs();
$logs[] = $this->make_message( $data, $type );
$logs = $this->upkeep_logs( $logs );
@ -95,12 +137,12 @@ class OpenID_Connect_Generic_Option_Logger {
}
/**
* Retrieve all log messages
* Retrieve all log messages.
*
* @return array
*/
public function get_logs() {
if ( is_null( $this->logs ) ) {
if ( empty( $this->logs ) ) {
$this->logs = get_option( $this->option_name, array() );
}
@ -108,41 +150,40 @@ class OpenID_Connect_Generic_Option_Logger {
}
/**
* Get the name of the option where this log is stored
* Get the name of the option where this log is stored.
*
* @return string
*/
public function get_option_name(){
public function get_option_name() {
return $this->option_name;
}
/**
* Create a message array containing the data and other information
* Create a message array containing the data and other information.
*
* @param $data mixed
* @param $type
* @param mixed $data The log message data.
* @param mixed $type The log message type.
*
* @return array
*/
private function make_message( $data, $type ){
// determine the type of message
private function make_message( $data, $type ) {
// Determine the type of message.
if ( empty( $type ) ) {
$this->default_message_type;
$type = $this->default_message_type;
if ( is_array( $data ) && isset( $data['type'] ) ){
if ( is_array( $data ) && isset( $data['type'] ) ) {
$type = $data['type'];
}
else if ( is_wp_error( $data ) ){
} else if ( is_wp_error( $data ) ) {
$type = $data->get_error_code();
}
}
// construct our message
// Construct the message.
$message = array(
'type' => $type,
'time' => time(),
'user_ID' => get_current_user_id(),
'uri' => preg_replace('/code=([^&]+)/i', 'code=', $_SERVER['REQUEST_URI']),
'uri' => preg_replace( '/code=([^&]+)/i', 'code=', $_SERVER['REQUEST_URI'] ),
'data' => $data,
);
@ -150,54 +191,59 @@ class OpenID_Connect_Generic_Option_Logger {
}
/**
* Keep our log count under the limit
* Keep the log count under the limit.
*
* @param array $logs The plugin logs.
*
* @param $message array - extra data about the message
* @return array
*/
private function upkeep_logs( $logs ) {
$items_to_remove = count( $logs ) - $this->log_limit;
if ( $items_to_remove > 0 ){
// keep only the last $log_limit messages from the end
$logs = array_slice( $logs, ( $items_to_remove * -1) );
if ( $items_to_remove > 0 ) {
// Only keep the last $log_limit messages from the end.
$logs = array_slice( $logs, ( $items_to_remove * -1 ) );
}
return $logs;
}
/**
* Save the log messages
* Save the log messages.
*
* @param array $logs The array of log messages.
*
* @param $logs
* @return bool
*/
private function save_logs( $logs ){
// save our logs
private function save_logs( $logs ) {
// Save the logs.
$this->logs = $logs;
return update_option( $this->option_name, $logs, false );
}
/**
* Clear all log messages
* Clear all log messages.
*
* @return void
*/
public function clear_logs(){
public function clear_logs() {
$this->save_logs( array() );
}
/**
* Get a simple html table of all the logs
* Get a simple html table of all the logs.
*
* @param array $logs The array of log messages.
*
* @param array $logs
* @return string
*/
public function get_logs_table( $logs = array() ){
public function get_logs_table( $logs = array() ) {
if ( empty( $logs ) ) {
$logs = $this->get_logs();
}
$logs = array_reverse( $logs );
ini_set( 'xdebug.var_display_max_depth', -1 );
ini_set( 'xdebug.var_display_max_depth', '-1' );
ob_start();
?>
@ -221,7 +267,7 @@ class OpenID_Connect_Generic_Option_Logger {
</div>
<div>
<label><?php _e( 'Date' ); ?>: </label>
<?php print date( 'Y-m-d H:i:s', $log['time'] ); ?>
<?php print gmdate( 'Y-m-d H:i:s', $log['time'] ); ?>
</div>
<div>
<label><?php _e( 'User' ); ?>: </label>

@ -1,60 +1,177 @@
<?php
/**
* Class OpenId_Connect_Generic_Option_Settings
* WordPress options handling class.
*
* @package OpenID_Connect_Generic
* @category Settings
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* OpenId_Connect_Generic_Option_Settings class.
*
* WordPress options handling.
*
* @package OpenID_Connect_Generic
* @category Settings
*
* Legacy Settings:
*
* @property string $ep_login The login endpoint.
* @property string $ep_token The token endpoint.
* @property string $ep_userinfo The userinfo endpoint.
*
* OAuth Client Settings:
*
* @property string $login_type How the client (login form) should provide login options.
* @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.
* @property string $endpoint_login The IDP authorization endpoint URL.
* @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.
*
* 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 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.
* @property string $displayname_format The key(s) in the user claim array to formulate the user's display name.
* @property bool $identify_with_username The flag which indicates how the user's identity will be determined.
* @property int $state_time_limit The valid time limit of the state, in seconds. Defaults to 180 seconds.
*
* Plugin Settings:
*
* @property bool $enforce_privacy The flag to indicates whether a user us required to be authenticated to access the site.
* @property bool $alternate_redirect_uri The flag to indicate whether to use the alternative redirect URI.
* @property bool $token_refresh_enable The flag whether to support refresh tokens by IDPs.
* @property bool $link_existing_users The flag to indicate whether to link to existing WordPress-only accounts or greturn an error.
* @property bool $create_if_does_not_exist The flag to indicate whether to create new users or not.
* @property bool $redirect_user_back The flag to indicate whether to redirect the user back to the page on which they started.
* @property bool $redirect_on_logout The flag to indicate whether to redirect to the login screen on session expiration.
* @property bool $enable_logging The flag to enable/disable logging.
* @property int $log_limit The maximum number of log entries to keep.
*/
class OpenID_Connect_Generic_Option_Settings {
// wp option name/key
/**
* WordPress option name/key.
*
* @var string
*/
private $option_name;
// stored option values array
/**
* Stored option values array.
*
* @var array<mixed>
*/
private $values;
// default plugin settings values
/**
* Default plugin settings values.
*
* @var array<mixed>
*/
private $default_settings;
/**
* @param $option_name
* @param array $default_settings
* @param bool|TRUE $granular_defaults
* The class constructor.
*
* @param string $option_name The option name/key.
* @param array<mixed> $default_settings The default plugin settings values.
* @param bool $granular_defaults The granular defaults.
*/
function __construct( $option_name, $default_settings = array(), $granular_defaults = true ){
function __construct( $option_name, $default_settings = array(), $granular_defaults = true ) {
$this->option_name = $option_name;
$this->default_settings = $default_settings;
$this->values = get_option( $this->option_name, $this->default_settings );
$this->values = array();
if ( ! empty( $this->option_name ) ) {
get_option( $this->option_name, $this->default_settings );
}
if ( $granular_defaults ) {
$this->values = array_replace_recursive( $this->default_settings, $this->values );
}
}
function __get( $key ){
/**
* Magic getter for settings.
*
* @param string $key The array key/option name.
*
* @return mixed
*/
function __get( $key ) {
if ( isset( $this->values[ $key ] ) ) {
return $this->values[ $key ];
}
}
function __set( $key, $value ){
/**
* Magic setter for settings.
*
* @param string $key The array key/option name.
* @param mixed $value The option value.
*
* @return void
*/
function __set( $key, $value ) {
$this->values[ $key ] = $value;
}
function __isset( $key ){
/**
* Magic method to check is an attribute isset.
*
* @param string $key The array key/option name.
*
* @return bool
*/
function __isset( $key ) {
return isset( $this->values[ $key ] );
}
function __unset( $key ){
unset( $this->values[ $key ]);
/**
* Magic method to clear an attribute.
*
* @param string $key The array key/option name.
*
* @return void
*/
function __unset( $key ) {
unset( $this->values[ $key ] );
}
function get_values(){
/**
* Get the plugin settings array.
*
* @return array
*/
function get_values() {
return $this->values;
}
/**
* Get the plugin WordPress options name.
*
* @return string
*/
function get_option_name() {
return $this->option_name;
}
function save(){
/**
* Save the plugin options to the WordPress options table.
*
* @return void
*/
function save() {
update_option( $this->option_name, $this->values );
}
}

@ -1,35 +1,205 @@
<?php
/**
* Plugin Admin settings page class.
*
* @package OpenID_Connect_Generic
* @category Settings
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
/**
* Class OpenID_Connect_Generic_Settings_Page.
* OpenID_Connect_Generic_Settings_Page class.
*
* Admin settings page.
*
* @package OpenID_Connect_Generic
* @category Settings
*/
class OpenID_Connect_Generic_Settings_Page {
// local copy of the settings provided by the base plugin
/**
* Local copy of the settings provided by the base plugin.
*
* @var OpenID_Connect_Generic_Option_Settings
*/
private $settings;
// The controlled list of settings & associated
// defined during construction for i18n reasons
/**
* Instance of the plugin logger.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
/**
* The controlled list of settings & associated defined during
* construction for i18n reasons.
*
* @var array
*/
private $settings_fields = array();
// options page slug
/**
* Options page slug.
*
* @var string
*/
private $options_page_name = 'openid-connect-generic-settings';
// options page settings group name
/**
* Options page settings group name.
*
* @var string
*/
private $settings_field_group;
/**
* @param OpenID_Connect_Generic_Option_Settings $settings
* @param OpenID_Connect_Generic_Option_Logger $logger
* Settings page class constructor.
*
* @param OpenID_Connect_Generic_Option_Settings $settings The plugin settings object.
* @param OpenID_Connect_Generic_Option_Logger $logger The plugin logging class object.
*/
function __construct( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_Option_Logger $logger ) {
$this->settings = $settings;
$this->logger = $logger;
$this->settings_field_group = $this->settings->get_option_name() . '-group';
/*
* Simple settings fields simply have:
$fields = $this->get_settings_fields();
// Some simple pre-processing.
foreach ( $fields as $key => &$field ) {
$field['key'] = $key;
$field['name'] = $this->settings->get_option_name() . '[' . $key . ']';
}
// Allow alterations of the fields.
$this->settings_fields = $fields;
}
/**
* Hook the settings page into WordPress.
*
* @param OpenID_Connect_Generic_Option_Settings $settings A plugin settings object instance.
* @param OpenID_Connect_Generic_Option_Logger $logger A plugin logger object instance.
*
* @return void
*/
static public function register( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_Option_Logger $logger ) {
$settings_page = new self( $settings, $logger );
// Add our options page the the admin menu.
add_action( 'admin_menu', array( $settings_page, 'admin_menu' ) );
// Register our settings.
add_action( 'admin_init', array( $settings_page, 'admin_init' ) );
}
/**
* Implements hook admin_menu to add our options/settings page to the
* dashboard menu.
*
* @return void
*/
public function admin_menu() {
add_options_page(
__( 'OpenID Connect - Generic Client' ),
__( 'OpenID Connect Client' ),
'manage_options',
$this->options_page_name,
array( $this, 'settings_page' )
);
}
/**
* Implements hook admin_init to register our settings.
*
* @return void
*/
public function admin_init() {
register_setting(
$this->settings_field_group,
$this->settings->get_option_name(),
array(
$this,
'sanitize_settings',
)
);
add_settings_section(
'client_settings',
__( 'Client Settings' ),
array( $this, 'client_settings_description' ),
$this->options_page_name
);
add_settings_section(
'user_settings',
__( 'WordPress User Settings' ),
array( $this, 'user_settings_description' ),
$this->options_page_name
);
add_settings_section(
'authorization_settings',
__( 'Authorization Settings' ),
array( $this, 'authorization_settings_description' ),
$this->options_page_name
);
add_settings_section(
'log_settings',
__( 'Log Settings' ),
array( $this, 'log_settings_description' ),
$this->options_page_name
);
// Preprocess fields and add them to the page.
foreach ( $this->settings_fields as $key => $field ) {
// Make sure each key exists in the settings array.
if ( ! isset( $this->settings->{ $key } ) ) {
$this->settings->{ $key } = null;
}
// Determine appropriate output callback.
switch ( $field['type'] ) {
case 'checkbox':
$callback = 'do_checkbox';
break;
case 'select':
$callback = 'do_select';
break;
case 'text':
default:
$callback = 'do_text_field';
break;
}
// Add the field.
add_settings_field(
$key,
$field['title'],
array( $this, $callback ),
$this->options_page_name,
$field['section'],
$field
);
}
}
/**
* Get the plugin settings fields definition.
*
* @return array
*/
private function get_settings_fields() {
/**
* Simple settings fields have:
*
* - title
* - description
@ -205,131 +375,25 @@ class OpenID_Connect_Generic_Settings_Page {
),
);
$fields = apply_filters( 'openid-connect-generic-settings-fields', $fields );
// some simple pre-processing
foreach ( $fields as $key => &$field ) {
$field['key'] = $key;
$field['name'] = $this->settings->get_option_name() . '[' . $key . ']';
}
return apply_filters( 'openid-connect-generic-settings-fields', $fields );
// allow alterations of the fields
$this->settings_fields = $fields;
}
/**
* @param \OpenID_Connect_Generic_Option_Settings $settings
* @param \OpenID_Connect_Generic_Option_Logger $logger
* Sanitization callback for settings/option page.
*
* @return \OpenID_Connect_Generic_Settings_Page
*/
static public function register( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_Option_Logger $logger ){
$settings_page = new self( $settings, $logger );
// add our options page the the admin menu
add_action( 'admin_menu', array( $settings_page, 'admin_menu' ) );
// register our settings
add_action( 'admin_init', array( $settings_page, 'admin_init' ) );
return $settings_page;
}
/**
* Implements hook admin_menu to add our options/settings page to the
* dashboard menu
*/
public function admin_menu() {
add_options_page(
__( 'OpenID Connect - Generic Client' ),
__( 'OpenID Connect Client' ),
'manage_options',
$this->options_page_name,
array( $this, 'settings_page' ) );
}
/**
* Implements hook admin_init to register our settings
*/
public function admin_init() {
register_setting( $this->settings_field_group, $this->settings->get_option_name(), array(
$this,
'sanitize_settings'
) );
add_settings_section( 'client_settings',
__( 'Client Settings' ),
array( $this, 'client_settings_description' ),
$this->options_page_name
);
add_settings_section( 'user_settings',
__( 'WordPress User Settings' ),
array( $this, 'user_settings_description' ),
$this->options_page_name
);
add_settings_section( 'authorization_settings',
__( 'Authorization Settings' ),
array( $this, 'authorization_settings_description' ),
$this->options_page_name
);
add_settings_section( 'log_settings',
__( 'Log Settings' ),
array( $this, 'log_settings_description' ),
$this->options_page_name
);
// preprocess fields and add them to the page
foreach ( $this->settings_fields as $key => $field ) {
// make sure each key exists in the settings array
if ( ! isset( $this->settings->{ $key } ) ) {
$this->settings->{ $key } = null;
}
// determine appropriate output callback
switch ( $field['type'] ) {
case 'checkbox':
$callback = 'do_checkbox';
break;
case 'select':
$callback = 'do_select';
break;
case 'text':
default:
$callback = 'do_text_field';
break;
}
// add the field
add_settings_field( $key, $field['title'],
array( $this, $callback ),
$this->options_page_name,
$field['section'],
$field
);
}
}
/**
* Sanitization callback for settings/option page
*
* @param $input - submitted settings values
* @param array $input The submitted settings values.
*
* @return array
*/
public function sanitize_settings( $input ) {
$options = array();
// loop through settings fields to control what we're saving
// Loop through settings fields to control what we're saving.
foreach ( $this->settings_fields as $key => $field ) {
if ( isset( $input[ $key ] ) ) {
$options[ $key ] = sanitize_text_field( trim( $input[ $key ] ) );
}
else {
} else {
$options[ $key ] = '';
}
}
@ -338,12 +402,14 @@ class OpenID_Connect_Generic_Settings_Page {
}
/**
* Output the options/settings page
* Output the options/settings page.
*
* @return void
*/
public function settings_page() {
$redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' );
if ( $this->settings->alternate_redirect_uri ){
if ( $this->settings->alternate_redirect_uri ) {
$redirect_uri = site_url( '/openid-connect-authorize' );
}
?>
@ -356,7 +422,7 @@ class OpenID_Connect_Generic_Settings_Page {
do_settings_sections( $this->options_page_name );
submit_button();
// simple debug to view settings array
// Simple debug to view settings array.
if ( isset( $_GET['debug'] ) ) {
var_dump( $this->settings->get_values() );
}
@ -390,47 +456,55 @@ class OpenID_Connect_Generic_Settings_Page {
}
/**
* Output a standard text field
* Output a standard text field.
*
* @param array $field The settings field definition array.
*
* @param $field
* @return void
*/
public function do_text_field( $field ) {
?>
<input type="<?php print esc_attr( $field['type'] ); ?>"
id="<?php print esc_attr( $field['key'] ); ?>"
class="large-text"
name="<?php print esc_attr( $field['name'] ); ?>"
value="<?php print esc_attr( $this->settings->{ $field['key'] } ); ?>">
id="<?php print esc_attr( $field['key'] ); ?>"
class="large-text"
name="<?php print esc_attr( $field['name'] ); ?>"
value="<?php print esc_attr( $this->settings->{ $field['key'] } ); ?>">
<?php
$this->do_field_description( $field );
}
/**
* Output a checkbox for a boolean setting
* - hidden field is default value so we don't have to check isset() on save
* Output a checkbox for a boolean setting.
* - hidden field is default value so we don't have to check isset() on save.
*
* @param $field
* @param array $field The settings field definition array.
*
* @return void
*/
public function do_checkbox( $field ) {
?>
<input type="hidden" name="<?php print esc_attr( $field['name'] ); ?>" value="0">
<input type="checkbox"
id="<?php print esc_attr( $field['key'] ); ?>"
name="<?php print esc_attr( $field['name'] ); ?>"
value="1"
id="<?php print esc_attr( $field['key'] ); ?>"
name="<?php print esc_attr( $field['name'] ); ?>"
value="1"
<?php checked( $this->settings->{ $field['key'] }, 1 ); ?>>
<?php
$this->do_field_description( $field );
}
/**
* @param $field
* Output a select control.
*
* @param array $field The settings field definition array.
*
* @return void
*/
function do_select( $field ) {
$current_value = isset( $this->settings->{ $field['key'] } ) ? $this->settings->{ $field['key'] } : '';
?>
<select name="<?php print esc_attr( $field['name'] ); ?>">
<?php foreach ( $field['options'] as $value => $text ): ?>
<?php foreach ( $field['options'] as $value => $text ) : ?>
<option value="<?php print esc_attr( $value ); ?>" <?php selected( $value, $current_value ); ?>><?php print esc_html( $text ); ?></option>
<?php endforeach; ?>
</select>
@ -439,9 +513,11 @@ class OpenID_Connect_Generic_Settings_Page {
}
/**
* Simply output the field description, and example if present
* Output the field description, and example if present.
*
* @param $field
* @param array $field The settings field definition array.
*
* @return void
*/
public function do_field_description( $field ) {
?>
@ -455,18 +531,38 @@ class OpenID_Connect_Generic_Settings_Page {
<?php
}
/**
* Output the 'Client Settings' plugin setting section description.
*
* @return void
*/
public function client_settings_description() {
_e( 'Enter your OpenID Connect identity provider settings' );
}
/**
* Output the 'WordPress User Settings' plugin setting section description.
*
* @return void
*/
public function user_settings_description() {
_e( 'Modify the interaction between OpenID Connect and WordPress users' );
}
/**
* Output the 'Authorization Settings' plugin setting section description.
*
* @return void
*/
public function authorization_settings_description() {
_e( 'Control the authorization mechanics of the site' );
}
/**
* Output the 'Log Settings' plugin setting section description.
*
* @return void
*/
public function log_settings_description() {
_e( 'Log information about login attempts through OpenID Connect Generic' );
}

@ -2,13 +2,12 @@
/**
* OpenID Connect Generic Client
*
* This plugin provides the ability to authenticate users with Identity
* This plugin provides the ability to authenticate users with Identity
* Providers using the OpenID Connect OAuth2 API with Authorization Code Flow.
*
* @category Authentication
* @package OpenID_Connect_Generic
* @category General
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @author Tim Nolte <tim.nolte@ndigitals.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
* @link https://github.com/daggerhart
@ -20,6 +19,8 @@
* Version: 3.7.1
* Author: daggerhart
* Author URI: http://www.daggerhart.com
* Text Domain: daggerhart-openid-connect-generic
* Domain Path: /languages
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* GitHub Plugin URI: https://github.com/daggerhart/openid-connect-generic
@ -64,50 +65,80 @@ Notes
*/
/**
* OpenID_Connect_Generic class.
*
* Defines plugin initialization functionality.
*
* @package OpenID_Connect_Generic
* @category General
*/
class OpenID_Connect_Generic {
// plugin version
/**
* Plugin version.
*
* @var
*/
const VERSION = '3.7.1';
// plugin settings
/**
* Plugin settings.
*
* @var OpenID_Connect_Generic_Option_Settings
*/
private $settings;
// plugin logs
/**
* Plugin logs.
*
* @var OpenID_Connect_Generic_Option_Logger
*/
private $logger;
// openid connect generic client
/**
* Openid Connect Generic client
*
* @var OpenID_Connect_Generic_Client
*/
private $client;
// settings admin page
private $settings_page;
// login form adjustments
private $login_form;
/**
* Client wrapper.
*
* @var OpenID_Connect_Generic_Client_Wrapper
*/
private $client_wrapper;
/**
* Setup the plugin
*
* @param OpenID_Connect_Generic_Option_Settings $settings
* @param OpenID_Connect_Generic_Option_Logger $logger
* @param OpenID_Connect_Generic_Option_Settings $settings The settings object.
* @param OpenID_Connect_Generic_Option_Logger $logger The loggin object.
*
* @return void
*/
function __construct( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_Option_Logger $logger ){
function __construct( OpenID_Connect_Generic_Option_Settings $settings, OpenID_Connect_Generic_Option_Logger $logger ) {
$this->settings = $settings;
$this->logger = $logger;
}
/**
* WP Hook 'init'
* WordPress Hook 'init'.
*
* @return void
*/
function init(){
function init() {
$redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' );
if ( $this->settings->alternate_redirect_uri ){
if ( $this->settings->alternate_redirect_uri ) {
$redirect_uri = site_url( '/openid-connect-authorize' );
}
$state_time_limit = 180;
if ($this->settings->state_time_limit) {
$state_time_limit = intval($this->settings->state_time_limit);
if ( $this->settings->state_time_limit ) {
$state_time_limit = intval( $this->settings->state_time_limit );
}
$this->client = new OpenID_Connect_Generic_Client(
@ -127,42 +158,44 @@ class OpenID_Connect_Generic {
return;
}
$this->login_form = OpenID_Connect_Generic_Login_Form::register( $this->settings, $this->client_wrapper );
OpenID_Connect_Generic_Login_Form::register( $this->settings, $this->client_wrapper );
// add a shortcode to get the auth url
// Add a shortcode to get the auth URL.
add_shortcode( 'openid_connect_generic_auth_url', array( $this->client_wrapper, 'get_authentication_url' ) );
// add actions to our scheduled cron jobs
add_action( 'openid-connect-generic-cron-daily', [ $this, 'cron_states_garbage_collection'] );
// Add actions to our scheduled cron jobs.
add_action( 'openid-connect-generic-cron-daily', array( $this, 'cron_states_garbage_collection' ) );
$this->upgrade();
if ( is_admin() ){
$this->settings_page = OpenID_Connect_Generic_Settings_Page::register( $this->settings, $this->logger );
if ( is_admin() ) {
OpenID_Connect_Generic_Settings_Page::register( $this->settings, $this->logger );
}
}
/**
* Check if privacy enforcement is enabled, and redirect users that aren't
* logged in.
*
* @return void
*/
function enforce_privacy_redirect() {
if ( $this->settings->enforce_privacy && ! is_user_logged_in() ) {
// our client endpoint relies on the wp admind ajax endpoint
if ( ! defined( 'DOING_AJAX') || ! DOING_AJAX || ! isset( $_GET['action'] ) || $_GET['action'] != 'openid-connect-authorize' ) {
// The client endpoint relies on the wp admind ajax endpoint.
if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX || ! isset( $_GET['action'] ) || 'openid-connect-authorize' != $_GET['action'] ) {
auth_redirect();
}
}
}
/**
* Enforce privacy settings for rss feeds
* Enforce privacy settings for rss feeds.
*
* @param $content
* @param string $content The content.
*
* @return mixed
*/
function enforce_privacy_feeds( $content ){
function enforce_privacy_feeds( $content ) {
if ( $this->settings->enforce_privacy && ! is_user_logged_in() ) {
$content = 'Private site';
}
@ -171,13 +204,15 @@ class OpenID_Connect_Generic {
/**
* Handle plugin upgrades
*
* @return void
*/
function upgrade(){
function upgrade() {
$last_version = get_option( 'openid-connect-generic-plugin-version', 0 );
$settings = $this->settings;
if ( version_compare( self::VERSION, $last_version, '>' ) ) {
// upgrade required
// An upgrade is required.
self::setup_cron_jobs();
// @todo move this to another file for upgrade scripts
@ -190,7 +225,7 @@ class OpenID_Connect_Generic {
$settings->save();
}
// update the stored version number
// Update the stored version number.
update_option( 'openid-connect-generic-plugin-version', self::VERSION );
}
}
@ -198,21 +233,25 @@ class OpenID_Connect_Generic {
/**
* Expire state transients by attempting to access them and allowing the
* transient's own mechanisms to delete any that have expired.
*
* @return void
*/
function cron_states_garbage_collection() {
global $wpdb;
$states = $wpdb->get_col( "SELECT `option_name` FROM {$wpdb->options} WHERE `option_name` LIKE '_transient_openid-connect-generic-state--%'" );
if ( !empty( $states ) ) {
if ( ! empty( $states ) ) {
foreach ( $states as $state ) {
$transient = str_replace("_transient_", "", $state);
get_transient( $transient );
$transient = str_replace( '_transient_', '', $state );
get_transient( $transient );
}
}
}
/**
* Ensure cron jobs are added to the schedule.
*
* @return void
*/
static public function setup_cron_jobs() {
if ( ! wp_next_scheduled( 'openid-connect-generic-cron-daily' ) ) {
@ -222,6 +261,8 @@ class OpenID_Connect_Generic {
/**
* Activation hook.
*
* @return void
*/
static public function activation() {
self::setup_cron_jobs();
@ -229,31 +270,34 @@ class OpenID_Connect_Generic {
/**
* Deactivation hook.
*
* @return void
*/
static public function deactivation() {
wp_clear_scheduled_hook( 'openid-connect-generic-cron-daily' );
}
/**
* Simple autoloader
* Simple autoloader.
*
* @param string $class The class name.
*
* @param $class
* @return void
*/
static public function autoload( $class ) {
$prefix = 'OpenID_Connect_Generic_';
if ( stripos($class, $prefix) !== 0 ) {
if ( stripos( $class, $prefix ) !== 0 ) {
return;
}
$filename = $class . '.php';
// internal files are all lowercase and use dashes in filenames
// Internal files are all lowercase and use dashes in filenames.
if ( false === strpos( $filename, '\\' ) ) {
$filename = strtolower( str_replace( '_', '-', $filename ) );
}
else {
$filename = str_replace('\\', DIRECTORY_SEPARATOR, $filename);
} else {
$filename = str_replace( '\\', DIRECTORY_SEPARATOR, $filename );
}
$filepath = dirname( __FILE__ ) . '/includes/' . $filename;
@ -264,16 +308,23 @@ class OpenID_Connect_Generic {
}
/**
* Instantiate the plugin and hook into WP
* Instantiate the plugin and hook into WordPress.
*
* @return void
*/
static public function bootstrap(){
static public 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' ) );
$settings = new OpenID_Connect_Generic_Option_Settings(
'openid_connect_generic_settings',
// default settings values
// Default settings values.
array(
// oauth client settings
// OAuth client settings.
'login_type' => 'button',
'client_id' => '',
'client_secret' => '',
@ -283,7 +334,7 @@ class OpenID_Connect_Generic {
'endpoint_token' => '',
'endpoint_end_session' => '',
// non-standard settings
// Non-standard settings.
'no_sslverify' => 0,
'http_request_timeout' => 5,
'identity_key' => 'preferred_username',
@ -292,7 +343,7 @@ class OpenID_Connect_Generic {
'displayname_format' => '',
'identify_with_username' => false,
// plugin settings
// Plugin settings.
'enforce_privacy' => 0,
'alternate_redirect_uri' => 0,
'token_refresh_enable' => 1,
@ -311,15 +362,15 @@ class OpenID_Connect_Generic {
add_action( 'init', array( $plugin, 'init' ) );
// privacy hooks
// Privacy hooks.
add_action( 'template_redirect', array( $plugin, 'enforce_privacy_redirect' ), 0 );
add_filter( 'the_content_feed', array( $plugin, 'enforce_privacy_feeds' ), 999 );
add_filter( 'the_excerpt_rss', array( $plugin, 'enforce_privacy_feeds' ), 999 );
add_filter( 'the_excerpt_rss', array( $plugin, 'enforce_privacy_feeds' ), 999 );
add_filter( 'comment_text_rss', array( $plugin, 'enforce_privacy_feeds' ), 999 );
}
}
OpenID_Connect_Generic::bootstrap();
register_activation_hook( __FILE__, [ 'OpenID_Connect_Generic', 'activation' ] );
register_deactivation_hook( __FILE__, [ 'OpenID_Connect_Generic', 'deactivation' ] );
register_activation_hook( __FILE__, array( 'OpenID_Connect_Generic', 'activation' ) );
register_deactivation_hook( __FILE__, array( 'OpenID_Connect_Generic', 'deactivation' ) );

18708
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,60 @@
{
"name": "openid-connect-generic",
"version": "3.7.0",
"description": "OpenID Connect generic WordPress plugin.",
"main": "Gruntfile.js",
"repository": {
"type": "git",
"url": "https://github.com/daggerhart/openid-connect-generic"
},
"keywords": [
"wordpress",
"openid"
],
"author": "Jonathan Daggerhart",
"license": "GPL-2.0-only",
"bugs": {
"url": "https://github.com/daggerhart/openid-connect-generic/issues"
},
"homepage": "https://github.com/daggerhart/openid-connect-generic#readme",
"dependencies": {
"dev-require": "^0.1.0"
},
"devDependencies": {
"@floatwork/grunt-po2mo": "^0.3.0",
"@ndigitals/grunt-checkrepo": "^0.2.0",
"@wordpress/env": "^1.6.0",
"@wordpress/scripts": "^12.1.0",
"grunt": "~1.0.4",
"grunt-checkbranch": "^1.0.4",
"grunt-checktextdomain": "^1.0.1",
"grunt-contrib-clean": "^2.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-gitinfo": "^0.1.9",
"grunt-shell": "^2.1.0",
"grunt-wp-i18n": "^1.0.3",
"grunt-wp-readme-to-markdown": "~2.0.0",
"load-grunt-tasks": "^3.5",
"puppeteer": "^1.20.0",
"typescript": "^3.9.7"
},
"wp-env": {
"plugin-dir": "daggerhart-openid-connect-generic",
"plugin-name": "OpenID Connect Generic",
"docker-template": "./docker-compose.override.yml",
"welcome-build-command": "npm run env start"
},
"scripts": {
"grunt": "node_modules/.bin/grunt",
"test": "npm run grunt test",
"build": "npm run grunt build",
"wp-env": "wp-env",
"wp": "wp-env run cli wp",
"makepot": "wp-env run cli wp i18n make-pot . languages/ --slug=daggerhart-openid-connect-generic --include=openid-connect-generic.php,includes",
"lint": "npm run lint:php",
"lint:php": "composer run-script phpcs .",
"lint-fix:php": "composer run-script phpcbf .",
"analyze": "npm run analyze:php",
"analyze:php": "composer run-script phpstan analyze ."
}
}

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<ruleset name="WordPress Coding Standards for Plugins">
<description>Generally-applicable sniffs for WordPress plugins</description>
<rule ref="WordPress-Core">
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase" />
<exclude name="WordPress.Files.FileName.InvalidClassFileName" />
</rule>
<rule ref="WordPress-Docs" />
<!-- Check all PHP files in directory tree by default. -->
<arg name="basepath" value="." />
<arg name="extensions" value="php" />
<arg name="report" value="summary" />
<!-- Show colors. -->
<arg name="colors" />
<!-- Show progress. -->
<arg value="p" />
<arg value="n" />
<file>.</file>
<!-- Show sniff codes in all reports -->
<arg value="s"/>
<exclude-pattern>*/dist/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<exclude-pattern>*/tools/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/wordpress/*</exclude-pattern>
</ruleset>

@ -0,0 +1,51 @@
#$ composer update --optimize-autoloader
#$ vendor/bin/phpstan analyze
includes:
# @see https://github.com/phpstan/phpstan-src/blob/master/conf/bleedingEdge.neon
- phar://phpstan.phar/conf/bleedingEdge.neon
# Include this extension
# - vendor/szepeviktor/phpstan-wordpress/extension.neon
parameters:
level: 5
inferPrivatePropertyTypeFromConstructor: true
bootstrapFiles:
- tests/phpstan-bootstrap.php
# autoload_files:
# Missing constants, function and class stubs
# - tests/phpstan/bootstrap.php
# Plugin stubs
# - tests/phpstan/PLUGIN-stubs.php
# Procedural code
# - myplugin-functions.php
# autoload_directories:
# - inc/
paths:
- includes/
- ./
excludes_analyse:
- node_modules/
- scripts/
- tests/
- tools/
- vendor/
- wordpress/
# scanFiles:
# - includes/class.php
scanDirectories:
- wordpress/src/
ignoreErrors:
# Uses func_get_args()
- '#^Function apply_filters(_ref_array)? invoked with [34567] parameters, 2 required\.$#'
# Fixed in WordPress 5.3
# - '#^Function do_action(_ref_array)? invoked with [3456] parameters, 1-2 required\.$#'
# - '#^Function current_user_can invoked with 2 parameters, 1 required\.$#'
# - '#^Function add_query_arg invoked with [123] parameters?, 0 required\.$#'
# - '#^Function wp_sprintf invoked with [23456] parameters, 1 required\.$#'
# - '#^Function add_post_type_support invoked with [345] parameters, 2 required\.$#'
# - '#^Function ((get|add)_theme_support|current_theme_supports) invoked with [2345] parameters, 1 required\.$#'
# https://core.trac.wordpress.org/ticket/43304
# - '/^Parameter #2 \$deprecated of function load_plugin_textdomain expects string, false given\.$/'
# WP-CLI accepts a class as callable
# - '/^Parameter #2 \$callable of static method WP_CLI::add_command\(\) expects callable\(\): mixed, \S+ given\.$/'
# Please consider commenting ignores: issue URL or reason for ignoring

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<phpunit
bootstrap="tests/bootstrap.php"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite name="Includes">
<directory suffix="_test.php">./tests/phpunit/includes/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
<!-- <file>/path/to/file</file> -->
<exclude>
<directory suffix=".php">src/views</directory>
<!-- <file>/path/to/file</file> -->
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="clover.xml"/>
</logging>
</phpunit>

@ -0,0 +1,127 @@
#!/usr/bin/env bash
if [ $# -lt 3 ]; then
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]"
exit 1
fi
DB_NAME=$1
DB_USER=$2
DB_PASS=$3
DB_HOST=${4-localhost}
WP_VERSION=${5-latest}
SKIP_DB_CREATE=${6-false}
WP_TESTS_DIR=${WP_TESTS_DIR-${TMPDIR-/tmp}/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-${TMPDIR-/tmp}/wordpress/}
download() {
if [ `which curl` ]; then
curl -s "$1" > "$2";
elif [ `which wget` ]; then
wget -nv -O "$2" "$1"
fi
}
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then
WP_TESTS_TAG="tags/$WP_VERSION"
elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
WP_TESTS_TAG="trunk"
else
# http serves a single offer, whereas https serves multiple. we only want one
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
if [[ -z "$LATEST_VERSION" ]]; then
echo "Latest WordPress version could not be found"
exit 1
fi
WP_TESTS_TAG="tags/$LATEST_VERSION"
fi
set -ex
install_wp() {
if [ -d $WP_CORE_DIR ]; then
return;
fi
mkdir -p $WP_CORE_DIR
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
mkdir -p /tmp/wordpress-nightly
download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip
unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/
mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR
else
if [ $WP_VERSION == 'latest' ]; then
local ARCHIVE_NAME='latest'
else
local ARCHIVE_NAME="wordpress-$WP_VERSION"
fi
download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz
tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR
fi
download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
}
install_test_suite() {
# portable in-place argument for both GNU sed and Mac OSX sed
if [[ $(uname -s) == 'Darwin' ]]; then
local ioption='-i .bak'
else
local ioption='-i'
fi
# set up testing suite if it doesn't yet exist
if [ ! -d $WP_TESTS_DIR ]; then
# set up testing suite
mkdir -p $WP_TESTS_DIR
svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
fi
if [ ! -f wp-tests-config.php ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
fi
}
install_db() {
if [ ${SKIP_DB_CREATE} = "true" ]; then
return 0
fi
# parse DB_HOST for port or socket references
local PARTS=(${DB_HOST//\:/ })
local DB_HOSTNAME=${PARTS[0]};
local DB_SOCK_OR_PORT=${PARTS[1]};
local EXTRA=""
if ! [ -z $DB_HOSTNAME ] ; then
if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
elif ! [ -z $DB_SOCK_OR_PORT ] ; then
EXTRA=" --socket=$DB_SOCK_OR_PORT"
elif ! [ -z $DB_HOSTNAME ] ; then
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
fi
fi
# create database
mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
}
install_wp
install_test_suite
install_db

@ -0,0 +1,19 @@
<?php
/**
* Phpstan bootstrap file.
*
* @package OpenID_Connect_Generic
* @author Jonathan Daggerhart <jonathan@daggerhart.com>
* @author Tim Nolte <tim.nolte@ndigitals.com>
* @copyright 2015-2020 daggerhart
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
* @link https://github.com/daggerhart
*/
// Define WordPress language directory.
defined( 'WP_LANG_DIR' ) || define( 'WP_LANG_DIR', 'wordpress/src/wp-includes/languages/' );
defined( 'COOKIE_DOMAIN' ) || define( 'COOKIE_DOMAIN', 'localhost' );
defined( 'COOKIEPATH' ) || define( 'COOKIEPATH', '/');

@ -0,0 +1,30 @@
server {
index index.php index.html;
listen 80 default_server;
server_name localhost penguin.linux.test ${LOCAL_HOSTNAME};
client_max_body_size 1g;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/${LOCAL_DIR};
absolute_redirect off;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}

@ -0,0 +1,37 @@
<?php
/**
* Plugin Name: MailHog PhpMailer Setup
* Description: Establishes a connection between the PhpMailer library and the MailHog local-dev Docker container.
*
* @package OpenID_Connect_Generic_MuPlugins
*/
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Provides the configuration for PhpMailer to use MailHog.
*
* @param PHPMailer $phpmailer The PHPMailer instance.
*
* @return void
*/
function mailhog_phpmailer_setup( PHPMailer $phpmailer ) {
defined( 'SMTP_HOST' ) || define( 'SMTP_HOST', 'mailhog' );
// PHPMailer doesn't follow WordPress naming conventions so this can be ignored.
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$phpmailer->Host = SMTP_HOST;
defined( 'SMTP_PORT' ) || define( 'SMTP_PORT', 1025 );
// PHPMailer doesn't follow WordPress naming conventions so this can be ignored.
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$phpmailer->Port = SMTP_PORT;
$phpmailer->IsSMTP();
}
add_action( 'phpmailer_init', 'mailhog_phpmailer_setup', 10, 2 );
Loading…
Cancel
Save