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 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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'corona_presence_tracing/cwa_check_in'
4
+ require_relative 'corona_presence_tracing/version'
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CoronaPresenceTracing
4
+ VERSION = '0.1.0'
5
+ 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: []