pax_global_header00006660000000000000000000000064151436262200014513gustar00rootroot0000000000000052 comment=768444ba75222030cff5726f4c417c9b5e980d0e TeleSign-ruby_telesign-8ad846e/000077500000000000000000000000001514362622000165235ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/.gitignore000066400000000000000000000001341514362622000205110ustar00rootroot00000000000000*.gem coverage # RubyMine IDE .idea # Visual Studio Code IDE .vscode/ # Other .DS_Store TeleSign-ruby_telesign-8ad846e/.travis.yml000066400000000000000000000002521514362622000206330ustar00rootroot00000000000000language: ruby rvm: - "2.3.8" - "2.4.6" - "2.5.5" - "2.6.3" install: - gem build telesign.gemspec - gem install telesign-*.gem --development script: - rake TeleSign-ruby_telesign-8ad846e/LICENSE000066400000000000000000000020421514362622000175260ustar00rootroot00000000000000Copyright (c) 2023 Telesign Corp. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. TeleSign-ruby_telesign-8ad846e/README.md000066400000000000000000000045061514362622000200070ustar00rootroot00000000000000[![gem](https://img.shields.io/gem/v/telesign.svg)](https://rubygems.org/gems/telesign) [![license](https://img.shields.io/github/license/TeleSign/ruby_telesign.svg)](https://github.com/TeleSign/ruby_telesign/blob/master/LICENSE) # Telesign Self-service Ruby SDK [Telesign](https://telesign.com) connects, protects, and defends the customer experience with intelligence from billions of digital interactions and mobile signals. Through developer-friendly APIs that deliver user verification, digital identity, and omnichannel communications, we help the world's largest brands secure onboarding, maintain account integrity, prevent fraud, and streamline omnichannel engagement. ## Requirements * **Ruby 2+**. > **NOTE:** > > These instructions are for MacOS. They will need to be adapted if you are installing on Windows. ## Installation Follow these steps to add this SDK as a dependency to your project. 1. *(Optional)* Create a new directory for your Ruby project. Skip this step if you already have created a project. If you plan to create multiple Ruby projects that use Telesign, we recommend that you group them within a `telesign_integrations` directory. ``` cd ~/code/local mkdir telesign_integrations cd telesign_integrations mkdir {your project name} cd {your project name} ``` 2. Install the SDK as a dependency in the top-level directory of your project using the command below. Once the SDK is installed, you should see a message in the terminal notifying you that you have successfully installed the SDK. `gem install telesign` ## Authentication If you use a Telesign SDK to make your request, authentication is handled behind-the-scenes for you. All you need to provide is your Customer ID and API Key. The SDKs apply Digest authentication whenever they make a request to a Telesign service where it is supported. When Digest authentication is not supported, the SDKs apply Basic authentication. ## What's next * Learn to send a request to Telesign with code with one of our [tutorials](https://developer.telesign.com/enterprise/docs/tutorials). * Browse our [Developer Portal](https://developer.telesign.com) for tutorials, how-to guides, reference content, and more. * Check out our [sample code](https://github.com/TeleSign/sample_code) on GitHub. TeleSign-ruby_telesign-8ad846e/RELEASE000066400000000000000000000032451514362622000175320ustar00rootroot000000000000004.0.1 - Fix missing runtime dependency. 4.0.0 - Added support for Intelligence Cloud to use new endpoint 3.0.0 - Removed App Verify SDK 2.4.0 - Added support for PATCH requests and Basic auth 2.3.1 - Fixed logic code to add dependency version in user-agent value 2.3.0 - Added tracking to request 2.2.5 - Update comments in both methods of messaging.rb class - Add a comment in RELEASE file 2.2.4 - Permits use of net-http-persistent 4.x.x 2.2.2 - Fixed issue handling empty parameters for application/json content-type 2.2.1 - Added support for application/json content-type 2.2.0 - renamed Auto Verify to App Verify 2.1.2 - changed generate_telesign_headers to use string hash keys to support ruby 2.2 2.1.1 - fixed issue renaming api_host to rest_endpoint 2.1.0 - updated and improved README - secret_key refactored to api_key to align with docs and portal - api_host is now known as rest_endpoint to align with docs and portal - various doc string updates and fixes - added travis CI, codecov coverage and additional unit tests 2.0.0 - Major refactor and simplification into generic REST client. - API parameters are now passed as kwargs to endpoint handlers. - UserAgent is now set to track client usage and help debug issues. - generate_telesign_headers is now static and easily extracted from the SDK if custom behavior/implementation is required. - Now using net/http/persistent to take advantage of http connection pooling for performance, thread safety and graceful reconnects 1.0.2 - Fixed gem imports - Added number_deactivation and telebureau create, retrieve and delete endpoints. 1.0.0 - Initial version supporting commonly used Telesign endpoints. TeleSign-ruby_telesign-8ad846e/Rakefile000066400000000000000000000002501514362622000201650ustar00rootroot00000000000000require 'rake/testtask' Rake::TestTask.new do |t| t.libs << 'lib' << 'test' t.test_files = FileList['test/test*.rb'] t.verbose = true end task :default => :testTeleSign-ruby_telesign-8ad846e/examples/000077500000000000000000000000001514362622000203415ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/examples/messaging/000077500000000000000000000000001514362622000223165ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/examples/messaging/1_send_message.rb000066400000000000000000000006661514362622000255300ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = 'phone_number' message = 'You\'re scheduled for a dentist appointment at 2:30PM.' message_type = 'ARN' messaging_client = Telesign::MessagingClient.new(customer_id, api_key) response = messaging_client.message(phone_number, message, message_type) TeleSign-ruby_telesign-8ad846e/examples/messaging/2_send_message_with_verification_code.rb000066400000000000000000000012531514362622000323110ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = 'phone_number' verify_code = Telesign::Util.random_with_n_digits(5) message = "Your code is #{verify_code}" message_type = 'OTP' messaging_client = Telesign::MessagingClient.new(customer_id, api_key) response = messaging_client.message(phone_number, message, message_type) print 'Please enter the verification code you were sent: ' user_entered_verify_code = gets.strip if verify_code == user_entered_verify_code puts 'Your code is correct.' else puts 'Your code is incorrect.' end TeleSign-ruby_telesign-8ad846e/examples/phoneid/000077500000000000000000000000001514362622000217675ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/examples/phoneid/1_check_phone_type_to_block_voip.rb000066400000000000000000000010701514362622000307520ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = 'phone_number' phone_type_voip = '5' phoneid_client = Telesign::PhoneIdClient.new(customer_id, api_key) response = phoneid_client.phoneid(phone_number) if response.ok if response.json['phone_type']['code'] == phone_type_voip puts "Phone number #{phone_number} is a VOIP phone." else puts "Phone number #{phone_number} is not a VOIP phone." end end TeleSign-ruby_telesign-8ad846e/examples/phoneid/2_cleansing.rb000066400000000000000000000014021514362622000244750ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' extra_digit = '0' phone_number = 'phone_number' incorrect_phone_number = "#{phone_number}#{extra_digit}" phoneid_client = Telesign::PhoneIdClient.new(customer_id, api_key) response = phoneid_client.phoneid(incorrect_phone_number) if response.ok puts 'Cleansed phone number has country code %s and phone number is %s.' % [response.json['numbering']['cleansing']['call']['country_code'], response.json['numbering']['cleansing']['call']['phone_number']] puts 'Original phone number was %s.' % [response.json['numbering']['original']['complete_phone_number']] end TeleSign-ruby_telesign-8ad846e/examples/score/000077500000000000000000000000001514362622000214545ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/examples/score/1_check_phone_number_risk_level.rb000066400000000000000000000012631514362622000302600ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'ABC12345yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = '11234567890' account_lifecycle_event = 'create' score_client = Telesign::ScoreClient.new(customer_id, api_key) response = score_client.score(phone_number, account_lifecycle_event) if response.ok puts format( "Phone number %s has a '%s' risk level and the recommendation is to '%s' the transaction.", phone_number, response.json['risk']['level'], response.json['risk']['recommendation'] ) else puts "Request failed with status code: #{response.status_code}. Details: #{response.json}" end TeleSign-ruby_telesign-8ad846e/examples/voice/000077500000000000000000000000001514362622000214465ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/examples/voice/1_send_voice_call.rb000066400000000000000000000006471514362622000253330ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = 'phone_number' message = 'You\'re scheduled for a dentist appointment at 2:30PM.' message_type = 'ARN' voice_client = Telesign::VoiceClient.new(customer_id, api_key) response = voice_client.call(phone_number, message, message_type) TeleSign-ruby_telesign-8ad846e/examples/voice/2_send_voice_call_with_verification_code.rb000066400000000000000000000014451514362622000321200ustar00rootroot00000000000000require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = 'phone_number' verify_code = Telesign::Util.random_with_n_digits(5) verify_code_with_commas = verify_code.chars.join(', ') message = "Hello, your code is #{verify_code_with_commas}. Once again, your code is #{verify_code_with_commas}. Goodbye." message_type = 'OTP' voice_client = Telesign::VoiceClient.new(customer_id, api_key) response = voice_client.call(phone_number, message, message_type) print 'Please enter the verification code you were sent: ' user_entered_verify_code = gets.strip if verify_code == user_entered_verify_code puts 'Your code is correct.' else puts 'Your code is incorrect.' end TeleSign-ruby_telesign-8ad846e/examples/voice/3_send_voice_call_french.rb000066400000000000000000000007311514362622000266540ustar00rootroot00000000000000# encoding: UTF-8 require 'telesign' customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' phone_number = 'phone_number' message = 'N\'oubliez pas d\'appeler votre mère pour son anniversaire demain.' message_type = 'ARN' voice_client = Telesign::VoiceClient.new(customer_id, api_key) response = voice_client.call(phone_number, message, message_type, voice: 'f-FR-fr') TeleSign-ruby_telesign-8ad846e/lib/000077500000000000000000000000001514362622000172715ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/lib/telesign.rb000066400000000000000000000002321514362622000214250ustar00rootroot00000000000000require 'telesign/messaging' require 'telesign/phoneid' require 'telesign/rest' require 'telesign/score' require 'telesign/util' require 'telesign/voice' TeleSign-ruby_telesign-8ad846e/lib/telesign/000077500000000000000000000000001514362622000211035ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/lib/telesign/constants.rb000066400000000000000000000000551514362622000234440ustar00rootroot00000000000000module Telesign SDK_VERSION = '4.0.1' endTeleSign-ruby_telesign-8ad846e/lib/telesign/messaging.rb000066400000000000000000000021041514362622000234020ustar00rootroot00000000000000require 'telesign/rest' MESSAGING_RESOURCE = '/v1/messaging' MESSAGING_STATUS_RESOURCE = '/v1/messaging/%{reference_id}' module Telesign # TeleSign's Messaging API allows you to easily send SMS messages. You can send alerts, reminders, and notifications, # or you can send verification messages containing one-time passcodes (OTP). class MessagingClient < RestClient # Send an SMS message to the target phone number. # # See https://developer.telesign.com/docs/messaging-api for detailed API documentation. def message(phone_number, message, message_type, **params) self.post(MESSAGING_RESOURCE, phone_number: phone_number, message: message, message_type: message_type, **params) end # Retrieve the status of an SMS transaction. # # See https://developer.telesign.com/docs/messaging-api for detailed API documentation. def status(reference_id, **params) self.get(MESSAGING_STATUS_RESOURCE % {:reference_id => reference_id}, **params) end end end TeleSign-ruby_telesign-8ad846e/lib/telesign/phoneid.rb000066400000000000000000000013561514362622000230630ustar00rootroot00000000000000require 'telesign/rest' PHONEID_RESOURCE = '/v1/phoneid/%{phone_number}' module Telesign # A set of APIs that deliver deep phone number data attributes that help optimize the end user # verification process and evaluate risk. class PhoneIdClient < RestClient # The PhoneID API provides a cleansed phone number, phone type, and telecom carrier information to determine the # best communication method - SMS or voice. # # See https://developer.telesign.com/docs/phoneid-api for detailed API documentation. def phoneid(phone_number, **params) self.post(PHONEID_RESOURCE % {:phone_number => phone_number}, **params) end private def content_type "application/json" end end end TeleSign-ruby_telesign-8ad846e/lib/telesign/rest.rb000066400000000000000000000221541514362622000224110ustar00rootroot00000000000000require 'pp' require 'json' require 'time' require 'base64' require 'openssl' require 'securerandom' require 'net/http/persistent' require_relative 'constants' module Telesign # The TeleSign RestClient is a generic HTTP REST client that can be extended to make requests against any of # TeleSign's REST API endpoints. # # RequestEncodingMixin offers the function _encode_params for url encoding the body for use in string_to_sign outside # of a regular HTTP request. # # See https://developer.telesign.com for detailed API documentation. class RestClient # A simple HTTP Response object to abstract the underlying net/http library response. # * +http_response+ - A net/http response object. class Response attr_accessor :status_code, :headers, :body, :ok, :json def initialize(http_response) @status_code = http_response.code @headers = http_response.to_hash @body = http_response.body @ok = http_response.kind_of? Net::HTTPSuccess begin @json = JSON.parse(http_response.body) rescue JSON::JSONError @json = nil end end end # TeleSign RestClient, useful for making generic RESTful requests against the API. # # * +customer_id+ - Your customer_id string associated with your account. # * +api_key+ - Your api_key string associated with your account. # * +rest_endpoint+ - (optional) Override the default rest_endpoint to target another endpoint. # * +timeout+ - (optional) How long to wait for the server to send data before giving up, as a float. def initialize(customer_id, api_key, rest_endpoint: 'https://rest-api.telesign.com', proxy: nil, timeout: 10, source: 'ruby_telesign', sdk_version_origin: Telesign::SDK_VERSION, sdk_version_dependency: nil) @customer_id = customer_id @api_key = api_key @rest_endpoint = rest_endpoint @user_agent = "TeleSignSDK/ruby Ruby/#{RUBY_VERSION} net:http:persistent/#{Net::HTTP::VERSION} OriginatingSDK/#{source} SDKVersion/#{sdk_version_origin}" if (source != 'ruby_telesign' && !sdk_version_dependency.nil?) @user_agent += " DependencySDKVersion/#{sdk_version_dependency}" end @http = Net::HTTP::Persistent.new(name: 'telesign', proxy: proxy) unless timeout.nil? @http.open_timeout = timeout @http.read_timeout = timeout end end # Generates the TeleSign REST API headers used to authenticate requests. # # Creates the canonicalized string_to_sign and generates the HMAC signature. This is used to authenticate requests # against the TeleSign REST API. # # See https://developer.telesign.com/docs/authentication for detailed API documentation. # # * +customer_id+ - Your account customer_id. # * +api_key+ - Your account api_key. # * +method_name+ - The HTTP method name of the request as a upper case string, should be one of 'POST', 'GET', # 'PUT', 'PATCH' or 'DELETE'. # * +resource+ - The partial resource URI to perform the request against, as a string. # * +url_encoded_fields+ - HTTP body parameters to perform the HTTP request with, must be a urlencoded string. # * +date_rfc2616+ - The date and time of the request formatted in rfc 2616, as a string. # * +nonce+ - A unique cryptographic nonce for the request, as a string. # * +user_agent+ - (optional) User Agent associated with the request, as a string. def self.generate_telesign_headers(customer_id, api_key, method_name, resource, content_type, encoded_fields, date_rfc2616: nil, nonce: nil, user_agent: nil, auth_method: 'HMAC-SHA256') if date_rfc2616.nil? date_rfc2616 = Time.now.httpdate end if nonce.nil? nonce = SecureRandom.uuid end content_type = (%w[POST PUT PATCH].include? method_name) ? content_type : '' if auth_method == 'HMAC-SHA256' string_to_sign = "#{method_name}" string_to_sign << "\n#{content_type}" string_to_sign << "\n#{date_rfc2616}" string_to_sign << "\nx-ts-auth-method:#{auth_method}" string_to_sign << "\nx-ts-nonce:#{nonce}" if !content_type.empty? and !encoded_fields.empty? string_to_sign << "\n#{encoded_fields}" end string_to_sign << "\n#{resource}" digest = OpenSSL::Digest.new('sha256') key = Base64.decode64(api_key) signature = Base64.encode64(OpenSSL::HMAC.digest(digest, key, string_to_sign)).strip authorization = "TSA #{customer_id}:#{signature}" else credentials = Base64.strict_encode64("#{customer_id}:#{api_key}") authorization = "Basic #{credentials}" end headers = { 'Authorization'=>authorization, 'Date'=>date_rfc2616, 'x-ts-auth-method'=>auth_method, 'x-ts-nonce'=>nonce } unless user_agent.nil? headers['User-Agent'] = user_agent end if !content_type.empty? headers['Content-Type'] = content_type end headers end # Generic TeleSign REST API POST handler. # # * +resource+ - The partial resource URI to perform the request against, as a string. # * +params+ - Body params to perform the POST request with, as a hash. def post(resource, **params) execute(Net::HTTP::Post, 'POST', resource, **params) end # Generic TeleSign REST API GET handler. # # * +resource+ - The partial resource URI to perform the request against, as a string. # * +params+ - Body params to perform the GET request with, as a hash. def get(resource, **params) execute(Net::HTTP::Get, 'GET', resource, **params) end # Generic TeleSign REST API PUT handler. # # * +resource+ - The partial resource URI to perform the request against, as a string. # * +params+ - Body params to perform the PUT request with, as a hash. def put(resource, **params) execute(Net::HTTP::Put, 'PUT', resource, **params) end # Generic TeleSign REST API DELETE handler. # # * +resource+ - The partial resource URI to perform the request against, as a string. # * +params+ - Body params to perform the DELETE request with, as a hash. def delete(resource, **params) execute(Net::HTTP::Delete, 'DELETE', resource, **params) end # Generic Telesign REST API PATCH handler. # # * +resource+ - The partial resource URI to perform the request against, as a string. # * +auth_method+ - Method to auth. # * +params+ - Body params to perform the PATCH request with, as a hash. def patch(resource, auth_method: 'HMAC-SHA256', **params) execute(Net::HTTP::Patch, 'PATCH', resource, auth_method: auth_method, **params) end private # Generic TeleSign REST API request handler. # # * +method_function+ - The net/http request to perform the request. # * +method_name+ - The HTTP method name, as an upper case string. # * +resource+ - The partial resource URI to perform the request against, as a string. # * +params+ - Body params to perform the HTTP request with, as a hash. def execute(method_function, method_name, resource, auth_method: 'HMAC-SHA256', **params) resource_uri = URI.parse("#{@rest_endpoint}#{resource}") encoded_fields = '' if %w[POST PUT PATCH].include? method_name request = method_function.new(resource_uri.request_uri) if content_type == "application/x-www-form-urlencoded" unless params.empty? encoded_fields = URI.encode_www_form(params, Encoding::UTF_8) request.set_form_data(params) end else encoded_fields = params.to_json request.body = encoded_fields request.set_content_type("application/json") end else resource_uri.query = URI.encode_www_form(params, Encoding::UTF_8) request = method_function.new(resource_uri.request_uri) end headers = RestClient.generate_telesign_headers(@customer_id, @api_key, method_name, resource, content_type, encoded_fields, user_agent: @user_agent, auth_method: auth_method) headers.each do |k, v| request[k] = v end http_response = @http.request(resource_uri, request) Response.new(http_response) end def content_type "application/x-www-form-urlencoded" end end end TeleSign-ruby_telesign-8ad846e/lib/telesign/score.rb000066400000000000000000000026151514362622000225470ustar00rootroot00000000000000require 'telesign/rest' DETECT_HOST = 'https://detect.telesign.com' INTELLIGENCE_RESOURCE = '/intelligence/phone' module Telesign # Obtain a risk recommendation for a phone number using TeleSign Intelligence Cloud API. # Supports POST /intelligence/phone endpoint (Cloud migration). # Sends phone number and parameters in request body as form-urlencoded. # See https://developer.telesign.com/enterprise/reference/submitphonenumberforintelligencecloud for detailed API documentation. class ScoreClient < RestClient def initialize(customer_id, api_key, rest_endpoint: DETECT_HOST, **kwargs) super(customer_id, api_key, rest_endpoint: rest_endpoint, **kwargs) end # Required parameters: # - phone_number # - account_lifecycle_event ("create", "sign-in", "transact", "update", "delete") # Optional parameters: account_id, device_id, email_address, external_id, originating_ip, etc. def score(phone_number, account_lifecycle_event, **params) raise ArgumentError, 'phone_number cannot be null or empty' if phone_number.nil? || phone_number.empty? raise ArgumentError, 'account_lifecycle_event cannot be null or empty' if account_lifecycle_event.nil? || account_lifecycle_event.empty? params[:phone_number] = phone_number params[:account_lifecycle_event] = account_lifecycle_event self.post(INTELLIGENCE_RESOURCE, **params) end end end TeleSign-ruby_telesign-8ad846e/lib/telesign/util.rb000066400000000000000000000023151514362622000224060ustar00rootroot00000000000000require 'base64' require 'openssl' require 'securerandom' module Telesign class Util def self.random_with_n_digits(n) n.times.map { SecureRandom.random_number(10) }.join end # Verify that a callback was made by TeleSign and was not sent by a malicious client by verifying the signature. # # * +api_key+ - the TeleSign API api_key associated with your account. # * +signature+ - the TeleSign Authorization header value supplied in the callback, as a string. # * +json_str+ - the POST body text, that is, the JSON string sent by TeleSign describing the transaction status. def verify_telesign_callback_signature(api_key, signature, json_str) digest = OpenSSL::Digest.new('sha256') key = Base64.decode64(api_key) your_signature = Base64.encode64(OpenSSL::HMAC.digest(digest, key, json_str)).strip unless signature.length == your_signature.length return false end # avoid timing attack with constant time equality check signatures_equal = true signature.split('').zip(your_signature.split('')).each do |x, y| unless x == y signatures_equal = false end end signatures_equal end end end TeleSign-ruby_telesign-8ad846e/lib/telesign/voice.rb000066400000000000000000000020531514362622000225350ustar00rootroot00000000000000require 'telesign/rest' VOICE_RESOURCE = '/v1/voice' VOICE_STATUS_RESOURCE = '/v1/voice/%{reference_id}' module Telesign # TeleSign's Voice API allows you to easily send voice messages. You can send alerts, reminders, and notifications, # or you can send verification messages containing time-based, one-time passcodes (TOTP). class VoiceClient < RestClient # Send a voice call to the target phone_number. # # See https://developer.telesign.com/docs/voice-api for detailed API documentation. def call(phone_number, message, message_type, **params) self.post(VOICE_RESOURCE, phone_number: phone_number, message: message, message_type: message_type, **params) end # Retrieves the current status of the voice call. # # See https://developer.telesign.com/docs/voice-api for detailed API documentation. def status(reference_id, **params) self.get(VOICE_STATUS_RESOURCE % {:reference_id => reference_id}, **params) end end end TeleSign-ruby_telesign-8ad846e/ruby_banner.jpg000066400000000000000000001616521514362622000215460ustar00rootroot00000000000000IExifMM* p(1$2؇i$ ' 'Adobe Photoshop CC 2017 (Macintosh)2017:05:12 18:53:540221prz(HH Adobe_CMAdobed            &" ?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?PS T%mf}OρiftiĀ<4;5z?$gБ%H01"TR3H01:wS a R@)H T잝Ne68v^;wz}8o-%ԏL)3/x\TZ9J,kMm}Cw3gsm7gpaS\^?>V|^[1ͷ>+7'!@)jdv 70R2.驪C-u޳Cnpǩm6ߠz.d+,SQ>6C59ەyXc>#gN9\÷W_7;Z tޖw%:luW[͒Ӹ9To>c#aR8'da0[,u9k% [aH*I"NzIitYI' Ac$l)pX^)Τ as 'B\IazZPhotoshop 3.08BIMZ%GZ%GZ%G8BIM%ɟK=(H5j+8BIM: printOutputPstSboolInteenumInteClrmprintSixteenBitbool printerNameTEXT6th Floor ColorprintProofSetupObjc Proof Setup proofSetupBltnenum builtinProof proofCMYK8BIM;-printOutputOptionsCptnboolClbrboolRgsMboolCrnCboolCntCboolLblsboolNgtvboolEmlDboolIntrboolBckgObjcRGBCRd doub@oGrn doub@oBl doub@oBrdTUntF#RltBld UntF#RltRsltUntF#Pxl@R vectorDataboolPgPsenumPgPsPgPCLeftUntF#RltTop UntF#RltScl UntF#Prc@YcropWhenPrintingboolcropRectBottomlong cropRectLeftlong cropRectRightlong cropRectToplong8BIMHH8BIM&?8BIM 8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM8BIM8BIM08BIM-8BIM@@78BIM8BIMnullbaseNameTEXTUserboundsObjcRct1Top longLeftlongBtomlongRghtlongpslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongRghtlongpurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM( ?8BIM8BIM8BIM &F Adobe_CMAdobed            &" ?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?PS T%mf}OρiftiĀ<4;5z?$gБ%H01"TR3H01:wS a R@)H T잝Ne68v^;wz}8o-%ԏL)3/x\TZ9J,kMm}Cw3gsm7gpaS\^?>V|^[1ͷ>+7'!@)jdv 70R2.驪C-u޳Cnpǩm6ߠz.d+,SQ>6C59ەyXc>#gN9\÷W_7;Z tޖw%:luW[͒Ӹ9To>c#aR8'da0[,u9k% [aH*I"NzIitYI' Ac$l)pX^)Τ as 'B\IazZ8BIM!]Adobe PhotoshopAdobe Photoshop CC 20178BIMmopt<TargetSettings MttCObjc NativeQuadBl longGrn longRd longOptmboolQltylong< blurAmountdoubembedICCProfilebool fileFormatenum FileFormatJPEG noMatteColorbool progressivebool zonedQualityObjc ZonedInfo channelIDlong emphasizeTextboolemphasizeVectorsboolfloorlong8BIM-msetnullVersionlong8BIMms4w8BIMhttp://ns.adobe.com/xap/1.0/ !Adobed@p)    !1 A678952#3(" !1AQaq" 2#B%r3CSst&7x$4DTU6vw8'( !1 0AQa"q@2PBRbr#3uђt6`Ss$5eC4pc%v '-FD/%ePeDaFQSFaTXFPFSFXW5A\хsVXG5aTՅM(MXF\ՅsW5aXW5A\хsVMVPFSVaaPFSUAD-BD-FDmFmɼFѬ"*"͠""0*)*# 0h°h°h© ¹ 9j¹ **l % h#*" [(!h!HM25h4 <.дQELeDmBѶ APQaPDDSFSFUMi;eVT͘FM\ց\MXW5a\ՅsW5aXW5SVTXF\ՅA\ՄsTW5Be4aDaU4eDAF-AFLEmɴmɴpaV(h(Z(l"(l &Bt  ++h° m@j© j¹+j¹ +0h*h h© j * "9 "(l6d#h6dE,`RFѼFD&D-AP EDAPFSTaU4a\A)5A\хa\ՅsTTXU5U5aхՅsU6sFsVXW4aцsFsTZAՅAѕSFAXDAFQMS"D Eȴm FѬѼy]hh(d[( !l+60# ) +¹ ©+9 +j¹ j© #j°j+ +¹ 0*2h 0"ZŠh4RD-FmDmȶmEDSFQAFMVaXW4Ei4ATՅa\хsTg5aZSVXW5sVXW5A\ցTՅsW4aPZMPW5aTҁPG5aXDsFAPG4LbV AD EU FдQ FS&m]k6h(#h"( " # *0# 9 + ++9j¹k@k@j¹ h) @j¹G~?kNOA}o/g #0#)l"([ h#HMhR8-ɴmA-AQaUAFaVaTхAVMW4aXVM\цaai5A\MhXG6SVTg5sVsU4e4aXW5aTG R/\–/H^[m^zpuc} "¡h#( "B3ydZ4M;Jo& F-EAg̃n*|Wn{+w]=r|8w2:ΞP/Ȝlsق>}ܿdWpk͙oK<:4͹gȚz=Ü30K͗R97Q{0m~6 vb 9 *Rh© I0+# 3*~{BCinr5,@5$rߵͣs [֫.7[DAV aPUBD-a2Q fQ FѴ-G>vxLFѼмfD/&شU Co2|r:{wfjk=U\Ǣ|8/%?=I[+Ӳ?X^q>= '5D9?%Y޾,$wOǮ`/~ ϔp>#i@3 j+ +h0jϯC[NgK· u =slA7kOߥoAQbg4ADQMDAU eLa2iBѴmyUd67H}|<󦃦r_;>Q\|=n{)|ţ7{0^5ROg]},[_>^v$mb?=C=:{[y̺v`t _X+ j¹ 3 @l# j©* {6{CR+ݯvxw}.ڜ7eAaFBD aDF/%o!QGwY6y4d)hZ6 Z6wˎOcb۔ervnC~΂r.Y1=WrnogrgvF8Tӷ(?"cM:۹t;-J{c]^lLp.q^rnG9C9j© 3930hj+ ?O.NGy}cz}=brԞwct}1-~1FAFADBT-BLfDo"Ѷo%Z#y6dMy(" h"6 ""0" ) j©?<=k퉨x\ՅsVXU4a\ՆsW4aمsW4AXW6sFXU5a(<E9;O}o[|ִGJ7o_=9 "0"d Z6!hhF |*FiFѼBѴmA2mFBDADaPFaDMXFSJAXU5sFsTXW5sFsF͜sFXW5a\ՅSZMP׿{^Mgg7Oh#6h2h"# *°.f# * # *j)9 j¹ +kI " l0j¹ ) *°j "#h"#l^N "i--ɤmȴmFѴ-AQAF-ȶUMFaPFMXFA\ՄaTхa\sV͘G5A\ց\sW5aXW5se4VMhMhTՅsT\sVPVPFaTaDAT-ADQAD-fQ^^MdMhMy6y6#h!h"(""*0 @# # j0h°j¹ h*j+k@j j¹ +0#j°+0#+ "0Z( Z(d#7l84z5Z4ghMZhͰ" # 0* h j¹mI) iBH92lj-) @l0" + # j [ h"¡lAmc\H<:1 \b!b!9CbGXb!78"""9\DDp#""89""C D1A"""!DGXb""9GD1#Gp<c1ĈD~xb"8""""""b":"b#C1cDDDC"8 CD\":#sD_?z>Qt"C#b""!!#C\t"b>raQ|p"N_ G(1ÜC1cB#q#!DDGH""!E!b/܈s~txb".|3E <DDDD0C C D1 Dp"a,1Cqo.N>11b"bb!qsCDp"x DDGHb"""C$D1 1""!p#]"8~t8 G!aDDC""""z0]"!!{w6a>Է1 ?xii{Wo3z˗oבD=F":1G>Dq8ß"8 Cß" ,G!!b#C 1r Qq<<ݞs>QDD18<"cs?][U{<>ǪIn+'ewT wU{z׼r/l!PfȈil\{o;-{|;fm쾨Lwc3hǁ"""!"qD| 1u/-w~7y=ב=]`,%Cy>/g'^GkȎrp8 |ps!=aCw76Tho5_nOW)[y#D7;o=~Kez+3`(ly5{!Fxy5/^˻&k}cu:kO<}o||~>n2MY47^fz;/&ֽ3qs (!Z{{W7R{iLhG:ub":DG":sb/_?=ŋssb?:3|9XL}ZWb닧ȎsBE$<oDG؎#oNgi>s0`vyz K^~x3cLuduNW;F{py::{gQ_W7Zd͇\Gzӵ{i6>&f͸/p}9uS9,ݽ uNCorɿHr"b!ab!q DC}/.m|}5xpdYOegy/ZYyGnq'8p#Lm{5?lvyb4.;7;fo6{v+=yތ۟Rc{_m?Tow]w*vmif;Cs-W֭ F }؍zG=Y4?ٜx~w+RZ^nkQm.ZSn& Nsfyep[֒,,uD|18C'(?A8Sb.w8"81ʼ ԞWU>wfFåaDDCb0y""G~6E}3;1e2ŇrgNw;ַwoDC8uå׉ D<9_8b""8~1 p.wHb""#zG8b#E ds|;}/|Ky|yc;?]?oRb#ׁDp.qH8t~pb88$<$DGHDp!st-q_w[/^}_Ə{?iCfkb!xÜ1s@@C/s CG""8p>Q tN0b""9 < 1DQ>'""G C8b""braDp##11t$r>N\b"#:G0b1!DG(H#X~>H:",D|D<:pq#E|#ω!#p":(""""! 0\"Bc1!b"""8!8\"?IC#"CsyD|DD1$DC0p"#b>HDC D~D~8N'w8Bp#z1p!DDCG>Q <"":CCD1z!"CAu!$p~߼G8|">x "#䈏#'$E81p"#~>Icp |? 7)fII&fg$33$$̒I333333333332O0|^332L2L3 x330s;8I$ffIfIddfdd&dW:9}3oMItѺZjm-w^}ךԛmIvfx<ffgOd'× 33ew3rܣ;|3lpyy<>Ois{$dI&ffIdfI32_\pc>wzG.{GI4<jM'3q|/'II&dx<LYl Ei[6lN CWk Cw'MMao(ߗv'2fx~>__/>Y&fdfI&I$$7kzw]|u}u]($r]Ico332OffI Llda| p9adO,s+{Tw285FAÛkҙf>o MMt{org(~Kx;y{Mvjh!L%չ&iǍ7O:{T>-Et>mdj=ɒdI$&ffffIzz mye9 $gsr0M 4W=o˳ffd3źOC<L̓2I32Mx_=}paۈ332L3$̓333333$ol_ft^SxYo?e4vW|w`rQ}d2I2LI&fIg33'fd&fdfI&fIIfI$fd&fdpؽ` ȴր9}֚#/Ldy]a}`{~ӳ32I3333|g<,dfIdfd$3? LI$2I3$2^{ywd<#lN'zÃx64vrkۿYw4{̓3$̓333< x2OI&dffI&d&fdfd&dfd&fI$83$33<&fdМ,̓3&x3$$$̓$$$2L̒L3332I2L$fd W/МpKfx3ff3? 30l6 ab"l0accDsaDDCDB1 C 0a 1 6 00[ =0pbl-X -,,Ca0 1ayCl,D1b" C DD1 C D00 0a 0l=!al1b ual1 aa 0aa!(b"">Hs C C 1aaXb1 Ea H,< q =H0l,1a  D$|G8c0a C!!"""ṶDDC =K ,08c<=H 1 "c䇝 ,?V l+# 񇕅D1㭆>F Ԉ>p Xn" 1ŅaaacG( 0Caa aq^ 1 X[ 0Xl,0DB1 DD0!b"!b"DDC 0 a aal,0Ņ 0"a!,1 00r!c1 1" 1DE!Ϟ E,1aaa XXaXᇕqw6#Ua{';osh7+wrz}$or2ח!,,DCAb"b.q""YEj~7'/_< |,,6Xl,t1 aa CԈb6a!a,-0#fۍ۝ >@f=+crOOWv_y~L2{=w ͡O^lD6C C CCЈ.}>F^_K7ԏ?YҟfZ?oPw_a $|1= a aa,,C8a6 aCam;SW~jOoa[zݹZ?e>!ϝ;+i7U;]n~VԻB[ a1C DG1 0C2/Nob-֙c˛$zCM'3!akmn/;ү> `٭/Y#ԑmKx=7R=$i};:l]=/ocDml-  0 6! 1al,<󅈋 lO֞Fia{>LJ[~ioX2o4`獸[k=Cܞ-yXal,C t#u""!cyDGHaZ'JezF~Boog^?_ KYhޟ +}x{%K>!|L-Q9d\_^v 3}9O?,;ҹߧ3p_j^}dJCލ{//B 4NhXOO͎WOd>=Qv>#4gެ <ᅆ! aaa!![eޚF~$=two+*ڭ[zzgscv37Oc2<1 1 1R#bb>"! u tHo+O7fO׽/påIޢ/Ɠ\R}d^>Q;[ڃ I?w˼O^3 ԝc#}r3MGg_ۼx}dܭ#q筛Aľٽ5ۥ螃< ۝Gҏ}cb?_n}o2aaa n=wԝDT/ow;/8=Y/޽1&}ݿ5mC]~ڿߜKiF_pO[o_ꞃD}73]o[7+n?1o)_q D1 66~aC [kFw]g/Lwy>lƅNafjN޿?O,,,D,Cb""#1OD0#{={s'A^ oOG7%>r %6VCVy{믣~G?m^?ܟG@?N[*O@|SǽU\[WKuߗބ&+cvXAp~`u_}g}zm٧_o-&9)>Ca녆 0ŅaXw{}[c~2_/ .Yy 0ua"9D0 D1䈆"!a!"#aa6Ņ1 6  ņŅߕ:߿p7[v}n57s(;u7`7Ow22rH:0b"!b"!!!1saa:Xay aaa[ a! ayCa 1 6! * y7oFo#?7oeS N,޿Z|j V9ņ!Q =O8s"!ayDCXCab! b!6 ,-0 0 " al1 s1 DDC Cp"bb" CC C 1 !al- al- aa""bax8 DCc0(K8nnq#|X[ R96a1aHK Xb>0a#Xl,1(Xc C0ņ""9Ў |sE""s!AaXaaa; XyC 6!yCaabl-:( Qb#a XXl,Cx"""#aaal0aaal0 Ca a#0a" 1 D6DDXXaab""!D<9XaEa D6>Faa(aa^va,0 1al0ab aa C C!c!Q"".d1 01[ DD0aV,6! Ca Xaa""" |aa 1 C|%|D_"aE1"|$|Xl1aaE>0l< GaXl6E"8$_?ۿMkmoF`g=g}DMY xuYtLJN)e6 ٙ g~ ۗGe{:[ס^靛>Ht3>.vu:)߳C8|vWnnzy-lnq\ 7Wnl7time6gw+b[ALJGNiX 28ߍح:]ig Sn{sx~+ݏF}qm;<~A3񿣿G~+lW|:{{GR+gǩKk]CźOs-߫D$RD(ޠPkO-%}~yGr7^+ӱ%wcʤ?r?*oOf^BsE@;@>@=ăN &`֒BxdEj% x;F7tVz awèxlh37lϡ]ti RK :ˉCpΛbϘ-fYʅG*׈5~XCj 5EE aȎn!, >fO[55,D$ۂFOϋ/7jWnu~3$v**;KᠼFd}W~ ?,hޥOp7)$ a$FAwFDf3 wtxsWf_Փw:[ (C8f?-:l]kVӭ䷜1)jk\zmHxAF*@cv&ҽ`n-v-l,z1$̒q0( jp xo ㌑$+8HLZAKgN3#0ީùqXA2xK`/7j>K5!* Q)JƕrDiC$ޠɥVF;<;dZl, ~RV&2 NAM=Dp)fRuh7N)D9RLz.ǎÍhV_cAmփۍ*;f7;GWxbg~/JPJ| 񤶬ȶr `C(jVQXG'R" ֵZZ]$ڲā,k_hlB2礊ƨ5'.Fid\[2A Xۀ5ytOkHCL=+7 xXٽPf]MvoWe: :lSi (9N&jF;-uj}3As2 ќ;0GUȅ䒟k_)ውSޫ-@X\pzdE2FoGEB~Gԋǧ|q1^B*sV` ,cj][O"*NH9{OXs{OXs{OXscwoki#!NRW.Á]M慫Y^6 Y!r-+%T PiP12UeVJ;$T ң`dž5>a.MFI({3*(f!@0A4cHe g>,sDWQ^ mxORSܥ-i#xm/z% m"PʬUNgŭ[kT MenĖԜs Ʀ7!R (]#Nwn{wٮ09 ľk+i&$x}=pG<$€x$ kuV"2UX)ʟz|*0'Whoz-BM ?)jQNy:!'ŃN}Ndq%,E3eEH5Hfʘڇg+ٍ]՚iYb~s{/Z8j:-(2w7*]EpqĒTF47-ibJҙg}v#V2 |#s#麮j: +̒FdD"xHhhÌ.lk,ls#AKOK͡r[i0kRiN"NnƧy)k(\)F2b 58P.T-7ƥXl}$I)풾8寈V7#FF4U6f9zDZ٘C6nSqEq G>0`:iw{q~N~Kp`$2%R{4s$CSx ƇH7“J,RAbidPNL^.#AR'y)]™cJnpv̜D37XjdNXAoFE~.4$="P%2sgCA'o$!U[rѨLK2JųкDY +U4(O^sҖƵ5,F=5wc.yN[጖y.Ɏ>h Kw⯡Tz,^\fbȷP)%(sDOӓ Ѳ% Yx*I0 $$jN8aЯq S3\5zx>TA9F!_Q3-*d1B<'b!Ub$UonB/TI(Vey>w.(fчe {ť=ʺ^1G"Iu% +D^8ArsO.򅏼)WTQFX3&jkBq:6 og= YG#ʤԊ`'/Ge \>ʨ J"eUz@VI-FՖ {ŭ>HVhD@ xP^(*DNM]TB-QɞB~#* /8%<ֺō|$[yiqʀ${kǞm$ȣē6cjO`8iD"⩆یg`$I@B -Js+qi/k7$J0Elm({1̶ܳn%:Hi2)ȚV0q_>/$pz+2„?m$`s'=sɻӠ!zgFHɖbrq.|^s/0V. j J4 FC[4Sҭ\K<t L9JKa|~ 蛯R =TTr .j xH$ xe\UƯs.-A)2ͤ1HHAJR+nS3]ʧI w]B H~vAROsqJ2m }#ٖ/4W(fukU?W-/qF3 ?|euY+ @Lj{߂F5۱ZK};g}1ۀ}5[)ЮFƅ(?KQ[mvem3廤z]7`tG]]h3^oW:#f~ti4 Oey."$-dZ ku<sbCħbhdd)?\b(.ȶw Yޜ)HcƄ*IfQLVɦ@ ‡}ґ{XG[?fSЌBMB1f9Jz׊cۃ:#`[|:ucOQ^z<6uGL 'nR[_ﬠ'[y`OL²KT7'w2?̃.U-?g]Q/Nd]vRJ2dO" W'3Cu٠1r_ y o>i#Q)*kO 7i6){a|n zPev">`|D.mUy!R@$TE/Njz#=ЯR:5kѠSo7u[t+<xc~7ãc=^-38nN6Vmvxb;7tw)L^tvWE(1 x9c> Rާç`}!-AC.tx,@=W?a\^Bd @xƻP(Kx}kylS⌎+N$$dGOR~V~>:Wy dVʴ`@N96̚F.-ͽ\BW@8Ԟ"8EFr>k-kfPC/,$3GZ:ԣd  n#35ib{Hؠy"ky )b@#5gC5F5H(r $So`0Pq1{ͲITRX (E Ai,F)8 :g+MZ'kwl(`XuV~S_' 7154yrϗ7LU#շ(+5ns9͜:j 6eE+Z?hXs-#,̒[+Rݥ"@HCn5lg- EDdNƫgLVedfԟrHb-C{^ `Y^/wV6qBH!9R29TK-Z [HqsD=0hgCdRm#tc{/̧AhݸeXԕxأ{ i~`{N{XN_ϸIkr~b;M uXPff4UUbFdc9^9_P!!ݝ0~2ȡ$vr@\Z/|2=|~Jbu8 Cq&lshȆG $ !&ecBKF L/)O;M3RF&dhR]Wf_L# :.>nRԨBS1_5MR+b{XnOd$ <-' Ȁ[ 0t؊rqYXF_|ܢ aGcW _4n~W f4 rX:g6+xDjUi Kȣ?D}aoh[56# \0;E>OũBZ~20P FuFߟh <+MǑP}qcI,`9^ݳsIcFP¼h+Bg?Y=Q*8NF`>+{I¶qpwJ4c'>Ⴋbѷz0}BIbSA>SI/,༕.['4a]ۼ1sQ Eh~Z,r(hPRT03A#"DcݗFӺ)ˬZXRe@ODh㲘LԵ?;m=ި*O֙\jÍzKgYmt0Y4xD=D >s5ߢKCAz,doGnVPtFh`Eyjh#QAV Ҙ@ܪ Yڤ0z-(p^]U"45j8^-ۛ#_~SsU=puݡ!҄wߜ1jd)QٍkCR%';• `$?TH{ݏ1iqs)y$ VS*,~$ (>9MiHǼWP}Iy6lT+N+MĜq,3KУôq5h$v. ėD肭fʐiV9a[w+ARخzP׆x|*Łd%ǰ֟ZKJ$3}A*s5w׆o\+( \'V aA1.i$ ܬTƆl{*+ ,#]kJĒ=!f|\8OЯq9C9L2`3<&4ff`.AAU5 {t~q{~dCjeĵ 38˯TY*",YݎJ*I8Ӿ򭼞g2,baNQJ;W A+|A9~RgS¨;iݍIu F嫱 3)hG@C{i=ٲa^I&8 rD6#;\Ԅ71wf4"Ytj/m6:%eR^rqoj6k+7G:hܨR>_rqyS'oNvxWuFcxĐ΅eSB>c~*}5YI%W|ml_1e\GooDQ5(@_N-ZEz 5ɼ 5h֑X`>jbF?Qte@~UcOiR2 N-[E~ REH|kԶ@A{ ȡ( Aqڲ =ޭb22$h8S8)MhA M*k1?ǐ®=j>n*\zfR#VPZ*h)o]8nKfodh1ޟ}YaQUGCFI009gM(Qd#e~-ۛ|iEQnoީDaZ|+ۺIvyWZ6/}%u?2.#aI2TVݣ[]Y@WS0{Ր;wSԯjS~/D[)E${q_N!a$a;F$ITfa}LiP{G>RGkR#FU߄ *$ԁl{{ͧaQW 2|Vv-O8VҹҕߟvtƨWr?mR6=J)'TK 20aZvЃ_ۭsefQ_kPxR% ;NYk_㜿{fEOqg5?zԯ6yS.|3O-ŸH,+ԲA-*H63 ථөT(ޑd>aL8; 9u*a/&=KY7sNU*89V Oߏnk$Orǽ' ؐSz&suG$su!& j<1aܼCrR>w>hL̶xʧxS^Y׊b3bٸr(hEh7tKOJAaox2#_MS4RZRIBP!d(e1k Yjjp~= Bw"CAG+>uUǥ={x\ZЏ+fNN*hVe%0\`r~n;jIsIʴﮙ.h"r)rnESs4/7piJL{zs>UUga'ʾ$oōĜSykWbYN19څH.$<+5@vZcIex;/uJ[. [3Jd[ZiE[r :BHՔ 2ol&oѥGJ6FAhxj@:OrRT%G:.ʤu/sq@W(Q<[,aP[_xW:+wC'yƣ 1? n]ɇџUxO-B9Hel}7?cocPE<[ms$"DEnja}%Nگb'D_}KaB#P1S*xj>uL8Xr ܒO`./ {i AV&55*8z=%I昖+cR4@" _H77N9v1ipE]3H,/@G3js[%}BEE) HƏ˶p]FO/Vfg, U;mU"I?q`d1'vj??\rOJn|g 1fNBW7M?= # ,tU59dq䙌ܲ3P?#̪dnv;?tn_SǪ]On<: ԃDv7kw~H>H~@:ggp6?c 71k;w7 7 V-c߰7o eB.ˇj 1nAEoA|9vݡ_/ CX~{iolk5n/^![!xm AF!#uQw_`E n,. ߡ{=_š-s-$ZFhnZC[jm{7pmg> l{ GA8wohoCw`k}m ~#X1o"5xs ǿk Ggnx,Xݻpx7?݈l{ݭWhn"q\EڱZݡͻ1jo-_!=xoAa`}g xLZc߶q ֆ}~y=!,lk6L^g/0F el>C^>[!ٍ]ŵo -CY o-hѯSv7 _` 16{1g^ab!;_ߣ-opl0Egmѵ Dn<[Q.Ź1~^}X]Evzl |xry7Ůvyl堶k/m ~;ደahE7Wyacoeܷ A7^=o߷ j -۳onh/P͈n {Yrhkx1c3nѹnf -hۘn=xF>00܃kz=H_uޚ. ^눹lX CaGCkwwKbxr |GݢnZɆ -oAv E׎A oǗ=7 16b7a5ųhZ5×6Fѱ7载w5w1`c0~x1F=1=^D\H얤}d)=IXINR*7aL+U.ꔑ1)u2uBhp"3"y2 :4+TeT]OWŸL KM'DR ,x/<7ML21+zRMISUӉKS)'菾nxp<9i:Xrgcd4(*KP" DXF7D2|cp ᣳÞ.}P Gɱ 5ݻk7 o_fSwa|5 xѽzj\$^m8.P《[,ABʣdNCϼwO=YtT'#,˚JL'ZʁCddO@t̯+fCR+sVKTـ:47y‡ 7x*LR))rXZhAPRh9:7q]yLjȌr-LC[q 7m ͈ŭ\>Ck, o`kؘC6F7-}-\>9 l;oz :7zV!%ᾤӎd١5E>gstMTUTdT۲]צƺ`aw=渑u<!kyX$"򚑚#P/xyex'U7Tt띒ldaO *%=D2tuhP?DdO(GJSů'==J3)== yM9UUTo-4jDEw]aa.Dd\=SPL*y֥V&kf'G4$dl D(j>UM7,eMRKj9W=4,ūTI,v4W˞uLw?tt 23/|u_9k-I?Qɠ+UpT,HoG#c!UQT;s&i94 Af}?- \r%HVs#1Fa̠1Hi|inmԒyJ^D5$+idι ))'F7݁ i1ÿFַ=66Y6w1;yECaCm6LjoRMt둨 'R _M(Jen_!T4괩_O'Dǘ#Nz#6H'Y -sj6+E!}f%rʍ#),](bh-IfV ]3L%bc.C($ٛHWr*9OH}:]״:1#* i-=\QHږPS7!ƄDNP#hpl6 okZ=ơÐmF7c7 |kshރpmZfաho݁<5 abm9p Y4KP_Te0KB#z"!&OrgnT?uaJIl[1F; Fs' TQ+W$8n>DFOZ29piڎ*Yrib:+T"rQPʦJK #qpL]>WY0e9v9j iea#%y ye5^MJ"N(R%q|EC|g9sYA'ӕs7S6isM!hLY=Pg rS<2ԉR)zaa'Iʌ݃FML]T0Z 9yY7aFJ㏺>eh3چgyO{G02+js3ڮveBը߮qBz1 :+_Sڥfnwg}kZWW=9c=_+Vjq%#R؟\NIמpZjsVUԶl&1Lַ2tgֽ4OTXPv4HE35ՎdS,ĩu~ yOaaSTRIS5̗'޴РaȺW${49 V$:ui*I+'i,s.e,h ˜= }l`GULW͕]F2Ԩ5G8dv2H:W q'M|<|G:qZ fURLz0*J<6G 2iqkg'+̆ۆiR9ٕ9m43ELf K :~7^vKVt&CR"1\'؏?+&I:/&YAe`\eHu<8+:-9w7M*374jT! J8_.y)Z1['"Z呞q:4P"NJIW85HǩRz~#K)tbw0QF}G0]7\v!!Fo=K8ԙk1 PJ;sTz޵EwƗEG:<N _Q35L>%I8K#8BS Le4LJ C0I+9p#+HmyY+u=i<.49MhҲ7TÕ"t*W>\M^Ϫ .򲅨&AGFIMr{. $&t:s7S;,+U[=Tר1zd􆰨]]UXֿeF)"EK1L/B: E4ڭ&Ӕ:MTVT=i8U_1hAZ\AԲyZ]RRji>3FuC9`f=20%tm+=+2t3 ?"!=&*VJ2果-v 3ւ%[+|Ϊ $ξd8ez^Z¹M}I #P=?eX2rHPy jLd20a;Q"gyI̡'*YVo]|U(Dn=CC^nhu,SY*@VzVB1 pԛacv48QIn2ΕsFo(TNV?O2eLU,U7K.7Ie :{ć 2&#AٿSqTk IDLԞY]`3̹[!NDr$W`$V5o|NMW~?yY@{]fFf>C(3f:$Bhj:\WL5G&rYEp…?vVz_ۡp ֆ֐v| ~-ݡ fxmP, )e8}\Q'2N`$Ԯ>|`PtHj[QufvUj 3rEOG'\Dz7z O~Aky<ͬwoð^a[}Gav, n]F]m]1#lyfK7nݤ5na[Fx 7YhK:?zVtm|Y5e1?42"?r$Mݧq谝#r_;(_] )Γ`)j|?44WZ$dFXb Ҵ^L̊rxȱӚhK$Kugҝ@/$zgMovЦU*` ^37]w;ݡC?ì*vU 9`d[羮_5,BcR}LwJBJѯTr &S]*,/g99y|:IRJ1*qT*X+^$x P},]P(hnrr;S-E!Ϥhiii(~;ċAEK&h#4%TEets=!iHծ,7 pFR2zBة("XnG 3G3z\jZC /A NGifv(U.E/̱vXISB̈$hVі9zDVU-?"eՓ(婧VjU)~#]0~,W#xy]B WT*gt*(k-QJiܼHs JjO# QQ2c?zPRӉfԞboEN!HfjRSUU}NBUb 4?v oÆY\tVRgj֜9=#72P}^M;J+c?'q&g|[;| bJf;3<-dџL;z"P>LP[lzV~A`gp [j- lf<zmZ/[m5`z,lKGy []W1fAݍf!\;{!, 5wͼ&G^H\$w(M;"STe(srH8Gb>fdei {7T=E26ꪊ`qRTx~<{8[2}g.8D8돺8dW;p켎,FY FiWԭ 'st<LT"*GFtBTXƩ:;TM3^'h o+<]K e%b0,ŖGnaJ}59JH266PSY,Cv?R,BKZW_yҘBTGF&dn% t RoK&w79HTy׉37I#ьޙzY2oP;ARy*#^cM$?} vMM$z¶ۅ Av ~yv}Kx׼vlA|]]evى=\ cpz6]C}zq[M\5۱{nƏrG}b܎< ?m,%2YYqR@u*LG_0'"h.OZE5VS d)r}>J#ꐧsRwU3/v Y(wcМ&覒$d\|^Q/@3S,D4WbB 眈ddaJSB:z\;$٭(Na) b7ǤMejaYdEVOE# H?ޡaU-uǦdd@[[&l%(_8ɝ#y#;ǗfUJ+NSǛūSn%C/.-UXl7&)!(LBڧ$z{*Jf\Y< Nn*@8A8Q̑"7̌O׋MȺvfӮf93Y*(΢s ? JHޕAsUE35:(xeYy+ɨeDU'e̡4*b"ď O}ߣ; ΐgqN`Mн߳`3ϻiΔhVC/s'򘒜AL0bh#U JL G#PH`Т^GI}^sEfE%iT?5KIMu MJN!dSDN GmU'ZPOnLk5#ޢ U3]>N$;ш1 K'UlIGQU|qASeYk@_@WPT4˥oPG9ȑ7?OSџz!o>G6Ijִ>C͡DG7uo-ceRejJCx_P3(jar!6llGN8u ;72VqDi9CUJTUUi6m#[rI¡C^t'"*%U^ST]G+dSiL4)*Zc,A+83DQ7ptǝx/QIf&S*̜_9Y|ē%5=8GDB'lhuөQdR iIs/.~:sB3TTxE}SB9-_ԾkT_ZtlMQ1kD0#A&+M~8X֏Z嘴BY9hQAVޝ\][JP@.p<˞Zq_մ>yFjYt-s~nquiLH,|s(.~(1coԌΞ3ޗssiZmJ7%sO-(SV TthH@5Ѣ+LTQխ#kNE}E$Zn;-o*^+MFFalΥkR**C#HDKe>MXOD9-I/NsdJ5VeML948r~y"F,uJ'ELoTDFD%ftZnԔ42\ÐV9a(T+~S<]FE"^ULerϧK"~@.ysJ}EVhϙW/<_HeQ9S3EY%PHey\,~"DJIRbrǞtwȚLUkP5%M9L) L| o ~Oϴⴭ,6'5}M?ETQ1<ȣ`STdΰ`Ϋ|"O+(8n8ͨ5#oma7~ͷ<{}ÕkjXqӏ7QjyK?񂇉F{fDVG< D/FN -5!M[iX˞8[!3%4)ߪg:' ht[\CO eN6!OqLu'lӝk'X7V1 b9ꥴj&"%2z8i♤|̈M***m ̧I@$5.E5m:K똤( 7|D)u &fR@bHZtgr"rd"Ms%Y8K %w`Ϻ#^yfC߀U^2?2CC(91k/7ACAok6fzvb,K5ZVc5 xOGl ][qf1kj ^vZm`mя !x݆b5p!ߐMkFRfG@3RIYHדJuRxxTrT"M""vc:37^맳 B,mH4buG5OG0橡ӲT1r*nIRywJ(US|_TYZ_ʦ}9UJRe")=g:KIyƘ(*%U1ٿAkm[ɷ.m8cg߇pc nøb=M45YX7ߢk#T2TQq.^e*$(VLgav>$J294 ׅut8/*O;2Dnyo!=Dȋ&Ӧ,Yw?\59D&[9GuPt|,gn7^PVV29Nza^Q#,u[tGKQzDc8C CIzҜgddeNMr55)R^oT['[#7k6|+5^";}㡷 4Qt5 T=ٚzI ߌQ;Pq-f_tܰD+DKELs*K禾 K_i9D]oSxgSLek._f+_:Dnx>)kM ZAYz3W/Ѹ aTpK)zH1|f7+?:Am2+sYf}ѐ$L'ST^HDM$Q,dgLۗs{!Uߙn‰ɦqfڇP ^Ŭ ;Czn7~ofO}smѻwm鿎pbӴic#X[FWk|^-߯>Ŏ~wỰXon:gkaƬXj.kp&f.ׯj?An fkf%P=l^x3Z1m>ujy!k{y<r wƿ-v,oAفwF-?pL׋lb=os-ÿx7#=op.cxk,CcZZwqv17`m{\7am@KW;>A6 Pn~B@V_F75_}Z.s6\C65q7vGw5;/`! ؍Ǜ{[mἇۣn5 {w--xoo_y>:~?77v! 5mmm15ow~kwb\9poC6#}jpw!W>,hf!yleMOk;.gW6A8*!H+aY!:+_0Bp_zL!efP5?Wy}O5O<ޟ9`N56+7=y?KGn"(NBDZh*oו_Zkɩ֖?AafCr?ޏߩz8[KqncTWwW^k{}O=H,.Čw5~tΣћMUoEW`ͫ*fݎW++]#:5Uaͷ>+7'!@)jdv 70R2.驪C-u޳Cnpǩm6ߠz.d+,SQ>6C59ەyXc>#gN9\÷W_7;Z tޖw%:luW[͒Ӹ9To>c#aR8'da0[,u9k% [aH*I"NzIitYI' Ac$l)pX^)Τ as 'B\IazZPhotoshop 3.08BIMZ%GZ%GZ%G8BIM%ɟK=(H5j+8BIM: printOutputPstSboolInteenumInteClrmprintSixteenBitbool printerNameTEXT6th Floor ColorprintProofSetupObjc Proof Setup proofSetupBltnenum builtinProof proofCMYK8BIM;-printOutputOptionsCptnboolClbrboolRgsMboolCrnCboolCntCboolLblsboolNgtvboolEmlDboolIntrboolBckgObjcRGBCRd doub@oGrn doub@oBl doub@oBrdTUntF#RltBld UntF#RltRsltUntF#Pxl@R vectorDataboolPgPsenumPgPsPgPCLeftUntF#RltTop UntF#RltScl UntF#Prc@YcropWhenPrintingboolcropRectBottomlong cropRectLeftlong cropRectRightlong cropRectToplong8BIMHH8BIM&?8BIM 8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM8BIM8BIM08BIM-8BIM@@78BIM8BIMnullbaseNameTEXTUserboundsObjcRct1Top longLeftlongBtomlongRghtlongpslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongRghtlongpurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM( ?8BIM8BIM8BIM m&FQ Adobe_CMAdobed            &" ?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?PS T%mf}OρiftiĀ<4;5z?$gБ%H01"TR3H01:wS a s*i&'`sX=鹲[YH-$ cV\8<<5mg7*Ü& @FGV FE'f걤G? w5ۀ92dlsCnkZ;r|8H壏@/ds)˷P :۽s鷙rь]HLČq~ 7Qe^kko8>leMOk;.gW6A8*!H+aY!:+_0Bp_zL!efP5?Wy}O5O<ޟ9`N56+7=y?KGn"(NBDZh*oו_Zkɩ֖?AafCr?ޏߩz8[KqncTWwW^k{}O=H,.Čw5~tΣћMUoEW`ͫ*fݎW++]#:5Uaͷ>+7'!@)jdv 70R2.驪C-u޳Cnpǩm6ߠz.d+,SQ>6C59ەyXc>#gN9\÷W_7;Z tޖw%:luW[͒Ӹ9To>c#aR8'da0[,u9k% [aH*I"NzIitYI' Ac$l)pX^)Τ as 'B\IazZ8BIM!]Adobe PhotoshopAdobe Photoshop CC 20178BIMmopt<TargetSettings MttCObjc NativeQuadBl longGrn longRd longOptmboolQltylong< blurAmountdoubembedICCProfilebool fileFormatenum FileFormatJPEG noMatteColorbool progressivebool zonedQualityObjc ZonedInfo channelIDlong emphasizeTextboolemphasizeVectorsboolfloorlong8BIM-msetnullVersionlong8BIMms4w8BIMhttp://ns.adobe.com/xap/1.0/ !Adobed@p;     !1 A678952#3("&  !1AQaq"8 2 B#r%&7x3CSs$t6vw(HXR4DTUFV' !1 Aa0Q"@q27PRb#3Bru`CSs$4t56ႃevp'c% '-FD/%ePeDaFQSFaTXFPFSFXW5A\хsVXG5aTՅM(MXF\ՅsW5aXW5A\хsVMVPFSVaaPFSUAD-BD-FDmFmɼFѬ"*"͠""0*)*# 0h°h°h© ¹ 9j¹ **l % h#*" [(!h!HM25h4 <.дQELeDmBѶ APQaPDDSFSFUMi;eVT͘FM\ց\MXW5a\ՅsW5aXW5SVTXF\ՅA\ՄsTW5Be4aDaU4eDAF-AFLEmɴmɴpaV(h(Z(l"(l &Bt  ++h° m@jǾ;Ov # # 0+#雫rݨ*h h© j * "9 "(l6d#h6dE,`RFѼFD&D-AP EDAPFSTaU4a\A)5A\хa\ՅsTT/).WES0Ӳ'q6F;7:_kz%mM5__,ll>Y\>o_cMh99-j hʩ " #()lRdZ6hqhF <.Ѵm ɴmBLe2-ADQAATՄATaՄaPFTՄa\ՅaTXF;;Qs_Cy'XNlNb:z/>xp=fz7\CZ>Px_z?S?!ly+>R[Ԏ+)0j2*P+)+0j99g?>Io;/:,+h=ͳ[k<^Q㟨g|â}xמuHw=ztos`{K;km@{ؓ捵kH?o]o2]=c6}c;S+ *0j%39 k@+1NI K`W/v;?2jrg_ߖAsDUAD aT-EF/%BټFDmyUd6)LMh(!h?U.9?rr6nRqNy@D : Ⱥednj]ɺa1:6}S;Nܣ.N4nOfԵ)w 5u{A3iz5ʚs#sm/t +0h#h9 Ij+}?@:c.v5щRzݎ9ӵp~tFAFaD eP e2AFټCwk5y63h&M2"ʠvʰ"°+# 9 EhWhOg&]sVXW5aTхsV\цsDf\a\W5aTՄ󿟟]箴N~-?ts_)mdZgy*ߪkH* 0# # "1h(Z6y1Ԫ-ɤmFm FѴQɶ/%DQ AFQaDADaU5aFM(aTVPW5a\VW6sTTTW5a\ՅsVMi5C?^ҮcnrtpgK!Zg=꾇y+ 0"  #hZ( |*XFѼXD/&LfDmED-ALaG5ADAVMTa\ՅSFM(sV\Յag5A(TՅsV\ՅsTVNV?oWB;g>}[{z_=~od~u~P4܎ 0"© "!h [)h7`R&mɥEFѼaS&شU eDaDQDMFA\ՄsTsFaXW5sVVM\ՅsVXW5sVsU5a\ՅsVZSVXTPFaTхAVADAFQ AFe2-yM,Z5y6#y7h6("1h d# " 戫0#* # k@+0hj+j¹ j0j# j© 0+ "0# # 1h*Ţ Ch&J!l"4dZ82ԛ#y6dMh[Š6&MŠ#(0*0* *0"+ h°j© I *P+j0j¹ 0" + ¹0" # 9 " """!l#/<=IXFFFдS&LES!QaD aPFQMTAW4E\sFSVTՅsVMXW4aՄsf\sZSV\Օsg4a\sVW5SVa\a\aT҅e\AT-APQ ADmȴQ G~xy5R5Z7hE<^Mh"76"Z ")0*0# 0+0++j¹+* +j©&++¹ * +# 0[ )2"0"Z(y83ԋɤk8k8o&o"ѴQFдQBDE"U4EDAE5aVsVSFsTXG6asZsT\Յa\͔ҁXU5U5W5SVPJsTXW5AXG5AVSDaVaPAF-EF/'~yzy5KY6Y66Z( v #% *# ¹ © 3 @j¹j* j¹ +l0j h ##0"¡h!h E/Qqh7ZR6y6dZ6#hZ!h[+6 * " *0# 9 # &# 3 )# hʹj©0 -)2H°+h+9 !l* Š# [)bѴQ\qp1 C8abb"!P>""">"sab""#$DG8Ĉ̈1D?DDDC#"!#舏QD1 Dp! s"8r88 DG#"""""!"#8C Cp""! r|D1sG9D_~K(>""9|_QCc>Ȇ"Ժ*ҙoG-KК4& cAz4AjZ4hUVFw5\m䛭GZv:1|DDD|GC GQ 1"""/_Ir>8?a#}910|zuV>:ywa;ۜd; 5Nm7Wk={պw>M:^POޛH#bc숆!">a"""'81GD1!>b!""v!ݚTF5M cAz4-U UF\u]@:46Qt]󈼻0WԷOxcڞǵl>;y#^US˷kȈ"8ßG9"#19>b.pp"b"8D1 CĈDC{1dgݎAj</aC{cv96m[cDDDD<8DDE~D7_z9Mɷis޷Osb"8("81 G'{nߺ߷講O=aY~[Q&y0dU/g{֢8?b"">8pG(-iݭc} a]&CDD1">""#D08/dC+: Ff]_2Lj}CfZ]E}sͳ~Ya{$DCsω".q8>#_Xn%^1gӳx>pF=Z۾ҺRw[NxWW{+<"Ȉ#DGc;.{u{o#7Vmew~^j˾ܟ%Ǯ;׷S`yO[uv?gvRf__xh<5mO[ozvwwh ^m/xv%-͆ӻAoב[;3sc,0վek{6K;l`_ "!9"! Gb8=gc&.K PmSKlv>2&upl.q_Q#p""/|sqGD7;o=~Kez+3`(_ly5{!Fvy5/s^˫&k}cu:kOm;}o{|~>5n2MY47{^Pfz;&ƽ3qs (!Z{{W7RyiLh>b>bDG1;"+YgcctWozUf}o8i]^S7_~8$X^QrN#Dpp"8l{_ovo:}NS }L~7mgwo6;4:wPvGXdxj#nW Gs3ucuM:z5tw;WShcnoތێ߼ˏO~^q;>|Нͼwf;Aד[qWn7qv#B~ i5}!g]yesȽԺog}|uw;xyWs>#kL1}j?&5ogwo0mc.nnܭ9Agc ZZvvK]ꍤKiu<Кq6ݏ܍DXas"""D?# C ^yeK74fkwA{4;zo׍$XXD}18C'($C}<"sb# Cj,2 Ie}n]uvkl:_8.qaDDD1!a kMkŗ ˺=ӹ:G:^N|^""8/r7"/ۜ|DDG܆!""">ab911y#{c/W(,):.e1鎏z=u{W_CG"8|p?Ap8 1\CG1"b".q>o/1+}ョ-E>[x>wܼ c~2 D18s.q< GDCDCDDC DGC0 }\nq DDDDDD118n|.'""p#!1 1#}pc#s DD}_ĎQˑ߹~ DD}bGp! ?#GCg(Q>#,?D}d}1|DCBGρ~C#Q1? s}s>'CsGD|(""""!b.q q!c1ˀDrsb"|D0 DCG"cb>b!781DGDCDD1 } C G">#D0G\DG#psD'>!""">a DC DC0!"C>anqc?nĎ}x~ȏ$G"#,?qG#IaD}#_}ab/G?be"DD}~ 7)fII&fgdfffdIfffI$',x3$fd&dffI$ffO$&ffI&I$I&fd&fd&fx<fffffIfffI&fIfI&ffffId3332Lg 3fffI̓3333336.:=.k)}c;qgn5ΰZvWjۭ_;un|ޯӷ[GZv3332Lffd&gffx3333Iff$fgfffd32r6MI{W'7KqO%u}Z{w~Xn7s-NAݷa>Nd5ۇ~In'[q0rnoNw,fffffx2pfd< <ffI 33$$fffٴ%uZg)~ ow>aocvsLK`ZӲaӝO'}[mݏsrVK|33$̓332L̓333332LdfffIffI&ffdfI&I$&mRwm2.{Ïe{5f~AdO-;vzoNOI3,!/-ϴ{NJin!?w&3ݦڽMv]evIu1]>zn֥d&dfdfd ř fI&fffdffffngmbN}c;ogn5ΰ:vXNjۭ_;un|ޮӷŴ9>c:i$$%:ESsgo*6}T>6pU5>6Z}X_O=̶tY&IfIg3< ̓332L3332L33$3$̓32L3$̓$$̓z.;ժoTnltΞ;Pbϰd^9O0Å,33?l2L33<Ë\ÈE&dfI&I&fIffffffIIIdLKqv~Jn6jMCZSlvkruvC.}F o5.zO/fg&ffx$Ix<9pI̓36_u>-;,6{,wsfg陙&fI&fO&I&fI&dffK־,8Uy!s;z$=-S?0#6k ]82#}ffI$ffx3fx3333igYxvru=_t̷w__љr,)ٴ?m9{./fn״YyOohM3ê4><=Oisz$dI&ffIdfI32^0e;|=]e\w6hw[>"ԛsN#g=O*$32L33 x3333rl Eiy5hϩɺs/ËO&yidڇ=L-˳UpϴO ZOnҽzК{ AmO?te=Yw[RjQ`Қ7'β}KAgCtrʞ#Fcs zҿSI$3$)&IffKXw_n^cxn\nuySjvݎmZto&0oʤ3$$g,ffg$هG;>Ct_/ EϮͤo;,֝scg{ŗ /2swY&<[zgU#p][tfv]Om9MYodY,'ҿIΨͻvparIffI$~ffffffd YFkvGDi|BlM#iӧzO>h&9dffIs33<g3wt^GbɳA1 Ͱf=6lN Ckk Cu5g:Ofp182߸vԹwpjbaCfSO̳}>Q4e;2NPe^|ҿӶ_}32L$̒LI'$7ח.s݌QgxsQH%(<&fffffx3<^)333Sd6Mf̰;<cI{ >mGS)ÃTd\9:=-)vfi36ެdzzNuf.trAc'qO֎q$r][v{fLvmos{$znSNSj-YNC݆Knܙ&I$Iffffd׏Ǎ]\ݧ{xv6SN{7# 4ސʜE #vx2L̓3333×xg33$3$ݟ_k{uWM33$32L3333332L޽͔kss1΋{n/'LXs].awڝl.Xaϓ3?$$fd/yL̓32pffIffI&dfd$&dI&fIffKay':z[y^錃:O4˻=/f̒Ls33333?333$d$&I&dfgII$fI&d&KO ڎlqݑb ܯpoƝ>w6?.<X|pw6>qnޯ"YfdfffgI3$̓333332L3$̓333$̒L̓33$̒L̓$$333'd&fg$̓řfx3$d$$d$$&IIffffI&Ix$32L̓333. r~&fnS3̓3fdfI'dIs3b&I&I&II$fx33g<gOd&fgLMCL3?L&N I333?3$32I33$2LO+ܧfx3<d&I&I̒L̓33332L̓333$33rffffffffffffx<I&x332L̓337. $2I$2LLI2I3$̓2L32L32LܦI&fxx<&g&ffdI$fI&fIffffd&fg$&ffffg/,33͊x<1L33336) 33?L3<ffx37""bq""!b",0l,<0[ aXl,1 XX1 aa 1 DC q 1 }|DG8DD1dD,C1 } 6aal0 C  a ac[ XX ab9Xb9D?# C DD1 C D00 0a 0l?Caa XXl1 ab>a[ Cabb"a!l,1~yCDDDG.|D1 D1 0XR#aŅb !IztfmɷQwa=-os7ocwû}t5N[ٮa Xl0b!#|Ꮨa C snp ""! 08GQyad<-\wf"u>o߇:c6wSx]{k7WBele?XڳZx;k@f:_Q6x>ݡt׆[?l? ==cِD1c ">ϼ,Cab"9D_1 p8 1 0 XXaa>"9D00y=΢mdo̵M!dg7Me/q=;njMu֛mnv5K-C5}ʲÍOJCx=?!paXa"!"!b"a""!CDC 0D1aaa ?a aЮ_ydپͅxtf^Ke9:T1z9ȍ.m>3ܬs'; yrUk [嚛yw[_wEdky=>_eE } 1 1aa!#1 DCa10DXa"",1 6|ņb,1a y1m˷QuaM۸=-ow7{oA,ac06.j/WdMmtGlutLVnqќϸ1uC ?8R>,,}0b#""!b"!aQ6 Ds"!b,,0aC >aaaa!+g;Oj!9^l%{ ׯ|g?VĘ;/茵aa 1 CC E>"!b!dYި6Яѯ;˼{,,6Xl,|XXbyE~a XX 01 0Ņ# =NYh=/B;s5<ٽ)|S٧v_9}evy93f^mm|vCaD101}?D1s8sb~F">xzOdHz#Ϭ ?=h1A|,00C ,,C8a6 aCam:]SW~AjOEڝovSςL}mci7U;`٭/a3lG#;xoxI7^5>Q>Mm hۿh8[ abC 0 l- X[ (l,}b"u=ǩ5qmn^>g]A`˷3gӭtmrz_ܵaaa 1 G ?|1<#~KDLBh_vmdx[/ KYhQ[=WS+S?u =B[}D:k'󵿈oaӟ}io~~,;ҹ״?~ǰWĽ}d~=x5߂~){.+@\A܍UdA(oz4Sz՞a ,1 G8l- C G u7C.4{LO`~?>}xiʶV&n݌iXih̥؏; DB C CD}C X~c""c#Hoa;5W>oN{_''zO[~lEΈ{{Py<6㦟C]8ǽ<4'|oZyYk}cޏϭ.M_2'oa!aD6>b>,/""""q8c;k-sN~/2{C򀅿?[{%lk+$?%t$GAGsygޞ3g%;ǿ&?$t~ na|U !]3Qjl~|[L5ľ<+=S]x벛m~cWC C a!~aamz.˯=]O1z=Ƅ{x>y5'O_ߎ򧯖e;"!"!  8X"8 G1 1#y{Gmﳟ=qd|{]0Ca#៬q>Gmɽ'BZA _M |C{<#m^ ҧ n?幺Oh_>|ǽ1Uw%/>'ot޻$+yzڽc_;Tw xi {/oe[MsS۟|^1 DXa 6a ab ah'-yF1!̶3s--e޻;[Ӛ֛tè?0 G8!b"G9bbbdXba"!-6k{Ϲ9Vu&rjߓWȭ^Va CXyl0Xb0 β2Ou< չ^_;[7{m:Gk2_..Yny 0| 0a"qDC C[ }0l0R XXb[ b C Ņ[ ababo{Iߘ>_onW[xᏐǧ;[ԙ^Hvy_ivhw[!$C1 6b.|Hb"!"!b!yD1 D01ᰰ6ba 0- al,XXa,- 00Xn<&x7S#Mލq?7Cd=ǐSoh=Nֈ= aa#aa Cl,|BX^v aX[ """#興aaal0aaal0 Ca a#0a" 1 D6DDXXaab""!D<90"0r" DCc 00X0Xal/; 0Xaa Xa !!a Ds1DG(9DD?DĔ!"b aav" 1ņ6al0a 0 0DDXa,,1!b"""##b""#}DXl<>X~쏢"> ,?xl?X~ aXl6> a"_"#舰G?W}wM;z:1F}5g>wtӣ[!E\ wuOAљ g>wE:rWǦJG\q?gӟ˯^G=Q8SǣS{+twtWCHEhdw"_jY QƎ.o7{n[5+5zSTQZ"6BP+ΎePr#N>zy=\^re_3~#@篙˿q8gG.o3#@b˿1r WwG9s+<9~\_4~#?Wnv^vc^6۝6H>;(#oSBi=bݕUWVd)Tx&@J9*p+ǭ:k^SS3xէHˢ_Eq\W2^=zt5 v[D϶sTuDm,7ڸZ ؾ$BA @B)brA#n?8~we$c[E#M0C9Z!j|m ҉='{w@4i@xr$d;Um>hr7rwqևqH ZO]= ōg4Gm'iU )j)7MK{.Nۭg$C-H+FaB'ؠs ZC$+#GU'aO P~]y70^;ogFޥ|It䄫6۽n6{E+ i!2'ħԹ= %4p^S/j"I>Z3$,ёpixz:ݎ:اN}9`I#6MjMZa$%ŞƪS33PfēRN?i#x8cKM^"x*=658ݶ?.v-LsAC*%$u4$2$p8״[{<I4,x<7rۚSiBgmL>)IqscmSF$r(ttt`Uєe`CAb>ZyqmR"41PB6kH DaIp)Hg U DW,IU1&˼ni FA}|oii{ I} *XYJC~iGhh*" P:Y,j $2I9;I።myk] qt"YXg7s$2[_.W+39A^wS_}=Ng:nw\6;霼oŠGl̏g"b1iZ9^s09{8or|79{>zy=\^or|s79w>z=]r 6?9w>{9w>{\hG.~4~#?@a9s1),=`#"ETP@elZp1nw,Ɖ;J;7I5_^De}?G,~&~\/;W*N~2~U?wr￧3/!rG| ~ Q qy5<n!:dEj JAWCJp`pF}wH#;S.3:3W.>IqYjPI7N CCgw3,Bˤ U֔#.9b}Kmۊ.Ѫ*(h {DvE,԰ dN_{ IfAbOm1+Щ<}zגdYPib@z ziGYnEomf&c@&-}@ܤ@+Sل7 AAAG_GNcR^z.ϩ^_ɍ;-!3Ff{~.i[1)jk޹{mHtV&ڼڲ`֮<[-l,xcoh$̒q0( jǀ !*R1q*ߛh;ͺ|xڬc}h\ 1{k> PJ}X9vg3K"|5b#$j!{p/t\w=һ3|mvbC z8OOçqbhfAȦz4{R4͆P4 x8VaF8vaAW"J~[_D S{Eciq`3")"3}:-3>^=sS!5f =_ cmX {)BE@tI"i,_?[k[{[H$) i5*r8o6-P 6dhX*hJJh߹pM2*ԫ# BE@*:;t m"2HG`YQEK1 H>I h DlѤ,;C(xV= R>g^.n%=z޿ǻY[I4O{*{M$€w$+@Q*O=ǻشZoz-BM =chc}QNy:!'Ճ'>O?$Ȧlf` ~,o~=دf8u{[4ҳb~ŏ=՗? ڳΧg;NS.Y5ĒTFiMF~!6kݩxk(btQs#j: +̒FdD"tц=: <Ɨͱr[m0֥W]55svf5+,vaO8g.85O ptPǍ5hɠ3S@|YTPт:pv.Lf:i+,KBxRM$heV! Ђ`0pƥRI'a}lEH]1F&L[c$(~&^{$hU!Xo ` F!aGMyxۦPC-ũ*HG_t:d`H [8͹U$F6u,bf̬%BsPܧ;,ɶZoei3jdHr0AE59srlO8ܶ N"7* *C #˜ռ ũd$T,/Fbב[,t.mH&Yf) 0Ԡh,Ḟ{kc*E3yyDE仈H}!y-f֊ὅSDk@dY'f"OX$h>˯U(#{ՎF[wN!2-5=mݵPw{in@*]ཹ֊8xZE@3g*;qk;uB43Ĩ*]5P`ʮCjRm<˽bA!i[M{V"H\oɽwKi( Ԅ @ASD ]hn7o[o.9OaGXc{F2?/V3m*H!-u(!9b F$I4R9D_k0xb b /& K5Q\:R)c*N#q585)NWݎis+^j/ʭ EU6f5v\nc3 ,hxjey>St;vTixNYNBXwj} VB)47ݙ kt W̜6p-77{/" *2 㕹s~n$@WS9qs/.Gg|E*#)Yd^OT3BYrO Z_ܫcr$RPE.ν1(X2yOe8ڤ@jڤМsͻB[`"B-_F"TLdP+UA)PY$PU %tfчƲp-oyEڶx3F$Rk e⊽]2DإKD"$(>2_eծ%<ֻō}-e@{i>:兀r}ni->/c90#kz[Ru+^~u8uzxb;k=ݬ1nincQ'E'ۥ1#/񲟑 4PÝClRՊߤe:ipI˫STzj\g^qW:}h 6|~SY^_HI YZA؅cŢ:S[KcV2'E;JA*$1ք*IfQLVɶ@ ‡ʑrHs>9qA-˟3F!F& qَRy1Ψ˧Sݎ=~>jNDzy\y7Do, ᕒ88VUWY4%֪pP&wXdu2i]w_)9ZʏuI+ Y>Ԋ1_)9-rtQy[pK}n!laLU]4[ h_ r[ 0c‡ɍWod'!yHU縅HaK,E )f*sS,wRukkՠSs:sS?B:tC88GW>}9uTVбGԯNx>wc8㻫݊c>tWã3xt :qMzx`u;L^=JSO|z;ԧGI˩@=9:Su{NcAlSo qg9tO^u;_J=L :xw㿪:z{Ezxr[اTtөǧ8:t}vW?wqfIX`OTuAH?GWҟ_s F_w=?$!Xa$2]\IQ+M<( #^_v>imm ;7w#wHPs km'6Ocs??,~c'|glW1͟>~y~Y]5O?gw>?,ߚ'|s_^kW_ K-Amndۮ:>PYc.Y\[YjMZYHPZ ](䫪3G|}9q|} 6s6j,qxY|pE7T-Jq"4.@U PWm?\{];VXawyͳC+-g)2ŗWX2rqn} k0#OLKYb!SټiigfXX"42"=ɶ/6 qn\-y=M s &hTǚO+_^~|fl{38ǦQSyi߳\X]^EAdZ5T2*#ɑ{\OW/}ٯ"xė;3,D>RξX?>.-sͬHM[De"ȥK꼱< v$!wH8/R54GmS(|hǒvkĂA43YtZ3 ۓ bWvm|]7ZQ*0̤o0<9r ΍q3KlWLNM@ckvr%2$ ]ŏؔ䧈ԹӰb4^SxUIѝwI H[Cz.=gWλL|߿x{sA_ٗ <]sʸ&v ܡYml4P XL55Өgմ7,ᴉFE?yxWW=5tǕ[O缼mi9-,̪Y8i1ynn,o??+'ռ|ocs??,~c?'|glWq͟>~y~Y]5O?gw>?-~ۚ'|s_nt=瘡RWRG8x}fxNQQI *D9n~]mJ̻I Qqz^Y=s+7+YyՍP&D3pte֤e\sw>g[ dm#b'ĉ2x`0·) Fo+DN_=2 v͌O+m'GkrF ?Ow 3K@T9Ė+IGE67b]UV3Uxx;}ޠvvXUєE #Y͵]c#ۍS3-7c[FD08i I 3.1$`4s7+opHkYfF)+JbA4ܑw:`ZS#cJf1Ec^k DgҲ jǙpⲱ,"/yEp ƯM2VP%/-)s`!fa^R.P;/YH cMJ9| 9 `Gd5 nDS6# X0=ZE>OŹB߭h?|`  gTn9Púx{ "_?-$j{zg1nƌu"dqFjHr4Kh\H(I' ۛ5*iCVţoZ}l>iv$REu*qi%啳 d,+Çv.c4"P ~Hq eE n Tv#dAȌ{֔i꧅Jc2niP:e4qLEen[؎oez%IZ#yqIt- 1HPGե`w_s5߂KCDx,dqGXୃۍ݊5W G:FV Ҙ@પ Yۤ0{-(p^]U"45j8^H},=|;=j=|/ $q&>,~pfIcOǕGǟōcR%'8c-..e/3k=v1{oo|ʪɸ޷"@nТ铐֚Z ˵~ė\#f%HZp$勏f\x/`#V hZ,{Brj1]ėD肭fJiV 9aԶWԃ%]_ z>^|i]>;1`sY qzP)bCU$~ϲ(5Nfaej(>覙RIՈg"44U,3c B<02[L_V$08KI <?]X-֬Qוm>|ciud%w0nwnv|5?⭔T4cr]`ٹj@kpZ&/Q&!PFx{gVy}g`zG Lʯ$@&8 rD6#;\Ԅm i罢Yv3v3^9+,muK˩J=tW+xDcqY^19ԴW`FGb-ٛS~'ovwWFcĐ΅eSBP@Zo@!97HPxiS 88:-D V9=ܑL%-QU}A@|Z/֡SZ$TW ]Kd˨/aY1_AP" ՐϹIkё 6]|`]h!L2kEe':jiS\?^<qQ;qY]K [b9VAkفuqֹ0=1 ņIcP%Z?6,aۥ1pWBZ^z>׹]4EԶlj+Ă Z)JXHG/c䏩=j{Ͱ+nfDG1EnWl5hʣvva$Q ,h 2΂ĜMD8s<s$1ԈDz|q.yZ۱K{=H+;} W Ay>)o]n]e7rTfo,ذЀC#$dp&αNGE~3^([-=TJ# _ޚ^5 NX۵mʽݤw zM6/|u?Q[If*+OWhĖVP${U28md>WHjS/D[dĢxۍ׈Sni0xE~.щ-nmU==FsT2QOĩQԠh2*22@ZВj@W6=fӰA=PxG@\ҕ㟫:ctyiy+h9}>mR6>% *q%Ϥ20´4$ <[Z˭UE~6ŵJv5>k^㜿?>9? ؏rhl .y/df[ͻp$h#REX#8,/v_%vL--Hxr cT>Nu] ~ 8ɹOfoa ӵJP xnUSk75k'ckz~1 qS1x?k/7?O1.:ɂah^! )UO>3-2P|uZ\uTic،#mP94"U tC%rJr 7;XWaT9 B(}xM4, OY (!@{)Xlg/"SUeIzhdE>RMr +Q`9w2#خ=$Fo ^$e)!Џe'*hV^ڂ~;=\6Ě7zf:V$Zf VwLG49f^_Y9kg CB8"Ȁxbۚ{ _H"uӆ)@)rvgʪ>"}$eƣ,l$:'kZjxDx3n !Y>H4imO Ue( qP4J ŵt^uW E&qa1}8.Ƥ4@" _7oI6rUþ=V^Ze4z˒ s6ill) ֋URAilߤ 0Y2HiVv$P4©cCzQZoKhow}lrېĮ&$lJdw )SR}<.XxǴ:'yG~7 `cij2BVUh@Zi$ r6To q;,kU6P(*9ji[G`UzjnWooFo{6od} ˎn-{ W h䐬e#ǫX `psMhɤ@~R\)Ќ7lq 1?h|~G:6"|qB:C#NQM7N:'6tOa"~#OLuvw2:OHF =\}{v}4dH}buh<#D{}c\m9~q8:o{c\bB'7kq>zwxӄN#==8٪0׮>O_FY 4jٳG8 ٢{:#o q8θ>Q#duv@c{`"|#wdqߢ'zY>>'#OhǗp''ӳ Q~21#F?(z=c=|F;C&B't{!Ӳ1_h0٢{}c|bg8'z{퉝g>gc'O''n٢~>>#c:tOD13Olko4aL}q:#WjwGvٷ\LwGw׎3'zb}5DGFGOOltu #gh!;N"}B'F3Ӈ tvdN͚{؝uqǦ'7G ]z}1"|{=p:v:g #k۲&D"{5tǏ$4kծ&wGn8GxLOGtޏ}q;"vtDȄ}{3n:}RJ*NS˺ZZn kunw#JV6̤M: *0e>˞zy̸80(pĹsY57D? D?NiF 4'E#W)8l? 6o+†@h(^5A>h8G$S?4X1*d'?[!?\lal?I_!<ChycY3?;Y3`ZȎCo"x'|8sees5WVsNϮocU)6)? )˚ҹfnBԨ]Ϯ,$M҂ˮ:cTF:b}4HlN#1Ѿv7==~:6@uƭ}3:{bG|m#7uo٠"wGϲo|n}x}ѼxGcfF>n:W'XGD;#πc=qDxG wN!qլ#g6F\p 8tG'\}lN?HGv~1#f8뎙Ռykx8;rͦJM(sw4 W7ppMvZC]L,WeT`WR4QtO0Oxtok BQF+)!f ep8mt\3vc)}\uPĉ%ԄR'UUMeIQ@SXSSWeep#AԗEt7Z5gU]D+)4ħ%QL}e-]޽ۆ*zHl6l Lh8֛Pr:@pY }7^ke\?e3uۘ=J2^(PQRUt]t 5-u[j#1{gGR0+2Q19cˋ=2οm;5(RW[ͻR%7R*V 8SX _#̻tg#VOe7UʚsQ:[Тza^D@MEgg6r>3M(pc>ۺ l-%SQ)8KU J +s0Hrs!c~1ű ?#PUUTaFd\(ޝ9[ ЭJ=Hq%qc]c+7؏y*5+).U< u1bw7P~4Q2+pHWUKYsnOq7.VžnQ\7Ts%"t"E R`@O h>5U/Ar1xrDqRܭO!+uGEcʐ|+وοNF:[o7*qXffXl.mSx&BeCqnXUZBX~: tiCl(W uFMR1c;D\Ou;1H>uFxٶ=8vh>hy@ztkٌq1ڶn|q덝ڻ:t}'~$$#uGOO5n\9woKc*j1]^3){ZUލ)9;H#nuQM_ ( 2^ڷq],R5uڈMG2J*-IL$h+tJs͚lPW*osy-QФ| c>.mXCjz5 1=wBvGc(Ay)}$h*.  Lir sݺ b3Ulo5*(PW[ޝjҺjV@Т\[NQI*V!' wr[w%PnN7R55# GPF5a=3' ףhgpG>>:=N2=n#zc˳py׫FO OOc>ѿuG=8 :tOTla>1ۣwDacgo'PpcD@l磌p]\"|t}!n,ậB)͂Ģ, \ 8k L.  `t\p0U]m̌PPICPi F.qy+ob^7iTM* **(&4Xֱ+cr878"ӄ<1dm鎉ӶwGvȟX8i=es)(;w[zzB퍷J BДBQJ2i|#\ӈŽehxysO?OG>ijC |yHr G'%#R?4~_O \y(2EW!?\yw+d/¾Ctgpݗ3;d 9t|ٮ?9"r˜Pm/9~˴+k/a*-uCTAƨӫ@OWO٢{uCum/ @ޭr[bG UWPNg1ꊠ2r%+*ş9o+࿝"Q09 ( Byn6v4S]g+TNK\ـ:45mTePUu:SsE-rJTJEP讚j!'|O~<#v7nx#3ma< 'h1OF1D__Q썸}4}cǿ\ytz{#'l^c;@0.!'f0G-G`Z*몱9kvY@ * iz] !8Z**sF*1:TA8hlFD81M':6}bz{ DGDN;;Lj>Q=]7ꐎ포NzB+:ZZNnoHP ׭Pu%U?]urɓ9[r8gnTWwj1Y[\_v1rU;45'P]@^Y";5+rf u_TRE  Se.)CEE"oƑE5N][ǵ3YcwKz6m_%Etc{XKeey_FQ^h5ŻQ]oJ)#TpUu]rW/58ժvvwԪFwuYs/լFxe\ &~fh4)[SsmpNb*˺͗)MS`es[mGϛVSmu'P*,c7F̛1.jik%;j4U@j젪B5OM'mZ>c@lףp>1;wN#_8:x1ߣ|㫯G`O gGLy _=qYIc&z ț1fϤ٬G""iG8;9U*f.dvZIkL ƧɹT%8 lPލ%VT%.WEۭ&͒lP5-*n'۫SO&v׎`!vX+b^`lR -.kG.n(عZn bn7#{\plD#i|fepyn[~SN 9 wI/.LIԨaov&7 i9!72MB*56k`1yܭYׅuZI.`\ ڭ2ݑ3Fm j(:#+G;2-Cb(ұm|QoHkvmN SA@u6)2Z?lɧמc3=eVQxgx-ѷ.m7]e~Z)N]FMJ h0%V亪1Jg )YڑjiIeP2o4#N}vc .lً-w%P.w'IQ@-\N*,QwGMN[\K`'7Hd\du.fcv2xJB% E"qX]zv"'ΆH,K<2'1Kǚc5*,պ4w1}P#iELK"i 3fC-\IaT[ųe%Z.FJWY*,DjGF1>zGvh荱dbup|OLN<{7N]quF؟\uƩ'PrE/9BjV(,i52TY 5Ps4|<^+,ĩңo)2:pk//l\QlFP:\WIh(+^! '+QPwxBrD@?rzxTBrӆM54C]rލwZ̎w%5',M !(TbHMT@hy8Ṯgy v۳ k" ?ZZ-af8]vrB`YyurI6]qX/7e_M8j (XZ+\.>K֚{D?h0쬲sۘȴyZS]`S+6쩴 HW6J Q)`!UXXVB0*x#;~;gGO};xODz;#'g@hǯDN#]1=qT !@њ?69h ݳ/5%y]z>K捪Jڑ7DhF%HՍX]!͖aeVXҳY62&u컸 JcuTywR}_h`zsMa.+zWu^7 AqeR&V4f$_/ޅE`Q)()I$ٵz:sIrhSfږ**(jT^x9iXMk|\nHcÖS[5a,HY!Red+X5yNg\+?Srr.U-w3#M.^`tmi ifRZuTYd<ӔKYfZp&yGUUh4U_ Tj~A.0u}r$[-,.VqdD]GTƵ*NZƐۉ%)*p؆(5=ԦʋQaFڥP;b4#(]9WYflNFL쮅%ݠ+*aVKRl3Ց9ms歺y.h{3aK̦5 gXSXdyYEaek?k³\m8U1̂VnHm)Y֚TOQE`f=0ݶخx:[_`ɏ,,ޫ%Y2x[M}\6geK);ꬄd+,Umןs= WS/Ize7E0BmH 8L(IjsE3G-[+3m1sjfWGFt754UcbݯNYfQ6 7S6W{T*m,#B4+.Yʮbr ++˚nWZˈw4ӧ¬G xG/ <_Y0q9|],JF2𼶴͖H_2t tbqm$$WMo|^GQ!ΛYƤA  _/ŲƂp|ʹ//A.h}rךt4]K3ERj JYgQ5Ǟ{p}''&qB5Gw7vnuϔn߇;Ŭ 'W'ճ43=ƒHmhgmKEjW8*DDq 7o-;.n 3*xSB%u61ͦ8Q _`M?Wm|'RHY#isTj..q?^m#'*1LWs>czɗ5Y\H „Y*  ~"jAxh3̀…8Vn5qf)Ch5*9#k5U @iQ٠΋ Q9uQ5 N"-i2( z1g#%Tݭ7Z 楌ĽIm[t4@'ATT 4R1\:8K8[7≾ zvU?IFW7wXaucMZ 4`HH^3.ٖ&E֡ĕI6+. PKFoff䵛x+X=[mxB Q$AIY+̤ḿT:)[rfxA}HkVwyfc*+ѨI,>4˗3m62I5-k1jL9ILj.v%f 6MuP c?nDd5mا-b\jL޲Ŷ5=zuD;\.wI5ae*\f՞S\bw/+Nn e ݸX0P)MQ25)u4i2 X4Cm-򤵊UH>Qc2#`8xF1V'&GF4uΐg~n ؙ:X\ >13#hWd)X}nH-!3X!OϮ%q9sWw<=i,^h&MRU5Et>TT4ƺGUtum#1Z+; #nx1Qvq"NUE4!7|:2vʰܪDq`xKƥjJ@f+mXVZ~/>WhJ ͧ~(-ln.-aIoT%Pm5H8fվXB 9mXVs F.j Di#S+<;% RvU3!TZâ?=EGj;A0KbV:"٢z#ӫdmն><|#XwDӇtOxtǷ~ >qcW\j__B=wuGdtFۄ{OLtчdߣˣVꐍQc˾qr23#͗bo̬3Rj֪&V-AUQbsSue?9LY5jfj_s%u a?"[-$1MgAUVݡz'FDI\[ەŽq)D՚b%.2D@@aJSB9,VqiJPJM]u!M4B'uJl̔JK{N&JmkRVR ĕQMS1p!1u-flrJa'& xEy"{voSEOO)wdNmlmp.nZŋK!J( 4F49#+ܤaZNk>ezF*!s3:ֳ\jEdWMe 0ι܋kuu׋Ls{H<}n j WpM8%Q@R܎ E&i#QF7^G)}~n[JYCc4+!5i;HkQi9JN@.NtG9x_Y्Ju-5t E 1=GAJj!wdOda[2( ;]r·:kf9 'II%{q.o 1;U.n!;5m7Ǘ4tjiReC?+_͖ZУ1js!&#?߈jDS[} ]E`ZG9h\ϣX%MeuA1ӔPqE 5T9{nm~fd;&0-\,ø֛Bt6.b甅1jMIH~5)q< y?.wQBf-3/'ن3KQԩj9(o^pq'~#Hי|ro.t~l9u_J>"-+Շi3PjpEu5~Be zePZ-mPr\EEMռNY%,W#Ƶ#E#H<_7۽x`yfJo.?̌m<1AQ3DĄyΨ:z5u ':#}}#~Z{X$F^=xa<ၕ 5UP45UPi1?m)MفkruS#oUiM [OBvӋ"+oGZaM+I;m,Mvok7`6c)n"jPUTAf4W1QN¢-BkCKQMTM$ &2LO@uXjsU3&3+/ ,tN 5q?=pRU1xT(M',9zJ蘯v>u'C&.sQL]g~fodASe6NFg四*0Qe彳8L)3YGYj?_ųW) qrͪUuqmo0gQPuv@Tuu) vG,սl|v(nB7;E{Ff=ɖvT3\57Е5E]Dp˯.g{~8m9CWJT]WlYkWn\iT`_ƺj9'".%W^SSϸV>[J\S2e=k8kƁĤӖ5@WH SPˇ0vǜ< eB33'$1{ىV iLJVB"BӍTE5zu-Y,ƥ˘y}vȷO̪PءAH RUBmt닦q2p42޷]imRPz as~ &mXk€fsczWE˖b ]m̿||LetfP]ZyonU²d KI4@@6\c׭喹dݮ[BbDsr&I_/B^3׸so jQ՘,𪥯,T+3VTJR]f)%R G6c-hf>_fEq2-COzZF?5D!H8s5/Mkےи VAMr~MxU#zt  K2|rs=ЅÓH`sSS9vSo S0$bSs3KI.tw%V.e^9`pGm(9r-簠!ˌ~zXoZRP"˺Nq *j u" ZrӺrӖ74 3 c^CCLm \Zgd^i YO|=1:^ă*JNSUp~@<ڶ9)MCE'eTg0ܾ\ -Ԯ>{;'_H2>TUMCaYG.Af[nYsf]yom92sEhgk|?ES=R[5D"*PBoye}? .^Y7cfLr렄6=mX J%Zr#"KttR.ٕw:_c󁕬y(oK~  j@?1|ZC/yו9Bew\ -y|,g>'QRe YAuUV3'tvDO\yngtzlӷ>DFȳ5?EAPa" ; ag/Fjx 4׿6uBu9DJVeIĈ?dNT&.DŽ|jb̧gEbe^yfùf["AA N2S~0˻?KÌΨ8DcTN 3:έկF'>:G}c_wv螘:;'L6#WuD1٢d#'L}z #ߺ'Iwњe bmGOm.Ja:"u5#MEHH:ѯ4l+| 1C~E6uc5ri.U%5Pɐ'q>,n1zppNuFcOD2Gla86DtNt(V녕2i .^Ȅ$kIeӈ|e?3d\'gT:g(*k; sYVPJD Ta<P7I4$AEeY7UIB,dH?gan )2uuծ~M+&MQXT"V@bXͧ,YOGe{!2ܡkT[ijPw5̍^yasmCz9 x~-X)±Kq.uKQAHՈH|G_#1&`)˜0GG='_o8艑'~C4#zvFߣ8@h>'TciVsFцo1;xy:h񏶟y嫌miS;uD4}6a|vyy<{CTj>עzxF!Obzt7݊.iCb Ն"̿2Ӷ+[ŽqSj e9-OEy{@ך,G]NeΑ04ulՍAx j@YŲZѨ.Tޗ Q'hl'Zƕh誡6eU|-eȔs>r߮7+?;A~ecuxC|eX=sӪBj(2,),V2 .-.̺J036UZ&ވҗXUx]9Pؗª2s2AuvCb<0rl #B6y洆o6a 6:'O~զCv? ޝ{ <4On0{}p;;$tuV<~=Q#f';tyDΨO'g=}cd{p(g϶;GLO{ǷvhuF#8bB=#ttDΨ FDƹ;;cO"4UETE@#MTWEAYFX`4QTB^Z^'\ꢢ/%b]D5B%c CkUi5&mUVg㢊*G[?RA&u|aF;7-(Y r.ɥX4^: PDŵG-e9-r5_5iz =qM,)c3/.`yxI|c_ʩt$PRz)B]W0Iq3 q jUMCTR6.hTKscQp!]CXuA1[“(E]Ę8 TC=w=hYB⤺@,mV#o7xmGlףvV:θW'TyV>QÎZ':'= Ѻ}"|uhժvꎍ2nj36τ}|4uHDìbgq"|`#vv}cqעz"ztO@TFp׿Oq썻4kIvFú'lL9ODxH:uxg0鎎pߣ&F8LO'F>p|"vh>"CO##x z5xDq=DG>1#ÇDuθH#~ӆ뀉lOTwapьyG񉝱==XFhǾvGtNQ|{D "~DHƨX7"u:bvD'â'&CF3t}7ѿWVp|"B'&wuF7GOtc=oÿF->qFOF;:C<35G\O\cӿ<$6ӳ0<"{bzOOh7DΨ î&p7m4k߇ Ѻ>>h5Lmcl}]qtj'\mǧ:`{"{#l9Owlh>}ٲ0L|8DHb@zczyDxc포퍺#18a#&vGF'PǷ::kǻ {=#xOTN5=wdq':q׻^ȑZcGFuGa}#:G=0|>H8F8jxݱólt߆vć"|1zótlbx Z'ˢ83kqDG÷~|;# {m }$#TlݯvGg`l<#:|d#w8:wgdO^~=:n-13za:5vO>CGׯGDhâ'@O~<0q˪'ll :''ݣ#ZV6mq>q>Q<'n3ѷqh ۲'8OL{G\Oq6D\tT7LouDt{DGvȟXѷ߾&vײ8o;#7v-zwt 'oHθ3ӣ_&qzcXzgT}cьn=ui ;C=tGTO>Q嫧DѠ>Gc}o[6ON#&COLkDHLntN۪'Q|0hOOODoDzbglpY|B'؞H㎎;:gV3FoXxǦ&uGȟxDb|#GDF;>?H 'gguaoz"wdO'߷4o<#ۮ7{wF|<:=#7m'8{۶' <7Gtp޾lO~gOۇo<6Hlv`$< :|c۫;Cn8vm߳|ONFX#GWvFc0=SDv@m'l;cn8|ww[m06oLOgv1;a8@u޿8 @Hnv 7ݳ~ѻgl{͛;6{GۣvۿGhGwuۆo!8vαTeleSign-ruby_telesign-8ad846e/telesign.gemspec000066400000000000000000000016661514362622000217130ustar00rootroot00000000000000require_relative 'lib/telesign/constants' Gem::Specification.new do |s| s.name = 'telesign' s.version = Telesign::SDK_VERSION s.licenses = ['MIT'] s.date = '2017-05-25' s.summary = 'Telesign Ruby SDK' s.description = 'Telesign Ruby SDK' s.authors = ['Telesign'] s.email = 'support@telesign.com' s.files = Dir['lib/**/*rb'] s.homepage = 'http://rubygems.org/gems/telesign' s.add_runtime_dependency 'net-http-persistent', '>= 3.0.0', '< 5.0' s.add_runtime_dependency 'base64' s.add_development_dependency 'rake' s.add_development_dependency 'uuid' s.add_development_dependency 'mocha' s.add_development_dependency 'webmock' s.add_development_dependency 'codecov' s.add_development_dependency 'simplecov' s.add_development_dependency 'test-unit' endTeleSign-ruby_telesign-8ad846e/test/000077500000000000000000000000001514362622000175025ustar00rootroot00000000000000TeleSign-ruby_telesign-8ad846e/test/helper.rb000066400000000000000000000004421514362622000213060ustar00rootroot00000000000000require 'simplecov' SimpleCov.start if ENV['CI'] == 'true' require 'codecov' SimpleCov.formatter = SimpleCov::Formatter::Codecov end require 'uuid' require 'time' require 'test/unit' require 'webmock/test_unit' require 'mocha/setup' require 'telesign' class Test::Unit::TestCase endTeleSign-ruby_telesign-8ad846e/test/test_rest.rb000066400000000000000000000265171514362622000220560ustar00rootroot00000000000000require 'helper' class TestRest < Test::Unit::TestCase def setup @customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' @api_key = 'EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' end def test_rest_client_constructors client = Telesign::RestClient.new(@customer_id, @api_key, rest_endpoint: 'http://localhost') end def test_generate_telesign_headers_with_post method_name = 'POST' date_rfc2616 = 'Wed, 14 Dec 2016 18:20:12 GMT' nonce = 'A1592C6F-E384-4CDB-BC42-C3AB970369E9' resource = '/v1/resource' body_params_url_encoded = 'test=param' expected_authorization_header = 'TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:' + '2xVlmbrxLjYrrPun3G3WMNG6Jon4yKcTeOoK9DjXJ/Q=' content_type = "application/x-www-form-urlencoded" actual_headers = Telesign::RestClient.generate_telesign_headers(@customer_id, @api_key, method_name, resource, content_type, body_params_url_encoded, date_rfc2616: date_rfc2616, nonce: nonce, user_agent: 'unit_test') assert_equal(expected_authorization_header, actual_headers['Authorization'], 'Authorization header is not as expected') end def test_generate_telesign_headers_unicode_content method_name = 'POST' date_rfc2616 = 'Wed, 14 Dec 2016 18:20:12 GMT' nonce = 'A1592C6F-E384-4CDB-BC42-C3AB970369E9' resource = '/v1/resource' body_params_url_encoded = 'test=%CF%BF' expected_authorization_header = 'TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:' + 'h8d4I0RTxErbxYXuzCOtNqb/f0w3Ck8e5SEkGNj01+8=' content_type = "application/x-www-form-urlencoded" actual_headers = Telesign::RestClient.generate_telesign_headers(@customer_id, @api_key, method_name, resource, content_type, body_params_url_encoded, date_rfc2616: date_rfc2616, nonce: nonce, user_agent: 'unit_test') assert_equal(expected_authorization_header, actual_headers['Authorization'], 'Authorization header is not as expected') end def test_generate_telesign_headers_with_get method_name = 'GET' date_rfc2616 = 'Wed, 14 Dec 2016 18:20:12 GMT' nonce = 'A1592C6F-E384-4CDB-BC42-C3AB970369E9' resource = '/v1/resource' body_params_url_encoded = 'test=%CF%BF' expected_authorization_header = 'TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:' + 'aUm7I+9GKl3ww7PNeeJntCT0iS7b+EmRKEE4LnRzChQ=' content_type = "application/x-www-form-urlencoded" actual_headers = Telesign::RestClient.generate_telesign_headers(@customer_id, @api_key, method_name, resource, content_type, body_params_url_encoded, date_rfc2616: date_rfc2616, nonce: nonce, user_agent: 'unit_test') assert_equal(expected_authorization_header, actual_headers['Authorization'], 'Authorization header is not as expected') end def test_generate_telesign_headers_with_default_values method_name = 'GET' resource = '/v1/resource' expected_authorization_header = 'TSA FFFFFFFF-EEEE-DDDD-1234-AB1234567890:' + 'aUm7I+9GKl3ww7PNeeJntCT0iS7b+EmRKEE4LnRzChQ=' content_type = "application/x-www-form-urlencoded" actual_headers = Telesign::RestClient.generate_telesign_headers(@customer_id, @api_key, method_name, content_type, resource, '') assert_not_nil(UUID.validate(actual_headers['x-ts-nonce']), 'x-ts-nonce header is not a valid UUID') assert_nothing_raised do Time.httpdate(actual_headers['Date']) end end def test_rest_client_post test_resource = '/test/resource' test_params = {:'test' => "123_\u03ff_test"} stub_request(:post, 'localhost/test/resource').to_return(body: '{}') client = Telesign::RestClient.new(@customer_id, @api_key, rest_endpoint: 'http://localhost') client.post(test_resource, **test_params) assert_requested :post, 'http://localhost/test/resource' assert_requested :post, 'http://localhost/test/resource', body: 'test=123_%CF%BF_test' assert_requested :post, 'http://localhost/test/resource', headers: {'Content-Type' => 'application/x-www-form-urlencoded'} assert_requested :post, 'http://localhost/test/resource', headers: {'x-ts-auth-method' => 'HMAC-SHA256'} assert_requested :post, 'http://localhost/test/resource', headers: {'x-ts-nonce' => /.*\S.*/} assert_requested :post, 'http://localhost/test/resource', headers: {'Date' => /.*\S.*/} end def test_rest_client_get test_resource = '/test/resource' test_params = {:'test' => "123_\u03ff_test"} stub_request(:get, 'localhost/test/resource?test=123_%CF%BF_test').to_return(body: '{}') client = Telesign::RestClient.new(@customer_id, @api_key, rest_endpoint: 'http://localhost') client.get(test_resource, **test_params) assert_requested :get, 'http://localhost/test/resource?test=123_%CF%BF_test' assert_requested :get, 'http://localhost/test/resource?test=123_%CF%BF_test', body: '' assert_not_requested :get, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'Content-Type' => /.*\S.*/} assert_requested :get, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'x-ts-auth-method' => 'HMAC-SHA256'} assert_requested :get, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'x-ts-nonce' => /.*\S.*/} assert_requested :get, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'Date' => /.*\S.*/} end def test_rest_client_put test_resource = '/test/resource' test_params = {:'test' => "123_\u03ff_test"} stub_request(:put, 'localhost/test/resource').to_return(body: '{}') client = Telesign::RestClient.new(@customer_id, @api_key, rest_endpoint: 'http://localhost') client.put(test_resource, **test_params) assert_requested :put, 'http://localhost/test/resource' assert_requested :put, 'http://localhost/test/resource', body: 'test=123_%CF%BF_test' assert_requested :put, 'http://localhost/test/resource', headers: {'Content-Type' => 'application/x-www-form-urlencoded'} assert_requested :put, 'http://localhost/test/resource', headers: {'x-ts-auth-method' => 'HMAC-SHA256'} assert_requested :put, 'http://localhost/test/resource', headers: {'x-ts-nonce' => /.*\S.*/} assert_requested :put, 'http://localhost/test/resource', headers: {'Date' => /.*\S.*/} end def test_rest_client_delete test_resource = '/test/resource' test_params = {:'test' => "123_\u03ff_test"} stub_request(:delete, 'localhost/test/resource?test=123_%CF%BF_test').to_return(body: '{}') client = Telesign::RestClient.new(@customer_id, @api_key, rest_endpoint: 'http://localhost') client.delete(test_resource, **test_params) assert_requested :delete, 'http://localhost/test/resource?test=123_%CF%BF_test' assert_requested :delete, 'http://localhost/test/resource?test=123_%CF%BF_test', body: '' assert_not_requested :delete, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'Content-Type' => /.*\S.*/} assert_requested :delete, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'x-ts-auth-method' => 'HMAC-SHA256'} assert_requested :delete, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'x-ts-nonce' => /.*\S.*/} assert_requested :delete, 'http://localhost/test/resource?test=123_%CF%BF_test', headers: {'Date' => /.*\S.*/} end def test_phoneid stub_request(:post, 'localhost/v1/phoneid/1234567890').to_return(body: '{}') client = Telesign::PhoneIdClient::new(@customer_id, @api_key, rest_endpoint: 'http://localhost') client.phoneid('1234567890') assert_requested :post, 'http://localhost/v1/phoneid/1234567890' assert_requested :post, 'http://localhost/v1/phoneid/1234567890', body: '{}' assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'Content-Type' => 'application/json'} assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'x-ts-auth-method' => 'HMAC-SHA256'} assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'x-ts-nonce' => /.*\S.*/} assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'Date' => /.*\S.*/} end def test_phoneid_with_addons stub_request(:post, 'localhost/v1/phoneid/1234567890').to_return(body: '{}') client = Telesign::PhoneIdClient::new(@customer_id, @api_key, rest_endpoint: 'http://localhost') client.phoneid('1234567890', addons: {'contact': {}}) assert_requested :post, 'http://localhost/v1/phoneid/1234567890' assert_requested :post, 'http://localhost/v1/phoneid/1234567890', body: '{"addons":{"contact":{}}}' assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'Content-Type' => 'application/json'} assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'x-ts-auth-method' => 'HMAC-SHA256'} assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'x-ts-nonce' => /.*\S.*/} assert_requested :post, 'http://localhost/v1/phoneid/1234567890', headers: {'Date' => /.*\S.*/} end end TeleSign-ruby_telesign-8ad846e/test/test_score.rb000066400000000000000000000035671514362622000222140ustar00rootroot00000000000000require 'test/unit' require 'webmock/test_unit' require 'telesign' class TestScore < Test::Unit::TestCase DETECT_HOST = 'https://detect.telesign.com' def setup @customer_id = 'FFFFFFFF-EEEE-DDDD-1234-AB1234567890' @api_key = 'ABC12345yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==' @score_client = Telesign::ScoreClient.new(@customer_id, @api_key, rest_endpoint: DETECT_HOST) end def test_score_success phone_number = '11234567890' account_lifecycle_event = 'create' stub_request(:post, "#{DETECT_HOST}/intelligence/phone") .with( body: hash_including({ 'phone_number' => phone_number, 'account_lifecycle_event' => account_lifecycle_event }), headers: { 'Content-Type' => 'application/x-www-form-urlencoded' } ) .to_return( status: 200, body: '{"risk": {"level": "low", "recommendation": "allow"}}', headers: { 'Content-Type' => 'application/json' } ) puts Telesign::ScoreClient.instance_method(:score).source_location response = @score_client.score(phone_number, account_lifecycle_event) puts "Response status code: #{response.status_code}" puts "Response headers: #{response.headers}" puts "Response body: #{response.body}" puts "Parsed JSON response: #{response.json}" assert response.ok assert_equal 'low', response.json['risk']['level'] assert_equal 'allow', response.json['risk']['recommendation'] end def test_score_phone_number_validation assert_raise(ArgumentError) { @score_client.score(nil, 'create') } assert_raise(ArgumentError) { @score_client.score('', 'create') } end def test_score_account_lifecycle_event_validation assert_raise(ArgumentError) { @score_client.score('phone_number', nil) } assert_raise(ArgumentError) { @score_client.score('phone_number', '') } end end