globalid 0.4.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +95 -49
- data/lib/global_id/fixture_set.rb +13 -0
- data/lib/global_id/global_id.rb +5 -0
- data/lib/global_id/identification.rb +1 -5
- data/lib/global_id/railtie.rb +14 -5
- data/lib/global_id/uri/gid.rb +9 -8
- data/lib/global_id.rb +13 -3
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e5a7361595eea95f395b66f4a79123ad35e61b7008d256646bea775291fa9f41
|
4
|
+
data.tar.gz: a9ece75bb440f6b49b8ad623b976bc05f4f3da2151104d045d1b7305c4a8d9b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8290a4a79953bb92053c2f52ae847c557e7f5d4f0528124c7f1217ee4a34b5c7d42290a2584d729e0c5011c05cd1df4c9c073be49bde7d4fa13597f44a52513f
|
7
|
+
data.tar.gz: 2d9e7c573f7223406733b8be7ce1d6d764cca584bcfbb0a3f2ffb8d6f71f82970ceacf827e9c8a251975c0681b5d7d8293c1e158927dbf38f362e1337fd027ce
|
data/README.md
CHANGED
@@ -24,17 +24,17 @@ Mix `GlobalID::Identification` into any model with a `#find(id)` class method.
|
|
24
24
|
Support is automatically included in Active Record.
|
25
25
|
|
26
26
|
```ruby
|
27
|
-
|
28
|
-
=> #<GlobalID ...
|
27
|
+
person_gid = Person.find(1).to_global_id
|
28
|
+
# => #<GlobalID ...
|
29
29
|
|
30
|
-
|
31
|
-
=> #<URI ...
|
30
|
+
person_gid.uri
|
31
|
+
# => #<URI ...
|
32
32
|
|
33
|
-
|
34
|
-
=> "gid://app/Person/1"
|
33
|
+
person_gid.to_s
|
34
|
+
# => "gid://app/Person/1"
|
35
35
|
|
36
|
-
|
37
|
-
=> #<Person:0x007fae94bf6298 @id="1">
|
36
|
+
GlobalID::Locator.locate person_gid
|
37
|
+
# => #<Person:0x007fae94bf6298 @id="1">
|
38
38
|
```
|
39
39
|
|
40
40
|
### Signed Global IDs
|
@@ -42,79 +42,125 @@ Support is automatically included in Active Record.
|
|
42
42
|
For added security GlobalIDs can also be signed to ensure that the data hasn't been tampered with.
|
43
43
|
|
44
44
|
```ruby
|
45
|
-
|
46
|
-
=> #<SignedGlobalID:0x007fea1944b410>
|
45
|
+
person_sgid = Person.find(1).to_signed_global_id
|
46
|
+
# => #<SignedGlobalID:0x007fea1944b410>
|
47
47
|
|
48
|
-
|
49
|
-
=> #<SignedGlobalID:0x007fea1944b410>
|
48
|
+
person_sgid = Person.find(1).to_sgid
|
49
|
+
# => #<SignedGlobalID:0x007fea1944b410>
|
50
50
|
|
51
|
-
|
52
|
-
=> "BAhJIh5naWQ6Ly9pZGluYWlkaS9Vc2VyLzM5NTk5BjoGRVQ=--81d7358dd5ee2ca33189bb404592df5e8d11420e"
|
53
|
-
|
54
|
-
>> GlobalID::Locator.locate_signed person_sgid
|
55
|
-
=> #<Person:0x007fae94bf6298 @id="1">
|
51
|
+
person_sgid.to_s
|
52
|
+
# => "BAhJIh5naWQ6Ly9pZGluYWlkaS9Vc2VyLzM5NTk5BjoGRVQ=--81d7358dd5ee2ca33189bb404592df5e8d11420e"
|
56
53
|
|
54
|
+
GlobalID::Locator.locate_signed person_sgid
|
55
|
+
# => #<Person:0x007fae94bf6298 @id="1">
|
57
56
|
```
|
58
|
-
You can even bump the security up some more by explaining what purpose a Signed Global ID is for.
|
59
|
-
In this way evildoers can't reuse a sign-up form's SGID on the login page. For example.
|
60
57
|
|
61
|
-
|
62
|
-
>> signup_person_sgid = Person.find(1).to_sgid(for: 'signup_form')
|
63
|
-
=> #<SignedGlobalID:0x007fea1984b520
|
64
|
-
|
65
|
-
>> GlobalID::Locator.locate_signed(signup_person_sgid.to_s, for: 'signup_form')
|
66
|
-
=> #<Person:0x007fae94bf6298 @id="1">
|
67
|
-
```
|
58
|
+
**Expiration**
|
68
59
|
|
69
|
-
|
60
|
+
Signed Global IDs can expire some time in the future. This is useful if there's a resource
|
70
61
|
people shouldn't have indefinite access to, like a share link.
|
71
62
|
|
72
63
|
```ruby
|
73
|
-
|
74
|
-
=> #<SignedGlobalID:0x008fde45df8937 ...>
|
64
|
+
expiring_sgid = Document.find(5).to_sgid(expires_in: 2.hours, for: 'sharing')
|
65
|
+
# => #<SignedGlobalID:0x008fde45df8937 ...>
|
75
66
|
|
76
67
|
# Within 2 hours...
|
77
|
-
|
78
|
-
=> #<Document:0x007fae94bf6298 @id="5">
|
68
|
+
GlobalID::Locator.locate_signed(expiring_sgid.to_s, for: 'sharing')
|
69
|
+
# => #<Document:0x007fae94bf6298 @id="5">
|
79
70
|
|
80
71
|
# More than 2 hours later...
|
81
|
-
|
82
|
-
=> nil
|
72
|
+
GlobalID::Locator.locate_signed(expiring_sgid.to_s, for: 'sharing')
|
73
|
+
# => nil
|
74
|
+
```
|
83
75
|
|
84
|
-
|
85
|
-
|
76
|
+
**In Rails, an auto-expiry of 1 month is set by default.** You can alter that deal
|
77
|
+
in an initializer with:
|
86
78
|
|
87
|
-
|
88
|
-
|
89
|
-
|
79
|
+
```ruby
|
80
|
+
# config/initializers/global_id.rb
|
81
|
+
Rails.application.config.global_id.expires_in = 3.months
|
82
|
+
```
|
83
|
+
|
84
|
+
You can assign a default SGID lifetime like so:
|
90
85
|
|
86
|
+
```ruby
|
87
|
+
SignedGlobalID.expires_in = 1.month
|
88
|
+
```
|
89
|
+
|
90
|
+
This way any generated SGID will use that relative expiry.
|
91
|
+
|
92
|
+
It's worth noting that _expiring SGIDs are not idempotent_ because they encode the current timestamp; repeated calls to `to_sgid` will produce different results. For example, in Rails
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
Document.find(5).to_sgid.to_s == Document.find(5).to_sgid.to_s
|
96
|
+
# => false
|
97
|
+
```
|
98
|
+
|
99
|
+
You need to explicitly pass `expires_in: nil` to generate a permanent SGID that will not expire,
|
100
|
+
|
101
|
+
```ruby
|
91
102
|
# Passing a false value to either expiry option turns off expiration entirely.
|
92
|
-
|
93
|
-
=> #<SignedGlobalID:0x008fde45df8937 ...>
|
103
|
+
never_expiring_sgid = Document.find(5).to_sgid(expires_in: nil)
|
104
|
+
# => #<SignedGlobalID:0x008fde45df8937 ...>
|
94
105
|
|
95
106
|
# Any time later...
|
96
|
-
|
97
|
-
=> #<Document:0x007fae94bf6298 @id="5">
|
107
|
+
GlobalID::Locator.locate_signed never_expiring_sgid
|
108
|
+
# => #<Document:0x007fae94bf6298 @id="5">
|
98
109
|
```
|
99
110
|
|
111
|
+
It's also possible to pass a specific expiry time
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
explicit_expiring_sgid = SecretAgentMessage.find(5).to_sgid(expires_at: Time.now.advance(hours: 1))
|
115
|
+
# => #<SignedGlobalID:0x008fde45df8937 ...>
|
116
|
+
|
117
|
+
# 1 hour later...
|
118
|
+
GlobalID::Locator.locate_signed explicit_expiring_sgid.to_s
|
119
|
+
# => nil
|
120
|
+
```
|
100
121
|
Note that an explicit `:expires_at` takes precedence over a relative `:expires_in`.
|
101
122
|
|
102
|
-
|
123
|
+
**Purpose**
|
124
|
+
|
125
|
+
You can even bump the security up some more by explaining what purpose a Signed Global ID is for.
|
126
|
+
In this way evildoers can't reuse a sign-up form's SGID on the login page. For example.
|
103
127
|
|
104
128
|
```ruby
|
105
|
-
|
129
|
+
signup_person_sgid = Person.find(1).to_sgid(for: 'signup_form')
|
130
|
+
# => #<SignedGlobalID:0x007fea1984b520
|
131
|
+
|
132
|
+
GlobalID::Locator.locate_signed(signup_person_sgid.to_s, for: 'signup_form')
|
133
|
+
# => #<Person:0x007fae94bf6298 @id="1">
|
106
134
|
```
|
107
135
|
|
108
|
-
|
136
|
+
### Locating many Global IDs
|
109
137
|
|
110
|
-
|
111
|
-
|
138
|
+
When needing to locate many Global IDs use `GlobalID::Locator.locate_many` or `GlobalID::Locator.locate_many_signed` for Signed Global IDs to allow loading
|
139
|
+
Global IDs more efficiently.
|
140
|
+
|
141
|
+
For instance, the default locator passes every `model_id` per `model_name` thus
|
142
|
+
using `model_name.where(id: model_ids)` versus `GlobalID::Locator.locate`'s `model_name.find(id)`.
|
143
|
+
|
144
|
+
In the case of looking up Global IDs from a database, it's only necessary to query
|
145
|
+
once per `model_name` as shown here:
|
112
146
|
|
113
147
|
```ruby
|
114
|
-
|
115
|
-
|
148
|
+
gids = users.concat(people).sort_by(&:id).map(&:to_global_id)
|
149
|
+
# => [#<GlobalID:0x00007ffd6a8411a0 @uri=#<URI::GID gid://app/User/1>>,
|
150
|
+
#<GlobalID:0x00007ffd675d32b8 @uri=#<URI::GID gid://app/Student/1>>,
|
151
|
+
#<GlobalID:0x00007ffd6a840b10 @uri=#<URI::GID gid://app/User/2>>,
|
152
|
+
#<GlobalID:0x00007ffd675d2c28 @uri=#<URI::GID gid://app/Student/2>>,
|
153
|
+
#<GlobalID:0x00007ffd6a840480 @uri=#<URI::GID gid://app/User/3>>,
|
154
|
+
#<GlobalID:0x00007ffd675d2598 @uri=#<URI::GID gid://app/Student/3>>]
|
155
|
+
|
156
|
+
GlobalID::Locator.locate_many gids
|
157
|
+
# SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2, $3) [["id", 1], ["id", 2], ["id", 3]]
|
158
|
+
# SELECT "students".* FROM "students" WHERE "students"."id" IN ($1, $2, $3) [["id", 1], ["id", 2], ["id", 3]]
|
159
|
+
# => [#<User id: 1>, #<Student id: 1>, #<User id: 2>, #<Student id: 2>, #<User id: 3>, #<Student id: 3>]
|
116
160
|
```
|
117
161
|
|
162
|
+
Note the order is maintained in the returned results.
|
163
|
+
|
118
164
|
### Custom App Locator
|
119
165
|
|
120
166
|
A custom locator can be set for an app by calling `GlobalID::Locator.use` and providing an app locator to use for that app.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GlobalID
|
4
|
+
module FixtureSet
|
5
|
+
def signed_global_id(fixture_set_name, label, column_type: :integer, **options)
|
6
|
+
identifier = identify(label, column_type)
|
7
|
+
model_name = default_fixture_model_name(fixture_set_name)
|
8
|
+
uri = URI::GID.build([GlobalID.app, model_name, identifier, {}])
|
9
|
+
|
10
|
+
SignedGlobalID.new(uri, **options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/global_id/global_id.rb
CHANGED
@@ -63,6 +63,11 @@ class GlobalID
|
|
63
63
|
def ==(other)
|
64
64
|
other.is_a?(GlobalID) && @uri == other.uri
|
65
65
|
end
|
66
|
+
alias_method :eql?, :==
|
67
|
+
|
68
|
+
def hash
|
69
|
+
self.class.hash | @uri.hash
|
70
|
+
end
|
66
71
|
|
67
72
|
def to_param
|
68
73
|
# remove the = padding character for a prettier param -- it'll be added back in parse_encoded_gid
|
@@ -1,11 +1,7 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
|
3
1
|
class GlobalID
|
4
2
|
module Identification
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
3
|
def to_global_id(options = {})
|
8
|
-
|
4
|
+
GlobalID.create(self, options)
|
9
5
|
end
|
10
6
|
alias to_gid to_global_id
|
11
7
|
|
data/lib/global_id/railtie.rb
CHANGED
@@ -5,22 +5,26 @@ else
|
|
5
5
|
require 'global_id'
|
6
6
|
require 'active_support'
|
7
7
|
require 'active_support/core_ext/string/inflections'
|
8
|
+
require 'active_support/core_ext/integer/time'
|
8
9
|
|
9
10
|
class GlobalID
|
10
11
|
# = GlobalID Railtie
|
11
12
|
# Set up the signed GlobalID verifier and include Active Record support.
|
12
13
|
class Railtie < Rails::Railtie # :nodoc:
|
13
14
|
config.global_id = ActiveSupport::OrderedOptions.new
|
15
|
+
config.eager_load_namespaces << GlobalID
|
14
16
|
|
15
17
|
initializer 'global_id' do |app|
|
18
|
+
default_expires_in = 1.month
|
19
|
+
default_app_name = app.railtie_name.remove('_application').dasherize
|
16
20
|
|
17
|
-
app.config.global_id.app ||=
|
18
|
-
|
19
|
-
|
20
|
-
app.config.global_id.expires_in ||= 1.month
|
21
|
-
SignedGlobalID.expires_in = app.config.global_id.expires_in
|
21
|
+
GlobalID.app = app.config.global_id.app ||= default_app_name
|
22
|
+
SignedGlobalID.expires_in = app.config.global_id.fetch(:expires_in, default_expires_in)
|
22
23
|
|
23
24
|
config.after_initialize do
|
25
|
+
GlobalID.app = app.config.global_id.app ||= default_app_name
|
26
|
+
SignedGlobalID.expires_in = app.config.global_id.fetch(:expires_in, default_expires_in)
|
27
|
+
|
24
28
|
app.config.global_id.verifier ||= begin
|
25
29
|
GlobalID::Verifier.new(app.key_generator.generate_key('signed_global_ids'))
|
26
30
|
rescue ArgumentError
|
@@ -33,6 +37,11 @@ class GlobalID
|
|
33
37
|
require 'global_id/identification'
|
34
38
|
send :include, GlobalID::Identification
|
35
39
|
end
|
40
|
+
|
41
|
+
ActiveSupport.on_load(:active_record_fixture_set) do
|
42
|
+
require 'global_id/fixture_set'
|
43
|
+
send :extend, GlobalID::FixtureSet
|
44
|
+
end
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
data/lib/global_id/uri/gid.rb
CHANGED
@@ -123,9 +123,6 @@ module URI
|
|
123
123
|
private
|
124
124
|
COMPONENT = [ :scheme, :app, :model_name, :model_id, :params ].freeze
|
125
125
|
|
126
|
-
# Extracts model_name and model_id from the URI path.
|
127
|
-
PATH_REGEXP = %r(\A/([^/]+)/?([^/]+)?\z)
|
128
|
-
|
129
126
|
def check_host(host)
|
130
127
|
validate_component(host)
|
131
128
|
super
|
@@ -145,11 +142,11 @@ module URI
|
|
145
142
|
end
|
146
143
|
|
147
144
|
def set_model_components(path, validate = false)
|
148
|
-
_, model_name, model_id = path.
|
149
|
-
model_id = CGI.unescape(model_id) if model_id
|
150
|
-
|
145
|
+
_, model_name, model_id = path.split('/', 3)
|
151
146
|
validate_component(model_name) && validate_model_id(model_id, model_name) if validate
|
152
147
|
|
148
|
+
model_id = CGI.unescape(model_id) if model_id
|
149
|
+
|
153
150
|
@model_name = model_name
|
154
151
|
@model_id = model_id
|
155
152
|
end
|
@@ -162,7 +159,7 @@ module URI
|
|
162
159
|
end
|
163
160
|
|
164
161
|
def validate_model_id(model_id, model_name)
|
165
|
-
return model_id unless model_id.blank?
|
162
|
+
return model_id unless model_id.blank? || model_id.include?('/')
|
166
163
|
|
167
164
|
raise MissingModelIdError, "Unable to create a Global ID for " \
|
168
165
|
"#{model_name} without a model id."
|
@@ -173,5 +170,9 @@ module URI
|
|
173
170
|
end
|
174
171
|
end
|
175
172
|
|
176
|
-
|
173
|
+
if respond_to?(:register_scheme)
|
174
|
+
register_scheme('GID', GID)
|
175
|
+
else
|
176
|
+
@@schemes['GID'] = GID
|
177
|
+
end
|
177
178
|
end
|
data/lib/global_id.rb
CHANGED
@@ -1,9 +1,19 @@
|
|
1
1
|
require 'global_id/global_id'
|
2
|
+
require 'active_support'
|
2
3
|
|
3
4
|
autoload :SignedGlobalID, 'global_id/signed_global_id'
|
4
5
|
|
5
6
|
class GlobalID
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
eager_autoload do
|
10
|
+
autoload :Locator
|
11
|
+
autoload :Identification
|
12
|
+
autoload :Verifier
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.eager_load!
|
16
|
+
super
|
17
|
+
require 'global_id/signed_global_id'
|
18
|
+
end
|
9
19
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: globalid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '5.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
|
-
version:
|
26
|
+
version: '5.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -47,6 +47,7 @@ files:
|
|
47
47
|
- MIT-LICENSE
|
48
48
|
- README.md
|
49
49
|
- lib/global_id.rb
|
50
|
+
- lib/global_id/fixture_set.rb
|
50
51
|
- lib/global_id/global_id.rb
|
51
52
|
- lib/global_id/identification.rb
|
52
53
|
- lib/global_id/locator.rb
|
@@ -59,7 +60,7 @@ homepage: http://www.rubyonrails.org
|
|
59
60
|
licenses:
|
60
61
|
- MIT
|
61
62
|
metadata: {}
|
62
|
-
post_install_message:
|
63
|
+
post_install_message:
|
63
64
|
rdoc_options: []
|
64
65
|
require_paths:
|
65
66
|
- lib
|
@@ -67,16 +68,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
68
|
requirements:
|
68
69
|
- - ">="
|
69
70
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
71
|
+
version: 2.5.0
|
71
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
73
|
requirements:
|
73
74
|
- - ">="
|
74
75
|
- !ruby/object:Gem::Version
|
75
76
|
version: '0'
|
76
77
|
requirements: []
|
77
|
-
|
78
|
-
|
79
|
-
signing_key:
|
78
|
+
rubygems_version: 3.5.0.dev
|
79
|
+
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: 'Refer to any model with a URI: gid://app/class/id'
|
82
82
|
test_files: []
|