Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document metadata support for MessageEncryptor #29892

Merged
merged 2 commits into from Jul 24, 2017
Merged

Document metadata support for MessageEncryptor #29892

merged 2 commits into from Jul 24, 2017

Conversation

assain
Copy link
Contributor

@assain assain commented Jul 22, 2017

@kaspth

This pull request adds the necessary documentation for the metadata support added to the MessageEncryptor: #29599

@rails-bot
Copy link

Thanks for the pull request, and welcome! The Rails team is excited to review your changes, and you should hear from @eileencodes (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

This repository is being automatically checked for code quality issues using Code Climate. You can see results for this analysis in the PR status below. Newly introduced issues should be fixed before a Pull Request is considered ready to review.

Please see the contribution instructions for more information.

@@ -21,7 +21,29 @@ module ActiveSupport
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
# crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
# crypt.decrypt_and_verify(encrypted_data) ` # => "my secret data"
Copy link
Member

Choose a reason for hiding this comment

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

You've added a back-tick char by mistake between the code and the #.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops! I'll correct it right away 😅

#
# * <tt>:purpose</tt> - The purpose for which the message is intended to be used.
# * <tt>:expires_at</tt> - The expiration date of the message.
# * <tt>:expires_in</tt> - The relative expiration date. Note that, :expires_at takes
Copy link
Member

Choose a reason for hiding this comment

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

Maybe you can surround :expires_at and :expires_in with a + sign to make them formatted on the generated documentation.

# encrypted_data = crypt.encrypt_and_sign(address, purpose: "shipping", expires_at: 1.year.from_now) # => "B/yFvIcSzfaVQcjQYU+pGq..."
#
# Trying to use a message for a different purpose or trying to use an expired message
# would fail and return +nil+
Copy link
Member

Choose a reason for hiding this comment

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

Nit-picky but you can add a period at the end of this sentence.

# would fail and return +nil+
#
# different_purpose = crypt.decrypt_and_verify(encrypted_data, purpose: "login") # => nil
# address = crypt.decrypt_and_verify(encrypted_data, purpose: :shipping) # => "#23 main street"
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about removing the variables ? They complicate a bit the example and without them, we can easily see that only the :purpose key is different between the two lines.

# different_purpose = crypt.decrypt_and_verify(encrypted_data, purpose: "login") # => nil
# address = crypt.decrypt_and_verify(encrypted_data, purpose: :shipping) # => "#23 main street"
#
# You could use the expires_in option to set the expiration date relatively.
Copy link
Member

Choose a reason for hiding this comment

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

You can also use +:expires_in+ here to properly format the text.

#
# encrypted_data = crypt.encrypt_and_sign(address, expires_in: 2.months) # => "Dwx8ht2FWhtQbA
#
# This generates an encrypted message that expires in 2 months from now.
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about:

This generates an encrypted message that will expire in 2 months.

@robin850 robin850 assigned kaspth and unassigned eileencodes Jul 22, 2017
@assain
Copy link
Contributor Author

assain commented Jul 22, 2017

Hi, @robin850
I've made the suggested changes 😄

Copy link
Member

@robin850 robin850 left a comment

Choose a reason for hiding this comment

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

Thanks for the quick update ! :-) Left a few comments ; could you also add [ci skip] to the commit message ? This will prevent Travis CI from running a build.

#
# * <tt>+:purpose+</tt> - The purpose for which the message is intended to be used.
# * <tt>+:expires_at+</tt> - The expiration date of the message.
# * <tt>+:expires_in+</tt> - The relative expiration date. Note that, :expires_at takes
Copy link
Member

@robin850 robin850 Jul 22, 2017

Choose a reason for hiding this comment

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

Whoops, sorry, my comment wasn't clear. Actually, RDoc allows you to write formatted code snippet through <tt>code</tt> or with +code+. Though, the latter, while simpler, doesn't support a lot of fashions.

My original comment was about the :expires_at and :expires_in references in this sentence that could be wrapped around plus signs.

# * <tt>+:purpose+</tt> - The purpose for which the message is intended to be used.
# * <tt>+:expires_at+</tt> - The expiration date of the message.
# * <tt>+:expires_in+</tt> - The relative expiration date. Note that, :expires_at takes
# precedence over :expires_in.
Copy link
Member

Choose a reason for hiding this comment

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

Could you align the beginning of this line with the beginning of the above one ? Otherwise, RDoc will think that this is a code snippet.

You can check-out how the documentation you write will look on the API site running bundle exec rake rdoc and by opening the index.html file under doc/rdoc. :-)

# precedence over :expires_in.
#
# address = "#23 main street"
# encrypted_data = crypt.encrypt_and_sign(address, purpose: "shipping", expires_at: 1.year.from_now) # => "B/yFvIcSzfaVQcjQYU+pGq..."
Copy link
Member

Choose a reason for hiding this comment

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

I missed it the first time around but actually it's four spaces to create a code block.

# crypt.decrypt_and_verify(encrypted_data, purpose: "login") # => nil
# crypt.decrypt_and_verify(encrypted_data, purpose: :shipping) # => "#23 main street"
#
# You could use the +expires_in+ option to set the expiration date relatively.
Copy link
Member

Choose a reason for hiding this comment

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

Missing : in front of :expires_in.

# Trying to use a message for a different purpose or trying to use an expired message
# would fail and return +nil+.
#
# crypt.decrypt_and_verify(encrypted_data, purpose: "login") # => nil
Copy link
Member

Choose a reason for hiding this comment

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

Nit-picky but could you align the two # please ? 😊

# * <tt>+:expires_at+</tt> - The expiration date of the message.
# * <tt>+:expires_in+</tt> - The relative expiration date. Note that, :expires_at takes
# precedence over :expires_in.
#
Copy link
Member

Choose a reason for hiding this comment

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

Could you add a tiny "For example:" here ?

@assain
Copy link
Contributor Author

assain commented Jul 22, 2017

Hi @robin850 , sorry for the trouble, I've made the requested changes! 😄

# You could use the +:expires_in+ option, to set the expiration date relatively.
#
# # Generates an encrypted message that expires in 2 months.
# encrypted_data = crypt.encrypt_and_sign(address, expires_in: 2.months) # => "Dwx8ht2FWhtQb.."
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I'm not too much of a fan of the raw options list and prefer to read things more as a "story" kind of.

How does this sound?

# === Confining messages to a specific purpose
#
# By default any message can be used throughout your app. But they can also be
# confined to a specific +:purpose+.
# 
#   token = crypt.encrypt_and_sign("this is the chair", purpose: :login)
#
# Then that same purpose must be passed when verifying to get the data back out:
#
#   crypt.decrypt_and_verify(token, purpose: :login)    # => "this is the chair"
#   crypt.decrypt_and_verify(token, purpose: :shipping) # => nil
#   crypt.decrypt_and_verify(token)                     # => nil
#
# Likewise, if a message has no purpose it won't be returned when verifying with
# a specific purpose.
# 
#   token = crypt.encrypt_and_sign("the conversation is lively")
#   crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil
#   crypt.decrypt_and_verify(token)                          # => "the conversation is lively"
#
# === Making messages expire
#
# By default messages last forever and verifying one years from now will still
# return the original value. But messages can be set to expire at a given
# time with +:expires_in+ or +:expires_at+.
#
#   crypt.encrypt_and_sign(parcel, expires_in: 1.month)
#   crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
#
# Then the messages can be verified and returned upto the expire time.
# Thereafter, verifying returns +nil+.

(Note: we don't indent code by 4 spaces, only 3 after #)

Copy link
Contributor Author

@assain assain Jul 23, 2017

Choose a reason for hiding this comment

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

Wow, this looks elegant 😄

@robin850
Copy link
Member

robin850 commented Jul 23, 2017

(No problem @assain ; I am sorry for wasting your time, I should've waited for Kasper's feedback. 😊 )

Ugh, and regarding 3 spaces for code blocks, yeah, it looks like I had a bug with RDoc parsing your example.

@assain
Copy link
Contributor Author

assain commented Jul 23, 2017

Hi, @kaspth. I've added the suggested changes. 😄

#
# Then the messages can be verified and returned upto the expire time.
# Thereafter, the +verified+ method returns +nil+ and the +verify+ method would
# raise +InvalidSignature+.
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we sure that the verify method would raise? Let's be sure with a test in the MessageVerifierMetadataTest please.

(If yes, the InvalidSignature exception should also be fully qualified to <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>)

Copy link
Contributor

Choose a reason for hiding this comment

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

We're mixing present and future tense here too (returns then later would raise).

Let's also ditch the the x method construct and just say x.

# Thereafter, +verified+ returns +nil+ while +verify+ raises
# <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.

#29599 - Added expiry and purpose to MessageEncryptor
#29854 - Added expiry and purpose to MessageVerifier

*Assain Jaleel guided by Kasper Timm Hansen*
Copy link
Contributor

Choose a reason for hiding this comment

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

Just put your name here 😉

Copy link
Contributor Author

@assain assain Jul 23, 2017

Choose a reason for hiding this comment

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

I was just wondering whether I could leave it this way? 😅

Copy link
Contributor

@kaspth kaspth Jul 23, 2017

Choose a reason for hiding this comment

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

I appreciate the sentiment, but from a GSoC point of view I'd like you to stand on your own two feet :)

Copy link
Contributor Author

@assain assain Jul 23, 2017

Choose a reason for hiding this comment

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

👍 thanks

@verifier.verify(token, purpose: :shipping)

#29599 - Added expiry and purpose to MessageEncryptor
#29854 - Added expiry and purpose to MessageVerifier
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's just put these down like Pull requests: #29599, #29854 (add this one too), as people can see the title when they click on it.

@@ -1,3 +1,28 @@
* Add expiry and purpose to MessageEncryptor & MessageVerifier.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's say purpose and expiry support and wrap the verifier and encryptor in backticks.

@@ -1,3 +1,28 @@
* Add expiry and purpose to MessageEncryptor & MessageVerifier.
Copy link
Contributor

Choose a reason for hiding this comment

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

Also let's make it verifier first, encryptor second throughout the changelog since signing is less complex than encrypting (which for the MessageEncryptor is really encrypt and sign).


token = @verifier.generate(address, purpose: "shipping", expires_in: 1.month)
@verifier.verified(token, purpose: "shipping")
@verifier.verify(token, purpose: :shipping)
Copy link
Contributor

Choose a reason for hiding this comment

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

Think this is too detailed for a changelog entry. How does this attempt at condensing the description sound?

For instance, to ensure a message is only usable for one intended purpose:

    token = @verifier.generate("x", purpose: :shipping)

    @verifier.verified(token, purpose: :shipping) # => "x"
    @verifier.verified(token)                     # => nil

Or make it expire after a set time:

    @verifier.generate("x", expires_in: 1.month)
    @verifier.generate("y", expires_at: Time.now.end_of_year)

Showcased with `ActiveSupport::MessageVerifier`, but works the same for 
`ActiveSupport::MessageEncryptor`'s `encrypt_and_sign` and `decrypt_and_verify`.

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 is much better style 👍

Copy link
Contributor Author

@assain assain Jul 23, 2017

Choose a reason for hiding this comment

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

I see all the changelog entries using backticks for class and method names, but I still don't get why? 😅

@assain
Copy link
Contributor Author

assain commented Jul 23, 2017

Just wondering 😅 , how come the tests pass, although there's an issue with one of my commits

# @verifier.verified(token) # => nil
#
# @verifier.verify(token, purpose: :login) # => "this is the chair"
# @verifier.verify(token, purpose: shipping) # => ActiveSupport::MessageVerifier::InvalidSignature
Copy link
Member

Choose a reason for hiding this comment

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

: is missing before shipping

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing it out 😄

@assain
Copy link
Contributor Author

assain commented Jul 23, 2017

Hi, @kaspth . I've made the suggested changes, again, 😅

@assain
Copy link
Contributor Author

assain commented Jul 24, 2017

Hello, @kaspth 😄

I've got the tests passing. Is it okay if I add the new tests to MessageVerifier via a new PR?

@kaspth kaspth merged commit e4bf6b0 into rails:master Jul 24, 2017
@kaspth
Copy link
Contributor

kaspth commented Jul 24, 2017

Yup, that's fine 😉

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.

None yet

6 participants