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 +5 -5
- data/lib/global_id/global_id.rb +14 -7
- data/lib/global_id/identification.rb +5 -5
- data/lib/global_id/locator.rb +65 -1
- data/lib/global_id/railtie.rb +6 -1
- data/lib/global_id/signed_global_id.rb +52 -6
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: d325de1fd4887ed3e91ebb2ec6d58d4ed0d552d0
|
4
|
+
data.tar.gz: e7d0091c1cc92b20c682093faa424b528c32940f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98f92c4c3fa1e9a127abcc44f1eb51ca529f5388dec8529aacce43c18a9fc9cc9aa947854620abe0bd609ebb46dfcee22a1d81089bffb83d106d29967c457147
|
7
|
+
data.tar.gz: 989269225c2d49d7a25cb6bb5e7f9bca1087268d5688d27558bb8d2fa1d1062360bbf72f2d8004cb764c886217f4d6a7eed73da1e0a1fe190b1ba250f1026bcb
|
data/lib/global_id/global_id.rb
CHANGED
@@ -6,7 +6,7 @@ require 'uri'
|
|
6
6
|
|
7
7
|
class GlobalID
|
8
8
|
class << self
|
9
|
-
|
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
|
-
|
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
|
7
|
+
def to_global_id
|
8
8
|
@global_id ||= GlobalID.create(self)
|
9
9
|
end
|
10
|
-
alias
|
10
|
+
alias to_gid to_global_id
|
11
11
|
|
12
|
-
def
|
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
|
15
|
+
alias to_sgid to_signed_global_id
|
16
16
|
end
|
17
17
|
end
|
data/lib/global_id/locator.rb
CHANGED
@@ -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.
|
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
|
data/lib/global_id/railtie.rb
CHANGED
@@ -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,
|
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,
|
26
|
-
|
27
|
-
|
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(
|
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.
|
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-
|
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: []
|