diff --git a/acme/api/account.go b/acme/api/account.go index bb6c92d6..35f7f929 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -65,7 +65,9 @@ func (u *UpdateAccountRequest) Validate() error { } return nil default: - return acme.MalformedErr(errors.Errorf("empty update request")) + // According to the ACME spec (https://tools.ietf.org/html/rfc8555#section-7.3.2) + // accountUpdate should ignore any fields not recognized by the server. + return nil } } @@ -148,6 +150,8 @@ func (h *Handler) GetUpdateAccount(w http.ResponseWriter, r *http.Request) { return } + // If PostAsGet just respond with the account, otherwise process like a + // normal Post request. if !payload.isPostAsGet { var uar UpdateAccountRequest if err := json.Unmarshal(payload.value, &uar); err != nil { @@ -159,9 +163,12 @@ func (h *Handler) GetUpdateAccount(w http.ResponseWriter, r *http.Request) { return } var err error + // If neither the status nor the contacts are being updated then ignore + // the updates and return 200. This conforms with the behavior detailed + // in the ACME spec (https://tools.ietf.org/html/rfc8555#section-7.3.2). if uar.IsDeactivateRequest() { acc, err = h.Auth.DeactivateAccount(prov, acc.GetID()) - } else { + } else if len(uar.Contact) > 0 { acc, err = h.Auth.UpdateAccount(prov, acc.GetID(), uar.Contact) } if err != nil { diff --git a/acme/api/account_test.go b/acme/api/account_test.go index a3ebf55c..65dfbb61 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -143,6 +143,11 @@ func TestUpdateAccountRequestValidate(t *testing.T) { }, } }, + "ok/accept-empty": func(t *testing.T) test { + return test{ + uar: &UpdateAccountRequest{}, + } + }, } for name, run := range tests { tc := run(t) @@ -700,7 +705,29 @@ func TestHandlerGetUpdateAccount(t *testing.T) { statusCode: 200, } }, - "ok/new-account": func(t *testing.T) test { + "ok/update-empty": func(t *testing.T) test { + uar := &UpdateAccountRequest{} + b, err := json.Marshal(uar) + assert.FatalError(t, err) + ctx := context.WithValue(context.Background(), provisionerContextKey, prov) + ctx = context.WithValue(ctx, accContextKey, &acc) + ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) + return test{ + auth: &mockAcmeAuthority{ + getLink: func(typ acme.Link, provID string, abs bool, in ...string) string { + assert.Equals(t, typ, acme.AccountLink) + assert.Equals(t, provID, acme.URLSafeProvisionerName(prov)) + assert.True(t, abs) + assert.Equals(t, in, []string{accID}) + return fmt.Sprintf("https://ca.smallstep.com/acme/%s/account/%s", + acme.URLSafeProvisionerName(prov), accID) + }, + }, + ctx: ctx, + statusCode: 200, + } + }, + "ok/update-contacts": func(t *testing.T) test { uar := &UpdateAccountRequest{ Contact: []string{"foo", "bar"}, }