Plugin Directory

Changeset 3481099


Ignore:
Timestamp:
03/12/2026 11:24:37 AM (3 weeks ago)
Author:
docid
Message:

1.1.1: minor bugfixes

Location:
docid/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • docid/trunk/README.txt

    r3303344 r3481099  
    44Requires at least: 6.2
    55Requires PHP: 7.4
    6 Tested up to: 6.8
    7 Stable tag: 1.1.0
     6Tested up to: 6.9
     7Stable tag: 1.1.1
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    5656== Changelog ==
    5757
    58 = 1.0.2 – 1.1.0 =
     58= 1.0.2 – 1.1.1 =
    5959* Minor bugfixes
    6060
  • docid/trunk/admin/class-docid-metaboxes.php

    r3303320 r3481099  
    169169
    170170        $action = sanitize_key($action);
     171        $can_edit_post = current_user_can('edit_post', $post_id);
    171172        $is_autosave = wp_is_post_autosave($post_id);
    172173        $is_revision = wp_is_post_revision($post_id);
    173174
    174         return !($is_autosave || $is_revision) && wp_verify_nonce($nonce, $action);
     175        return $can_edit_post && !($is_autosave || $is_revision) && wp_verify_nonce($nonce, $action);
    175176    }
    176177
     
    222223
    223224}
    224 
  • docid/trunk/docid.php

    r3303341 r3481099  
    1414 * Plugin Name:       DocID
    1515 * Description:       The DocID plugin provides all the functionalities required for a secure and legally compliant authentication of healthcare professionals on your website.
    16  * Version:           1.1.0
     16 * Version:           1.1.1
    1717 * Author:            8awake GmbH <support@docid.de>
    1818 * Author URI:        https://docid.de
  • docid/trunk/frontend/class-docid-frontend.php

    r3303341 r3481099  
    5050
    5151    /**
     52     * Set the DocID session cookie.
     53     *
     54     * @param string $value
     55     * @param int    $expires
     56     * @return void
     57     */
     58    private function docid_set_session_cookie($value, $expires)
     59    {
     60        $cookieOptions = array(
     61            'expires' => $expires,
     62            'path' => '/',
     63            'secure' => is_ssl(),
     64            'httponly' => true,
     65            'samesite' => is_ssl() ? 'None' : 'Lax',
     66        );
     67
     68        $cookieDomain = $this->docid_get_cookie_domain();
     69        if (is_string($cookieDomain) && $cookieDomain !== '') {
     70            $cookieOptions['domain'] = $cookieDomain;
     71        }
     72
     73        setcookie(self::DOCID_COOKIE_SESSION_ID, $value, $cookieOptions);
     74    }
     75
     76    /**
    5277     * Add noindex meta and header whenever an authentication token is present
    5378     *
     
    5984
    6085        if (
    61             str_contains($server_request_uri, '?' . self::DOCID_TOKEN_PARAMETER_NAME . '=') ||
    62             str_contains($server_request_uri, '&' . self::DOCID_TOKEN_PARAMETER_NAME . '=') ||
    63             str_contains($server_request_uri, '?' . self::DOCID_SKIP_TOKEN_PARAMETER_NAME . '=') ||
    64             str_contains($server_request_uri, '&' . self::DOCID_SKIP_TOKEN_PARAMETER_NAME . '=')
     86            strpos($server_request_uri, '?' . self::DOCID_TOKEN_PARAMETER_NAME . '=') !== false ||
     87            strpos($server_request_uri, '&' . self::DOCID_TOKEN_PARAMETER_NAME . '=') !== false ||
     88            strpos($server_request_uri, '?' . self::DOCID_SKIP_TOKEN_PARAMETER_NAME . '=') !== false ||
     89            strpos($server_request_uri, '&' . self::DOCID_SKIP_TOKEN_PARAMETER_NAME . '=') !== false
    6590        ) {
    6691
     
    85110    {
    86111        $assetSecret = get_option('docid_general_asset_secret');
     112        $secretFromQuery = isset($_GET[self::DOCID_SECRET_PARAMETER_NAME])
     113            ? wp_unslash($_GET[self::DOCID_SECRET_PARAMETER_NAME])
     114            : null;
    87115
    88116        /**
    89117         * Check if anon secret was provided
    90118         */
    91         if(isset($_GET[self::DOCID_SECRET_PARAMETER_NAME]) && !empty($_GET[self::DOCID_SECRET_PARAMETER_NAME]) && sanitize_text_field(wp_unslash($_GET[self::DOCID_SECRET_PARAMETER_NAME])) === $assetSecret) {
     119        if (
     120            is_string($secretFromQuery) &&
     121            $secretFromQuery !== '' &&
     122            sanitize_text_field($secretFromQuery) === $assetSecret
     123        ) {
    92124
    93125            $guestToken = bin2hex(random_bytes(16));
     
    97129            set_transient('docid_skip_auth_' . $this->docid_hashed($skipAuthToken), true, 30);
    98130
    99             setcookie(self::DOCID_COOKIE_SESSION_ID, $guestToken, ['expires' => time() + 3600, 'path' => '/', 'domain' => $this->docid_get_cookie_domain(), 'secure' => is_ssl(), 'httponly' => true, 'samesite' => is_ssl() ? 'None' : 'Lax']);
     131            $this->docid_set_session_cookie($guestToken, time() + 3600);
    100132
    101133            $this->_docid_remove_auth_token($skipAuthToken);
     
    105137        }
    106138
    107         $authToken = isset($_GET[self::DOCID_TOKEN_PARAMETER_NAME]) && !empty($_GET[self::DOCID_TOKEN_PARAMETER_NAME] && preg_match('/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/', $_GET[self::DOCID_TOKEN_PARAMETER_NAME])) ? wp_unslash($_GET[self::DOCID_TOKEN_PARAMETER_NAME]) : false;
     139        $rawAuthToken = isset($_GET[self::DOCID_TOKEN_PARAMETER_NAME])
     140            ? wp_unslash($_GET[self::DOCID_TOKEN_PARAMETER_NAME])
     141            : null;
     142        $authToken = false;
     143
     144        if (
     145            is_string($rawAuthToken) &&
     146            $rawAuthToken !== '' &&
     147            preg_match('/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/', $rawAuthToken) === 1
     148        ) {
     149            $authToken = $rawAuthToken;
     150        }
    108151
    109152        /**
     
    130173
    131174            } else {
     175                $statusCode = wp_remote_retrieve_response_code($response);
     176                if ($statusCode < 200 || $statusCode >= 300) {
     177                    if (defined('WP_DEBUG') && WP_DEBUG === true) {
     178                        error_log('Error docid_handle_auth_token: unexpected response code ' . $statusCode);
     179                    }
     180
     181                    $this->_docid_remove_auth_token();
     182                    return;
     183                }
    132184
    133185                $body = wp_remote_retrieve_body($response);
    134186                $data = json_decode($body, true);
    135187
    136                 if (empty($data) || is_null($data)) {
     188                if (!is_array($data) || empty($data) || isset($data['error'])) {
    137189                    $this->_docid_remove_auth_token();
    138190                    return;
     
    148200                    set_transient('docid_skip_auth_' . $this->docid_hashed($skipAuthToken), true, 30);
    149201
    150                     setcookie(self::DOCID_COOKIE_SESSION_ID, $authToken, ['expires' => time() + 3600, 'path' => '/', 'domain' => $this->docid_get_cookie_domain(), 'secure' => is_ssl(), 'httponly' => true, 'samesite' => is_ssl() ? 'None' : 'Lax']);
     202                    $this->docid_set_session_cookie($authToken, time() + 3600);
    151203
    152204                }
     
    248300    {
    249301        $logoutNonce = isset($_REQUEST['docid_logout_nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['docid_logout_nonce'])) : '';
    250 
    251         if (wp_verify_nonce($logoutNonce, 'docid_logout_nonce')) {
     302        $isValidNonce = wp_verify_nonce($logoutNonce, 'docid_logout_nonce');
     303
     304        if ($isValidNonce) {
    252305            /**
    253306             * Clear session
    254307             */
    255             if(isset($_COOKIE['docid_session_id'])) {
    256                 delete_transient("docid_user_" . $this->docid_hashed(sanitize_text_field(wp_unslash($_COOKIE[self::DOCID_COOKIE_SESSION_ID]))));
    257             }
    258             setcookie(self::DOCID_COOKIE_SESSION_ID, '', ['expires' => time() + 1, 'path' => '/', 'domain' => $this->docid_get_cookie_domain(), 'secure' => is_ssl(), 'httponly' => true, 'samesite' => is_ssl() ? 'None' : 'Lax']);
    259         }
    260 
    261         wp_redirect(home_url('/'));
     308            if(isset($_COOKIE[self::DOCID_COOKIE_SESSION_ID])) {
     309                $sessionId = sanitize_text_field(wp_unslash($_COOKIE[self::DOCID_COOKIE_SESSION_ID]));
     310                delete_transient("docid_user_" . $this->docid_hashed($sessionId));
     311            }
     312        }
     313
     314        /**
     315         * Always clear browser cookie variants.
     316         * If nonce is invalid, this still allows user-initiated cleanup of stale sessions.
     317         */
     318        $this->docid_clear_session_cookie();
     319
     320        nocache_headers();
     321        wp_safe_redirect(home_url('/'));
     322        exit;
     323    }
     324
     325    /**
     326     * Expire the DocID session cookie for all relevant domain variants.
     327     *
     328     * @return void
     329     */
     330    private function docid_clear_session_cookie()
     331    {
     332        $cookieName = self::DOCID_COOKIE_SESSION_ID;
     333        $cookieDomains = array();
     334
     335        $configuredCookieDomain = $this->docid_get_cookie_domain();
     336        if (is_string($configuredCookieDomain) && $configuredCookieDomain !== '') {
     337            $cookieDomains[] = $configuredCookieDomain;
     338            $cookieDomains[] = ltrim($configuredCookieDomain, '.');
     339        }
     340
     341        if (defined('COOKIE_DOMAIN') && is_string(COOKIE_DOMAIN) && COOKIE_DOMAIN !== '') {
     342            $cookieDomains[] = COOKIE_DOMAIN;
     343            $cookieDomains[] = ltrim(COOKIE_DOMAIN, '.');
     344        }
     345
     346        if (isset($_SERVER['HTTP_HOST'])) {
     347            $httpHostRaw = wp_unslash($_SERVER['HTTP_HOST']);
     348            $httpHost = wp_parse_url('http://' . $httpHostRaw, PHP_URL_HOST);
     349            if (!is_string($httpHost) || $httpHost === '') {
     350                $httpHost = preg_replace('/[^a-zA-Z0-9.-]/', '', $httpHostRaw);
     351            }
     352            if (is_string($httpHost) && $httpHost !== '') {
     353                $cookieDomains[] = $httpHost;
     354            }
     355        }
     356
     357        $cookieDomains[] = null; // host-only cookie variant (no Domain attribute)
     358        $cookieDomains = array_values(array_unique($cookieDomains, SORT_REGULAR));
     359
     360        $cookiePaths = array('/');
     361        $pathCandidates = array(
     362            parse_url(home_url('/'), PHP_URL_PATH),
     363            parse_url(site_url('/'), PHP_URL_PATH),
     364            parse_url(admin_url('/'), PHP_URL_PATH),
     365            defined('COOKIEPATH') ? COOKIEPATH : null,
     366            defined('SITECOOKIEPATH') ? SITECOOKIEPATH : null,
     367            defined('ADMIN_COOKIE_PATH') ? ADMIN_COOKIE_PATH : null,
     368            defined('PLUGINS_COOKIE_PATH') ? PLUGINS_COOKIE_PATH : null,
     369        );
     370
     371        foreach ($pathCandidates as $pathCandidate) {
     372            if (!is_string($pathCandidate) || $pathCandidate === '') {
     373                continue;
     374            }
     375
     376            $normalizedPath = '/' . ltrim($pathCandidate, '/');
     377            $normalizedPath = preg_replace('#/+#', '/', $normalizedPath);
     378            $normalizedPath = rtrim($normalizedPath, '/');
     379            $normalizedPath = ($normalizedPath === '') ? '/' : $normalizedPath;
     380
     381            $cookiePaths[] = $normalizedPath;
     382
     383            if ($normalizedPath !== '/') {
     384                $cookiePaths[] = $normalizedPath . '/';
     385            }
     386
     387            if (preg_match('#/wp-admin$#', $normalizedPath) === 1) {
     388                $wpRootPath = preg_replace('#/wp-admin$#', '', $normalizedPath);
     389                $wpRootPath = (is_string($wpRootPath) && $wpRootPath !== '') ? $wpRootPath : '/';
     390                $cookiePaths[] = $wpRootPath;
     391                if ($wpRootPath !== '/') {
     392                    $cookiePaths[] = $wpRootPath . '/';
     393                }
     394            }
     395        }
     396
     397        $cookiePaths = array_values(array_unique($cookiePaths));
     398        $cookieVariants = array(
     399            array(
     400                'secure' => true,
     401                'samesite' => 'None',
     402            ),
     403            array(
     404                'secure' => false,
     405                'samesite' => 'Lax',
     406            ),
     407        );
     408
     409        if (headers_sent($file, $line)) {
     410            if (defined('WP_DEBUG') && WP_DEBUG === true) {
     411                error_log(
     412                    sprintf(
     413                        'DocID logout could not clear cookie "%s" because headers were already sent at %s:%d',
     414                        $cookieName,
     415                        $file,
     416                        $line
     417                    )
     418                );
     419            }
     420
     421            unset($_COOKIE[$cookieName]);
     422            return;
     423        }
     424
     425        foreach ($cookieDomains as $domain) {
     426            foreach ($cookiePaths as $path) {
     427                foreach ($cookieVariants as $cookieVariant) {
     428                    $cookieOptions = array(
     429                        'expires' => time() - 3600,
     430                        'path' => $path,
     431                        'secure' => $cookieVariant['secure'],
     432                        'httponly' => true,
     433                        'samesite' => $cookieVariant['samesite'],
     434                    );
     435
     436                    if (is_string($domain) && $domain !== '') {
     437                        $cookieOptions['domain'] = $domain;
     438                    }
     439
     440                    setcookie($cookieName, '', $cookieOptions);
     441                }
     442            }
     443        }
     444
     445        unset($_COOKIE[$cookieName]);
    262446    }
    263447
  • docid/trunk/includes/class-docid-base.php

    r3303341 r3481099  
    1818     * @since   1.0.0
    1919     */
    20     const DOCID_OPEN_URL_ENDPOINT = 'https://docid.de/open';
     20    const DOCID_OPEN_URL_ENDPOINT = 'https://docid.pro/open';
    2121
    2222    /**
     
    2525     * @since   1.0.0
    2626     */
    27     const DOCID_USER_ACCOUNT_ENDPOINT = 'https://docid.de/api/accounts/user/external';
     27    const DOCID_USER_ACCOUNT_ENDPOINT = 'https://docid.pro/api/accounts/user/external';
    2828
    2929    /**
     
    102102     * Get cookie domain
    103103     *
    104      * @return  string
     104     * @return  string|null
    105105     * @since   1.1.0
    106106     * @access  public
     
    109109    {
    110110        $domain = parse_url(home_url(), PHP_URL_HOST);
     111        if (!is_string($domain) || $domain === '') {
     112            return null;
     113        }
     114
     115        $domain = strtolower($domain);
    111116        $domain = preg_replace('/^www\./', '', $domain);
    112         return "." . $domain;
     117        $domain = ltrim($domain, '.');
     118
     119        if ($domain === '' || $domain === 'localhost' || filter_var($domain, FILTER_VALIDATE_IP)) {
     120            return null;
     121        }
     122
     123        if (strpos($domain, '.') === false) {
     124            return null;
     125        }
     126
     127        return '.' . $domain;
     128    }
     129
     130    /**
     131     * Check whether the provided session id has a valid DocID format.
     132     *
     133     * Supports:
     134     * - JWT user tokens
     135     * - random hex guest tokens used for did_s based sessions
     136     *
     137     * @param   string $sessionId
     138     * @return  bool
     139     * @since   1.1.1
     140     * @access  private
     141     */
     142    private function docid_is_valid_session_id($sessionId)
     143    {
     144        if (!is_string($sessionId) || $sessionId === '') {
     145            return false;
     146        }
     147
     148        if (preg_match('/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/', $sessionId)) {
     149            return true;
     150        }
     151
     152        if (preg_match('/^[A-Fa-f0-9]{32,128}$/', $sessionId)) {
     153            return true;
     154        }
     155
     156        return false;
    113157    }
    114158
     
    125169         * @note no nonce handling as this is an authentication handler
    126170         */
    127         if(isset($_COOKIE[self::DOCID_COOKIE_SESSION_ID]) && preg_match('/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/', $_COOKIE[self::DOCID_COOKIE_SESSION_ID])) {
     171        if(isset($_COOKIE[self::DOCID_COOKIE_SESSION_ID])) {
    128172            $session_id = wp_unslash($_COOKIE[self::DOCID_COOKIE_SESSION_ID]);
    129             if(get_transient("docid_user_" . $this->docid_hashed($session_id))) {
     173            if($this->docid_is_valid_session_id($session_id) && get_transient("docid_user_" . $this->docid_hashed($session_id))) {
    130174                return true;
    131175            }
    132         } elseif(isset($_GET[self::DOCID_SKIP_TOKEN_PARAMETER_NAME])) {
     176        }
     177
     178        if(isset($_GET[self::DOCID_SKIP_TOKEN_PARAMETER_NAME])) {
    133179            $skip_token_id = sanitize_text_field(wp_unslash($_GET[self::DOCID_SKIP_TOKEN_PARAMETER_NAME]));
    134             if (get_transient("docid_skip_auth_" . $this->docid_hashed($skip_token_id))) {
    135                 return true;
    136             }
     180
     181            if (preg_match('/^[A-Fa-f0-9]{32,128}$/', $skip_token_id) === 1) {
     182                static $validated_skip_tokens = array();
     183
     184                $skip_token_hash = $this->docid_hashed($skip_token_id);
     185                if (isset($validated_skip_tokens[$skip_token_hash])) {
     186                    return true;
     187                }
     188
     189                $transientKey = "docid_skip_auth_" . $skip_token_hash;
     190                if (get_transient($transientKey)) {
     191                    delete_transient($transientKey);
     192                    $validated_skip_tokens[$skip_token_hash] = true;
     193                    return true;
     194                }
     195            }
     196
    137197        }
    138198
     
    149209    public function docid_get_user()
    150210    {
    151         if(isset($_COOKIE[self::DOCID_COOKIE_SESSION_ID]) && preg_match('/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/', $_COOKIE[self::DOCID_COOKIE_SESSION_ID])) {
     211        if(isset($_COOKIE[self::DOCID_COOKIE_SESSION_ID])) {
    152212            $session_id = wp_unslash($_COOKIE[self::DOCID_COOKIE_SESSION_ID]);
     213            if(!$this->docid_is_valid_session_id($session_id)) {
     214                return null;
     215            }
    153216            $user = get_transient("docid_user_" . $this->docid_hashed($session_id));
    154217            if($user) {
  • docid/trunk/includes/class-docid.php

    r3298583 r3481099  
    5959
    6060        $this->plugin_name = 'docid';
    61         $this->version     = '1.0.1';
     61        $this->version     = '1.1.1';
    6262
    6363        $this->set_loader();
Note: See TracChangeset for help on using the changeset viewer.