~singpolyma/sgx-jmp

7b6c0a067d230cf77abc3e796fd2649cab1ced4d — Stephen Paul Weber 9 months ago 4c37bad
Reset sip account using v2 API

SipAccount now uses only v2 APIs for lookup, create, update, and delete
10 files changed, 117 insertions(+), 169 deletions(-)

M Gemfile
M config-schema.dhall
M config.dhall.sample
M lib/customer.rb
M lib/sip_account.rb
M sgx_jmp.rb
D test/data/catapult_create_sip.json
M test/test_customer.rb
M test/test_helper.rb
M test/test_sip_account.rb
M Gemfile => Gemfile +1 -1
@@ 18,7 18,7 @@ gem "multibases"
gem "multihashes"
gem "ougai"
gem "roda"
gem "ruby-bandwidth-iris"
gem "ruby-bandwidth-iris", git: "https://github.com/singpolyma/ruby-bandwidth-iris", branch: "sip_credential"
gem "sentry-ruby", "<= 4.3.1"
gem "slim"
gem "statsd-instrument", git: "https://github.com/singpolyma/statsd-instrument.git", branch: "graphite"

M config-schema.dhall => config-schema.dhall +1 -1
@@ 1,7 1,6 @@
{ activation_amount : Natural
, admins : List Text
, adr : Text
, bandwidth_app : Text
, bandwidth_peer : Text
, bandwidth_site : Text
, braintree :


@@ 44,6 43,7 @@
      }
, server : { host : Text, port : Natural }
, sgx : Text
, sip : { app : Text, realm : Text }
, sip_host : Text
, upstream_domain : Text
, web : < Inet : { interface : Text, port : Natural } | Unix : Text >

M config.dhall.sample => config.dhall.sample +4 -1
@@ 33,7 33,6 @@ in
	},
	bandwidth_site = "",
	bandwidth_peer = "",
	bandwidth_app = "", -- This can be any voice app
	braintree = {
		environment = "sandbox",
		merchant_id = "",


@@ 47,6 46,10 @@ in
	xep0157 = [
		{ var = "support-addresses", value = "xmpp:+14169938000@cheogram.com", label = "Support" }
	],
	sip = {
		realm = "",
		app = ""
	},
	notify_admin = "muc@example.com",
	sip_host = "sip.jmp.chat",
	plans = [

M lib/customer.rb => lib/customer.rb +1 -3
@@ 92,9 92,7 @@ class Customer
	end

	def reset_sip_account
		SipAccount::New.new(username: customer_id).put.catch do
			sip_account.then { |acct| acct.with_random_password.put }
		end
		sip_account.with_random_password.put
	end

	def btc_addresses

M lib/sip_account.rb => lib/sip_account.rb +44 -60
@@ 1,44 1,38 @@
# frozen_string_literal: true

require "em-synchrony/em-http" # For aget vs get
require "digest"
require "securerandom"
require "value_semantics/monkey_patched"

require_relative "./catapult"
require_relative "./mn_words"
require_relative "mn_words"

class SipAccount
	def self.find(name)
		CATAPULT.endpoint_find(name).then do |found|
			next New.new(username: name) unless found

			new(username: found["name"], url: found["url"])
		end
		new(BandwidthIris::SipCredential.get(name))
	rescue BandwidthIris::Errors::GenericError # 404
		New.new(BandwidthIris::SipCredential.new(
			user_name: name,
			realm: CONFIG[:sip][:realm],
			http_voice_v2_app_id: CONFIG[:sip][:app]
		))
	end

	module Common
		def with_random_password
			with(password: MN_WORDS.sample(3).join(" "))
		end

	protected

		def create
			CATAPULT.create_endpoint(
				name: username,
				credentials: { password: password }
			).then do |url|
				with(url: url)
			end
		end
	def initialize(api_object, password: nil)
		@api_object = api_object
		@password = password
	end

	include Common
	def with(password:)
		self.class.new(@api_object.class.new(@api_object.to_data.merge(
			hash1: Digest::MD5.hexdigest("#{username}:#{server}:#{password}"),
			hash1b: Digest::MD5.hexdigest(
				"#{username}:#{server}:#{server}:#{password}"
			)
		)), password: password)
	end

	value_semantics do
		url String
		username String
		password Either(String, nil), default: nil
	def with_random_password
		with(password: MN_WORDS.sample(3).join(" "))
	end

	def form


@@ 48,7 42,7 @@ class SipAccount

		form.fields = [
			{ var: "username", value: username, label: "Username" },
			{ var: "password", value: password, label: "Password" },
			{ var: "password", value: @password, label: "Password" },
			{ var: "server", value: server, label: "Server" }
		]



@@ 56,47 50,37 @@ class SipAccount
	end

	def put
		delete.then { create }
		@api_object.update(
			hash1: @api_object.hash1,
			hash1b: @api_object.hash1b,
			realm: server,
			http_voice_v2_app_id: @api_object.http_voice_v2_app_id
		)
		self
	end

	def delete
		CATAPULT.delete(url).then do |http|
			unless http.response_header.status == 200
				raise "Delete old SIP account failed"
			end

			self
		end
		@api_object.delete
	end

protected

	protected :url, :username, :password
	def username
		@api_object.user_name.to_s
	end

	def server
		CATAPULT.sip_host
		@api_object.realm
	end

	class New
		include Common

		value_semantics do
			username String
			password String, default_generator: -> { MN_WORDS.sample(3).join(" ") }
		end

	class New < SipAccount
		def put
			create
		end

		def with(**kwargs)
			if kwargs.key?(:url)
				SipAccount.new(internal_to_h.merge(kwargs))
			else
				super
			end
			BandwidthIris::SipCredential.create(
				user_name: username,
				hash1: @api_object.hash1,
				hash1b: @api_object.hash1b,
				realm: server,
				http_voice_v2_app_id: @api_object.http_voice_v2_app_id
			)
			self
		end

		protected :username, :password
	end
end

M sgx_jmp.rb => sgx_jmp.rb +3 -3
@@ 473,7 473,7 @@ Command.new(
			CONFIG[:creds][:account],
			body: customer.fwd.create_call_request do |cc|
				cc.from = customer.registered?.phone
				cc.application_id = CONFIG[:bandwidth_app]
				cc.application_id = CONFIG[:sip][:app]
				cc.answer_url = "#{CONFIG[:web_root]}/ogm/start?" \
				                "customer_id=#{customer.customer_id}"
			end


@@ 599,9 599,9 @@ Command.new(
	"reset sip account",
	"Create or Reset SIP Account"
) {
	Command.customer.then(&:reset_sip_account).then do |sip_account|
	Command.customer.then do |customer|
		Command.finish do |reply|
			reply.command << sip_account.form
			reply.command << customer.reset_sip_account.form
		end
	end
}.register(self).then(&CommandList.method(:register))

D test/data/catapult_create_sip.json => test/data/catapult_create_sip.json +0 -1
@@ 1,1 0,0 @@
{"applicationId":"catapult_app","name":"12345","credentials":{"password":"old password"}}

M test/test_customer.rb => test/test_customer.rb +14 -42
@@ 13,14 13,6 @@ CustomerPlan::DB = Minitest::Mock.new
CustomerUsage::REDIS = Minitest::Mock.new
CustomerUsage::DB = Minitest::Mock.new

class SipAccount
	public :username, :url

	class New
		public :username
	end
end

class CustomerTest < Minitest::Test
	def test_bill_plan_activate
		CustomerPlan::DB.expect(:transaction, nil) do |&block|


@@ 152,14 144,13 @@ class CustomerTest < Minitest::Test
	def test_sip_account_new
		req = stub_request(
			:get,
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
			"https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/test"
		).with(
			headers: {
				"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
				"Authorization" => "Basic Og=="
			}
		).to_return(status: 404)
		sip = customer.sip_account.sync
		sip = customer.sip_account
		assert_kind_of SipAccount::New, sip
		assert_equal "test", sip.username
		assert_requested req


@@ 169,52 160,33 @@ class CustomerTest < Minitest::Test
	def test_sip_account_existing
		req1 = stub_request(
			:get,
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
			"https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/test"
		).with(
			headers: {
				"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
				"Authorization" => "Basic Og=="
			}
		).to_return(status: 200, body: [
			{ name: "NOTtest", domainId: "domain", id: "endpoint" }
		].to_json)

		req2 = stub_request(
			:get,
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/catapult_domain/endpoints?page=1&size=1000"
		).with(
			headers: {
				"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
		).to_return(status: 200, body: {
			SipCredential: {
				UserName: "test",
				Realm: "sip.example.com"
			}
		).to_return(status: 200, body: [
			{ name: "test", domainId: "domain", id: "endpoint" }
		].to_json)
		}.to_xml)

		sip = customer.sip_account.sync
		sip = customer.sip_account
		assert_kind_of SipAccount, sip
		assert_equal "test", sip.username
		assert_equal(
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/domain/endpoints/endpoint",
			sip.url
		)

		assert_requested req1
		assert_requested req2
	end
	em :test_sip_account_existing

	def test_sip_account_error
		stub_request(
			:get,
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
		).to_return(status: 400)
			"https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/test"
		).to_return(status: 404)

		assert_raises(RuntimeError) do
			customer.sip_account.sync
		end
		assert_equal "test", customer.sip_account.username
	end
	em :test_sip_account_error


M test/test_helper.rb => test/test_helper.rb +4 -0
@@ 94,6 94,10 @@ CONFIG = {
			USD: "merchant_usd"
		}
	},
	sip: {
		realm: "sip.example.com",
		app: "sipappid"
	},
	credit_card_url: ->(*) { "http://creditcard.example.com" },
	electrum_notify_url: ->(*) { "http://notify.example.com" },
	upstream_domain: "example.net"

M test/test_sip_account.rb => test/test_sip_account.rb +45 -57
@@ 4,21 4,18 @@ require "test_helper"
require "sip_account"

class SipAccount
	public :password, :url

	class New
		public :password
	end
	attr_reader :password
end

class SipAccountTest < Minitest::Test
	def setup
		@sip = SipAccount.new(
			url: "https://api.catapult.inetwork.com/v1/" \
			     "users/catapult_user/domains/catapult_domain/endpoints/test",
			username: "12345",
			password: "old password"
		)
			BandwidthIris::SipCredential.new(
				user_name: "12345",
				realm: "sip.example.com",
				http_voice_v2_app_id: "sipappid"
			)
		).with(password: "old password")
	end

	def test_with_random_password


@@ 32,61 29,52 @@ class SipAccountTest < Minitest::Test
		form = @sip.form
		assert_equal "12345", form.field("username").value
		assert_equal "old password", form.field("password").value
		assert_equal "host.bwapp.io.example.com", form.field("server").value
		assert_equal "sip.example.com", form.field("server").value
	end

	def test_put
		delete = stub_request(:delete, @sip.url).with(
			headers: {
				"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
			}
		).to_return(status: 200)

		post = stub_request(
			:post,
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/catapult_domain/endpoints"
		put = stub_request(
			:put,
			"https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/12345"
		).with(
			body: open(__dir__ + "/data/catapult_create_sip.json").read.chomp,
			body: {
				Hash1: "73b05bcaf9096438c978aecff5f7cc45",
				Hash1b: "2b7fe68f6337ef4db29e752684a18db4",
				Realm: "sip.example.com",
				HttpVoiceV2AppId: "sipappid"
			}.to_xml(indent: 0, root: "SipCredential"),
			headers: {
				"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0",
				"Content-Type" => "application/json"
				"Authorization" => "Basic Og=="
			}
		).to_return(
			status: 201,
			headers: { "Location" => "http://example.com/endpoint" }
		)

		new_sip = @sip.put.sync
		assert_equal "http://example.com/endpoint", new_sip.url
		assert_requested delete
		assert_requested post
		new_sip = @sip.put
		assert_equal "12345", new_sip.username
		assert_requested put
	end
	em :test_put

	def test_put_delete_fail
		stub_request(:delete, @sip.url).to_return(status: 400)
		assert_raises(RuntimeError) { @sip.put.sync }
	end
	em :test_put_delete_fail

	def test_put_post_fail
		stub_request(:delete, @sip.url).to_return(status: 200)
	def test_put_fail
		stub_request(
			:post,
			"https://api.catapult.inetwork.com/v1/users/" \
			"catapult_user/domains/catapult_domain/endpoints"
			:put,
			"https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/12345"
		).to_return(status: 400)
		assert_raises(RuntimeError) { @sip.put.sync }
		assert_raises(BandwidthIris::Errors::GenericError) { @sip.put }
	end
	em :test_put_post_fail
	em :test_put_fail

	class NewTest < Minitest::Test
		def setup
			@sip = SipAccount::New.new(
				username: "12345",
				password: "old password"
			)
			@sip = SipAccount.new(
				BandwidthIris::SipCredential.new(
					user_name: "12345",
					realm: "sip.example.com",
					http_voice_v2_app_id: "sipappid"
				)
			).with(password: "old password")
		end

		def test_with_random_password


@@ 98,22 86,22 @@ class SipAccountTest < Minitest::Test

		def test_put
			post = stub_request(
				:post,
				"https://api.catapult.inetwork.com/v1/users/" \
				"catapult_user/domains/catapult_domain/endpoints"
				:put,
				"https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/12345"
			).with(
				body: open(__dir__ + "/data/catapult_create_sip.json").read.chomp,
				body: {
					Hash1: "73b05bcaf9096438c978aecff5f7cc45",
					Hash1b: "2b7fe68f6337ef4db29e752684a18db4",
					Realm: "sip.example.com",
					HttpVoiceV2AppId: "sipappid"
				}.to_xml(indent: 0, root: "SipCredential"),
				headers: {
					"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0",
					"Content-Type" => "application/json"
					"Authorization" => "Basic Og=="
				}
			).to_return(
				status: 201,
				headers: { "Location" => "http://example.com/endpoint" }
			)
			).to_return(status: 201)

			new_sip = @sip.put.sync
			assert_equal "http://example.com/endpoint", new_sip.url
			new_sip = @sip.put
			assert_equal "12345", new_sip.username
			assert_requested post
		end
		em :test_put