Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

Mutation improvements #870

Merged
merged 6 commits into from Nov 30, 2017
Merged

Mutation improvements #870

merged 6 commits into from Nov 30, 2017

Conversation

gdbelvin
Copy link
Contributor

In the continued quest for easier account account logic...

This PR gives entry.Mutation the following new responsibilities:

  • Fully setting up the UpdateRequest proto so callers don't have to manually fix it up.
  • Verifying whether an update was applied correctly - Check()

As a result, the client can now operate on units of entry.Mutation rather than raw request protos.
This allows us to fix an outstanding TODO that will resolve two racing clients gracefully by recreating requests from scratch on each Retry rather than blindly resubmitting the same request.

Other nits:

  • All functions that accept userID and appID should do so in domainID, appID, userID order.

- Add a sanity check to the sign operation.
- Selectively copy fields from previous entry.
- Check() now returns bool, error
@codecov-io
Copy link

codecov-io commented Nov 29, 2017

Codecov Report

Merging #870 into master will increase coverage by 3.14%.
The diff coverage is 54.05%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #870      +/-   ##
==========================================
+ Coverage   45.64%   48.78%   +3.14%     
==========================================
  Files          34       34              
  Lines        2905     2353     -552     
==========================================
- Hits         1326     1148     -178     
+ Misses       1379     1001     -378     
- Partials      200      204       +4
Impacted Files Coverage Δ
core/client/kt/requests.go 21.05% <30.76%> (+21.05%) ⬆️
core/client/kt/verify.go 50% <50%> (-2.64%) ⬇️
core/mutator/entry/mutation.go 76.25% <68.18%> (+2.5%) ⬆️
impl/authorization/authorization.go 92.3% <0%> (-1.45%) ⬇️
core/keyserver/validate.go 44.11% <0%> (-1.34%) ⬇️
impl/google/authentication/google.go 17.3% <0%> (-0.88%) ⬇️
core/crypto/dev/dev.go 100% <0%> (ø) ⬆️
core/monitor/monitor.go 0% <0%> (ø) ⬆️
core/monitor/sign.go 0% <0%> (ø) ⬆️
... and 24 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a9b1090...5a8026b. Read the comment docs.

// Check verifies that an update was successfully applied.
// Returns nil if newLeaf is equal to the entry in this mutation.
func (m *Mutation) Check(newLeaf []byte) error {
// XXX Mutations are no longer stable serialized byte slices, so we need to use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XXX - is this a todo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more of a warning and a TODO that's related to our discussion of ObjectHash.
Storing objects is really nice because I get to avoid lots of UnMarshals, but then I need to do object comparison in a reliable way...

}

if got, want := leafValue, m.entry; !proto.Equal(got, want) {
return errors.New("newLeaf does not match expected value")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen advice that there's no special reason to use errors.New just because you have no varargs items for a fmt.Errorf() call. But I think you have pointed me to https://en.wikipedia.org/wiki/Uncontrolled_format_string on this topic, am I recalling correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. A secure way of using fmt.Errorf in this case would look like:

 fmt.Errorf("%v", "A string with no variables")

@@ -148,3 +153,19 @@ func (m *Mutation) sign(signers []signatures.Signer) (*pb.Entry, error) {
m.entry.Signatures = sigs
return m.entry, nil
}

// Check verifies that an update was successfully applied.
// Returns nil if newLeaf is equal to the entry in this mutation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the function is answering a yes/no question, but that errors can occur.
With this func signature the caller can't distinguish between 'no' and 'oops'.

A (bool, error) return would make it unambiguous but is ugly.

Better would be an error constant that the caller can check for.
You could just move the errors.New("message") to an constant in this package and return that.

Does Check need to be exported? I see it being used in this same file. If so then the error constant should be too.

Copy link
Contributor Author

@gdbelvin gdbelvin Nov 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(bool, error) is what I ended up doing.
I thought about defining a public error, but then considered that it would force calling sites into a just as awkward error parsing.

if err := m.Check(); err == ErrNotEqual {
} else if err != nil {
return err
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I don't mind much either way, good you considered both options.

@@ -59,9 +58,13 @@ func NewMutation(index []byte, domainID, appID, userID string) *Mutation {
}
}

// CopyPrevious indicates whether to overwrite all values in the current entry with
// the previous entry's values.
type CopyPrevious bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a readability gain from having a typedef'd bool.

In SetPrevious itself, you write 'if copyPrevious { ... }'
You'd write that whether or not you define the type here.

If you want the extra type safety / documentation then making this more of an enum so that values are named as well would be more helpful.

type CopyPrevious int
const (
  copyPrevious = iota
  dontCopyPrevious
)

maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I use a plain bool then?

@@ -156,16 +167,13 @@ func (m *Mutation) sign(signers []signatures.Signer) (*pb.Entry, error) {

// Check verifies that an update was successfully applied.
// Returns nil if newLeaf is equal to the entry in this mutation.
func (m *Mutation) Check(newLeaf []byte) error {
func (m *Mutation) Check(newLeaf []byte) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, I didn't see you've already thought about this since the previous commit.
Do consider the error constant alternative though.

@@ -230,9 +228,9 @@ func (c *Client) ListHistory(ctx context.Context, userID, appID string, start, e

// Update creates an UpdateEntryRequest for a user, attempt to submit it multiple
// times depending on RetryCount.
func (c *Client) Update(ctx context.Context, userID, appID string, profileData []byte,
func (c *Client) Update(ctx context.Context, appID, userID string, profileData []byte,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hope you caught all the call-sites of this change!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I did... the unit tests helped me track down places where I had missed it.

}

// Retry will take a pre-fabricated request and send it again.
func (c *Client) Retry(ctx context.Context, req *pb.UpdateEntryRequest, opts ...grpc.CallOption) error {
// Retry will take a mutation and send it again.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "Retry takes a mutation and..."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you disagree? I just thought working the comment in future tense seemed odd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment slipped by. Fixed now.

return nil, fmt.Errorf("Error unmarshaling Entry from leaf proof: %v", err)
// NewMutation creates a Mutation given the userID, desired state, and previous entry.
func (v *Verifier) NewMutation(
domainID, appID, userID string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having no stronger typing and a list of 3 different fooIDs makes me a bit nervous... it'd be easy to transpose args by mistake in a call site and get some badly broken behaviour as a result...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... Consistency between APIs should help, but I'm not sure what else to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the strong typing option is available:

type DomainID string
type AppID string
type UserID string

Do you think that would cause a lot of churn and annoyance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of think it would cause more churn and type casting than really needed.
If a call site gets it wrong all subsequent crypto proofs will fail, so there's an easy way to tell for sure if you got it right.

{true, true, GetNewOutgoingContextWithFakeAuth("bob"), "bob", signers1, authorizedKeys1}, // Update
{true, true, GetNewOutgoingContextWithFakeAuth("bob"), "bob", signers2, authorizedKeys2}, // Update, changing keys
{true, true, GetNewOutgoingContextWithFakeAuth("bob"), "bob", signers3, authorizedKeys3}, // Update, using new keys
{"Empty", false, false, context.Background(), "noalice", signers1, authorizedKeys1},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pushing the limit of what's readable and maintainable in a test table full of one-liners (and no labels!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added labels :-)

@gdbelvin
Copy link
Contributor Author

Updated. PTAL

@gdbelvin gdbelvin merged commit 0cbe7d5 into google:master Nov 30, 2017
@gdbelvin gdbelvin deleted the mutation branch November 30, 2017 16:30
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants