Markus Michalski FAQ

API Key Wildcard: Technical Details

How API Key Wildcard works: request flow and plugin lifecycle as diagrams, classes and return values, the SQL check, auto-update, the api signal and the file structure.

Deutsche Version

Back to overview

API Key Wildcard: Technical Details

Request flow

graph TD
    A["POST /api/wildcard/tickets.json"] --> B["api/.htaccess: rule wildcard/ to wildcard.php"]
    B --> C["api/wildcard.php with PATH_INFO /tickets.json"]
    C --> D{"Route POST /tickets.json, .xml or .email?"}
    D -->|"No"| E["No route of the wildcard check"]
    D -->|"Yes"| F["WildcardTicketApiController.create"]
    F --> G["WildcardApiController.requireApiKey"]
    G -->|"key missing, unknown, inactive or IP mismatch"| H["401"]
    G -->|"key valid"| I{"Key IP is 0.0.0.0 and logging enabled?"}
    I -->|"Yes"| J["Debug entry in the system log"]
    I -->|"No"| K["Ticket is created"]
    J --> K

wildcard.php bootstraps osTicket through ../main.inc.php, loads include/plugins/api-key-wildcard/api.wildcard.inc.php and api.tickets.php. It defines WildcardTicketApiController as a subclass of TicketApiController whose requireApiKey() delegates to WildcardApiController. It then builds the dispatcher, sends the api signal and resolves the path.

API key check

WildcardApiController.requireApiKey() works in this order:

  1. No key in the request: 401 Valid API key required.
  2. Lookup with wildcard_getIdByKey(): without a match 401 API key not found or IP not authorized.
  3. Load the key: missing or not active gives 401 API key not active.
  4. Key IP is neither 0.0.0.0 nor the caller's IP: 401 Source IP not authorized.
  5. Key IP is 0.0.0.0 and log_wildcard_access is enabled: debug entry, then the key is returned.

The SQL condition of the lookup is:

SELECT id FROM API_KEY_TABLE WHERE apikey = ? AND (ipaddr = ? OR ipaddr = "0.0.0.0")

The standard API in contrast checks only for the exact IP. After the SQL lookup, step 4 is an additional safeguard.

Plugin lifecycle

graph TD
    A["bootstrap()"] --> B{"installed_version empty or lower?"}
    B -->|"Yes"| C["performUpdate()"]
    B -->|"No"| D["Nothing to do"]
    C --> E["PluginInstaller.update()"]
    E --> F["HtaccessManager.applyAll(), if api/.htaccess exists"]
    F --> G["store installed_version, entry in the PHP error log"]
    H["enable()"] --> I["Create singleton instance if none exists"]
    I --> J["PluginInstaller.install()"]
    J --> K["HtaccessManager.applyAll()"]
    K --> L["store installed_version"]
    M["disable()"] --> N["PluginInstaller.uninstall()"]
    N --> O["removeWildcardRule() and removeMultiViews()"]

Classes and return values

Class Responsibility Important methods
ApiKeyWildcardPlugin Plugin class, singleton bootstrap(), enable(), disable(), performUpdate() (public)
ApiKeyWildcardConfig Configuration fields getOptions()
PluginInstaller Copy and delete the endpoint file install() and update() return an array of errors, uninstall() returns bool
HtaccessManager Changes to api/.htaccess applyAll() returns an array of errors, addMultiViews(), addWildcardRule(), removeWildcardRule(), removeMultiViews() return bool
PluginConstants Constants (slug, file name, 0.0.0.0, comments, file mode 0755)
WildcardApiController API key check with wildcard requireApiKey()
WildcardTicketApiController Ticket controller using this check (in wildcard.php) requireApiKey()

enable() returns true or an array of errors. disable() always returns true, the results of its individual steps are not evaluated. performUpdate() returns an array with all errors, otherwise true.

Auto-update

On every bootstrap() the plugin reads installed_version from the configuration and compares it with the version in plugin.php using version_compare. If the value is empty or lower, performUpdate() runs: the endpoint file is copied again, api/.htaccess is extended (if it exists) and the new version is stored. On success performUpdate() writes one line to the PHP error log (installation or update). An equal or higher installed version triggers no update.

enable() writes no installation or update log. It writes to the PHP error log only when it creates the singleton instance automatically: [API Key Wildcard] Auto-created singleton instance on success, [API Key Wildcard] Failed to auto-create instance: ... on failure.

Signal api

Signal Sent by Purpose
api wildcard.php Lets other plugins register routes on the dispatcher of the wildcard endpoint. These routes use their own key check.

The plugin class itself connects to no signal.

File structure

Excerpt of the repository. This is not the content of the release ZIP.

osticket-api-key-wildcard/
β”œβ”€β”€ plugin.php                  # Registration (ID, name, plugin class)
β”œβ”€β”€ class.ApiKeyWildcard.php    # Plugin class and configuration
β”œβ”€β”€ class.PluginConstants.php   # Constants
β”œβ”€β”€ class.PluginInstaller.php   # Copy and delete the endpoint file
β”œβ”€β”€ class.HtaccessManager.php   # Changes to api/.htaccess
β”œβ”€β”€ api.wildcard.inc.php        # WildcardApiController and helper functions
β”œβ”€β”€ wildcard.php                # Endpoint, copied to api/
β”œβ”€β”€ .htaccess                   # Reference, repository only, not used by the plugin
β”œβ”€β”€ README.md
β”œβ”€β”€ CHANGELOG.md
β”œβ”€β”€ LICENSE
β”œβ”€β”€ .releaseinclude             # Files of the release ZIP
β”œβ”€β”€ .gitignore
└── .github/                    # CI workflow

According to .releaseinclude the release ZIP contains only *.php, README.md, README-de.md, LICENSE and CHANGELOG.md. The .htaccess is therefore not in the ZIP. The file README-de.md is listed in .releaseinclude but does not exist in the repository.