Skip to content

Several fixes and improvements#18

Open
jpmorby wants to merge 14 commits into
blesta:masterfrom
jpmorby:master
Open

Several fixes and improvements#18
jpmorby wants to merge 14 commits into
blesta:masterfrom
jpmorby:master

Conversation

@jpmorby
Copy link
Copy Markdown
Contributor

@jpmorby jpmorby commented Mar 4, 2026

Summary

This PR brings the OpenSRS module from a minimal skeleton to a
fully-functional domain registrar module for Blesta, covering API hardening,
new management features, and UX improvements.

Bug fixes & hardening

  • API resilience: Fix cURL error handling, add timeouts, harden response
    parsing, add early returns on API failures
  • Null safety: Add getModuleRowOrFail() helper replacing all 15 bare
    getModuleRow() calls; add null checks throughout
  • Security: Replace deterministic MD5-based passwords with cryptographically
    random random_bytes() passwords
  • DNSSEC endpoint: Fix getDnssecRecords using wrong API type (domain_auth_info
    → dnssec)
  • Postal code: Fix setDomainContacts overwriting postal code with 00000
  • TLD pricing: Remove dead foreach(range(1,10)) loops that overwrote pricing
    data 10 times
  • .FR validation: Fix inverted condition that was unsetting company fields for
    the wrong registrant type
  • Transfer check: Replace naive inverse-lookup with proper checkTransfer() API
    call
  • Language keys: Namespace .FR registrant type keys to prevent collision with
    .UK keys
  • Deprecations: Fix implicit nullable parameters and dynamic property
    declaration warnings

New features

  • DNS zone management: Admin and client tabs for viewing, adding, and deleting
    DNS records; reset to defaults
  • URL forwarding: Admin and client tabs to manage domain forwarding rules
    (301/302/frame)
  • DNSSEC: Admin and client tabs for DS record management with
    algorithm/digest-type dropdowns
  • Service lifecycle: cancelService, suspendService, unsuspendService,
    editService, restoreDomain
  • Country dropdowns: Full ISO 3166-1 alpha-2 lookup table; all contact country
    fields converted from free-text to select

UX

  • Mask API key in settings view (show last 4 chars only); use password input
    in add/edit row forms

jpmorby and others added 14 commits February 26, 2026 23:27
- Add CURLOPT_CONNECTTIMEOUT (30s) and CURLOPT_TIMEOUT (60s) to prevent
  hanging requests on unresponsive API endpoints
- Return empty OpensrsResponse instead of passing false to constructor
  when curl_exec fails, preventing downstream type errors
- Fix $siganture typo to $signature (cosmetic, no functional impact as
  the variable was used consistently)
- Escape XML attribute values with htmlspecialchars() to prevent
  malformed XML from special characters in domain contact data
- Make raw() null-safe with ?? '' fallback
- Cache formatResponse() result in errors() to avoid 3 redundant parses
- Return 'ERROR' instead of null from status() when XML is missing,
  so callers can safely compare without null checks
- Handle null from response->errors() gracefully with fallback message
- Add early returns after processResponse() in getDomainContacts,
  getDomainInfo, getDomainIsLocked, getDomainIsPrivate, and
  getDomainNameServers to prevent dereferencing null responses
- Add null-coalescing for attribute access (lock_state, state) to
  prevent undefined index errors
- Replace gethostbyname() fallback with empty string for missing IPs
  to avoid DNS lookups that could hang or return wrong data
- Guard nameserver_list iteration with ?? [] for missing key
- Fix addService() blanket Input::setErrors([]) that was clearing
  real registration errors along with nameserver errors
- Filter empty nameservers before passing to setDomainNameservers
- Add getModuleRowOrFail() helper that sets a user-friendly error when
  the module row cannot be found, instead of causing a fatal PHP error
  from accessing properties on null
- Replace all 15 direct getModuleRow() calls with getModuleRowOrFail()
  plus early returns in: getFilteredTldPricing, manageSettings,
  checkAvailability, getDomainContacts, setDomainContacts,
  getDomainInfo, getDomainIsLocked, getDomainIsPrivate,
  setDomainNameservers, lockDomain, unlockDomain, registerDomain,
  renewDomain, getExpirationDate, getRegistrationDate
- Rename misleading $domains_provisioning to $domains_ns in
  setDomainNameservers (it was instantiating OpensrsDomainsNs)
- Add language key for module_row.missing error
- Replace deterministic password (md5 of client ID) with
  cryptographically random password using random_bytes()
- Add null-coalescing defaults for registrar_lock and
  whois_privacy_state POST values to prevent PHP notices
- Add null check for $client before WHOIS fields loop in addService()
  to prevent fatal error when client_id is missing
- Wrap validateConnection() in try-catch and add null safety for
  response properties to prevent crashes on invalid credentials
- Remove dead foreach(range(1,10)) loops wrapping pricing API calls;
  the API returns all periods in a single response via all_periods=1,
  so the loop just overwrote the same key 10 times
- Fix .FR domain validation: unset company fields (VAT ID, SIREN,
  trademark) for 'individual' registrants, not 'organization' ones
  (the condition was inverted)
- Replace naive checkTransferAvailability() inverse-lookup with proper
  OpensrsDomainsTransfer::checkTransfer() API call that checks the
  transferrable attribute, with fallback to old behavior
- Mask API key in manage view, showing only last 4 characters
- Change API key input to password field in add/edit row views
- Remove unused $i variable in client WHOIS tab view
- Namespace .FR domain language keys (fr_registrant_type) to prevent
  collision with .UK registrant_type keys that share the same key name
- Update .FR config to reference namespaced language keys
…toreDomain

- cancelService: Disables auto-renew and sets expire_action to let
  domain expire at end of current period via provisioning modify API
- suspendService: Delegates to cancelService (standard Blesta pattern
  for domain modules — no OpenSRS suspend API exists)
- unsuspendService: Re-enables auto-renew via provisioning modify
- editService: Minimal implementation returning null to preserve
  existing service meta fields
- restoreDomain: Wires up existing OpensrsDomainsProvisioning::redeem()
  method for domains in the redemption grace period
- Add language keys for service lifecycle error messages
- Register tabDns/tabClientDns in service tab arrays, conditional on
  dns_management feature being enabled for the service
- Add manageDns() private method handling:
  - GET: Fetches DNS zone records via OpensrsDomainsDns::getDnsZone()
  - POST add_record: Appends new record to zone via setDnsZone()
  - POST delete_record: Removes record by type and index via setDnsZone()
  - POST reset_zone: Resets zone to defaults via resetDnsZone()
- Create tab_dns.pdt (admin view) with record table, add form, and
  reset button
- Create tab_client_dns.pdt (client view) with Bootstrap-styled table
  and form matching existing client tab patterns
- Add all DNS-related language keys
- The OpensrsDomainsDns class already existed but was never wired into
  the module UI
- Create OpensrsDomainsForwarding class with get/set/create/delete
  methods for domain forwarding via the OpenSRS API
- Register tabUrlForwarding/tabClientUrlForwarding in service tabs,
  conditional on dns_management feature
- Add manageUrlForwarding() private method handling:
  - GET: Fetches forwarding records from DNS zone
  - POST set_forwarding: Adds forwarding rule with subdomain,
    destination URL, and redirect type (301/302/frame)
  - POST delete_forwarding: Removes forwarding rule by index
- Create tab_url_forwarding.pdt (admin) and
  tab_client_url_forwarding.pdt (client) views
- Add language keys for all forwarding labels
- Create OpensrsDomainsDnssec class wrapping provisioning modify with
  data='dnssec' for add/remove, and get with type='domain_auth_info'
  for fetching existing DS records
- Register tabDnssec/tabClientDnssec in service tabs, conditional on
  dns_management feature
- Add manageDnssec() private method handling:
  - GET: Fetches existing DS records
  - POST add_ds_record: Adds DS record with key_tag, algorithm,
    digest_type, and digest fields
  - POST delete_ds_record: Removes DS record by index
- Create tab_dnssec.pdt (admin) and tab_client_dnssec.pdt (client)
  views with DS record table and add form
- Provide algorithm (3-16) and digest type (1,2,4) dropdowns with
  human-readable labels
- Add all DNSSEC-related language keys
…or handling

- Use type=dnssec for getDnssecRecords (was incorrectly using domain_auth_info)
- Log all DNSSEC API fetches via logRequest for easier debugging
- Remove erroneous Input::setErrors([]) that silently cleared preceding errors
- Fix postal_code being overwritten with 00000 in setDomainContacts
- Fix restoreDomain signature to match RegistrarModule parent (add array $vars = [])
- Declare $logger property on OpensrsApi to fix dynamic property deprecation
- Fix implicit nullable parameter deprecations across all tab and manage methods
- Extract per-domain error detail from attributes.details in error responses
- Move DNSSEC tabs outside dns_management feature flag (show unconditionally)
…downs

Defines Opensrs.countries as a full ISO 3166-1 alpha-2 country code map at
the top of the config file. Converts all free-text country inputs (owner,
tech, admin, billing whois contacts and .law/.abogado jurisdiction country)
to select fields backed by this lookup table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When deleting the last DNSSEC record, the remaining empty array was
serialized as <dt_assoc/> because isset([][0]) is false. OpenSRS
requires <dt_array/> for the dnssec attribute, causing "Array/list
expected for dnssec attribute" errors on delete.

Treat empty arrays as lists so they serialize as dt_array, consistent
with non-empty sequential arrays.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant