Skip to content

Enable registrars to cancel a pending registrant change - #2946

Open
OlegPhenomenon wants to merge 2 commits into
masterfrom
issue-2939-cancel-pending-registrant-change
Open

Enable registrars to cancel a pending registrant change#2946
OlegPhenomenon wants to merge 2 commits into
masterfrom
issue-2939-cancel-pending-registrant-change

Conversation

@OlegPhenomenon

Copy link
Copy Markdown
Contributor

Closes #2939

Problem

Once a registrant change goes pending, the registrar has no way back — either wait 48 hours for the expiry cron, or ask the registry to clear it by hand. pendingDelete has an escape hatch (a renew cancels it, Epp::Domain#renew), pendingUpdate has none.

Approach

A pending update never touches the domain in the database — Domain#pending_update! reloads the record and the requested frame lives in pending_json until the registrant confirms. So cancelling is a matter of wiping the pending data; there is nothing to roll back.

Domains::CancelPendingUpdate does that and is shared by both interfaces.

EPP

RFC 3731 defines no way to manipulate pending operations — transform commands MUST be rejected while a pending* status is set, with limited exceptions for transfer only, and op="cancel" belongs to <transfer>. So, as the issue suggests, cancelling is expressed as a new domain:update: a request asking for the registrant the domain already has is treated as a cancel. Any other update of a pendingUpdate domain still returns 2304. Same idea as domain:renew cancelling pendingDelete.

No schema change, and the response reuses the existing 1000 template.

REPP

op is not used anywhere in REPP (transfer is modelled as its own endpoints), so cancelling gets an explicit one:

DELETE /repp/v1/domains/:domain_id/pending_update

Scoped to the sponsoring registrar via BaseController#set_domain. Returns 1000; 2304 when the domain has no pending update.

Note the asymmetry: PUT /repp/v1/domains/:id with the current registrant still returns 2304 — in REPP the explicit DELETE is the way.

Notifications

Both registrants get a RegistrantChangeMailer#cancelled mail: the confirmation link they received stops working once the token is cleared. No poll message to the registrar — they initiated the cancel themselves.

Race between confirm and cancel

The registrant decision arrives through DomainUpdateConfirmJob, so it can land after the pending update was already cancelled — or cleaned up by the expiry cron — and replay an empty frame (empty update, a stray "accepted" mail and a "confirmed" poll message). Domains::UpdateConfirm::ProcessAction now bails out when the domain is no longer pending. This closes the pre-existing cron-vs-confirm variant of the same race too.

Tests

5 REPP + 5 EPP + 6 interaction + 1 for the race guard. Full run of test/integration/epp/domain/update/, test/integration/repp/v1/domains/, test/interactions/domains/, test/models/domain/, test/jobs/, admin and registrant-API confirm paths: 0 failures.

Several tests in domain_update_confirm_job_test.rb set the domain up without pendingUpdate — impossible in any real flow, since both registrant_update_confirmable? and the admin controller require it. Three of them broke on the new guard and four were passing vacuously; they now reflect the actual state.

For review

  • With legaldoc_mandatory, a registrar has to attach a legalDocument to cancel as well — validate_update requires one whenever chg > registrant is present. Not special-cased on purpose; say if it should be.
  • Rubocop was not run locally (the gem is not installed in the container and .rubocop.yml is gitignored) — leaving it to CI.
  • The registrar portal button is a separate PR in registrar_center2, against this endpoint.

Once a registrant change went pending, the registrar had no way back:
either wait 48 hours for the expiry cron or ask the registry to clear it
by hand. Unlike pendingDelete, which a renew cancels, pendingUpdate had
no escape hatch.

Since the pending update never touches the domain in the database - the
requested frame only lives in pending_json until the registrant confirms
- cancelling is a matter of wiping the pending data, not rolling
anything back. Domains::CancelPendingUpdate does that and is shared by
both interfaces.

EPP has no command for manipulating pending operations (RFC 3731 covers
transfer only), so a domain:update asking for the registrant the domain
already has is treated as a cancel request. Every other update of a
pendingUpdate domain still gets 2304. This mirrors domain:renew
cancelling pendingDelete. REPP gets an explicit endpoint instead:
DELETE /repp/v1/domains/:domain_id/pending_update.

Both registrants are notified by mail, since the confirmation link they
received stops working.

The registrant decision arrives through a background job, so it could
land after the pending update was already cancelled - or cleaned up by
the expiry cron - and replay an empty frame. ProcessAction now bails out
when the domain is no longer pending. Several job tests were set up
without pendingUpdate on the domain, which cannot happen in any real
flow and left parts of them passing vacuously; they now reflect the
actual state.

Closes #2939
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.

Enable registrars to cancel pending registrant change

1 participant