diff --git a/generate_licenses.rb b/generate_licenses.rb index e02e547..3047508 100644 --- a/generate_licenses.rb +++ b/generate_licenses.rb @@ -1,408 +1,7 @@ -require 'openssl' -require 'date' -require 'json' -require 'base64' - -module Gitlab - class License - VERSION = '2.1.0'.freeze - end -end - -module Gitlab - class License - class Encryptor - class Error < StandardError; end - class KeyError < Error; end - class DecryptionError < Error; end - - attr_accessor :key - - def initialize(key) - raise KeyError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA) - - @key = key - end - - def encrypt(data) - raise KeyError, 'Provided key is not a private key.' unless key.private? - - # Encrypt the data using symmetric AES encryption. - cipher = OpenSSL::Cipher::AES128.new(:CBC) - cipher.encrypt - aes_key = cipher.random_key - aes_iv = cipher.random_iv - - encrypted_data = cipher.update(data) + cipher.final - - # Encrypt the AES key using asymmetric RSA encryption. - encrypted_key = key.private_encrypt(aes_key) - - encryption_data = { - 'data' => Base64.encode64(encrypted_data), - 'key' => Base64.encode64(encrypted_key), - 'iv' => Base64.encode64(aes_iv) - } - - json_data = JSON.dump(encryption_data) - Base64.encode64(json_data) - end - - def decrypt(data) - raise KeyError, 'Provided key is not a public key.' unless key.public? - - json_data = Base64.decode64(data.chomp) - - begin - encryption_data = JSON.parse(json_data) - rescue JSON::ParserError - raise DecryptionError, 'Encryption data is invalid JSON.' - end - - unless %w[data key iv].all? { |key| encryption_data[key] } - raise DecryptionError, 'Required field missing from encryption data.' - end - - encrypted_data = Base64.decode64(encryption_data['data']) - encrypted_key = Base64.decode64(encryption_data['key']) - aes_iv = Base64.decode64(encryption_data['iv']) - - begin - # Decrypt the AES key using asymmetric RSA encryption. - aes_key = self.key.public_decrypt(encrypted_key) - rescue OpenSSL::PKey::RSAError - raise DecryptionError, 'AES encryption key could not be decrypted.' - end - - # Decrypt the data using symmetric AES encryption. - cipher = OpenSSL::Cipher::AES128.new(:CBC) - cipher.decrypt - - begin - cipher.key = aes_key - rescue OpenSSL::Cipher::CipherError - raise DecryptionError, 'AES encryption key is invalid.' - end - - begin - cipher.iv = aes_iv - rescue OpenSSL::Cipher::CipherError - raise DecryptionError, 'AES IV is invalid.' - end - - begin - data = cipher.update(encrypted_data) + cipher.final - rescue OpenSSL::Cipher::CipherError - raise DecryptionError, 'Data could not be decrypted.' - end - - data - end - end - end -end - -module Gitlab - class License - module Boundary - BOUNDARY_START = /(\A|\r?\n)-*BEGIN .+? LICENSE-*\r?\n/.freeze - BOUNDARY_END = /\r?\n-*END .+? LICENSE-*(\r?\n|\z)/.freeze - - class << self - def add_boundary(data, product_name) - data = remove_boundary(data) - - product_name.upcase! - - pad = lambda do |message, width| - total_padding = [width - message.length, 0].max - - padding = total_padding / 2.0 - [ - '-' * padding.ceil, - message, - '-' * padding.floor - ].join - end - - [ - pad.call("BEGIN #{product_name} LICENSE", 60), - data.strip, - pad.call("END #{product_name} LICENSE", 60) - ].join("\n") - end - - def remove_boundary(data) - after_boundary = data.split(BOUNDARY_START).last - in_boundary = after_boundary.split(BOUNDARY_END).first - - in_boundary - end - end - end - end -end - -module Gitlab - class License - class Error < StandardError; end - class ImportError < Error; end - class ValidationError < Error; end - - class << self - attr_reader :encryption_key - @encryption_key = nil - - def encryption_key=(key) - raise ArgumentError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA) - - @encryption_key = key - @encryptor = nil - end - - def encryptor - @encryptor ||= Encryptor.new(encryption_key) - end - - def import(data) - raise ImportError, 'No license data.' if data.nil? - - data = Boundary.remove_boundary(data) - - begin - license_json = encryptor.decrypt(data) - rescue Encryptor::Error - raise ImportError, 'License data could not be decrypted.' - end - - begin - attributes = JSON.parse(license_json) - rescue JSON::ParseError - raise ImportError, 'License data is invalid JSON.' - end - - new(attributes) - end - end - - attr_reader :version - attr_accessor :licensee, :starts_at, :expires_at, :notify_admins_at, - :notify_users_at, :block_changes_at, :last_synced_at, :next_sync_at, - :activated_at, :restrictions, :cloud_licensing_enabled, - :offline_cloud_licensing_enabled, :auto_renew_enabled, :seat_reconciliation_enabled, - :operational_metrics_enabled, :generated_from_customers_dot - - alias_method :issued_at, :starts_at - alias_method :issued_at=, :starts_at= - - def initialize(attributes = {}) - load_attributes(attributes) - end - - def valid? - if !licensee || !licensee.is_a?(Hash) || licensee.empty? - false - elsif !starts_at || !starts_at.is_a?(Date) - false - elsif expires_at && !expires_at.is_a?(Date) - false - elsif notify_admins_at && !notify_admins_at.is_a?(Date) - false - elsif notify_users_at && !notify_users_at.is_a?(Date) - false - elsif block_changes_at && !block_changes_at.is_a?(Date) - false - elsif last_synced_at && !last_synced_at.is_a?(DateTime) - false - elsif next_sync_at && !next_sync_at.is_a?(DateTime) - false - elsif activated_at && !activated_at.is_a?(DateTime) - false - elsif restrictions && !restrictions.is_a?(Hash) - false - elsif !cloud_licensing? && offline_cloud_licensing? - false - else - true - end - end - - def validate! - raise ValidationError, 'License is invalid' unless valid? - end - - def will_expire? - expires_at - end - - def will_notify_admins? - notify_admins_at - end - - def will_notify_users? - notify_users_at - end - - def will_block_changes? - block_changes_at - end - - def will_sync? - next_sync_at - end - - def activated? - activated_at - end - - def expired? - will_expire? && Date.today >= expires_at - end - - def notify_admins? - will_notify_admins? && Date.today >= notify_admins_at - end - - def notify_users? - will_notify_users? && Date.today >= notify_users_at - end - - def block_changes? - will_block_changes? && Date.today >= block_changes_at - end - - def cloud_licensing? - cloud_licensing_enabled == true - end - - def offline_cloud_licensing? - offline_cloud_licensing_enabled == true - end - - def auto_renew? - auto_renew_enabled == true - end - - def seat_reconciliation? - seat_reconciliation_enabled == true - end - - def operational_metrics? - operational_metrics_enabled == true - end - - def generated_from_customers_dot? - generated_from_customers_dot == true - end - - def restricted?(key = nil) - if key - restricted? && restrictions.has_key?(key) - else - restrictions && restrictions.length >= 1 - end - end - - def attributes - hash = {} - - hash['version'] = version - hash['licensee'] = licensee - - hash['issued_at'] = starts_at - hash['expires_at'] = expires_at if will_expire? - - hash['notify_admins_at'] = notify_admins_at if will_notify_admins? - hash['notify_users_at'] = notify_users_at if will_notify_users? - hash['block_changes_at'] = block_changes_at if will_block_changes? - - hash['next_sync_at'] = next_sync_at if will_sync? - hash['last_synced_at'] = last_synced_at if will_sync? - hash['activated_at'] = activated_at if activated? - - hash['cloud_licensing_enabled'] = cloud_licensing? - hash['offline_cloud_licensing_enabled'] = offline_cloud_licensing? - hash['auto_renew_enabled'] = auto_renew? - hash['seat_reconciliation_enabled'] = seat_reconciliation? - hash['operational_metrics_enabled'] = operational_metrics? - - hash['generated_from_customers_dot'] = generated_from_customers_dot? - - hash['restrictions'] = restrictions if restricted? - - hash - end - - def to_json(*_args) - JSON.dump(attributes) - end - - def export(boundary: nil) - validate! - - puts to_json - - data = self.class.encryptor.encrypt(to_json) - - data = Boundary.add_boundary(data, boundary) if boundary - - data - end - - private - - def load_attributes(attributes) - attributes = attributes.transform_keys(&:to_s) - - version = attributes['version'] || 1 - raise ArgumentError, 'Version is too new' unless version && version == 1 - - @version = version - - @licensee = attributes['licensee'] - - %w[issued_at expires_at notify_admins_at notify_users_at block_changes_at].each do |attr_name| - set_date_attribute(attr_name, attributes[attr_name]) - end - - %w[last_synced_at next_sync_at activated_at].each do |attr_name| - set_datetime_attribute(attr_name, attributes[attr_name]) - end - - %w[ - cloud_licensing_enabled - offline_cloud_licensing_enabled - auto_renew_enabled - seat_reconciliation_enabled - operational_metrics_enabled - generated_from_customers_dot - ].each do |attr_name| - public_send("#{attr_name}=", attributes[attr_name] == true) - end - - restrictions = attributes['restrictions'] - if restrictions&.is_a?(Hash) - restrictions = restrictions.transform_keys(&:to_sym) - @restrictions = restrictions - end - end - - def set_date_attribute(attr_name, value, date_class = Date) - value = date_class.parse(value) rescue nil if value.is_a?(String) - - return unless value - - public_send("#{attr_name}=", value) - end - - def set_datetime_attribute(attr_name, value) - set_date_attribute(attr_name, value, DateTime) - end - end -end +require_relative 'lib/license.rb' # MARK: GENERATOR - +# if !File.file?("license_key") || !File.file?("license_key.pub") puts "License key not found" puts "Generate a RSA key pair using generate_keys.rb" @@ -417,15 +16,16 @@ Gitlab::License.encryption_key = private_key license = Gitlab::License.new license.licensee = { - "Name" => "GitLab Inc.", - "Company" => "GitLab Inc.", - "Email" => "support@gitlab.com" + "Name" => "Tim Cook", + "Company" => "Apple Computer, Inc.", + "Email" => "tcook@apple.com" } -license.starts_at = Date.new(2000, 1, 1) -license.restrictions = { +license.starts_at = Date.new(1976, 4, 1) +license.expires_at = Date.new(8848, 4, 1) +license.restrictions = { plan: 'Ultimate', - active_user_count: 100000000, + active_user_count: 1145141919810, } data = license.export diff --git a/lib/license.rb b/lib/license.rb new file mode 100644 index 000000000..eaac533 --- /dev/null +++ b/lib/license.rb @@ -0,0 +1,291 @@ +require 'openssl' +require 'date' +require 'json' +require 'base64' + +require_relative 'license/version' +require_relative 'license/encryptor' +require_relative 'license/boundary' + +module Gitlab + class License + class Error < StandardError; end + class ImportError < Error; end + class ValidationError < Error; end + + class << self + attr_reader :encryption_key + @encryption_key = nil + + def encryption_key=(key) + raise ArgumentError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA) + + @encryption_key = key + @encryptor = nil + end + + def encryptor + @encryptor ||= Encryptor.new(encryption_key) + end + + def import(data) + raise ImportError, 'No license data.' if data.nil? + + data = Boundary.remove_boundary(data) + + begin + license_json = encryptor.decrypt(data) + rescue Encryptor::Error + raise ImportError, 'License data could not be decrypted.' + end + + begin + attributes = JSON.parse(license_json) + rescue JSON::ParseError + raise ImportError, 'License data is invalid JSON.' + end + + new(attributes) + end + end + + attr_reader :version + attr_accessor :licensee, :starts_at, :expires_at, :notify_admins_at, + :notify_users_at, :block_changes_at, :last_synced_at, :next_sync_at, + :activated_at, :restrictions, :cloud_licensing_enabled, + :offline_cloud_licensing_enabled, :auto_renew_enabled, :seat_reconciliation_enabled, + :operational_metrics_enabled, :generated_from_customers_dot + + alias_method :issued_at, :starts_at + alias_method :issued_at=, :starts_at= + + def initialize(attributes = {}) + load_attributes(attributes) + end + + def valid? + if !licensee || !licensee.is_a?(Hash) || licensee.empty? + puts "Invalid License - licensee is not a hash or is empty" + false + elsif !starts_at || !starts_at.is_a?(Date) + puts "Invalid License - starts_at is not a date" + false + elsif !expires_at && !gl_team_license? && !jh_team_license? + puts "Invalid License - expires_at is not a date" + false + elsif expires_at && !expires_at.is_a?(Date) + puts "Invalid License - expires_at is not a date" + false + elsif notify_admins_at && !notify_admins_at.is_a?(Date) + puts "Invalid License - notify_admins_at is not a date" + false + elsif notify_users_at && !notify_users_at.is_a?(Date) + puts "Invalid License - notify_users_at is not a date" + false + elsif block_changes_at && !block_changes_at.is_a?(Date) + puts "Invalid License - block_changes_at is not a date" + false + elsif last_synced_at && !last_synced_at.is_a?(DateTime) + puts "Invalid License - last_synced_at is not a datetime" + false + elsif next_sync_at && !next_sync_at.is_a?(DateTime) + puts "Invalid License - next_sync_at is not a datetime" + false + elsif activated_at && !activated_at.is_a?(DateTime) + puts "Invalid License - activated_at is not a datetime" + false + elsif restrictions && !restrictions.is_a?(Hash) + puts "Invalid License - restrictions is not a hash" + false + elsif !cloud_licensing? && offline_cloud_licensing? + puts "Invalid License - offline_cloud_licensing_enabled is true but cloud_licensing_enabled is false" + false + else + puts "License is valid" + true + end + end + + def validate! + raise ValidationError, 'License is invalid' unless valid? + end + + def will_expire? + expires_at + end + + def will_notify_admins? + notify_admins_at + end + + def will_notify_users? + notify_users_at + end + + def will_block_changes? + block_changes_at + end + + def will_sync? + next_sync_at + end + + def activated? + activated_at + end + + def expired? + will_expire? && Date.today >= expires_at + end + + def notify_admins? + will_notify_admins? && Date.today >= notify_admins_at + end + + def notify_users? + will_notify_users? && Date.today >= notify_users_at + end + + def block_changes? + will_block_changes? && Date.today >= block_changes_at + end + + def cloud_licensing? + cloud_licensing_enabled == true + end + + def offline_cloud_licensing? + offline_cloud_licensing_enabled == true + end + + def auto_renew? + auto_renew_enabled == true + end + + def seat_reconciliation? + seat_reconciliation_enabled == true + end + + def operational_metrics? + operational_metrics_enabled == true + end + + def generated_from_customers_dot? + generated_from_customers_dot == true + end + + def gl_team_license? + licensee['Company'].to_s.match?(/GitLab/i) && licensee['Email'].to_s.end_with?('@gitlab.com') + end + + def jh_team_license? + licensee['Company'].to_s.match?(/GitLab/i) && licensee['Email'].to_s.end_with?('@jihulab.com') + end + + def restricted?(key = nil) + if key + restricted? && restrictions.has_key?(key) + else + restrictions && restrictions.length >= 1 + end + end + + def attributes + hash = {} + + hash['version'] = version + hash['licensee'] = licensee + + # `issued_at` is the legacy name for starts_at. + # TODO: Move to starts_at in a next version. + hash['issued_at'] = starts_at + hash['expires_at'] = expires_at if will_expire? + + hash['notify_admins_at'] = notify_admins_at if will_notify_admins? + hash['notify_users_at'] = notify_users_at if will_notify_users? + hash['block_changes_at'] = block_changes_at if will_block_changes? + + hash['next_sync_at'] = next_sync_at if will_sync? + hash['last_synced_at'] = last_synced_at if will_sync? + hash['activated_at'] = activated_at if activated? + + hash['cloud_licensing_enabled'] = cloud_licensing? + hash['offline_cloud_licensing_enabled'] = offline_cloud_licensing? + hash['auto_renew_enabled'] = auto_renew? + hash['seat_reconciliation_enabled'] = seat_reconciliation? + hash['operational_metrics_enabled'] = operational_metrics? + + hash['generated_from_customers_dot'] = generated_from_customers_dot? + + hash['restrictions'] = restrictions if restricted? + + hash + end + + def to_json(*_args) + JSON.dump(attributes) + end + + def export(boundary: nil) + validate! + + data = self.class.encryptor.encrypt(to_json) + + data = Boundary.add_boundary(data, boundary) if boundary + + data + end + + private + + def load_attributes(attributes) + attributes = attributes.transform_keys(&:to_s) + + version = attributes['version'] || 1 + raise ArgumentError, 'Version is too new' unless version && version == 1 + + @version = version + + @licensee = attributes['licensee'] + + # `issued_at` is the legacy name for starts_at. + # TODO: Move to starts_at in a next version. + %w[issued_at expires_at notify_admins_at notify_users_at block_changes_at].each do |attr_name| + set_date_attribute(attr_name, attributes[attr_name]) + end + + %w[last_synced_at next_sync_at activated_at].each do |attr_name| + set_datetime_attribute(attr_name, attributes[attr_name]) + end + + %w[ + cloud_licensing_enabled + offline_cloud_licensing_enabled + auto_renew_enabled + seat_reconciliation_enabled + operational_metrics_enabled + generated_from_customers_dot + ].each do |attr_name| + public_send("#{attr_name}=", attributes[attr_name] == true) + end + + restrictions = attributes['restrictions'] + if restrictions&.is_a?(Hash) + restrictions = restrictions.transform_keys(&:to_sym) + @restrictions = restrictions + end + end + + def set_date_attribute(attr_name, value, date_class = Date) + value = date_class.parse(value) rescue nil if value.is_a?(String) + + return unless value + + public_send("#{attr_name}=", value) + end + + def set_datetime_attribute(attr_name, value) + set_date_attribute(attr_name, value, DateTime) + end + end +end diff --git a/lib/license/boundary.rb b/lib/license/boundary.rb new file mode 100644 index 000000000..019d5fc --- /dev/null +++ b/lib/license/boundary.rb @@ -0,0 +1,40 @@ +module Gitlab + class License + module Boundary + BOUNDARY_START = /(\A|\r?\n)-*BEGIN .+? LICENSE-*\r?\n/.freeze + BOUNDARY_END = /\r?\n-*END .+? LICENSE-*(\r?\n|\z)/.freeze + + class << self + def add_boundary(data, product_name) + data = remove_boundary(data) + + product_name.upcase! + + pad = lambda do |message, width| + total_padding = [width - message.length, 0].max + + padding = total_padding / 2.0 + [ + '-' * padding.ceil, + message, + '-' * padding.floor + ].join + end + + [ + pad.call("BEGIN #{product_name} LICENSE", 60), + data.strip, + pad.call("END #{product_name} LICENSE", 60) + ].join("\n") + end + + def remove_boundary(data) + after_boundary = data.split(BOUNDARY_START).last + in_boundary = after_boundary.split(BOUNDARY_END).first + + in_boundary + end + end + end + end +end diff --git a/lib/license/encryptor.rb b/lib/license/encryptor.rb new file mode 100644 index 000000000..6c70a07 --- /dev/null +++ b/lib/license/encryptor.rb @@ -0,0 +1,92 @@ +module Gitlab + class License + class Encryptor + class Error < StandardError; end + class KeyError < Error; end + class DecryptionError < Error; end + + attr_accessor :key + + def initialize(key) + raise KeyError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA) + + @key = key + end + + def encrypt(data) + raise KeyError, 'Provided key is not a private key.' unless key.private? + + # Encrypt the data using symmetric AES encryption. + cipher = OpenSSL::Cipher::AES128.new(:CBC) + cipher.encrypt + aes_key = cipher.random_key + aes_iv = cipher.random_iv + + encrypted_data = cipher.update(data) + cipher.final + + # Encrypt the AES key using asymmetric RSA encryption. + encrypted_key = key.private_encrypt(aes_key) + + encryption_data = { + 'data' => Base64.encode64(encrypted_data), + 'key' => Base64.encode64(encrypted_key), + 'iv' => Base64.encode64(aes_iv) + } + + json_data = JSON.dump(encryption_data) + Base64.encode64(json_data) + end + + def decrypt(data) + raise KeyError, 'Provided key is not a public key.' unless key.public? + + json_data = Base64.decode64(data.chomp) + + begin + encryption_data = JSON.parse(json_data) + rescue JSON::ParserError + raise DecryptionError, 'Encryption data is invalid JSON.' + end + + unless %w[data key iv].all? { |key| encryption_data[key] } + raise DecryptionError, 'Required field missing from encryption data.' + end + + encrypted_data = Base64.decode64(encryption_data['data']) + encrypted_key = Base64.decode64(encryption_data['key']) + aes_iv = Base64.decode64(encryption_data['iv']) + + begin + # Decrypt the AES key using asymmetric RSA encryption. + aes_key = self.key.public_decrypt(encrypted_key) + rescue OpenSSL::PKey::RSAError + raise DecryptionError, 'AES encryption key could not be decrypted.' + end + + # Decrypt the data using symmetric AES encryption. + cipher = OpenSSL::Cipher::AES128.new(:CBC) + cipher.decrypt + + begin + cipher.key = aes_key + rescue OpenSSL::Cipher::CipherError + raise DecryptionError, 'AES encryption key is invalid.' + end + + begin + cipher.iv = aes_iv + rescue OpenSSL::Cipher::CipherError + raise DecryptionError, 'AES IV is invalid.' + end + + begin + data = cipher.update(encrypted_data) + cipher.final + rescue OpenSSL::Cipher::CipherError + raise DecryptionError, 'Data could not be decrypted.' + end + + data + end + end + end +end diff --git a/lib/license/version.rb b/lib/license/version.rb new file mode 100644 index 000000000..3b734b9 --- /dev/null +++ b/lib/license/version.rb @@ -0,0 +1,5 @@ +module Gitlab + class License + VERSION = '2.2.1'.freeze + end +end diff --git a/license_key b/license_key index 665e32a..8ed3b3a 100644 --- a/license_key +++ b/license_key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/license_key.pub b/license_key.pub index fcd4b9d..64eb81d 100644 --- a/license_key.pub +++ b/license_key.pub @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAud4kAhXh7dQg9qlhqbwU -d1wrknf06vrT2Wc72lhag5jMtWSsHWEC817zNOTNkev3q6RYXUO0cm+fcZsO4g3I -y4ZbM9s5kXY1jDaM4Mc6JCehO7o11f4mY6FY6D1RHpk7GCKkS7ApbIjYkNt7TRYV -NUeMoY82g14NWjehWroOi23FOn4UXPX20mBCRjK0RFLQ26j0rWxHpD3yz0cbY8qx -3b9v/Jazlk282IakorxaHVTYN5aplspif3M1Ku36mRVXUVkoYRJl8vjUgicut6vc -qzrTk14l3quyPKri4Tnps/ZLvWGpd53UvEEevZxJVKVxdtgH0O2UCRVRXSuiG74Z -CwIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAreEfP/ncA1A5cuxBz7rS +0Z9DDxdSymLwt2OUSM5WJa+dVB3zSpQjinifdNZq+iHVt8toZBZZ02H3unbn8td0 +rIifoj4oVpLhvnOAVjUn5tZeUX17tWMA+yyBpf6w6IFxeYBXFd14WOKEarS05U9B +59DjBxNqSm+GzhljHO7vvTKy2xXQQ7Fa702DZ7jwr4DJnL87bDXfarnYksuawqtK +wQbFHAOvxFj8ghBh1Gshap1abExD4l7QWxFMTCVOkLJmXiqfOi5KuMiaMsSUsCBN +QDE3A5aKvpwLGozsvpGRMy5Tt4SgHC7ZbgerBNe75olOoPDxZf7bBt0+O5A/UjK/ +HwIDAQAB -----END PUBLIC KEY----- diff --git a/result.gitlab-license b/result.gitlab-license index b532deb..53a6ce3 100644 --- a/result.gitlab-license +++ b/result.gitlab-license @@ -1,22 +1,23 @@ -eyJkYXRhIjoieThyOWZHMjQyMGlMcEFnaS9nOFYxZmNobitoakxWSmszYTRU -czhTdTZ0VG80akFLdXhNM1NQbGJMNi92XG4yRFNQcXJBQWltRE1vVnNmSkJu -ZG9uUVA5WHByRlJ1NlhHQndBK3p3eDB2b01hNURLNlVaS3hYUDN5QnFcbnda -VzdLanpUK0lYNU5nNmlGSktLWFJVdy9nNnRYeHFmdEVtc2laK0RlR3FNbzl3 -M0VBT0t4cHgyN2Z0clxuQ2VHckZqQTNaVGpVRkdkMWNwTlE5dHJjR21oQnhI -TXdmQlp5OUFVZFNWR2RkeC9HMU85N24xNC9sRG56XG5lVTJHUmRvcUtUbmtv -bnJkQzlFOTBuY21ERkFlaTQ1dGlrM2V4enFlMjBEN3hFbFQ5bm80N3lXT2VR -QU9cbitLMTBwZlMzWnoxNTBNSk9XZTBxUVdkeE8wQ0FwNVI3NFJmUjQ2Zlg0 -Tkd4WDlCSGhuK1U4TWZ3aDRhWlxuMU9ZT2FxemFwUDFiQmZNNnNHenV6bHJT -SnN0Um8yUHFEYVp6WlJjZEtSQnU0elRPV1FxSzBzVmlzWFhvXG55TDJxM3l1 -cmFWaFA1UW5DK2ljL0VDcGJ0M2w3dWtNR095UCtDc0hPRWsveE5jekdXQWNo -V2xmYzVaNTZcbld6UnFpcnkvR1dWR1ZJNXpMMUt0ekFVS2FlMkZtYXFxSFoy -WjlKK3JSdGxXMkR4T3NCOWpidz09XG4iLCJrZXkiOiJZby9MK0xBQy9tRWZz -MTVnTVI4TTNLQ29zdDQzTkVJY01zQXlKY1JVeGs4aG1mV2xLZmpXWkhBaG9D -TUlcbkZzanloalpWeUsvMkVHOFNML05uQmQzckErYjl5T3RlbEpFOTMzQko1 -aFZWcTdrZFRBNE5CTTlGYzArWlxueFJtTDUzcXNiQlA1MUtDL2t4K2t1ZDRJ -ZXNFalFiRk5NQVpyWGE5Rkd6VmVySUR2U2phQzZPc3BwQ0lVXG5PTjR0NEhP -eTQ5dUszbUdPRDlvMGRZTUNCd25qMW50RzZhUlNhRjBhZXlQbS9rZy9ZcHhv -QTExZ2k3UWtcbmtHbCtuSzBnZEtkMGNnemk1TzlUcDBCUHVEWTVZd2wwcFlW -WGJSNU4yWE90WlBnWFJkSmJqMjVMK1F5VlxuNmZGcnBHZmxzdllaWjB3OEFH -TnBlQU5WS3l4bytBVVVEcTlMNlgzdU5BPT1cbiIsIml2IjoielJPZGwxbTcr -b1NKVmptYmFDaWUwQT09XG4ifQ== +eyJkYXRhIjoiRGdQZUxTaXFiSEdWNk9qUnRtNlZ0OHcrZTg3ZTdDbWZIdCtK +UnZVMVAyV0wrK3I4OUZ5VjNqWGk3aEFrXG5uTElTdlJUWUFPMjZDUi9JWEsv +L29FeFJuYUZmYlkxUXc0TnpoWVl5VjN3bjlIdVAzZHhvY2t6d3lad2Ncbmdt +Q0ZlMk5TSWJYYjd3WWtIUGd0RFZmS0tYVW5CUFhqbXVDeFdsTVdqa2M4L05h +NkQySFRFcTNXRm9WN1xuMkhaNGFrTnFzV2Z6SUdBOHhZQ0FHY096S2tRVzZY +SFJrd3BlWjkwemV3R1oxYXJsVzFBdDRpNkN1U0NiXG5vb2E3QVVUYzJFTGxi +U25SdGhnd05rcDBCcW1nVE02VmFCL3MrYVA3NXhQNzRNcmo2WWFzeExybGZL +aklcbm5WTmtyRlk4R25TNzNMRkFaUmZrY04reEF0NHFsV1ZhVkx1YVM4MmVR +d0tmU3NUaFR1UDhnZHk4cGtUOVxuMkNIT052QlJCYzlaYmZIS2kvblNDSmtt +YVB6OTd6eU41TnVWR2NsN1Z3TXd4VDBsSHV2N1VSQmxZVGRuXG5wM1FaWG5O +MW5qb0JlTlN1dk5CM1RIZ09KMzRMbDk3WWV2MUhMeDljQ0dTMFdHd3FZVHFz +azFnbDlUTXZcbmNuVzZKem9oWFZoTW81ZkhKVTFNZ1hZcDNvbDR3SFJvZUlo +dXNYVWhKWmc0eTdLYWVWMUp2VFBxVDFiQ1xuQnlSMkdsOEI4c1h1YU9kUGor +N3dBK1hpN2VYM0VYSmdqYlYwXG4iLCJrZXkiOiJEcy9Ma2ZXamI5elZuTVRF +TElPbUJ6WkFZeGxrbHVraDRKR2J0MlM1WWxaTGo4S3VhcExLTjF2ajdvNDVc +bkxUaFZUOHJwTy9Hc1ZUMVV6L2xtOTJoa2NtS2FtQkdQN1ZIT2F1eVI0QS9z +ZEpXZVc0L2poU25SRUFMelxuUHVvK3A3aGxsekN5aEZ0RUlXdENWZ1ptUjFC +ejJoVm16bjdvMUFXdDIwZkFzTkN4OHArbERkVkJDL05YXG53cGl1TGVxTjBY +anpLTG8waWN1VWNOSmg4Ynp2WkoxTzQ2bzhtRUxpelVmb2xWMlFqUXg5OUdS +eWF0bjZcbmZKeTJvMEw1MWRXUnZHZUJsM09LR203WXdnTDRZTFFUd2tTeGtu +dmwwRGVVV0lmUk1vYmN3SFlmeTF3TlxuYU9TUVFUb3M3dHp6cXBRbmtJRjZH +cUlLNUdXUmZ3Y0ZQSldVdlRONFV3PT1cbiIsIml2IjoiamllcXNJK0Q0bHpP +dDNtbkRVdU1yZz09XG4ifQ==