corona_presence_tracing 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|