Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Webex InstantConnect driver #283

Merged
merged 9 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions drivers/webex/instant_connect.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class Webex::InstantConnect < PlaceOS::Driver
# Discovery Information
generic_name :InstantConnect
descriptive_name "Webex InstantConnect"
uri_base "https://mtg-broker-a.wbx2.com"

default_settings({
bot_access_token: "token",
jwt_audience: "a4d886b0-979f-4e2c-a958-3e8c14605e51",
})

@jwt_audience : String = "a4d886b0-979f-4e2c-a958-3e8c14605e51"
@bot_access_token : String = ""

def on_load
on_update
end

def on_update
@audience_setting = setting?(String, :jwt_audience) || "a4d886b0-979f-4e2c-a958-3e8c14605e51"
@bot_access_token = setting(String, :bot_access_token)
end

def create_meeting(room_id : String)
expiry = 24.hours.from_now.to_unix
request = {
"aud": @jwt_audience,
"jwt": {
"sub": room_id,
"exp": expiry,
},
}.to_json

get_meeting_details get_hash(request)
end

protected def get_meeting_details(meeting_keys)
response = get("api/v1/space/?int=jose&data=#{meeting_keys[:host]}")
raise "host token request failed with #{response.status_code}" unless response.status_code == 200 && !response.body.nil?
meeting_config = Hash(String, String | Bool).from_json(response.body)

response = get("api/v1/space/?int=jose&data=#{meeting_keys[:guest]}")
raise "guest token request failed with #{response.status_code}" unless response.status_code == 200 && !response.body.nil?
guest_token = String.from_json(response.body, root: "token")

{
# space_id seems to be an internal id for the meeting room
space_id: meeting_config["spaceId"],
host_token: meeting_config["token"],
guest_token: guest_token,
}
end

protected def get_hash(request : String)
response = post("/api/v1/joseencrypt", body: request, headers: {
"Content-Type" => "application/json",
"Authorization" => "Bearer #{@bot_access_token}",
})

raise "request failed with #{response.status_code}" unless response.status_code == 200 && !response.body.nil?

response = NamedTuple(host: Array(String), guest: Array(String)).from_json(response.body)
{
host: response[:host].first,
guest: response[:guest].first,
}
end
end
87 changes: 87 additions & 0 deletions drivers/webex/instant_connect_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
DriverSpecs.mock_driver "Webex::InstantConnect" do
# Send the request
retval = exec(:create_meeting,
room_id: "1"
)

# HTTP request to get host/guest hash
expect_http_request do |request, response|
headers = request.headers
io = request.body
if io
data = io.gets_to_end
request = JSON.parse(data)
if request.to_s.includes?(%("aud" => "a4d886b0-979f-4e2c-a958-3e8c14605e51")) && headers["Authorization"].includes?(%(Bearer))
response.status_code = 200
response << RAW_HASH_RESPONSE
else
response.status_code = 401
end
else
raise "expected request to include aud & sub details #{request.inspect}"
end
end

# HTTP request to get token/spaceId using host JWT
expect_http_request do |request, response|
headers = request.headers
if request.resource.includes?("api/v1/space/?int=jose&data=")
response.status_code = 200
response << RAW_HOST_RESPONSE
else
response.status_code = 401
end
end

# HTTP request to get token using guest JWT
expect_http_request do |request, response|
headers = request.headers
if request.resource.includes?("api/v1/space/?int=jose&data=")
response.status_code = 200
response << RAW_GUEST_RESPONSE
else
response.status_code = 401
end
end

retval.get.should eq(JSON.parse(RETVAL))
end

RAW_HOST_RESPONSE = %({
"userIdentifier": "Host",
"isLoggedIn": false,
"isHost": true,
"organizationId": "16917798-5582-49a7-92d0-4410f6964000",
"orgName": "PlaceOS",
"token": "NmFmZGQwODYtZmIzNi00OTlmLWE3N2QtNzUyNzk2MDk4NDU5MjZlNmM2YmQtNjY2_PF84_e2d06a2e-ac4e-464f-968d-a5f8a5ac6303",
"spaceId": "Y2lzY29zcGFyazovL3VzL1JPT00vODhhZGM1ODAtOThmMi0xMWVjLThiYjQtZjM2MmNkNDBlZDQ1",
"visitId": "1",
"integrationType": "jose"
})

RAW_GUEST_RESPONSE = %({
"userIdentifier": "Guest",
"isLoggedIn": false,
"isHost": false,
"organizationId": "16917798-5582-49a7-92d0-4410f6964000",
"orgName": "PlaceOS",
"token": "NmFmZGQwODYtZmIzNi05OTlmLWE3N2QtMzUyNzk2MDk4NDU5MeZlNmM2YmQtNjY2_PF84_e2d06a2e-ac4e-464f-968d-a5f8a5ac6303",
"spaceId": "Y2lzY29zcGFyazovL3VzL1JPT00vODhhZGM1ODAtOThmMi0xMWVjLThiYjQtZjM2MmNkNDBlZDQ1",
"visitId": "1",
"integrationType": "jose"
})

RAW_HASH_RESPONSE = %({
"host": [
"eyJwMnMiOiJCWXpoYmV4W"
],
"guest": [
"eyJwMnMiOiJaVVJsejNsb1"
]
})

RETVAL = %({
"token":"NmFmZGQwODYtZmIzNi00OTlmLWE3N2QtNzUyNzk2MDk4NDU5MjZlNmM2YmQtNjY2_PF84_e2d06a2e-ac4e-464f-968d-a5f8a5ac6303",
"spaceId":"Y2lzY29zcGFyazovL3VzL1JPT00vODhhZGM1ODAtOThmMi0xMWVjLThiYjQtZjM2MmNkNDBlZDQ1",
"guest_token":"NmFmZGQwODYtZmIzNi05OTlmLWE3N2QtMzUyNzk2MDk4NDU5MeZlNmM2YmQtNjY2_PF84_e2d06a2e-ac4e-464f-968d-a5f8a5ac6303"
})