Skip to content

Commit

Permalink
Introduce separate UpdateRegistrationContact & UpdateRegistrationKey …
Browse files Browse the repository at this point in the history
…methods in RA & SA (#7735)

Introduce separate UpdateRegistrationContact & UpdateRegistrationKey
methods in RA & SA

Clear contact field during DeactivateRegistration

Part of #7716
Part of #5554
  • Loading branch information
jprenken authored Nov 6, 2024
1 parent 84b15eb commit 6a2819a
Show file tree
Hide file tree
Showing 10 changed files with 1,540 additions and 588 deletions.
599 changes: 381 additions & 218 deletions ra/proto/ra.pb.go

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions ra/proto/ra.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import "google/protobuf/empty.proto";
service RegistrationAuthority {
rpc NewRegistration(core.Registration) returns (core.Registration) {}
rpc UpdateRegistration(UpdateRegistrationRequest) returns (core.Registration) {}
rpc UpdateRegistrationContact(UpdateRegistrationContactRequest) returns (core.Registration) {}
rpc UpdateRegistrationKey(UpdateRegistrationKeyRequest) returns (core.Registration) {}
rpc PerformValidation(PerformValidationRequest) returns (core.Authorization) {}
rpc DeactivateRegistration(core.Registration) returns (google.protobuf.Empty) {}
rpc DeactivateAuthorization(core.Authorization) returns (google.protobuf.Empty) {}
Expand All @@ -33,6 +35,16 @@ message UpdateRegistrationRequest {
core.Registration update = 2;
}

message UpdateRegistrationContactRequest {
int64 registrationID = 1;
repeated string contacts = 2;
}

message UpdateRegistrationKeyRequest {
int64 registrationID = 1;
bytes jwk = 2;
}

message UpdateAuthorizationRequest {
core.Authorization authz = 1;
int64 challengeIndex = 2;
Expand Down
76 changes: 76 additions & 0 deletions ra/proto/ra_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 42 additions & 1 deletion ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -1620,7 +1620,8 @@ func (ra *RegistrationAuthorityImpl) checkNewOrderLimits(ctx context.Context, na
// UpdateRegistration updates an existing Registration with new values. Caller
// is responsible for making sure that update.Key is only different from base.Key
// if it is being called from the WFE key change endpoint.
// TODO(#5554): Split this into separate methods for updating Contacts vs Key.
//
// Deprecated: Use UpdateRegistrationContact or UpdateRegistrationKey instead.
func (ra *RegistrationAuthorityImpl) UpdateRegistration(ctx context.Context, req *rapb.UpdateRegistrationRequest) (*corepb.Registration, error) {
// Error if the request is nil, there is no account key or IP address
if req.Base == nil || len(req.Base.Key) == 0 || len(req.Base.InitialIP) == 0 || req.Base.Id == 0 {
Expand Down Expand Up @@ -1659,6 +1660,46 @@ func (ra *RegistrationAuthorityImpl) UpdateRegistration(ctx context.Context, req
return update, nil
}

// UpdateRegistrationContact updates an existing Registration's contact.
// The updated contacts field may be empty.
func (ra *RegistrationAuthorityImpl) UpdateRegistrationContact(ctx context.Context, req *rapb.UpdateRegistrationContactRequest) (*corepb.Registration, error) {
if core.IsAnyNilOrZero(req.RegistrationID) {
return nil, errIncompleteGRPCRequest
}

err := ra.validateContacts(req.Contacts)
if err != nil {
return nil, fmt.Errorf("invalid contact: %w", err)
}

update, err := ra.SA.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{
RegistrationID: req.RegistrationID,
Contacts: req.Contacts,
})
if err != nil {
return nil, fmt.Errorf("failed to update registration contact: %w", err)
}

return update, nil
}

// UpdateRegistrationKey updates an existing Registration's key.
func (ra *RegistrationAuthorityImpl) UpdateRegistrationKey(ctx context.Context, req *rapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) {
if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) {
return nil, errIncompleteGRPCRequest
}

update, err := ra.SA.UpdateRegistrationKey(ctx, &sapb.UpdateRegistrationKeyRequest{
RegistrationID: req.RegistrationID,
Jwk: req.Jwk,
})
if err != nil {
return nil, fmt.Errorf("failed to update registration key: %w", err)
}

return update, nil
}

func contactsEqual(a []string, b []string) bool {
if len(a) != len(b) {
return false
Expand Down
146 changes: 145 additions & 1 deletion ra/ra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,10 +671,21 @@ type NoUpdateSA struct {
sapb.StorageAuthorityClient
}

func (sa NoUpdateSA) UpdateRegistration(_ context.Context, _ *corepb.Registration, _ ...grpc.CallOption) (*emptypb.Empty, error) {
// Deprecated: When this function is removed, the NoUpdateSA should be moved
// down to join the other mocks and tests for UpdateRegistrationContact & Key,
// where it's also used.
func (sa *NoUpdateSA) UpdateRegistration(_ context.Context, _ *corepb.Registration, _ ...grpc.CallOption) (*emptypb.Empty, error) {
return nil, fmt.Errorf("UpdateRegistration() is mocked to always error")
}

func (sa *NoUpdateSA) UpdateRegistrationContact(_ context.Context, _ *sapb.UpdateRegistrationContactRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
return nil, fmt.Errorf("UpdateRegistrationContact() is mocked to always error")
}

func (sa *NoUpdateSA) UpdateRegistrationKey(_ context.Context, _ *sapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
return nil, fmt.Errorf("UpdateRegistrationKey() is mocked to always error")
}

func TestUpdateRegistrationSame(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()
Expand Down Expand Up @@ -4483,3 +4494,136 @@ func TestGetAuthorization(t *testing.T) {
test.AssertNotError(t, err, "should not fail")
test.AssertEquals(t, len(authz.Challenges), 0)
}

// mockSARecordingRegistration tests UpdateRegistrationContact and UpdateRegistrationKey.
type mockSARecordingRegistration struct {
sapb.StorageAuthorityClient
providedRegistrationID int64
providedContacts []string
providedJwk []byte
}

// UpdateRegistrationContact records the registration ID and updated contacts
// (optional) provided.
func (sa *mockSARecordingRegistration) UpdateRegistrationContact(ctx context.Context, req *sapb.UpdateRegistrationContactRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
sa.providedRegistrationID = req.RegistrationID
sa.providedContacts = req.Contacts

return &corepb.Registration{
Id: req.RegistrationID,
Contact: req.Contacts,
}, nil
}

// UpdateRegistrationKey records the registration ID and updated key provided.
func (sa *mockSARecordingRegistration) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
sa.providedRegistrationID = req.RegistrationID
sa.providedJwk = req.Jwk

return &corepb.Registration{
Id: req.RegistrationID,
Key: req.Jwk,
}, nil
}

// TestUpdateRegistrationContact tests that the RA's UpdateRegistrationContact
// method correctly: requires a registration ID; validates the contact provided;
// does not require a contact; passes the requested registration ID and contact
// to the SA; passes the updated Registration back to the caller; and can return
// an error.
func TestUpdateRegistrationContact(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()

expectRegID := int64(1)
expectContacts := []string{"mailto:[email protected]"}
mockSA := mockSARecordingRegistration{}
ra.SA = &mockSA

_, err := ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{})
test.AssertError(t, err, "should not have been able to update registration contact without a registration ID")
test.AssertContains(t, err.Error(), "incomplete gRPC request message")

_, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
Contacts: []string{"tel:+44123"},
})
test.AssertError(t, err, "should not have been able to update registration contact to an invalid contact")
test.AssertContains(t, err.Error(), "invalid contact")

res, err := ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
})
test.AssertNotError(t, err, "should have been able to update registration with a blank contact")
test.AssertEquals(t, res.Id, expectRegID)
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
test.AssertDeepEquals(t, res.Contact, []string(nil))
test.AssertDeepEquals(t, mockSA.providedContacts, []string(nil))

res, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
Contacts: expectContacts,
})
test.AssertNotError(t, err, "should have been able to update registration with a populated contact")
test.AssertEquals(t, res.Id, expectRegID)
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
test.AssertDeepEquals(t, res.Contact, expectContacts)
test.AssertDeepEquals(t, mockSA.providedContacts, expectContacts)

// Switch to a mock SA that will always error if UpdateRegistrationContact()
// is called.
ra.SA = &NoUpdateSA{}
_, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
Contacts: expectContacts,
})
test.AssertError(t, err, "should have received an error from the SA")
test.AssertContains(t, err.Error(), "failed to update registration contact")
test.AssertContains(t, err.Error(), "mocked to always error")
}

// TestUpdateRegistrationKey tests that the RA's UpdateRegistrationKey method
// correctly requires a registration ID and key, passes them to the SA, and
// passes the updated Registration back to the caller.
func TestUpdateRegistrationKey(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()

expectRegID := int64(1)
expectJwk := AccountKeyJSONA
mockSA := mockSARecordingRegistration{}
ra.SA = &mockSA

_, err := ra.UpdateRegistrationKey(context.Background(), &rapb.UpdateRegistrationKeyRequest{})
test.AssertError(t, err, "should not have been able to update registration key without a registration ID or key")
test.AssertContains(t, err.Error(), "incomplete gRPC request message")

_, err = ra.UpdateRegistrationKey(context.Background(), &rapb.UpdateRegistrationKeyRequest{RegistrationID: expectRegID})
test.AssertError(t, err, "should not have been able to update registration key without a key")
test.AssertContains(t, err.Error(), "incomplete gRPC request message")

_, err = ra.UpdateRegistrationKey(context.Background(), &rapb.UpdateRegistrationKeyRequest{Jwk: expectJwk})
test.AssertError(t, err, "should not have been able to update registration key without a registration ID")
test.AssertContains(t, err.Error(), "incomplete gRPC request message")

res, err := ra.UpdateRegistrationKey(context.Background(), &rapb.UpdateRegistrationKeyRequest{
RegistrationID: expectRegID,
Jwk: expectJwk,
})
test.AssertNotError(t, err, "should have been able to update registration key")
test.AssertEquals(t, res.Id, expectRegID)
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
test.AssertDeepEquals(t, res.Key, expectJwk)
test.AssertDeepEquals(t, mockSA.providedJwk, expectJwk)

// Switch to a mock SA that will always error if UpdateRegistrationKey() is
// called.
ra.SA = &NoUpdateSA{}
_, err = ra.UpdateRegistrationKey(context.Background(), &rapb.UpdateRegistrationKeyRequest{
RegistrationID: expectRegID,
Jwk: expectJwk,
})
test.AssertError(t, err, "should have received an error from the SA")
test.AssertContains(t, err.Error(), "failed to update registration key")
test.AssertContains(t, err.Error(), "mocked to always error")
}
Loading

0 comments on commit 6a2819a

Please sign in to comment.