Skip to content

Commit

Permalink
Signed Global IDs accept a :verifier and default to SignedGlobalID.ve…
Browse files Browse the repository at this point in the history
…rifier
  • Loading branch information
jeremy committed Aug 19, 2014
1 parent 1f18483 commit 5aaecaf
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 57 deletions.
2 changes: 1 addition & 1 deletion globalid.gemspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'globalid'
s.version = '0.2.2'
s.version = '0.2.3'
s.summary = 'Refer to any model with a URI: gid://app/class/id'
s.description = 'URIs for your models makes it easy to pass references around.'

Expand Down
18 changes: 9 additions & 9 deletions lib/global_id/global_id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ class << self
def create(model, options = {})
app = options.fetch :app, GlobalID.app
raise ArgumentError, "An app is required to create a GlobalID. Pass the :app option or set the default GlobalID.app." unless app
new URI("gid://#{app}/#{model.class.name}/#{model.id}")
new URI("gid://#{app}/#{model.class.name}/#{model.id}"), options
end

def find(gid, options = {})
parse(gid).try(:find, options)
end

def parse(gid)
gid.is_a?(self) ? gid : new(gid)
def parse(gid, options = {})
gid.is_a?(self) ? gid : new(gid, options)
rescue URI::Error
parse_encoded_gid(gid)
parse_encoded_gid(gid, options)
end

def parse_encoded_gid(gid)
new Base64.urlsafe_decode64(repad_gid(gid)) rescue nil
end

private
def parse_encoded_gid(gid, options)
new(Base64.urlsafe_decode64(repad_gid(gid)), options) rescue nil
end

# We removed the base64 padding character = during #to_param, now we're adding it back so decoding will work
def repad_gid(gid)
padding_chars = gid.length.modulo(4).zero? ? 0 : (4 - gid.length.modulo(4))
Expand All @@ -38,7 +38,7 @@ def repad_gid(gid)

attr_reader :uri, :app, :model_name, :model_id

def initialize(gid)
def initialize(gid, options = {})
extract_uri_components gid
end

Expand Down
41 changes: 27 additions & 14 deletions lib/global_id/signed_global_id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,40 @@
class SignedGlobalID < GlobalID
class << self
attr_accessor :verifier
end

def self.create(model, options = {})
self.verifier ||= options[:verifier]
with_present_verifier { super }
end
def parse(sgid, options = {})
if sgid.is_a? self
sgid
else
super verify(sgid, pick_verifier(options))
end
end

def self.parse(sgid)
sgid.is_a?(self) ? sgid : with_present_verifier { super(verifier.verify(sgid)) }
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
# Grab the verifier from options and fall back to SignedGlobalID.verifier.
# Raise ArgumentError if neither is available.
def pick_verifier(options)
options.fetch :verifier do
verifier || raise(ArgumentError, 'Pass a `verifier:` option with an `ActiveSupport::MessageVerifier` instance, or set a default SignedGlobalID.verifier.')
end
end

private
def verify(sgid, verifier)
verifier.verify(sgid)
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
end
end

def self.with_present_verifier(&block)
raise ArgumentError, "#{name}.verifier is nil. Set a verifier on #{name}" unless verifier
block.call
attr_reader :verifier

def initialize(gid, options = {})
super
@verifier = self.class.pick_verifier(options)
end
private_class_method :with_present_verifier

def to_s
@sgid ||= self.class.verifier.generate(super)
@sgid ||= @verifier.generate(super)
end
alias to_param to_s
end
77 changes: 44 additions & 33 deletions test/cases/signed_global_id_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,6 @@ class SignedGlobalIDTest < ActiveSupport::TestCase
@person_sgid = SignedGlobalID.create(Person.new(5))
end

test '.parse raises when verifier is nil' do
begin
gid = @person_sgid.to_s
SignedGlobalID.verifier = nil
assert_raise ArgumentError do
SignedGlobalID.parse(gid)
end
ensure
SignedGlobalID.verifier = VERIFIER
end
end

test '.create raises when verifier is nil' do
begin
SignedGlobalID.verifier = nil
assert_raise ArgumentError do
SignedGlobalID.create(Person.new(5))
end
ensure
SignedGlobalID.verifier = VERIFIER
end
end

test 'accepts a verifier on .create' do
begin
SignedGlobalID.verifier = nil
expected = SignedGlobalID.create(Person.new(5), verifier: VERIFIER)
assert_equal @person_sgid, expected
ensure
SignedGlobalID.verifier = VERIFIER
end
end

test 'as string' do
assert_equal 'Z2lkOi8vYmN4L1BlcnNvbi81--bd2dab1418d8577e10cf93f8ec055b4b61690755', @person_sgid.to_s
end
Expand All @@ -62,3 +29,47 @@ class SignedGlobalIDTest < ActiveSupport::TestCase
assert_equal @person_sgid.to_s, @person_sgid.to_param
end
end

class SignedGlobalIDVerifierTest < ActiveSupport::TestCase
setup do
@person_sgid = SignedGlobalID.create(Person.new(5))
end

test 'parse raises when default verifier is nil' do
gid = @person_sgid.to_s
with_default_verifier nil do
assert_raise ArgumentError do
SignedGlobalID.parse(gid)
end
end
end

test 'create raises when default verifier is nil' do
with_default_verifier nil do
assert_raise ArgumentError do
SignedGlobalID.create(Person.new(5))
end
end
end

test 'create accepts a :verifier' do
with_default_verifier nil do
expected = SignedGlobalID.create(Person.new(5), verifier: VERIFIER)
assert_equal @person_sgid, expected
end
end

test 'new accepts a :verifier' do
with_default_verifier nil do
expected = SignedGlobalID.new(Person.new(5).gid.uri, verifier: VERIFIER)
assert_equal @person_sgid, expected
end
end

def with_default_verifier(verifier)
original, SignedGlobalID.verifier = SignedGlobalID.verifier, verifier
yield
ensure
SignedGlobalID.verifier = original
end
end

0 comments on commit 5aaecaf

Please sign in to comment.