Enable registrars to cancel a pending registrant change - #2946
Open
OlegPhenomenon wants to merge 2 commits into
Open
Enable registrars to cancel a pending registrant change#2946OlegPhenomenon wants to merge 2 commits into
OlegPhenomenon wants to merge 2 commits into
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
pendingDeletehas an escape hatch (a renew cancels it,Epp::Domain#renew),pendingUpdatehas none.Approach
A pending update never touches the domain in the database —
Domain#pending_update!reloads the record and the requested frame lives inpending_jsonuntil the registrant confirms. So cancelling is a matter of wiping the pending data; there is nothing to roll back.Domains::CancelPendingUpdatedoes 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, andop="cancel"belongs to<transfer>. So, as the issue suggests, cancelling is expressed as a newdomain:update: a request asking for the registrant the domain already has is treated as a cancel. Any other update of apendingUpdatedomain still returns2304. Same idea asdomain:renewcancellingpendingDelete.No schema change, and the response reuses the existing
1000template.REPP
opis not used anywhere in REPP (transfer is modelled as its own endpoints), so cancelling gets an explicit one:Scoped to the sponsoring registrar via
BaseController#set_domain. Returns1000;2304when the domain has no pending update.Note the asymmetry:
PUT /repp/v1/domains/:idwith the current registrant still returns2304— in REPP the explicitDELETEis the way.Notifications
Both registrants get a
RegistrantChangeMailer#cancelledmail: 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::ProcessActionnow 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.rbset the domain up withoutpendingUpdate— impossible in any real flow, since bothregistrant_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
legaldoc_mandatory, a registrar has to attach a legalDocument to cancel as well —validate_updaterequires one wheneverchg > registrantis present. Not special-cased on purpose; say if it should be..rubocop.ymlis gitignored) — leaving it to CI.registrar_center2, against this endpoint.