From 8034211f176eb4128781937cf291a10242233a5c Mon Sep 17 00:00:00 2001 From: lunarfyre7 Date: Wed, 16 Dec 2020 11:02:23 -0800 Subject: [PATCH] Add 2fa (#47) * add 2fa endpoints add example for 2fa * change record_types * typo fix * update rubocop and it's config * update to new verification api * add required params to create connection Co-authored-by: Dan Tolbert --- .rubocop.yml | 42 +-- .rubocop_todo.yml | 300 +++++++++++++++++++++ Gemfile | 8 +- examples/2 factor authentication/Gemfile | 7 + examples/2 factor authentication/main.rb | 67 +++++ examples/2 factor authentication/readme.md | 5 + lib/telnyx.rb | 4 +- lib/telnyx/util.rb | 5 +- lib/telnyx/verification.rb | 27 ++ lib/telnyx/verify_profile.rb | 11 + test/telnyx/credential_connection_test.rb | 6 +- test/telnyx/verification_test.rb | 22 ++ test/telnyx/verify_profile_test.rb | 31 +++ 13 files changed, 492 insertions(+), 43 deletions(-) create mode 100644 .rubocop_todo.yml create mode 100644 examples/2 factor authentication/Gemfile create mode 100644 examples/2 factor authentication/main.rb create mode 100644 examples/2 factor authentication/readme.md create mode 100644 lib/telnyx/verification.rb create mode 100644 lib/telnyx/verify_profile.rb create mode 100644 test/telnyx/verification_test.rb create mode 100644 test/telnyx/verify_profile_test.rb diff --git a/.rubocop.yml b/.rubocop.yml index 5d27b6d..e2f8d21 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,15 +1,18 @@ +inherit_from: .rubocop_todo.yml + AllCops: DisplayCopNames: true - TargetRubyVersion: 2.0 + TargetRubyVersion: 2.4 + NewCops: disable Layout/CaseIndentation: EnforcedStyle: end -Layout/IndentArray: +Layout/FirstArrayElementIndentation: EnforcedStyle: consistent -Layout/IndentHash: +Layout/FirstHashElementIndentation: EnforcedStyle: consistent Metrics/MethodLength: @@ -26,36 +29,3 @@ Style/FrozenStringLiteralComment: Style/StringLiterals: EnforcedStyle: double_quotes - -Style/TrailingCommaInLiteral: - EnforcedStyleForMultiline: consistent_comma - -Metrics/AbcSize: - Max: 52 - -Metrics/BlockLength: - Max: 498 - -Metrics/ClassLength: - Max: 659 - -# Offense count: 11 -Metrics/CyclomaticComplexity: - Max: 15 - -Metrics/LineLength: - Max: 310 - -Metrics/ParameterLists: - Max: 7 - -Metrics/PerceivedComplexity: - Max: 17 - -Style/ClassVars: - Exclude: - - 'lib/telnyx/telnyx_object.rb' - - 'test/telnyx/api_resource_test.rb' - -Style/Documentation: - Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..f0c5969 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,300 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2020-12-11 00:11:23 UTC using RuboCop version 1.6.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. +# Include: **/*.gemspec +Gemspec/OrderedDependencies: + Exclude: + - 'telnyx.gemspec' + +# Offense count: 1 +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/RequiredRubyVersion: + Exclude: + - 'telnyx.gemspec' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleAlignWith, Severity. +# SupportedStylesAlignWith: start_of_line, begin +Layout/BeginEndAlignment: + Exclude: + - 'lib/telnyx/util.rb' + +# Offense count: 8 +# Cop supports --auto-correct. +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'lib/telnyx.rb' + - 'lib/telnyx/call.rb' + - 'lib/telnyx/list_object.rb' + - 'lib/telnyx/messaging_phone_number.rb' + - 'lib/telnyx/singleton_api_resource.rb' + - 'lib/telnyx/util.rb' + - 'test/telnyx_mock.rb' + +# Offense count: 43 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'lib/telnyx/util.rb' + - 'telnyx.gemspec' + +# Offense count: 4 +# Cop supports --auto-correct. +Layout/LeadingEmptyLines: + Exclude: + - 'test/telnyx/number_lookup_test.rb' + - 'test/telnyx/number_order_document_test.rb' + - 'test/telnyx/phone_number_regulatory_requirement_test.rb' + - 'test/telnyx/regulatory_requirement_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Layout/RescueEnsureAlignment: + Exclude: + - 'lib/telnyx/util.rb' + +# Offense count: 3 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'test/telnyx/api_operations_test.rb' + - 'test/telnyx/telnyx_object_test.rb' + +# Offense count: 3 +# Configuration parameters: MaximumRangeSize. +Lint/MissingCopEnableDirective: + Exclude: + - 'lib/telnyx/api_operations/nested_resource.rb' + +# Offense count: 1 +Lint/MissingSuper: + Exclude: + - 'lib/telnyx/errors.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Lint/RedundantCopDisableDirective: + Exclude: + - 'Gemfile' + +# Offense count: 12 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 55 + +# Offense count: 28 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine +Metrics/BlockLength: + Max: 372 + +# Offense count: 10 +# Configuration parameters: CountComments, CountAsOne. +Metrics/ClassLength: + Max: 495 + +# Offense count: 5 +# Configuration parameters: IgnoredMethods. +Metrics/CyclomaticComplexity: + Max: 16 + +# Offense count: 2 +# Configuration parameters: CountKeywordArgs, MaxOptionalParameters. +Metrics/ParameterLists: + Max: 6 + +# Offense count: 5 +# Configuration parameters: IgnoredMethods. +Metrics/PerceivedComplexity: + Max: 18 + +# Offense count: 13 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to +Naming/MethodParameterName: + Exclude: + - 'lib/telnyx/errors.rb' + - 'lib/telnyx/list_object.rb' + - 'lib/telnyx/telnyx_client.rb' + - 'lib/telnyx/telnyx_object.rb' + - 'lib/telnyx/util.rb' + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: + Exclude: + - 'lib/telnyx/errors.rb' + - 'lib/telnyx/telnyx_client.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/CaseLikeIf: + Exclude: + - 'lib/telnyx.rb' + - 'lib/telnyx/errors.rb' + - 'lib/telnyx/util.rb' + +# Offense count: 2 +Style/ClassVars: + Exclude: + - 'lib/telnyx/telnyx_object.rb' + - 'test/telnyx/api_resource_test.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE +Style/CommentAnnotation: + Exclude: + - 'lib/telnyx/api_operations/save.rb' + - 'test/telnyx/telnyx_object_test.rb' + - 'test/telnyx/util_test.rb' + +# Offense count: 45 +Style/Documentation: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/Encoding: + Exclude: + - 'test/telnyx/api_operations_test.rb' + - 'test/telnyx/api_resource_test.rb' + +# Offense count: 19 +# Cop supports --auto-correct. +Style/ExpandPathArguments: + Enabled: false + +# Offense count: 10 +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed. +# SupportedStyles: annotated, template, unannotated +Style/FormatStringToken: + EnforcedStyle: unannotated + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys +Style/HashSyntax: + Exclude: + - 'test/telnyx/telnyx_object_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/HashTransformValues: + Exclude: + - 'lib/telnyx/telnyx_object.rb' + +# Offense count: 8 +# Cop supports --auto-correct. +Style/IfUnlessModifier: + Exclude: + - 'lib/telnyx.rb' + - 'lib/telnyx/api_operations/request.rb' + - 'lib/telnyx/api_operations/save.rb' + - 'lib/telnyx/api_resource.rb' + - 'lib/telnyx/singleton_api_resource.rb' + - 'lib/telnyx/telnyx_client.rb' + - 'lib/telnyx/telnyx_object.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IgnoredMethods. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/telnyx/telnyx_client.rb' + +# Offense count: 1 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/telnyx/telnyx_object.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantAssignment: + Exclude: + - 'lib/telnyx/telnyx_client.rb' + +# Offense count: 40 +# Cop supports --auto-correct. +Style/RedundantFreeze: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'lib/telnyx/telnyx_object.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/StderrPuts: + Exclude: + - 'lib/telnyx/api_operations/request.rb' + - 'lib/telnyx/telnyx_client.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/StringConcatenation: + Exclude: + - 'lib/telnyx/call.rb' + - 'lib/telnyx/telnyx_client.rb' + +# Offense count: 29 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArrayLiteral: + Exclude: + - 'test/telnyx/list_object_test.rb' + - 'test/telnyx/number_order_test.rb' + - 'test/telnyx/outbound_voice_profile_test.rb' + - 'test/telnyx/telnyx_object_test.rb' + - 'test/telnyx/util_test.rb' + - 'test/test_data.rb' + +# Offense count: 68 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Enabled: false + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: WordRegex. +# SupportedStyles: percent, brackets +Style/WordArray: + EnforcedStyle: percent + MinSize: 4 + +# Offense count: 21 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 310 diff --git a/Gemfile b/Gemfile index c76e1b4..e0defdd 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ group :development do gem "guard" gem "guard-rake" gem "guard-rubocop" - gem "rubocop", "0.50.0" + gem "rubocop", "~> 1.6" # Rack 2.0+ requires Ruby >= 2.2.2 which is problematic for the test suite on # older Ruby versions. Check Ruby the version here and put a maximum @@ -34,8 +34,8 @@ group :development do end platforms :mri do - gem "byebug" - gem "pry" - gem "pry-byebug" + # gem "byebug" + # gem "pry" + # gem "pry-byebug" end end diff --git a/examples/2 factor authentication/Gemfile b/examples/2 factor authentication/Gemfile new file mode 100644 index 0000000..a412bad --- /dev/null +++ b/examples/2 factor authentication/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "amazing_print" +gem "telnyx", path: "../../." +gem "tty-prompt" diff --git a/examples/2 factor authentication/main.rb b/examples/2 factor authentication/main.rb new file mode 100644 index 0000000..1630136 --- /dev/null +++ b/examples/2 factor authentication/main.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "telnyx" +require "amazing_print" # for displaying the api requests nicely. +require "tty-prompt" # for prettier interactive prompts. + +Telnyx.api_key = ENV["TELNYX_KEY"] # Set your key here +raise "Please set your API key in this script, or set the TELNYX_KEY env variable!" unless Telnyx.api_key + +class Demo + def initialize + @prompt = TTY::Prompt.new + end + + def start + create_profile + puts "Profile object returned from API:" + ap @profile + + verify + puts "Verification object returned from API:" + ap @verification + + puts "A SMS should be sent to the phone number soon." + check_prompt + end + + def create_profile + # Ask what to call the new verification profile. + profile_name = @prompt.ask("Profile name:", default: "demo profile") + # Check if we already created a demo profile. + existing_profile = Telnyx::VerifyProfile.list.first { |p| p.name == profile_name } + # Check if we should use the existing one. + return @profile = existing_profile if existing_profile && @prompt.yes?(%(Found a profile already called "#{profile_name}", would you like to use that instead of creating a new profile?)) + + # Create a new profile. + @profile = Telnyx::VerifyProfile.create name: profile_name + end + + def verify + # Create a new verification, this will send a code via SMS to the number provided. + @verification = Telnyx::Verification.create( + type: "sms", + verify_profile_id: @profile.id, + phone_number: @prompt.ask("Enter a phone number to send a verification code to (must be e164 format, e.g., +15554443333):", value: "+1") { |p| p.validate(/^\+?[1-9]\d{1,14}$/) } + ) + end + + def check_prompt + loop do + # Ask the user for the verification code. + code = @prompt.ask "Enter verification code:" + # Ask the api if this is the correct code. + response = Telnyx::Verification.submit_code code: code, phone_number: @verification.phone_number + puts "Api responded with:" + ap response + + # If it's not the right code try again. + break if response.response_code == "accepted" + + puts "\e[31mIncorrect code, try again!\e[0m" + create_profile if @prompt.yes?("Resend code?") + end + end +end + +Demo.new.start diff --git a/examples/2 factor authentication/readme.md b/examples/2 factor authentication/readme.md new file mode 100644 index 0000000..d7cf15e --- /dev/null +++ b/examples/2 factor authentication/readme.md @@ -0,0 +1,5 @@ +# Usage +``` +bundle +bundle exec ruby main.rb +``` \ No newline at end of file diff --git a/lib/telnyx.rb b/lib/telnyx.rb index 9683c99..e551c06 100644 --- a/lib/telnyx.rb +++ b/lib/telnyx.rb @@ -40,10 +40,10 @@ require "telnyx/alphanumeric_sender_id" require "telnyx/available_phone_number" require "telnyx/billing_group" +require "telnyx/call_control_application" require "telnyx/call" require "telnyx/conference" require "telnyx/connection" -require "telnyx/call_control_application" require "telnyx/credential_connection" require "telnyx/event" require "telnyx/fqdn_connection" @@ -64,6 +64,8 @@ require "telnyx/public_key" require "telnyx/regulatory_requirement" require "telnyx/sim_card" +require "telnyx/verification" +require "telnyx/verify_profile" require "telnyx/wireless_detail_records_report" module Telnyx diff --git a/lib/telnyx/util.rb b/lib/telnyx/util.rb index b8f226f..f53c91e 100644 --- a/lib/telnyx/util.rb +++ b/lib/telnyx/util.rb @@ -66,9 +66,12 @@ def self.object_classes PhoneNumberRegulatoryRequirement::OBJECT_NAME => PhoneNumberRegulatoryRequirement, "phone_number_regulatory_group" => PhoneNumberRegulatoryRequirement, Portout::OBJECT_NAME => Portout, + VerifyProfile::OBJECT_NAME => VerifyProfile, PublicKey::OBJECT_NAME => PublicKey, RegulatoryRequirement::OBJECT_NAME => RegulatoryRequirement, SimCard::OBJECT_NAME => SimCard, + Verification::OBJECT_NAME => Verification, + "verification" => Verification::Response, WirelessDetailRecordsReport::OBJECT_NAME => WirelessDetailRecordsReport, } end @@ -87,7 +90,7 @@ def self.push_object_class(key, klass) # # ==== Attributes # - # * +data+ - Hash of fields and values to be converted into a TelnyxObject. + # * +Data+ - Hash of fields and values to be converted into a TelnyxObject. # * +opts+ - Options for +TelnyxObject+ like an API key that will be reused # on subsequent API calls. def self.convert_to_telnyx_object(data, opts = {}) diff --git a/lib/telnyx/verification.rb b/lib/telnyx/verification.rb new file mode 100644 index 0000000..08509c2 --- /dev/null +++ b/lib/telnyx/verification.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Telnyx + class Verification < APIResource + # Type for verification responses + class Response < TelnyxObject; end + + extend APIOperations::Create + extend APIOperations::NestedResource + + nested_resource_class_methods "by_telephone", + path: "by_tn", + operations: [:retrieve], + instance_methods: { + retrieve: "by_telephone", + } + + def self.submit_code(phone_number: nil, code: nil) + url = "#{resource_url}/by_phone_number/#{CGI.escape phone_number}/actions/verify" + resp, _opts = request(:post, url, code: code) + Response.construct_from resp.data[:data] + end + + OBJECT_NAME = "verify_verification".freeze + RESOURCE_PATH = "verifications" + end +end diff --git a/lib/telnyx/verify_profile.rb b/lib/telnyx/verify_profile.rb new file mode 100644 index 0000000..01556af --- /dev/null +++ b/lib/telnyx/verify_profile.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Telnyx + class VerifyProfile < APIResource + extend APIOperations::List + extend APIOperations::Create + include APIOperations::Save + + OBJECT_NAME = "verify_profile".freeze + end +end diff --git a/test/telnyx/credential_connection_test.rb b/test/telnyx/credential_connection_test.rb index 74ef0ee..2835960 100644 --- a/test/telnyx/credential_connection_test.rb +++ b/test/telnyx/credential_connection_test.rb @@ -12,7 +12,11 @@ class CredentialConnectionTest < Test::Unit::TestCase end should "create credential connection" do - CredentialConnection.create + CredentialConnection.create( + connection_name: "Test connection_name", + user_name: "Test user_name", + password: "correct-horse-battery-staple" + ) assert_requested :post, "#{Telnyx.api_base}/v2/credential_connections" end diff --git a/test/telnyx/verification_test.rb b/test/telnyx/verification_test.rb new file mode 100644 index 0000000..6ad43b3 --- /dev/null +++ b/test/telnyx/verification_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Telnyx + class VerificationTest < Test::Unit::TestCase + should "create verification" do + Verification.create phone_number: "+15555555555", twofa_profile_id: "1234", type: "sms", verify_profile_id: "foobar" + assert_requested :post, "#{Telnyx.api_base}/v2/verifications" + end + + should "retrieve verification" do + verification = Verification.retrieve("id") + assert_requested :get, "#{Telnyx.api_base}/v2/verifications/id" + assert_kind_of Verification, verification + end + + should "send verification code" do + Verification.submit_code code: "12345", phone_number: "+13035551234" + + assert_requested :post, "#{Telnyx.api_base}/v2/verifications/by_phone_number/+13035551234/actions/verify" + end + end +end diff --git a/test/telnyx/verify_profile_test.rb b/test/telnyx/verify_profile_test.rb new file mode 100644 index 0000000..71183ed --- /dev/null +++ b/test/telnyx/verify_profile_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Telnyx + class VerifyProfileTest < Test::Unit::TestCase + should "list verify_profiles" do + verify_profile = VerifyProfile.list + assert_requested :get, "#{Telnyx.api_base}/v2/verify_profiles" + assert_kind_of ListObject, verify_profile + assert_kind_of VerifyProfile, verify_profile.first + end + + should "create verify_profile" do + VerifyProfile.create name: "test" + assert_requested :post, "#{Telnyx.api_base}/v2/verify_profiles" + end + + should "retrieve verify_profile" do + verify_profile = VerifyProfile.retrieve("id") + assert_requested :get, "#{Telnyx.api_base}/v2/verify_profiles/id" + assert_kind_of VerifyProfile, verify_profile + end + + should "update verify_profile" do + verify_profile = VerifyProfile.retrieve("id") + + verify_profile.name = "123" + verify_profile.save + assert_requested :patch, "#{Telnyx.api_base}/v2/verify_profiles/#{verify_profile.id}" + end + end +end