diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index f9f60d0..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 560d1a6..76afb57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,8 @@ *.gem -*.rbc -.bundle -.config -coverage -InstalledFiles -lib/bundler/man -pkg -rdoc -spec/reports -test/tmp -test/version_tmp -tmp -# YARD artifacts -.yardoc -_yardoc -doc/ +# RubyMine IDE +.idea + +# Other +.DS_Store + diff --git a/LICENSE.txt b/LICENSE similarity index 96% rename from LICENSE.txt rename to LICENSE index 11bbd46..e0474dd 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 TeleSign Corp. +Copyright (c) 2017 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 diff --git a/README.rst b/README.rst index fb6a842..294dc48 100644 --- a/README.rst +++ b/README.rst @@ -2,134 +2,78 @@ TeleSign ======== -**Information**: For more information, visit the `TeleSign website `_ or the `TeleSign Developer Portal `_. - -**Author**: Telesign Corp. - -TeleSign Web Services: Ruby SDK ---------------------------------- - -**TeleSign web services** conform to the `REST Web Service Design Model `_. Services are exposed as URI-addressable resources through the set of *RESTful* procedures in our **TeleSign REST API**. - -The **TeleSign Ruby SDK** is a set of software development tools—a *Ruby Library* that wraps the TeleSign REST API, and it simplifies TeleSign application development in the `Ruby programming language `_. The SDK software is packaged as a Ruby gem called **telesign**, and is distributed using `Ruby Gems `_. - -The Ruby Classes ------------------- - -With just two classes, **Telesign::API** abstracts much of the complexity of the TeleSign REST API. - -+---------------------------+--------------------------------------------------------------------------+ -| Ruby Class | Description | -+===========================+==========================================================================+ -| Telesign::API::PhoneId | The **PhoneId** class exposes four services that each provide | -| | information about a specified phone number. | -| | | -| | *standard* | -| | Retrieves the standard set of details about the specified phone | -| | number. This includes the type of phone (for example, land line or | -| | mobile), and its approximate geographic location. | -| | *score* | -| | Retrieves a score for the specified phone number. This ranks the | -| | phone number's "risk level" on a scale from 0 to 1000, so you can | -| | code your web application to handle particular use cases (for | -| | example, to stop things like chargebacks, identity theft, fraud, and | -| | spam). | -| | *contact* | -| | In addition to the information retrieved by *standard*, this service | -| | provides the name and address associated with the specified phone | -| | number. | -| | *live* | -| | In addition to the information retrieved by *standard*, this service | -| | provides actionable data associated with the specified phone number. | -| | *number_deactivation* | -| | In addition to the information retrieved by *standard*, this service | -| | provides information on number deactivation for the phone number | -| | provided. | -+---------------------------+--------------------------------------------------------------------------+ -| Telesign::API::Verify | The **Verify** class exposes five services for sending users a | -| | verification token (a three to five-digit number). You can use this | -| | mechanism to test whether you can reach users at the phone number | -| | they supplied, or you can have them use the token to authenticate | -| | themselves with your web application. In addition, this class also | -| | exposes a service that allows you to confirm the result of the | -| | authentication. | -| | | -| | You can use this verification factor in combination with *username* | -| | and *password* to provide *two-factor* authentication for higher | -| | security. | -| | | -| | *call* | -| | Calls the specified phone number and uses speech synthesis to speak | -| | the verification code to the user. | -| | *sms* | -| | Sends a text message containing the verification code to the | -| | specified phone number (supported for mobile phones only). | -| | *smart* | -| | Smart intelligently determines the best service to use based on | -| | the end user device and then attempts to place a call, send an SMS, | -| | or send a push request. | -| | *push* | -| | Sends a push notification containing the verification code to the | -| | specified phone number (supported for registered devices only). | -| | *status* | -| | Retrieves the verification result. You make this call in your web | -| | application after users complete the authentication transaction | -| | (using either a *call* or *sms*). | -| | | -+---------------------------+--------------------------------------------------------------------------+ -| Telesign::API::TeleBureau | The **Telebureau** class exposes services for creating, retrieving, | -| | updating and deleting telebureau fraud events. You can use this | -| | mechanism to test whether you can reach Telebureau services. | -| | | -| | *create* | -| | Creates a Telebureau event corresponding to supplied data. | -| | *retrieve* | -| | Retrieves the fraud event status. You make this call in your web | -| | application after completion of create/update transaction for a | -| | Telebureau event. | -| | *delete* | -| | Deletes a previously submitted fraud event. You make this call in | -| | your web application after completion of submit/update transaction | -| | for a Telebureau event. | -| | | -+---------------------------+--------------------------------------------------------------------------+ +TeleSign provides the world’s most comprehensive approach to account security for Web and mobile applications. + +For more information about TeleSign, visit the `TeleSign website `_. + +TeleSign REST API: Ruby SDK +--------------------------- + +**TeleSign web services** conform to the `REST Web Service Design Model +`_. Services are exposed as URI-addressable resources +through the set of *RESTful* procedures in our **TeleSign REST API**. + +The **TeleSign Ruby SDK** is a set modules and functions — a *Ruby Library* that wraps the +TeleSign REST API, and it simplifies TeleSign application development in the `Ruby programming language +`_. The SDK software is distributed on +`GitHub `_ and also as a Ruby Gem using `Ruby Gems `_. + +Documentation +------------- + +Detailed documentation for TeleSign REST APIs is available in the `Developer Portal `_. Installation ------------ -With `Ruby Gems `_ -installed, type **gem install telesign** at the command prompt. +To install the TeleSign Ruby SDK: + +.. code-block:: bash -Ruby Code Example: To Verify a Call -------------------------------------- + $ gem install telesign -Here's a basic code example. +Alternatively, you can download the project source, and execute **gem build telesign.gemspec && gem install telesign-[version].gem**. -:: +Ruby Code Example: Messaging +---------------------------- + +Here's a basic code example with JSON response. + +.. code-block:: ruby require 'telesign' - phone_number = "13103409700" - cust_id = "FFFFFFFF-EEEE-DDDD-1234-AB1234567890" - secret_key = "EXAMPLE----TE8sTgg45yusumoN6BYsBVkh+yRJ5czgsnCehZaOYldPJdmFh6NeX8kunZ2zU1YWaUw/0wV6xfw==" - verify = Telesign::API::Verify.new(cust_id, secret_key) # Instantiate a Verify instance object, - result = verify.call(phone_number) # and use it to call the "call" method. - p result.body - {"reference_id"=>"254CADA5F1D40E0090405467DE244D05", "resource_uri"=>"/v1/verify/254CADA5F1D40E0090405467DE244D05", "sub_resource"=>"call", "errors"=>[], "verify"=>{"code_state"=>"UNKNOWN", "code_entered"=>""}, "status"=>{"updated_on"=>"2016-02-29T05:04:06.814381Z", "code"=>103, "description"=>"Call in progress"}} + customer_id = 'customer_id' + secret_key = 'secret_key' -For more examples, see the Documentation section below. + phone_number = 'phone_number' + message = 'You\'re scheduled for a dentist appointment at 2:30PM.' + message_type = 'ARN' -Authentication -------------- + messaging_client = Telesign::MessagingClient.new(customer_id, secret_key) + response = messaging_client.message(phone_number, message, message_type) -You will need a Customer ID and API Key in order to use TeleSign’s REST API. If you are already a customer and need an API Key, you can generate one in `TelePortal `_. If you are not a customer and would like to get an API Key, please contact `support@telesign.com `_ +.. code-block:: javascript + + {"reference_id"=>"B56A497C9A74016489525132F8840634", + "status"=> + {"updated_on"=>"2017-03-03T04:13:14.028347Z", + "code"=>103, + "description"=>"Call in progress"}} + +For more examples, see the examples folder or visit `TeleSign Developer Portal `_. + +Authentication +-------------- +You will need a Customer ID and API Key in order to use TeleSign’s REST API. If you are already a customer and need an +API Key, you can generate one in the `Portal `_. -Support and Feedback --------------------- +Testing +------- -For more information about the Phone Verify and PhoneID Standard services, please contact your TeleSign representative: +To run the Ruby SDK test suite: -Email: `support@telesign.com `_ +.. code-block:: bash -Phone: +1 310 740 9700 + $ rake test diff --git a/RELEASE b/RELEASE new file mode 100644 index 0000000..30df5e5 --- /dev/null +++ b/RELEASE @@ -0,0 +1,18 @@ +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. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..008c6ae --- /dev/null +++ b/Rakefile @@ -0,0 +1,7 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs << 'test' + t.test_files = FileList['test/test*.rb'] + t.verbose = true +end diff --git a/examples/autoverify/1_get_status_by_external_id.rb b/examples/autoverify/1_get_status_by_external_id.rb new file mode 100644 index 0000000..354e9ea --- /dev/null +++ b/examples/autoverify/1_get_status_by_external_id.rb @@ -0,0 +1,16 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +external_id = 'external_id' + +av_client = Telesign::AutoVerifyClient.new(customer_id, secret_key) +response = av_client.status(external_id) + +if response.ok + puts 'AutoVerify transaction with external_id %s has status code %s and status description %s.' % + [external_id, + response.json['status']['code'], + response.json['status']['description']] +end diff --git a/examples/messaging/1_send_message.rb b/examples/messaging/1_send_message.rb new file mode 100644 index 0000000..04202e4 --- /dev/null +++ b/examples/messaging/1_send_message.rb @@ -0,0 +1,11 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +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, secret_key) +response = messaging_client.message(phone_number, message, message_type) diff --git a/examples/messaging/2_send_message_with_verification_code.rb b/examples/messaging/2_send_message_with_verification_code.rb new file mode 100644 index 0000000..7e3b5db --- /dev/null +++ b/examples/messaging/2_send_message_with_verification_code.rb @@ -0,0 +1,21 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +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, secret_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 diff --git a/examples/phoneid/1_check_phone_type_to_block_voip.rb b/examples/phoneid/1_check_phone_type_to_block_voip.rb new file mode 100644 index 0000000..1be79f9 --- /dev/null +++ b/examples/phoneid/1_check_phone_type_to_block_voip.rb @@ -0,0 +1,18 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +phone_number = 'phone_number' +phone_type_voip = '5' + +phoneid_client = Telesign::PhoneIdClient.new(customer_id, secret_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 diff --git a/examples/phoneid/2_cleansing.rb b/examples/phoneid/2_cleansing.rb new file mode 100644 index 0000000..b1448d7 --- /dev/null +++ b/examples/phoneid/2_cleansing.rb @@ -0,0 +1,20 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +extra_digit = '0' +phone_number = 'phone_number' +incorrect_phone_number = "#{phone_number}#{extra_digit}" + +phoneid_client = Telesign::PhoneIdClient.new(customer_id, secret_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 diff --git a/examples/score/1_check_phone_number_risk_level.rb b/examples/score/1_check_phone_number_risk_level.rb new file mode 100644 index 0000000..f405d5d --- /dev/null +++ b/examples/score/1_check_phone_number_risk_level.rb @@ -0,0 +1,17 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +phone_number = 'phone_number' +account_lifecycle_event = 'create' + +score_client = Telesign::ScoreClient.new(customer_id, secret_key) +response = score_client.score(phone_number, account_lifecycle_event) + +if response.ok + puts "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']] +end diff --git a/examples/voice/1_send_voice_call.rb b/examples/voice/1_send_voice_call.rb new file mode 100644 index 0000000..034f2b8 --- /dev/null +++ b/examples/voice/1_send_voice_call.rb @@ -0,0 +1,11 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +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, secret_key) +response = voice_client.call(phone_number, message, message_type) diff --git a/examples/voice/2_send_voice_call_with_verification_code.rb b/examples/voice/2_send_voice_call_with_verification_code.rb new file mode 100644 index 0000000..7499191 --- /dev/null +++ b/examples/voice/2_send_voice_call_with_verification_code.rb @@ -0,0 +1,22 @@ +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +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, secret_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 diff --git a/examples/voice/3_send_voice_call_french.rb b/examples/voice/3_send_voice_call_french.rb new file mode 100644 index 0000000..f3f7486 --- /dev/null +++ b/examples/voice/3_send_voice_call_french.rb @@ -0,0 +1,13 @@ +# encoding: UTF-8 +require 'telesign' + +customer_id = 'customer_id' +secret_key = 'secret_key' + +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, secret_key) +response = voice_client.call(phone_number, message, message_type, voice: 'f-FR-fr') diff --git a/lib/telesign.rb b/lib/telesign.rb index dce2811..774cead 100644 --- a/lib/telesign.rb +++ b/lib/telesign.rb @@ -1,405 +1,7 @@ -# -# Copyright (c) 2016 TeleSign -# -# TeleSign Ruby SDK REST API endpoints. -# -# The api module contains Python classes and methods that allow you to -# use the Ruby programming language to programmatically access the -# Verify and PhoneId TeleSign web services. -# - +require 'telesign/autoverify' +require 'telesign/messaging' +require 'telesign/phoneid' require 'telesign/rest' - -module Telesign - - module API - - # The PhoneId class exposes services that each provide detailed - # information about a specified phone number. - class PhoneId < Telesign::API::Rest - - def initialize(customer_id, - secret_key, - ssl=true, - api_host='rest.telesign.com', - timeout=nil) - - super(customer_id, - secret_key, - ssl, - api_host, - timeout) - end - - # Retrieves the standard set of details about the specified phone number. - # This includes the type of phone (e.g., land line or mobile), and it's - # approximate geographic location. - def standard(phone_number, - use_case_code=nil, - extra=nil, - timeout=nil) - - params = {} - - unless use_case_code.nil? - params[:ucid] = use_case_code - end - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/standard/#{phone_number}", - params, - nil, - timeout) - end - - # Retrieves a score for the specified phone number. This ranks the phone number's - # "risk level" on a scale from 0 to 1000, so you can code your web application to - # handle particular use cases (e.g., to stop things like chargebacks, identity - # theft, fraud, and spam). - def score(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/score/#{phone_number}", - params, - nil, - timeout) - end - - # In addition to the information retrieved by standard, this service provides the - # Name & Address associated with the specified phone number. - def contact(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/contact/#{phone_number}", - params, - nil, - timeout) - end - - # In addition to the information retrieved by standard, this service provides - # actionable data associated with the specified phone number. - def live(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/live/#{phone_number}", - params, - nil, - timeout) - end - - # In addition to the information retrieved by standard, this service provides - # data about potential sim_swaps associated with the specified phone number. - def sim_swap(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/sim_swap/check/#{phone_number}", - params, - nil, - timeout) - end - - # In addition to the information retrieved by standard, this service provides - # information on call forwarding for the phone number provided. - def call_forward(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/call_forward/#{phone_number}", - params, - nil, - timeout) - end - - # In addition to the information retrieved by standard, this service provides - # information on call forwarding for the phone number provided. - def number_deactivation(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/phoneid/number_deactivation/#{phone_number}", - params, - nil, - timeout) - end - - end - - # The Verify class exposes several services for sending users a verification - # token. You can use this mechanism to simply test whether you can reach users - # at the phone number they supplied, or you can have them use the token to - # authenticate themselves with your web application. - # - # This class also exposes a service that is used in conjunction with the first - # two services, in that it allows you to confirm the result of the authentication. - # - # You can use this verification factor in combination with username & password to - # provide two-factor authentication for higher security. - class Verify < Telesign::API::Rest - - def initialize(customer_id, - secret_key, - ssl=true, - api_host='rest.telesign.com', - timeout=nil) - - super(customer_id, - secret_key, - ssl, - api_host, - timeout) - end - - # Sends a text message containing the verification code, to the specified - # phone number (supported for mobile phones only). - def sms(phone_number, - use_case_code=nil, - extra=nil, - timeout=nil) - - params = {:phone_number => phone_number} - - unless use_case_code.nil? - params[:use_case_code] = use_case_code - end - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Post, - "/v1/verify/sms", - nil, - params, - timeout) - end - - # Calls the specified phone number, and using speech synthesis, speaks the - # verification code to the user. - def call(phone_number, - use_case_code=nil, - extra=nil, - timeout=nil) - - params = {:phone_number => phone_number} - - unless use_case_code.nil? - params[:use_case_code] = use_case_code - end - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Post, - "/v1/verify/call", - nil, - params, - timeout) - end - - # Calls the specified phone number, and using speech synthesis, speaks the - # verification code to the user. - def smart(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:phone_number => phone_number, - :ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Post, - "/v1/verify/smart", - nil, - params, - timeout) - - end - - # The **push** method sends a push notification containing the verification - # code to the specified phone number (supported for mobile phones only). - def push(phone_number, - use_case_code, - extra=nil, - timeout=nil) - - params = {:phone_number => phone_number, - :ucid => use_case_code} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Post, - "/v2/verify/push", - nil, - params, - timeout) - - end - - # Retrieves the verification result. You make this call in your web application - # after users complete the authentication transaction (using either a call or sms). - def status(reference_id, - verify_code=nil, - extra=nil, - timeout=nil) - - params = {} - - unless verify_code.nil? - params[:verify_code] = verify_code - end - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/verify/#{reference_id}", - params, - nil, - timeout) - end - end - - - # The **Telebureau** class exposes services for creating, retrieving, updating and - # deleting telebureau fraud events. You can use this mechanism to simply test whether - # you can reach telebureau services. - class TeleBureau < Telesign::API::Rest - - def initialize(customer_id, - secret_key, - ssl=true, - api_host='rest.telesign.com', - timeout=nil) - - super(customer_id, - secret_key, - ssl, - api_host, - timeout) - end - - # Creates a telebureau event corresponding to supplied data. - def create(phone_number, - fraud_type, - occurred_at, - extra=nil, - timeout=nil) - - params = {:phone_number => phone_number, - :fraud_type => fraud_type, - :occurred_at => occurred_at} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Post, - "/v1/telebureau/event", - nil, - params, - timeout) - end - - # Retrieves the fraud event status. You make this call in your web application after - # completion of create transaction for a telebureau event. - def retrieve(reference_id, - extra=nil, - timeout=nil) - - params = {} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Get, - "/v1/telebureau/event/#{reference_id}", - params, - nil, - timeout) - end - - # Deletes a previously submitted fraud event. You make this call in your web application - # after completion of the create transaction for a telebureau event. - def delete(reference_id, - extra=nil, - timeout=nil) - - params = {} - - unless extra.nil? - params.merge!(extra) - end - - execute(Net::HTTP::Delete, - "/v1/telebureau/event/#{reference_id}", - params, - nil, - timeout) - end - end - end -end \ No newline at end of file +require 'telesign/score' +require 'telesign/util' +require 'telesign/voice' diff --git a/lib/telesign/autoverify.rb b/lib/telesign/autoverify.rb new file mode 100644 index 0000000..9ef952e --- /dev/null +++ b/lib/telesign/autoverify.rb @@ -0,0 +1,23 @@ +require 'telesign/rest' + +AUTOVERIFY_STATUS_RESOURCE = '/v1/mobile/verification/status/%{external_id}' + +module Telesign + + # AutoVerify is a secure, lightweight SDK that integrates a frictionless user verification process into existing + # native mobile applications. + class AutoVerifyClient < RestClient + + # Retrieves the verification result for an AutoVerify transaction by external_id. To ensure a secure verification + # flow you must check the status using TeleSign's servers on your backend. Do not rely on the SDK alone to + # indicate a successful verification. + # + # See https://developer.telesign.com/docs/auto-verify-sdk#section-obtaining-verification-status for detailed API + # documentation. + def status(external_id, **params) + + self.get(AUTOVERIFY_STATUS_RESOURCE % {:external_id => external_id}, + **params) + end + end +end diff --git a/lib/telesign/messaging.rb b/lib/telesign/messaging.rb new file mode 100644 index 0000000..807cadc --- /dev/null +++ b/lib/telesign/messaging.rb @@ -0,0 +1,33 @@ +require '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 a message to the target phone_number. + # + # See https://developer.telesign.com/v2.0/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 + + # Retrieves the current status of the message. + # + # See https://developer.telesign.com/v2.0/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 diff --git a/lib/telesign/phoneid.rb b/lib/telesign/phoneid.rb new file mode 100644 index 0000000..01866e2 --- /dev/null +++ b/lib/telesign/phoneid.rb @@ -0,0 +1,21 @@ +require '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 + end +end diff --git a/lib/telesign/rest.rb b/lib/telesign/rest.rb index 45dfe59..1357f05 100644 --- a/lib/telesign/rest.rb +++ b/lib/telesign/rest.rb @@ -1,181 +1,222 @@ -# -# Copyright (c) 2016 TeleSign -# -# TeleSign Ruby SDK HMAC REST Auth. -# - require 'pp' require 'json' require 'time' require 'base64' require 'openssl' -require 'net/http' +require 'securerandom' +require 'net/http/persistent' module Telesign + SDK_VERSION = '2.0.0' + + # 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 + + @user_agent = "TeleSignSDK/ruby-{#{SDK_VERSION} #{RUBY_DESCRIPTION} net/http/persistent" + + # 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 - module API + # TeleSign RestClient, useful for making generic RESTful requests against the API. + # + # * +customer_id+ - Your customer_id string associated with your account. + # * +secret_key+ - Your secret_key string associated with your account. + # * +api_host+ - (optional) Override the default api_host to target another endpoint string. + # * +timeout+ - (optional) How long to wait for the server to send data before giving up, as a float. + def initialize(customer_id, + secret_key, + api_host: 'https://rest-api.telesign.com', + proxy: nil, + timeout: 10) + + @customer_id = customer_id + @secret_key = secret_key + @api_host = api_host + + @http = Net::HTTP::Persistent.new(name: 'telesign', proxy: proxy) + + unless timeout.nil? + @http.open_timeout = timeout + @http.read_timeout = timeout + end + end - # == TeleSign Ruby SDK REST API Helper + # Generates the TeleSign REST API headers used to authenticate requests. # - # Telesign::API::Rest provides helper classes and functions - # to handle the HMAC REST authentication. + # Creates the canonicalized string_to_sign and generates the HMAC signature. This is used to authenticate requests + # against the TeleSign REST API. # - # You can use these helper functions directly or via the Telesign::API::PhoneId - # and Telesign::API::Verify classes. Please see the TeleSign REST API docs at - # http://docs.telesign.com/rest/index.html for implementation details. + # See https://developer.telesign.com/docs/authentication-1 for detailed API documentation. # - class Rest - - # Creates a new Telesign::API::Rest object with the specified credentials - # and HTTP configuration. - # The +api_host+ should be a DNS hostname or IP address. - def initialize(customer_id, - secret_key, - ssl, - api_host, - timeout=nil) - - @customer_id = customer_id - @secret_key = secret_key - @ssl = ssl - @base_uri = URI("http#{ssl ? 's' : ''}://#{api_host}") - @timeout = timeout - @user_agent = 'Net::HTTP TeleSignSDK/ruby-1.0.0' + # * +customer_id+ - Your account customer_id. + # * +secret_key+ - Your account secret_key. + # * +method_name+ - The HTTP method name of the request as a upper case string, should be one of 'POST', 'GET', + # 'PUT' 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, + secret_key, + method_name, + resource, + url_encoded_fields, + date_rfc2616: nil, + nonce: nil, + user_agent: nil) + + if date_rfc2616.nil? + date_rfc2616 = Time.now.httpdate end - # Executes the REST API request based on the given configuration. - # See Telesign::API::PhoneId and Telesign::API::Verify for specific - # usage. - def execute(verb, - resource, - params=nil, - form_data=nil, - timeout=nil) - - # generate the headers - headers = generate_auth_headers( - @customer_id, - @secret_key, - resource, - verb, - form_data.nil? ? nil : URI.encode_www_form(form_data)) - - uri = URI.join(@base_uri, resource) - - # set query params - uri.query = URI.encode_www_form(params) unless params.nil? - - # configure HTTP object - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = @ssl - - http.open_timeout = timeout.nil? ? @timeout : timeout - http.read_timeout = http.open_timeout - http.ssl_timeout = http.open_timeout - http.continue_timeout = http.open_timeout - - #set headers - request = verb.new uri.request_uri - headers.each do |k, v| - request[k] = v - end + if nonce.nil? + nonce = SecureRandom.uuid + end - # set post data - request.set_form_data(form_data) unless form_data.nil? + content_type = (%w[POST PUT].include? method_name) ? 'application/x-www-form-urlencoded' : '' - # do the request - http_response = http.request(request) + auth_method = 'HMAC-SHA256' - # check response - unless http_response.is_a? Net::HTTPSuccess - if http_response.is_a? Net::HTTPUnauthorized - raise Telesign::API::AuthError.new(http_response) - else - raise Telesign::API::APIError.new(http_response) - end - end + string_to_sign = "#{method_name}" - Telesign::API::APIResponse.new(http_response) - end + string_to_sign << "\n#{content_type}" - # Function to generate the REST API authentication headers. A signature is - # computed based on the contents of the request and the client's secret key. - def generate_auth_headers (customer_id, - secret_key, - resource, - verb, - form_data=nil, - content_type='') + string_to_sign << "\n#{date_rfc2616}" - datetime_stamp = Time.now.utc.to_datetime.rfc822 - nonce = rand.to_s + string_to_sign << "\nx-ts-auth-method:#{auth_method}" + + string_to_sign << "\nx-ts-nonce:#{nonce}" + + if !content_type.empty? and !url_encoded_fields.empty? + string_to_sign << "\n#{url_encoded_fields}" + end - content_type = 'application/x-www-form-urlencoded' if verb == Net::HTTP::Post or verb == Net::HTTP::Put + string_to_sign << "\n#{resource}" - string_to_sign = "#{verb.name.split('::').last.upcase}\n" + - "#{content_type}\n\n" + - "x-ts-auth-method:#{'HMAC-SHA256'}\n" + - "x-ts-date:#{datetime_stamp}\n" + - "x-ts-nonce:#{nonce}" + digest = OpenSSL::Digest.new('sha256') + key = Base64.decode64(secret_key) - string_to_sign = "#{string_to_sign}\n#{form_data}" unless form_data.nil? + signature = Base64.encode64(OpenSSL::HMAC.digest(digest, key, string_to_sign)).strip - string_to_sign = string_to_sign + "\n#{resource}" + authorization = "TSA #{customer_id}:#{signature}" - signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), - Base64.decode64(secret_key), string_to_sign)).chomp + headers = { + 'Authorization': authorization, + 'Date': date_rfc2616, + 'x-ts-auth-method': auth_method, + 'x-ts-nonce': nonce + } - { - 'Authorization' => "TSA #{customer_id}:#{signature}", - 'x-ts-date' => datetime_stamp, - 'x-ts-auth-method' => 'HMAC-SHA256', - 'x-ts-nonce' => nonce, - 'User-Agent' => @user_agent - } + unless user_agent.nil? + headers['User-Agent'] = user_agent end + + headers + end - class APIResponse + # 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) - attr_accessor :body, :headers, :status, :verify_code + execute(Net::HTTP::Post, 'POST', resource, **params) - def initialize(http_response, - verify_code=nil) + 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) - @body = JSON.parse(http_response.body) - @headers = http_response.to_hash - @status = http_response.code - @verify_code = verify_code - end end - class APIError < StandardError + # 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) - attr_accessor :errors, :headers, :status, :body + execute(Net::HTTP::Put, 'PUT', resource, **params) - def initialize(http_response) + end - @errors = JSON.parse(http_response.body)['errors'] - @headers = http_response.to_hash - @status = http_response.code - @body = http_response.body - 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) - def to_s - result = '' - @errors.each do |error| - result = "#{result}#{error['description']}\n" - end + execute(Net::HTTP::Delete, 'DELETE', resource, **params) - result - end end - class AuthError < Telesign::API::APIError + 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, **params) + + resource_uri = URI.parse("#{@api_host}#{resource}") + + url_encoded_fields = URI.encode_www_form(params) + + headers = RestClient.generate_telesign_headers(@customer_id, + @secret_key, + method_name, + resource, + url_encoded_fields, + user_agent: @user_agent) + + request = method_function.new(resource_uri.request_uri) + + unless params.empty? + if %w[POST PUT].include? method_name + request.set_form_data(params) + else + resource_uri.query = url_encoded_fields + end + end - def initialize(http_response) - super(http_response) + headers.each do |k, v| + request[k] = v end + + http_response = @http.request(resource_uri, request) + + Response.new(http_response) end end -end \ No newline at end of file +end diff --git a/lib/telesign/score.rb b/lib/telesign/score.rb new file mode 100644 index 0000000..d96b2ce --- /dev/null +++ b/lib/telesign/score.rb @@ -0,0 +1,21 @@ +require 'telesign/rest' + +SCORE_RESOURCE = '/v1/score/%{phone_number}' + +module Telesign + + # Score provides risk information about a specified phone number. + class ScoreClient < RestClient + + # Score is an API that delivers reputation scoring based on phone number intelligence, traffic patterns, machine + # learning, and a global data consortium. + # + # See https://developer.telesign.com/docs/rest_api-phoneid-score for detailed API documentation. + def score(phone_number, account_lifecycle_event, **params) + + self.post(SCORE_RESOURCE % {:phone_number => phone_number}, + account_lifecycle_event: account_lifecycle_event, + **params) + end + end +end diff --git a/lib/telesign/util.rb b/lib/telesign/util.rb new file mode 100644 index 0000000..7e1112c --- /dev/null +++ b/lib/telesign/util.rb @@ -0,0 +1,39 @@ +require '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. + # + # * +secret_key+ - the TeleSign API secret_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(secret_key, signature, json_str) + + digest = OpenSSL::Digest.new('sha256') + key = Base64.decode64(secret_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 diff --git a/lib/telesign/voice.rb b/lib/telesign/voice.rb new file mode 100644 index 0000000..beca535 --- /dev/null +++ b/lib/telesign/voice.rb @@ -0,0 +1,33 @@ +require '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 diff --git a/sample.rb b/sample.rb deleted file mode 100644 index c5e0ff6..0000000 --- a/sample.rb +++ /dev/null @@ -1,84 +0,0 @@ -# -# Copyright (c) 2016 TeleSign -# -# TeleSign Ruby SDK sample usage. -# - -require 'telesign' -require 'date' -require 'pp' - -# update this with your credentials -customer_id = 'your customer_id' -secret_key = 'your secret_key' - -# update with target phone number -phone_number = 'target phone_number' - -use_case_code = 'OTHR' - -verify = Telesign::API::Verify.new(customer_id, secret_key) -phoneid = Telesign::API::PhoneId.new(customer_id, secret_key) -telebureau = Telesign::API::TeleBureau.new(customer_id, secret_key) - -# sample sms -verify_sms_response = verify.sms(phone_number) -pp verify_sms_response.body - -# sample status -verify_status_response = verify.status(verify_sms_response.body['reference_id']) -pp verify_status_response.body - -# sample voice -verify_voice_response = verify.call(phone_number) -p verify_voice_response.body - -# sample smart -verify_smart_response = verify.smart(phone_number, use_case_code) -pp verify_smart_response.body - -# sample push -verify_push_response = verify.push(phone_number, use_case_code) -pp verify_push_response.body - -# sample phoneid standard -phoneid_standard_response = phoneid.standard(phone_number) -pp phoneid_standard_response.body - -# sample phoneid contact -phoneid_contact_response = phoneid.contact(phone_number, use_case_code) -pp phoneid_contact_response.body - -# sample phoneid score -phoneid_score_response = phoneid.score(phone_number, use_case_code) -pp phoneid_score_response.body - -# sample phoneid live -phoneid_live_response = phoneid.live(phone_number, use_case_code) -pp phoneid_live_response.body - -# sample phoneid sim_swap -phoneid_sim_swap_response = phoneid.sim_swap(phone_number, use_case_code) -pp phoneid_sim_swap_response.body - -# sample phoneid call_forward -phoneid_call_forward_response = phoneid.call_forward(phone_number, use_case_code) -pp phoneid_call_forward_response.body - -# sample phoneid number_deactivation -phoneid_number_deactivation_response = phoneid.number_deactivation(phone_number, use_case_code) -pp phoneid_number_deactivation_response.body - -# sample telebureau create -fraud_type = 'takeover' -occurred_at = '2016-11-22T00:43:44Z' -telebureau_create_response = telebureau.create(phone_number, fraud_type, occurred_at) -pp telebureau_create_response.body - -# sample telebureau retrieve -telebureau_retrieve_response = telebureau.retrieve(telebureau_create_response.body['reference_id']) -pp telebureau_retrieve_response.body - -# sample telebureau delete -telebureau_delete_response = telebureau.delete(telebureau_create_response.body['reference_id']) -pp telebureau_delete_response.body diff --git a/telesign.gemspec b/telesign.gemspec index 36fea66..1270fb1 100644 --- a/telesign.gemspec +++ b/telesign.gemspec @@ -1,13 +1,13 @@ Gem::Specification.new do |s| s.name = 'telesign' - s.version = '1.0.2' + s.version = '2.0.0' + s.add_runtime_dependency 'net-http-persistent', '~> 3.0', '>= 3.0.0' s.licenses = ['MIT'] - s.date = '2016-11-22' + s.date = '2017-02-26' s.summary = 'TeleSign Ruby SDK' s.description = 'TeleSign Ruby SDK' - s.authors = ['Jarrad Lee'] - s.email = 'jarrad@telesign.com' - s.files = ['lib/telesign.rb', 'lib/telesign/rest.rb'] - s.homepage = - 'http://rubygems.org/gems/telesign' + s.authors = ['TeleSign'] + s.email = 'support@telesign.com' + s.files = Dir['lib/**/*rb'] + s.homepage = 'http://rubygems.org/gems/telesign' end \ No newline at end of file diff --git a/test/test_rest.rb b/test/test_rest.rb new file mode 100644 index 0000000..92906d9 --- /dev/null +++ b/test/test_rest.rb @@ -0,0 +1,11 @@ +require 'test/unit' +require_relative '../lib/telesign.rb' + +class TestRest < Test::Unit::TestCase + + def test_version + + puts Telesign::SDK_VERSION + end + +end \ No newline at end of file