corona_presence_tracing 0.1.0
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 +7 -0
- data/lib/corona_presence_tracing.rb +4 -0
- data/lib/corona_presence_tracing/base.rb +100 -0
- data/lib/corona_presence_tracing/cwa_check_in.rb +52 -0
- data/lib/corona_presence_tracing/proto/presence_tracing_pb.rb +56 -0
- data/lib/corona_presence_tracing/validator.rb +107 -0
- data/lib/corona_presence_tracing/version.rb +5 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6cb47ee319c9c21062ccdbd4796a75e6f26721e6ea06e8cc277bad4e9152e5c8
|
4
|
+
data.tar.gz: aa87a83fb4e8a29e4818b80f4d05b6c23c7831503a9a17fba4c3535b2a725d79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 593d7bcf06d9f02926d9d47f30844248ba540c8d53d9a2a1c5b90107ea9bbc252ba4c3c9c75e545f7e76645c3613295c9028b10ec9218017c1c682692d2f4d85
|
7
|
+
data.tar.gz: b64bdb30114568675a126f80fcef0f79e44d5707c80cd17d74a22455d7c36e80e27a0055737b6b11e0812c31f6f1abf9350916423f1fe614dbf910fb5be42449
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'validator'
|
4
|
+
require_relative 'proto/presence_tracing_pb'
|
5
|
+
|
6
|
+
require 'base64'
|
7
|
+
require 'securerandom'
|
8
|
+
|
9
|
+
module CoronaPresenceTracing
|
10
|
+
module Base
|
11
|
+
attr_reader :description, :address, :start_time, :end_time
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def decode(encoded_payload)
|
19
|
+
base64_segment = encoded_payload.split('#').last
|
20
|
+
qr_payload = QRCodePayload.decode(Base64.urlsafe_decode64(base64_segment))
|
21
|
+
location = qr_payload.locationData
|
22
|
+
|
23
|
+
new(
|
24
|
+
description: location.description,
|
25
|
+
address: location.address,
|
26
|
+
start_time: Time.at(location.startTimestamp),
|
27
|
+
end_time: Time.at(location.endTimestamp),
|
28
|
+
**vendor_info(qr_payload.countryData)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def vendor_info(_country_data)
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(options = {})
|
40
|
+
@description = options[:description]
|
41
|
+
@address = options[:address]
|
42
|
+
@start_time = options[:start_time]
|
43
|
+
@end_time = options[:end_time]
|
44
|
+
|
45
|
+
validate
|
46
|
+
build_payload
|
47
|
+
end
|
48
|
+
|
49
|
+
def encoded_payload
|
50
|
+
@encoded_payload ||= Base64.urlsafe_encode64(QRCodePayload.encode(@qr_payload))
|
51
|
+
end
|
52
|
+
|
53
|
+
def url
|
54
|
+
[self.class::BASE_URL, encoded_payload].join('#')
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def validate
|
60
|
+
CoronaPresenceTracing.const_get("#{self.class.name}Validator").new(self).validate
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_payload
|
64
|
+
@qr_payload = QRCodePayload.new(
|
65
|
+
version: 1,
|
66
|
+
locationData: location_data,
|
67
|
+
crowdNotifierData: crowd_notifier_data,
|
68
|
+
countryData: encoded_vendor_data
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def location_data
|
73
|
+
TraceLocation.new(
|
74
|
+
version: 1,
|
75
|
+
description: description,
|
76
|
+
address: address,
|
77
|
+
startTimestamp: start_time.to_i,
|
78
|
+
endTimestamp: end_time.to_i
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def crowd_notifier_data
|
83
|
+
CrowdNotifierData.new(
|
84
|
+
version: 1,
|
85
|
+
publicKey: Base64.decode64(self.class::PUBLIC_KEY),
|
86
|
+
cryptographicSeed: SecureRandom.random_bytes(16)
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def vendor_data
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def encoded_vendor_data
|
95
|
+
return unless vendor_data
|
96
|
+
|
97
|
+
vendor_data.class.encode(vendor_data)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'proto/presence_tracing_pb'
|
5
|
+
|
6
|
+
module CoronaPresenceTracing
|
7
|
+
class CWACheckIn
|
8
|
+
include Base
|
9
|
+
|
10
|
+
BASE_URL = 'https://e.coronawarn.app?v=1'
|
11
|
+
PUBLIC_KEY = 'gwLMzE153tQwAOf2MZoUXXfzWTdlSpfS99iZffmcmxOG9njSK4RTimFOFwDh6t0T' \
|
12
|
+
'yw8XR01ugDYjtuKwjjuK49Oh83FWct6XpefPi9Skjxvvz53i9gaMmUEc96pbtoaA'
|
13
|
+
|
14
|
+
attr_reader :location_type, :default_check_in_length
|
15
|
+
|
16
|
+
class << self
|
17
|
+
private
|
18
|
+
|
19
|
+
def vendor_info(country_data)
|
20
|
+
location_data = CWALocationData.decode(country_data)
|
21
|
+
{
|
22
|
+
location_type: location_type_from_payload(location_data),
|
23
|
+
default_check_in_length: location_data.defaultCheckInLengthInMinutes
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def location_type_from_payload(location_data)
|
28
|
+
location_data.type.to_s.delete_prefix('LOCATION_TYPE_').downcase.to_sym
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
@location_type = options[:location_type]
|
34
|
+
@default_check_in_length = options[:default_check_in_length]
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def vendor_data
|
41
|
+
CWALocationData.new(
|
42
|
+
version: 1,
|
43
|
+
type: mapped_location_type,
|
44
|
+
defaultCheckInLengthInMinutes: default_check_in_length
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def mapped_location_type
|
49
|
+
CoronaPresenceTracing::TraceLocationType.const_get("LOCATION_TYPE_#{location_type.to_s.upcase}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: proto/presence_tracing.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("proto/presence_tracing.proto", :syntax => :proto3) do
|
8
|
+
add_message "corona_presence_tracing.QRCodePayload" do
|
9
|
+
optional :version, :uint32, 1
|
10
|
+
optional :locationData, :message, 2, "corona_presence_tracing.TraceLocation"
|
11
|
+
optional :crowdNotifierData, :message, 3, "corona_presence_tracing.CrowdNotifierData"
|
12
|
+
optional :countryData, :bytes, 4
|
13
|
+
end
|
14
|
+
add_message "corona_presence_tracing.TraceLocation" do
|
15
|
+
optional :version, :uint32, 1
|
16
|
+
optional :description, :string, 2
|
17
|
+
optional :address, :string, 3
|
18
|
+
optional :startTimestamp, :uint64, 5
|
19
|
+
optional :endTimestamp, :uint64, 6
|
20
|
+
end
|
21
|
+
add_message "corona_presence_tracing.CrowdNotifierData" do
|
22
|
+
optional :version, :uint32, 1
|
23
|
+
optional :publicKey, :bytes, 2
|
24
|
+
optional :cryptographicSeed, :bytes, 3
|
25
|
+
optional :type, :uint32, 4
|
26
|
+
end
|
27
|
+
add_message "corona_presence_tracing.CWALocationData" do
|
28
|
+
optional :version, :uint32, 1
|
29
|
+
optional :type, :enum, 2, "corona_presence_tracing.TraceLocationType"
|
30
|
+
optional :defaultCheckInLengthInMinutes, :uint32, 3
|
31
|
+
end
|
32
|
+
add_enum "corona_presence_tracing.TraceLocationType" do
|
33
|
+
value :LOCATION_TYPE_UNSPECIFIED, 0
|
34
|
+
value :LOCATION_TYPE_PERMANENT_OTHER, 1
|
35
|
+
value :LOCATION_TYPE_TEMPORARY_OTHER, 2
|
36
|
+
value :LOCATION_TYPE_PERMANENT_RETAIL, 3
|
37
|
+
value :LOCATION_TYPE_PERMANENT_FOOD_SERVICE, 4
|
38
|
+
value :LOCATION_TYPE_PERMANENT_CRAFT, 5
|
39
|
+
value :LOCATION_TYPE_PERMANENT_WORKPLACE, 6
|
40
|
+
value :LOCATION_TYPE_PERMANENT_EDUCATIONAL_INSTITUTION, 7
|
41
|
+
value :LOCATION_TYPE_PERMANENT_PUBLIC_BUILDING, 8
|
42
|
+
value :LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT, 9
|
43
|
+
value :LOCATION_TYPE_TEMPORARY_CLUB_ACTIVITY, 10
|
44
|
+
value :LOCATION_TYPE_TEMPORARY_PRIVATE_EVENT, 11
|
45
|
+
value :LOCATION_TYPE_TEMPORARY_WORSHIP_SERVICE, 12
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module CoronaPresenceTracing
|
51
|
+
QRCodePayload = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("corona_presence_tracing.QRCodePayload").msgclass
|
52
|
+
TraceLocation = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("corona_presence_tracing.TraceLocation").msgclass
|
53
|
+
CrowdNotifierData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("corona_presence_tracing.CrowdNotifierData").msgclass
|
54
|
+
CWALocationData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("corona_presence_tracing.CWALocationData").msgclass
|
55
|
+
TraceLocationType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("corona_presence_tracing.TraceLocationType").enummodule
|
56
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CoronaPresenceTracing
|
4
|
+
class ValidationError < StandardError
|
5
|
+
attr_reader :check_in, :attr, :error
|
6
|
+
|
7
|
+
def initialize(check_in, attr, error, message)
|
8
|
+
@check_in = check_in
|
9
|
+
@attr = attr
|
10
|
+
@error = error
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CheckInValidator
|
16
|
+
def initialize(check_in)
|
17
|
+
@check_in = check_in
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
validate_presence_of :description, :address
|
22
|
+
validate_type_of :description, :address, %w[String]
|
23
|
+
validate_length_of :description, :address, max: 100
|
24
|
+
validate_type_of :start_time, :end_time, %w[DateTime Time Date]
|
25
|
+
validate_dates
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_presence_of(*attrs)
|
31
|
+
validate_attrs(attrs) do |attr, value|
|
32
|
+
raise_error(attr, :blank, "can't be blank") if value.nil? || value.is_a?(String) && value.empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_length_of(*attrs, options)
|
37
|
+
max = options[:max]
|
38
|
+
validate_attrs(attrs) do |attr, value|
|
39
|
+
raise_error(attr, :length, "is too long (max #{max} characters)") if value.size > max
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_type_of(*attrs, types)
|
44
|
+
validate_attrs(attrs) do |attr, value|
|
45
|
+
unless value.nil? || types.include?(value.class.to_s)
|
46
|
+
raise_error(attr, :type, "must be a #{types.join(' or ')}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate_inclusion_of(*attrs, values)
|
52
|
+
validate_attrs(attrs) do |attr, value|
|
53
|
+
unless value.nil? || values.include?(value)
|
54
|
+
raise_error(attr, :invalid, "must be one of the following values: #{values}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_dates
|
60
|
+
min = Time.at(0)
|
61
|
+
validate_attrs(%i[start_time end_time]) do |attr, value|
|
62
|
+
raise_error(attr, :invalid, "must be after #{min}") if value && value < min
|
63
|
+
end
|
64
|
+
|
65
|
+
return unless @check_in.end_time && @check_in.start_time && @check_in.end_time < @check_in.start_time
|
66
|
+
|
67
|
+
raise_error(:end_time, :invalid, "can't be before start_time")
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_attrs(attrs)
|
71
|
+
attrs.each do |attr|
|
72
|
+
yield attr, @check_in.public_send(attr)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def raise_error(attr, error, message)
|
77
|
+
raise ValidationError.new(@check_in, attr, error, "#{attr} #{message}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class CWACheckInValidator < CheckInValidator
|
82
|
+
ALLOWED_LOCATION_TYPES = %i[
|
83
|
+
permanent_retail permanent_food_service permanent_craft permanent_workplace
|
84
|
+
permanent_educational_institution permanent_public_building permanent_other
|
85
|
+
temporary_cultural_event temporary_club_activity temporary_private_event
|
86
|
+
temporary_worship_service temporary_other
|
87
|
+
].freeze
|
88
|
+
|
89
|
+
def validate
|
90
|
+
validate_type_of :location_type, %w[Symbol String]
|
91
|
+
validate_inclusion_of :location_type, ALLOWED_LOCATION_TYPES
|
92
|
+
validate_presence_of :default_check_in_length unless temporary_location?
|
93
|
+
validate_type_of :default_check_in_length, %w[Integer]
|
94
|
+
validate_inclusion_of :default_check_in_length, (0..1440)
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate_dates
|
99
|
+
validate_presence_of :start_time, :end_time if temporary_location?
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
def temporary_location?
|
104
|
+
@check_in.location_type.to_s.start_with?('temporary')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: corona_presence_tracing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Albrecht Oster
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: google-protobuf
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.15'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Implementation of the Corona Presence Tracing specification for check-in
|
112
|
+
links and QR codesusing Corona Contact Tracing apps.
|
113
|
+
email:
|
114
|
+
- albrecht@oster.online
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- lib/corona_presence_tracing.rb
|
120
|
+
- lib/corona_presence_tracing/base.rb
|
121
|
+
- lib/corona_presence_tracing/cwa_check_in.rb
|
122
|
+
- lib/corona_presence_tracing/proto/presence_tracing_pb.rb
|
123
|
+
- lib/corona_presence_tracing/validator.rb
|
124
|
+
- lib/corona_presence_tracing/version.rb
|
125
|
+
homepage: https://github.com/A1bi/corona_presence_tracing
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata:
|
129
|
+
homepage_uri: https://github.com/A1bi/corona_presence_tracing
|
130
|
+
source_code_uri: https://github.com/A1bi/corona_presence_tracing
|
131
|
+
changelog_uri: https://github.com/A1bi/corona_presence_tracing/blob/master/CHANGELOG.md
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: 2.5.0
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
requirements: []
|
147
|
+
rubygems_version: 3.1.4
|
148
|
+
signing_key:
|
149
|
+
specification_version: 4
|
150
|
+
summary: Corona check-in link and QR code generator
|
151
|
+
test_files: []
|