Add Entra group restriction, button-only mode and detailed README
Groups: a Graph-backed picker on the Security tab (search by name or paste object IDs) stores allowed group IDs. During sign-in membership is read from the ID token's groups claim when present, otherwise verified through Microsoft Graph checkMemberGroups (transitive). Verification failures refuse the sign-in. Button-only mode: hides the password form and the lost-password link and rejects password sign-ins on wp-login.php via the authenticate filter. A generated, rate-limited fallback key re-enables the form for 30 minutes per browser; M365_LOGIN_DISABLE_BUTTON_ONLY switches the mode off from wp-config.php. Also: new German-language README with sequence diagram, settings reference, troubleshooting and hook examples; readme.txt external services section now covers Microsoft Graph; translations updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJxAHYdMfKPoN4koRc4Ci2
This commit is contained in:
parent
1517e7e3bc
commit
1202283eda
20 changed files with 2241 additions and 517 deletions
|
|
@ -35,7 +35,11 @@ class M365_Login_Settings {
|
|||
'upn_fallback' => 1,
|
||||
'bind_oid' => 1,
|
||||
'allowed_domains' => '',
|
||||
'allowed_groups' => array(), // id => display name.
|
||||
'remember_me' => 0,
|
||||
// Button-only mode.
|
||||
'button_only' => 0,
|
||||
'fallback_key' => '',
|
||||
// Button appearance.
|
||||
'button_text' => __( 'Sign in with Microsoft', 'm365-login' ),
|
||||
'button_icon' => '', // Empty = bundled Microsoft logo.
|
||||
|
|
@ -157,6 +161,71 @@ class M365_Login_Settings {
|
|||
return array_values( array_unique( $out ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed Entra group IDs (lowercase GUIDs) mapped to display names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function allowed_groups() {
|
||||
$raw = $this->get( 'allowed_groups', array() );
|
||||
$out = array();
|
||||
if ( is_array( $raw ) ) {
|
||||
foreach ( $raw as $id => $name ) {
|
||||
$id = strtolower( (string) $id );
|
||||
if ( self::is_guid( $id ) ) {
|
||||
$out[ $id ] = (string) $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the password form is hidden and password sign-in blocked.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function button_only() {
|
||||
if ( defined( 'M365_LOGIN_DISABLE_BUTTON_ONLY' ) && M365_LOGIN_DISABLE_BUTTON_ONLY ) {
|
||||
return false;
|
||||
}
|
||||
return $this->is_configured() && (bool) $this->get( 'button_only' ) && '' !== $this->fallback_key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Secret key that re-enables the password form.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fallback_key() {
|
||||
$key = (string) $this->get( 'fallback_key', '' );
|
||||
return preg_match( '/^[A-Za-z0-9]{16,64}$/', $key ) ? $key : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* URL that shows the password form again when button-only mode is active.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fallback_url() {
|
||||
$key = $this->fallback_key();
|
||||
return '' === $key ? '' : add_query_arg( 'm365_fallback', $key, wp_login_url() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new fallback key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate_fallback_key() {
|
||||
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
|
||||
$key = '';
|
||||
for ( $i = 0; $i < 24; $i++ ) {
|
||||
$key .= $alphabet[ random_int( 0, strlen( $alphabet ) - 1 ) ];
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitises settings coming from the admin form.
|
||||
*
|
||||
|
|
@ -215,6 +284,31 @@ class M365_Login_Settings {
|
|||
$domains = preg_replace( '/[^a-z0-9.\-@,;\s]/i', '', $domains );
|
||||
$out['allowed_domains'] = trim( (string) $domains );
|
||||
|
||||
// Allowed groups: GUID => name.
|
||||
$groups = array();
|
||||
if ( ! empty( $input['allowed_groups'] ) && is_array( $input['allowed_groups'] ) ) {
|
||||
foreach ( $input['allowed_groups'] as $id => $name ) {
|
||||
$id = strtolower( trim( sanitize_text_field( wp_unslash( (string) $id ) ) ) );
|
||||
if ( ! self::is_guid( $id ) ) {
|
||||
continue;
|
||||
}
|
||||
$name = sanitize_text_field( wp_unslash( (string) $name ) );
|
||||
$groups[ $id ] = '' === $name ? $id : mb_substr( $name, 0, 120 );
|
||||
if ( count( $groups ) >= 100 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$out['allowed_groups'] = $groups;
|
||||
|
||||
// Button-only mode + fallback key.
|
||||
$out['button_only'] = empty( $input['button_only'] ) ? 0 : 1;
|
||||
$key = (string) $current['fallback_key'];
|
||||
if ( ! empty( $input['fallback_regenerate'] ) || ! preg_match( '/^[A-Za-z0-9]{16,64}$/', $key ) ) {
|
||||
$key = self::generate_fallback_key();
|
||||
}
|
||||
$out['fallback_key'] = $key;
|
||||
|
||||
// Button.
|
||||
$text = isset( $input['button_text'] ) ? sanitize_text_field( wp_unslash( $input['button_text'] ) ) : '';
|
||||
$out['button_text'] = '' === trim( $text ) ? $defaults['button_text'] : mb_substr( $text, 0, 80 );
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue