globalid 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of globalid might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3de819ff2f02d59893c4a4fd90539488ff7179e9
4
- data.tar.gz: e2e16efbcff7df37e004aa3102fb4f0fafa52dfa
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: d325de1fd4887ed3e91ebb2ec6d58d4ed0d552d0
4
+ data.tar.gz: e7d0091c1cc92b20c682093faa424b528c32940f
5
5
  SHA512:
6
- metadata.gz: 6c822d9f0ba64363ed40ecea47d85e36bd16a84c4fb171cc547dc510c886143217c58c403a5d8f8c37c259f60c34f631761c50ed005f9d7e9cfc57c3b0098ff8
7
- data.tar.gz: 00d2640d7e991dcee6fa4f69ea2d56a1aeae047aa0505864dea88b7f2de9aa4ef0892357c53902a433846750db5899209f1d5cfa7bea6881780da909fed90384
6
+ metadata.gz: 98f92c4c3fa1e9a127abcc44f1eb51ca529f5388dec8529aacce43c18a9fc9cc9aa947854620abe0bd609ebb46dfcee22a1d81089bffb83d106d29967c457147
7
+ data.tar.gz: 989269225c2d49d7a25cb6bb5e7f9bca1087268d5688d27558bb8d2fa1d1062360bbf72f2d8004cb764c886217f4d6a7eed73da1e0a1fe190b1ba250f1026bcb
@@ -6,7 +6,7 @@ require 'uri'
6
6
 
7
7
  class GlobalID
8
8
  class << self
9
- attr_accessor :app
9
+ attr_reader :app
10
10
 
11
11
  def create(model, options = {})
12
12
  app = options.fetch :app, GlobalID.app
@@ -15,7 +15,7 @@ class GlobalID
15
15
  end
16
16
 
17
17
  def find(gid, options = {})
18
- parse(gid).try(:find, options)
18
+ parse(gid, options).try(:find, options)
19
19
  end
20
20
 
21
21
  def parse(gid, options = {})
@@ -24,6 +24,17 @@ class GlobalID
24
24
  parse_encoded_gid(gid, options)
25
25
  end
26
26
 
27
+ def app=(app)
28
+ @app = validate_app(app)
29
+ end
30
+
31
+ def validate_app(app)
32
+ URI.parse('gid:///').hostname = app
33
+ rescue URI::InvalidComponentError
34
+ raise ArgumentError, 'Invalid app name. ' \
35
+ 'App names must be valid URI hostnames: alphanumeric and hyphen characters only.'
36
+ end
37
+
27
38
  private
28
39
  def parse_encoded_gid(gid, options)
29
40
  new(Base64.urlsafe_decode64(repad_gid(gid)), options) rescue nil
@@ -43,7 +54,7 @@ class GlobalID
43
54
  end
44
55
 
45
56
  def find(options = {})
46
- model_class.find model_id if find_allowed?(options[:only])
57
+ Locator.locate self, options
47
58
  end
48
59
 
49
60
  def model_class
@@ -79,8 +90,4 @@ class GlobalID
79
90
  raise URI::InvalidURIError, "Expected a URI like gid://app/Person/1234: #{@uri.inspect}"
80
91
  end
81
92
  end
82
-
83
- def find_allowed?(only = nil)
84
- only ? Array(only).any? { |c| model_class <= c } : true
85
- end
86
93
  end
@@ -4,14 +4,14 @@ class GlobalID
4
4
  module Identification
5
5
  extend ActiveSupport::Concern
6
6
 
7
- def global_id
7
+ def to_global_id
8
8
  @global_id ||= GlobalID.create(self)
9
9
  end
10
- alias gid global_id
10
+ alias to_gid to_global_id
11
11
 
12
- def signed_global_id
13
- @signed_global_id ||= SignedGlobalID.create(self)
12
+ def to_signed_global_id(options = {})
13
+ @signed_global_id ||= SignedGlobalID.create(self, options)
14
14
  end
15
- alias sgid signed_global_id
15
+ alias to_sgid to_signed_global_id
16
16
  end
17
17
  end
@@ -10,7 +10,9 @@ class GlobalID
10
10
  # instances of returned classes to those including that module. If no classes or
11
11
  # modules match, +nil+ is returned.
12
12
  def locate(gid, options = {})
13
- GlobalID.find gid, options
13
+ if gid = GlobalID.parse(gid)
14
+ locator_for(gid).locate gid if find_allowed?(gid.model_class, options[:only])
15
+ end
14
16
  end
15
17
 
16
18
  # Takes either a SignedGlobalID or a string that can be turned into a SignedGlobalID
@@ -24,6 +26,68 @@ class GlobalID
24
26
  def locate_signed(sgid, options = {})
25
27
  SignedGlobalID.find sgid, options
26
28
  end
29
+
30
+ # Tie a locator to an app.
31
+ # Useful when different apps collaborate and reference each others' Global IDs.
32
+ #
33
+ # The locator can be either a block or a class.
34
+ #
35
+ # Using a block:
36
+ #
37
+ # GlobalID::Locator.use :foo do |gid|
38
+ # FooRemote.const_get(gid.model_name).find(gid.model_id)
39
+ # end
40
+ #
41
+ # Using a class:
42
+ #
43
+ # GlobalID::Locator.use :bar, BarLocator.new
44
+ #
45
+ # class BarLocator
46
+ # def locate(gid)
47
+ # @search_client.search name: gid.model_name, id: gid.model_id
48
+ # end
49
+ # end
50
+ def use(app, locator = nil, &locator_block)
51
+ raise ArgumentError, 'No locator provided. Pass a block or an object that responds to #locate.' unless locator || block_given?
52
+
53
+ GlobalID.validate_app(app)
54
+
55
+ @locators[normalize_app(app)] = locator || BlockLocator.new(locator_block)
56
+ end
57
+
58
+ private
59
+ def locator_for(gid)
60
+ @locators.fetch(normalize_app(gid.app)) { default_locator }
61
+ end
62
+
63
+ def find_allowed?(model_class, only = nil)
64
+ only ? Array(only).any? { |c| model_class <= c } : true
65
+ end
66
+
67
+ def normalize_app(app)
68
+ app.to_s.downcase
69
+ end
27
70
  end
71
+
72
+ private
73
+ @locators = {}
74
+
75
+ class ActiveRecordFinder
76
+ def locate(gid)
77
+ gid.model_class.find gid.model_id
78
+ end
79
+ end
80
+
81
+ mattr_reader(:default_locator) { ActiveRecordFinder.new }
82
+
83
+ class BlockLocator
84
+ def initialize(block)
85
+ @locator = block
86
+ end
87
+
88
+ def locate(gid)
89
+ @locator.call(gid)
90
+ end
91
+ end
28
92
  end
29
93
  end
@@ -3,6 +3,8 @@ require 'rails/railtie'
3
3
  rescue LoadError
4
4
  else
5
5
  require 'global_id'
6
+ require 'active_support'
7
+ require 'active_support/core_ext/string/inflections'
6
8
 
7
9
  class GlobalID
8
10
  # = GlobalID Railtie
@@ -12,9 +14,12 @@ class GlobalID
12
14
 
13
15
  initializer 'global_id' do |app|
14
16
 
15
- app.config.global_id.app ||= app.railtie_name.remove('_application')
17
+ app.config.global_id.app ||= app.railtie_name.remove('_application').dasherize
16
18
  GlobalID.app = app.config.global_id.app
17
19
 
20
+ app.config.global_id.expires_in ||= 1.month
21
+ SignedGlobalID.expires_in = app.config.global_id.expires_in
22
+
18
23
  config.after_initialize do
19
24
  app.config.global_id.verifier ||= begin
20
25
  app.message_verifier(:signed_global_ids)
@@ -1,7 +1,10 @@
1
1
  require 'global_id'
2
2
  require 'active_support/message_verifier'
3
+ require 'time'
3
4
 
4
5
  class SignedGlobalID < GlobalID
6
+ class ExpiredMessage < StandardError; end
7
+
5
8
  class << self
6
9
  attr_accessor :verifier
7
10
 
@@ -9,7 +12,7 @@ class SignedGlobalID < GlobalID
9
12
  if sgid.is_a? self
10
13
  sgid
11
14
  else
12
- super verify(sgid, pick_verifier(options))
15
+ super verify(sgid, options), options
13
16
  end
14
17
  end
15
18
 
@@ -21,23 +24,66 @@ class SignedGlobalID < GlobalID
21
24
  end
22
25
  end
23
26
 
27
+ attr_accessor :expires_in
28
+
29
+ DEFAULT_PURPOSE = "default"
30
+
31
+ def pick_purpose(options)
32
+ options.fetch :for, DEFAULT_PURPOSE
33
+ end
34
+
24
35
  private
25
- def verify(sgid, verifier)
26
- verifier.verify(sgid)
27
- rescue ActiveSupport::MessageVerifier::InvalidSignature
36
+ def verify(sgid, options)
37
+ metadata = pick_verifier(options).verify(sgid)
38
+
39
+ raise_if_expired(metadata['expires_at'])
40
+
41
+ metadata['gid'] if pick_purpose(options) == metadata['purpose']
42
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ExpiredMessage
28
43
  nil
29
44
  end
45
+
46
+ def raise_if_expired(expires_at)
47
+ if expires_at && Time.now.utc > Time.iso8601(expires_at)
48
+ raise ExpiredMessage, 'This signed global id has expired.'
49
+ end
50
+ end
30
51
  end
31
52
 
32
- attr_reader :verifier
53
+ attr_reader :verifier, :purpose, :expires_at
33
54
 
34
55
  def initialize(gid, options = {})
35
56
  super
36
57
  @verifier = self.class.pick_verifier(options)
58
+ @purpose = self.class.pick_purpose(options)
59
+ @expires_at = pick_expiration(options)
37
60
  end
38
61
 
39
62
  def to_s
40
- @sgid ||= @verifier.generate(super)
63
+ @sgid ||= @verifier.generate(to_h)
41
64
  end
42
65
  alias to_param to_s
66
+
67
+ def to_h
68
+ # Some serializers decodes symbol keys to symbols, others to strings.
69
+ # Using string keys remedies that.
70
+ { 'gid' => @uri.to_s, 'purpose' => purpose, 'expires_at' => encoded_expiration }
71
+ end
72
+
73
+ def ==(other)
74
+ super && @purpose == other.purpose
75
+ end
76
+
77
+ private
78
+ def encoded_expiration
79
+ expires_at.utc.iso8601(3) if expires_at
80
+ end
81
+
82
+ def pick_expiration(options)
83
+ return options[:expires_at] if options.key?(:expires_at)
84
+
85
+ if expires_in = options.fetch(:expires_in) { self.class.expires_in }
86
+ expires_in.from_now
87
+ end
88
+ end
43
89
  end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: globalid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-19 00:00:00.000000000 Z
11
+ date: 2014-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: 4.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 4.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - ! '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  description: URIs for your models makes it easy to pass references around.
@@ -62,12 +62,12 @@ require_paths:
62
62
  - lib
63
63
  required_ruby_version: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ">="
65
+ - - ! '>='
66
66
  - !ruby/object:Gem::Version
67
67
  version: 1.9.3
68
68
  required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  requirements:
70
- - - ">="
70
+ - - ! '>='
71
71
  - !ruby/object:Gem::Version
72
72
  version: '0'
73
73
  requirements: []
@@ -75,5 +75,5 @@ rubyforge_project:
75
75
  rubygems_version: 2.2.2
76
76
  signing_key:
77
77
  specification_version: 4
78
- summary: 'Refer to any model with a URI: gid://app/class/id'
78
+ summary: ! 'Refer to any model with a URI: gid://app/class/id'
79
79
  test_files: []