-
Notifications
You must be signed in to change notification settings - Fork 12
[WIP] Add base64 serialization #28
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
Conversation
lib/mongoid/scroll/cursor.rb
Outdated
@@ -23,6 +23,10 @@ def criteria | |||
cursor_selector.__evolve_object_id__ | |||
end | |||
|
|||
def to_base64 | |||
Base64.strict_encode64({ value: to_s, field_type: field_type, field_name: field_name, direction: direction }.to_json) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Base64.strict_encode64
to follow RFC 4648 and have an URL-friendly string.
spec/mongoid/scroll_cursor_spec.rb
Outdated
@@ -8,6 +8,16 @@ | |||
its(:tiebreak_id) { should be_nil } | |||
its(:value) { should be_nil } | |||
its(:criteria) { should eq({}) } | |||
describe 'base64' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it follows the spec style of this gem. Feel free to correct me, and I'll add other specs after your feedback.
lib/mongoid/scroll/cursor.rb
Outdated
@@ -31,6 +35,13 @@ def from_record(record, options) | |||
cursor.tiebreak_id = record['_id'] | |||
cursor | |||
end | |||
|
|||
def from_base64(str) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll be the caller responsibility to either:
- encrypt/sign the Base64 string before making it public to be sure that it's not modified
- perform a check after deserializing the cursor to make sure that
Mongoid::Scroll::Cursor
params are allowed to sort (field_name
,direction
, etc...)
The README should probably be updated to mention this (in addition to a description of the new feature) if this PR is worth merging.
|
2.a. The gem user could also encrypt it (from their app) before making it public. For example, something like: crypt = ActiveSupport::MessageEncryptor.new("this secret must be 32 bytes 123")
encrypted = crypt.encrypt_and_sign("eyJ2YWx1ZSI6IjU0MTAxNGUxN2FhNThkOGNjZjAwMDAyNTo1NDEwMTRlMTdhYTU4ZDhjY2YwMDAwMjUiLCJmaWVsZF90eXBlIjoiQlNPTjo6T2JqZWN0SWQiLCJmaWVsZF9uYW1lIjoiX2lkIiwiZGlyZWN0aW9uIjoxfQ==")
# encrypted can now be shared publicly
# Decrypt when you receive the encrypted string from your end user
crypt.decrypt_and_verify(encrypted) This one uses
2.b. If the gem user doesn't want to encrypt, they can verify the cursor params (from their app) after deserializing. For example: cursor = Mongoid::Scroll::Cursor.from_base64("eyJ2YWx1ZSI6IjU0MTAxNGUxN2FhNThkOGNjZjAwMDAyNTo1NDEwMTRlMTdhYTU4ZDhjY2YwMDAwMjUiLCJmaWVsZF90eXBlIjoiQlNPTjo6T2JqZWN0SWQiLCJmaWVsZF9uYW1lIjoiX2lkIiwiZGlyZWN0aW9uIjoxfQ==")
raise "Cursor can't use another field_name than id" unless cursor.field_name == "_id" This should be stated in the README when describing the
2.c. That's an interesting idea. I think it could be implemented in all the [5] pry(main)> cursor = nil; Feed::Item.all.limit(2).scroll.map(&:id)
=> [BSON::ObjectId('541014e17aa58d8ccf000025'), BSON::ObjectId('541015010f4ca111df0000b0')]
[6] pry(main)> cursor
=> nil
[7] pry(main)> cursor = nil; Feed::Item.all.limit(2).scroll {|_, c| cursor = c}; cursor
=> #<Mongoid::Scroll::Cursor:0x0000562b394c55a8
@direction=1,
@field_name="_id",
@field_type="BSON::ObjectId",
@tiebreak_id=BSON::ObjectId('541015010f4ca111df0000b0'),
@value=BSON::ObjectId('541015010f4ca111df0000b0')>
[8] pry(main)> Feed::Item.all.limit(2).scroll(cursor).map(&:id)
=> [BSON::ObjectId('54101d4d0f4ca1fd04000056'), BSON::ObjectId('54101d960f4ca115b700001e')]
[9] pry(main)> cursor.direction = -1
=> -1
[10] pry(main)> Feed::Item.all.limit(2).scroll(cursor).map(&:id)
Exception: Cursor not following the original sort: [{:field_type=>"BSON::ObjectId", :field_name=>"_id", :direction=>1}, {:field_type=>"BSON::ObjectId", :field_name=>"_id", :direction=>-1}]
3.a. In theory, it makes sense. The issue is that [1] pry(main)> cursor = nil; Feed::Item.limit(2).scroll(nil, Mongoid::Scroll::Base64EncodedCursor) {|_, c| cursor = c}; cursor
=> #<Mongoid::Scroll::Base64EncodedCursor:0x000056175ba7f468
@direction=1,
@field_name="_id",
@field_type="BSON::ObjectId",
@tiebreak_id=BSON::ObjectId('541015010f4ca111df0000b0'),
@value=BSON::ObjectId('541015010f4ca111df0000b0')>
[2] pry(main)> cursor.to_s
=> "eyJ2YWx1ZSI6IjU0MTAxNTAxMGY0Y2ExMTFkZjAwMDBiMDo1NDEwMTUwMTBmNGNhMTExZGYwMDAwYjAiLCJmaWVsZF90eXBlIjoiQlNPTjo6T2JqZWN0SWQiLCJmaWVsZF9uYW1lIjoiX2lkIiwiZGlyZWN0aW9uIjoxfQ=="
3.b. IDK if it's really the role of the gem to chose signing algorithms. We could pass a block which would be used to sign the hash, but it's maybe a bit convoluted (I tried it in b22a195): > cursor = nil; Feed::Item.all.limit(2).scroll(nil, Mongoid::Scroll::SignedBase64EncodedCursor) {|_, c| cursor = c}; cursor
> p = Proc.new { |s| OpenSSL::HMAC.hexdigest("SHA256", key, s) }
> e = cursor.to_s(&p)
=> "eyJ2YWx1ZSI6IjU0MTAxNTAxMGY0Y2ExMTFkZjAwMDBiMDo1NDEwMTUwMTBmNGNhMTExZGYwMDAwYjAiLCJmaWVsZF90eXBlIjoiQlNPTjo6T2JqZWN0SWQiLCJmaWVsZF9uYW1lIjoiX2lkIiwiZGlyZWN0aW9uIjoxLCJzaWduIjoiYzNhNzU0MDg3MGU1OTVkYjE3YjU4ZGVhZTAzZWRkMTQyY2U1MzY4ZGY4NjI0NTg5NzM1NmMwOWRkNTU3ZWY4OSJ9"
> Mongoid::Scroll::SignedBase64EncodedCursor.deserialize(e, &p)
=> #<Mongoid::Scroll::SignedBase64EncodedCursor:0x000055a292ccc8d0
@direction=1,
@field_name="_id",
@field_type="BSON::ObjectId",
@tiebreak_id=BSON::ObjectId('541015010f4ca111df0000b0'),
@value="541015010f4ca111df0000b0">
> Mongoid::Scroll::SignedBase64EncodedCursor.deserialize(e) { |_| "invalid" }
Exception: Invalid signature FTR, the commits I referenced are not meant to be deeply reviewed. They are quick and dirty tests and mainly for demonstration purpose. If we don't want to go this way, we can just revert them. |
First, thank you for coming here and making all these proposals! Appreciate your patience and detailed suggestions, please don't take my pushing back as too nitpicky, do lmk when it becomes that way. I'm thinking out loud with you, and I'd love to work through each one of your suggestions and try to get the best solutions in. These are interesting problems to me!
I think you should pick the simplest of the things above and make separate PRs! Let's get some code merged. |
No worries, I'm all for constructive criticism 🙂 2a. and 2b. should become useless if 2c. is implemented. Since I started a PR for 2c., I won't tackle them. It'll indeed be better to split it into several PRs, time for a TODO list!
|
We have semver for that, we can just increment the major version or pretend like it's a fix ;) If you feel conservative, do the former. |
I do like the idea that users may want to serialize cursors differently, so I wouldn't dismiss the need to subclass |
Closing this PR since it was split into other PRs/issues. |
Hi,
This PR adds
Mongoid::Scroll::Cursor#to_base64
andMongoid::Scroll::Cursor.from_base64
convenience methods to easily serialize theMongoid::Scroll::Cursor
to an URL-friendly string.The goal is to use the
mongoid-scroll
gem in an API to build a cursor based pagination. The API response would look like this:Instead of implementing this mechanism in the application, I thought that it might be useful to integrate it to the gem.