Added more tests and a way to send responses to the user without going through hubot

This commit is contained in:
Melanie Mendoza 2018-08-13 17:42:19 -07:00
Родитель a3871df3c9
Коммит 9007425105
7 изменённых файлов: 384 добавлений и 311 удалений

Просмотреть файл

@ -43,6 +43,15 @@ class TextMiddleware extends BaseMiddleware
return message return message
# Constructs a text message response to indicate an error to the user in the
# message channel they are using
constructErrorResponse: (activity, text) ->
payload =
type: 'message'
text: "#{text}"
address: activity?.address
return payload
# Indicates that the authorization isn't supported for this middleware # Indicates that the authorization isn't supported for this middleware
supportsAuth: () -> supportsAuth: () ->
return false return false

Просмотреть файл

@ -23,7 +23,7 @@ class BotFrameworkAdapter extends Adapter
constructor: (robot) -> constructor: (robot) ->
super robot super robot
@appId = process.env.BOTBUILDER_APP_ID @appId = process.env.BOTBUILDER_APP_ID
@appPassword = process.env.BOTBUILDER_APP_PASSWORD @appPassword = process.env.BOTBUILDER_APP_PASSWORD
@endpoint = process.env.BOTBUILDER_ENDPOINT || "/api/messages" @endpoint = process.env.BOTBUILDER_ENDPOINT || "/api/messages"
@enableAuth = process.env.HUBOT_TEAMS_ENABLE_AUTH || 'false' @enableAuth = process.env.HUBOT_TEAMS_ENABLE_AUTH || 'false'
robot.logger.info "#{LogPrefix} Adapter loaded. Using appId #{@appId}" robot.logger.info "#{LogPrefix} Adapter loaded. Using appId #{@appId}"
@ -49,12 +49,21 @@ class BotFrameworkAdapter extends Adapter
@connector.onEvent (events, cb) => @onBotEvents events, cb @connector.onEvent (events, cb) => @onBotEvents events, cb
@connector.onInvoke (events, cb) => @sendTextToHubot events, cb @connector.onInvoke (events, cb) => @menuCardInvoke events, cb
sendTextToHubot: (invokeEvent, cb) ->
invokeEvent.text = invokeEvent.value.hubotMessage # If the command for the invoke doesn't need user input, handle the command
delete invokeEvent.value # normally. If it does need user input, return a prompt for user input.
@handleActivity(invokeEvent) menuCardInvoke: (invokeEvent, cb) ->
middleware = @using(invokeEvent.source)
payload = middleware.maybeConstructUserInputPrompt(invokeEvent)
if payload == null
invokeEvent.text = invokeEvent.value.hubotMessage
delete invokeEvent.value
@handleActivity(invokeEvent)
else
@sendPayload(@robot, payload)
return
using: (name) -> using: (name) ->
MiddlewareClass = Middleware.middlewareFor(name) MiddlewareClass = Middleware.middlewareFor(name)
@ -68,7 +77,8 @@ class BotFrameworkAdapter extends Adapter
handleActivity: (activity) -> handleActivity: (activity) ->
console.log("handle activity") console.log("handle activity")
console.log(activity) console.log(activity)
@robot.logger.info "#{LogPrefix} Handling activity Channel: #{activity.source}; type: #{activity.type}" @robot.logger.info "#{LogPrefix} Handling activity Channel:
#{activity.source}; type: #{activity.type}"
# Construct the middleware # Construct the middleware
middleware = @using(activity.source) middleware = @using(activity.source)
@ -79,17 +89,32 @@ class BotFrameworkAdapter extends Adapter
# the text middleware, otherwise use the Teams middleware # the text middleware, otherwise use the Teams middleware
if not middleware.supportsAuth() if not middleware.supportsAuth()
if @enableAuth == 'true' if @enableAuth == 'true'
@robot.logger.info "#{LogPrefix} Message source doesn't support authorization" # @robot.logger.info "#{LogPrefix} Message source doesn't support authorization"
activity.text = "hubot return source authorization not supported error" # activity.text = "hubot return source authorization not supported error"
# This redundant section is included if we do the short circuit sendPayload thing # # This redundant section is included if we do the short circuit sendPayload thing
event = middleware.toReceivable activity # event = middleware.toReceivable activity
if event? # if event?
@robot.receive event # @robot.receive event
# A payload has at least type and address
@robot.logger.info "#{LogPrefix} Authorization isn\'t supported for the channel"
text = "Authorization isn't supported for the channel"
payload = middleware.constructErrorResponse(activity, text)
@sendPayload(@robot, payload)
return
else else
event = middleware.toReceivable activity event = middleware.toReceivable activity
if event? if event?
@robot.receive event @robot.receive event
else else
# Check for clicks from the dropdown menu
if activity?.value?.needsUserInput == 'true'
payload = middleware.maybeConstructUserInputPrompt(activity)
if payload != null
@sendPayload(@robot, payload)
return
activity.text = activity.value.hubotMessage
delete activity.value
# Construct a TeamsChatConnector to pass to toReceivable # Construct a TeamsChatConnector to pass to toReceivable
teamsConnector = new BotBuilderTeams.TeamsChatConnector { teamsConnector = new BotBuilderTeams.TeamsChatConnector {
appId: @robot.adapter.appId appId: @robot.adapter.appId
@ -99,43 +124,18 @@ class BotFrameworkAdapter extends Adapter
if event? if event?
console.log("********************************") console.log("********************************")
console.log(event) console.log(event)
# If unauthorized error occurred, overwrite the text
if unauthorizedError if unauthorizedError
event.text = "hubot return unauthorized user error" @robot.logger.info "#{LogPrefix} Unauthorized user, sending error"
text = "You are not authorized to send commands to hubot.
To gain access, talk to your admins:"
payload = middleware.constructErrorResponse(activity, text, true)
@sendPayload(@robot, payload)
return
@robot.receive event @robot.receive event
# # Return an error to the user if authorization is enabled and the message
# # is either from a channel that doesn't support auth or if the user who sent the
# # message isn't authorized
# authorizedUsers = @robot.brain.get("authorizedUsers")
# aadObjectId = activity?.address?.user?.aadObjectId
# if @enableAuth == 'true'
# if middleware.supportsAuth()
# if aadObjectId is undefined or authorizedUsers[aadObjectId] is undefined
# @robot.logger.info "#{LogPrefix} Unauthorized user; returning error"
# activity.text = "hubot return unauthorized user error"
# # Change this to make a call to a middleware function that returns
# # a payload with the error text to return
# else
# @robot.logger.info "#{LogPrefix} Message source doesn't support authorization"
# activity.text = "hubot return source authorization not supported error"
# # If authorization isn't supported by the activity source, use
# # the text middleware, otherwise use the Teams middleware
# if not middleware.supportsAuth()
# event = middleware.toReceivable activity
# if event?
# @robot.receive event
# else
# # Construct a TeamsChatConnector to pass to toReceivable
# teamsConnector = new BotBuilderTeams.TeamsChatConnector {
# appId: @robot.adapter.appId
# appPassword: @robot.adapter.appPassword
# }
# middleware.toReceivable activity, teamsConnector, (event) =>
# if event?
# @robot.receive event
send: (context, messages...) -> send: (context, messages...) ->
@robot.logger.info "#{LogPrefix} send" @robot.logger.info "#{LogPrefix} send"
@reply context, messages... @reply context, messages...

Просмотреть файл

@ -3,12 +3,12 @@
HubotQueryParts = require './hubot-query-parts' HubotQueryParts = require './hubot-query-parts'
maybeConstructCard = (response, query) -> maybeConstructResponseCard = (response, query) ->
# Check if the response is from a list commands follow up button press. # Check if the response is from a list commands follow up button press.
# If so, construct the needed input card and return it # If so, construct the needed input card and return it
index = query.search("generate input card") # index = query.search("generate input card")
if (index != -1) # if (index != -1)
return constructMenuInputCard(query.replace("generate input card", ""), response.text) # return maybeConstructMenuInputCard(response.text)
# Check if response.text matches one of the reg exps in the LUT # Check if response.text matches one of the reg exps in the LUT
for regex of HubotResponseCards for regex of HubotResponseCards
@ -20,10 +20,19 @@ maybeConstructCard = (response, query) ->
return card return card
return null return null
# Constructs an input card # Constructs an input card if needed or returns null if the
constructMenuInputCard = (query, text) -> # query doesn't need user input
card = initializeAdaptiveCard(query) maybeConstructMenuInputCard = (query) ->
queryParts = HubotQueryParts[text] queryParts = HubotQueryParts[query]
# Check if the query needs a user input card
console.log(queryParts.inputParts is undefined)
console.log(queryParts.inputParts == undefined)
if queryParts.inputParts is undefined
return null
shortQuery = constructShortQuery(query)
card = initializeAdaptiveCard(shortQuery)
# Create the input fields of the sub card # Create the input fields of the sub card
for i in [0 ... queryParts.inputParts.length] for i in [0 ... queryParts.inputParts.length]
@ -38,7 +47,8 @@ constructMenuInputCard = (query, text) ->
# Create selector # Create selector
if index != -1 if index != -1
card.content.body.push(addSelector(query, inputPart.substring(index + 1), query + " - input" + "#{i}")) card.content.body.push(addSelector(query, inputPart.substring(index + 1),
query + " - input" + "#{i}"))
# Create text input # Create text input
else else
card.content.body.push(addTextInput(query + " - input" + "#{i}", inputPart)) card.content.body.push(addTextInput(query + " - input" + "#{i}", inputPart))
@ -126,13 +136,7 @@ addTextInput = (id, inputPart) ->
getFollowUpButtons = (query, regex) -> getFollowUpButtons = (query, regex) ->
actions = [] actions = []
for followUpQuery in HubotResponseCards[regex] for followUpQuery in HubotResponseCards[regex]
shortQuery = constructShortQuery(followUpQuery)
# Create a short version of the command by including only the
# start of the command to the first user input marked by ( or <
shortQueryEnd = followUpQuery.search(new RegExp("[(<]"))
if shortQueryEnd == -1
shortQueryEnd = followUpQuery.length
shortQuery = followUpQuery.substring(0, shortQueryEnd)
action = { action = {
'title': shortQuery 'title': shortQuery
} }
@ -246,6 +250,15 @@ appendCardActions = (card1, card2) ->
return card1 return card1
# Create a short version of the command by including only the
# start of the command to the first user input marked by ( or <
constructShortQuery = (query) ->
shortQueryEnd = query.search(new RegExp("[(<]"))
if shortQueryEnd == -1
shortQueryEnd = query.length
shortQuery = query.substring(0, shortQueryEnd)
return shortQuery.trim()
# HubotResponseCards maps from regex's of hubot queries to an array of follow up hubot # HubotResponseCards maps from regex's of hubot queries to an array of follow up hubot
# queries stored as strings # queries stored as strings
HubotResponseCards = { HubotResponseCards = {
@ -285,7 +298,8 @@ HubotResponseCards = {
} }
module.exports = { module.exports = {
maybeConstructCard, maybeConstructResponseCard,
maybeConstructMenuInputCard,
appendCardBody, appendCardBody,
appendCardActions appendCardActions
} }

Просмотреть файл

@ -25,7 +25,7 @@
BotBuilder = require 'botbuilder' BotBuilder = require 'botbuilder'
BotBuilderTeams = require 'botbuilder-teams' BotBuilderTeams = require 'botbuilder-teams'
HubotResponseCards = require './hubot-response-cards' HubotResponseCards = require './hubot-response-cards'
#MicrosoftGraph = require '@microsoft/microsoft-graph-client' HubotQueryParts = require './hubot-query-parts'
{ Robot, TextMessage, Message, User } = require 'hubot' { Robot, TextMessage, Message, User } = require 'hubot'
{ BaseMiddleware, registerMiddleware } = require './adapter-middleware' { BaseMiddleware, registerMiddleware } = require './adapter-middleware'
LogPrefix = "hubot-msteams:" LogPrefix = "hubot-msteams:"
@ -41,7 +41,8 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
@allowedTenants = [] @allowedTenants = []
if process.env.HUBOT_OFFICE365_TENANT_FILTER? if process.env.HUBOT_OFFICE365_TENANT_FILTER?
@allowedTenants = process.env.HUBOT_OFFICE365_TENANT_FILTER.split(",") @allowedTenants = process.env.HUBOT_OFFICE365_TENANT_FILTER.split(",")
@robot.logger.info("#{LogPrefix} Restricting tenants to #{JSON.stringify(@allowedTenants)}") @robot.logger.info("#{LogPrefix} Restricting tenants to \
#{JSON.stringify(@allowedTenants)}")
toReceivable: (activity, teamsConnector, authEnabled, cb) -> toReceivable: (activity, teamsConnector, authEnabled, cb) ->
@robot.logger.info "#{LogPrefix} toReceivable" @robot.logger.info "#{LogPrefix} toReceivable"
@ -54,45 +55,43 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
# Get the user # Get the user
user = getUser(activity) user = getUser(activity)
user = @robot.brain.userForId(user.id, user) user = @robot.brain.userForId(user.id, user)
console.log("++++++++++++++++++++++++++++++++++++++++++++++")
console.log(user)
# We don't want to save the activity or room in the brain since its something that changes per chat. # We don't want to save the activity or room in the brain since its
# something that changes per chat.
user.activity = activity user.activity = activity
user.room = getRoomId(activity) user.room = getRoomId(activity)
if activity.type != 'message' && activity.type != 'invoke' # Fetch the roster of members to do authorization based on UPN
return new Message(user) teamsConnector.fetchMembers activity?.address?.serviceUrl, \
else activity?.address?.conversation?.id, (err, chatMembers) =>
# Fetch the roster of members to do authorization based on UPN if err
teamsConnector.fetchMembers activity?.address?.serviceUrl, activity?.address?.conversation?.id, (err, chatMembers) => console.log("YUP AN ERR")
if err return
return
console.log("==========================================")
console.log(chatMembers)
# Set the unauthorizedError to true if auth is enabled and the user who sent
# the message is not authorized
unauthorizedError = false
if authEnabled
authorizedUsers = @robot.brain.get("authorizedUsers")
console.log("---------------")
console.log(chatMembers[0].userPrincipalName)
# Get the sender's UPN
senderUPN = getSenderUPN(user, chatMembers)
console.log(senderUPN)
if senderUPN is undefined or authorizedUsers[senderUPN] is undefined
@robot.logger.info "#{LogPrefix} Unauthorized user; returning error"
unauthorizedError = true
# activity.text = "hubot return unauthorized user error"
# Change this to make a call to a middleware function that returns
# a payload with the error text to return
# Add the sender's UPN to user
user.userPrincipalName = senderUPN
activity = fixActivityForHubot(activity, @robot, chatMembers) # Set the unauthorizedError to true if auth is enabled and the user who sent
message = new TextMessage(user, activity.text, activity.address.id) # the message is not authorized
cb(message, unauthorizedError) unauthorizedError = false
if authEnabled
authorizedUsers = @robot.brain.get("authorizedUsers")
# Get the sender's UPN
senderUPN = getSenderUPN(user, chatMembers)
if senderUPN is undefined or authorizedUsers[senderUPN] is undefined
@robot.logger.info "#{LogPrefix} Unauthorized user; returning error"
unauthorizedError = true
# activity.text = "hubot return unauthorized user error"
# Change this to make a call to a middleware function that returns
# a payload with the error text to return
# Add the sender's UPN to user
user.userPrincipalName = senderUPN
# Return a generic message if the activity isn't a message or invoke
if activity.type != 'message' && activity.type != 'invoke'
cb(new Message(user), unauthorizedError)
activity = fixActivityForHubot(activity, @robot, chatMembers)
message = new TextMessage(user, activity.text, activity.address.id)
cb(message, unauthorizedError)
toSendable: (context, message) -> toSendable: (context, message) ->
@robot.logger.info "#{LogPrefix} toSendable" @robot.logger.info "#{LogPrefix} toSendable"
@ -109,7 +108,7 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
# If the query sent by the user should trigger a card, # If the query sent by the user should trigger a card,
# construct the card to attach to the response # construct the card to attach to the response
# and remove sentQuery from the brain # and remove sentQuery from the brain
card = HubotResponseCards.maybeConstructCard(response, activity.text) card = HubotResponseCards.maybeConstructResponseCard(response, activity.text)
if card != null if card != null
delete response.text delete response.text
response.attachments = [card] response.attachments = [card]
@ -156,7 +155,7 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
return true return true
# Combines the text and attachments of multiple hubot messages sent in succession. # Combines the text and attachments of multiple hubot messages sent in succession.
# Most of the first received response is kept, and the text and attachments of # Most of the first received response is kept, and the text and attachments of
# subsequent responses received within 500ms of the first are combined into the # subsequent responses received within 500ms of the first are combined into the
# first response. # first response.
combineResponses: (storedPayload, newPayload) -> combineResponses: (storedPayload, newPayload) ->
@ -197,9 +196,53 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
if attachment.contentType != "application/vnd.microsoft.card.adaptive" if attachment.contentType != "application/vnd.microsoft.card.adaptive"
storedMessage.attachments.push(attachment) storedMessage.attachments.push(attachment)
else else
storedCard = HubotResponseCards.appendCardBody(storedCard, attachment) storedCard = HubotResponseCards.appendCardBody(storedCard, \
storedCard = HubotResponseCards.appendCardActions(storedCard, attachment) attachment)
storedCard = HubotResponseCards.appendCardActions(storedCard, \
attachment)
# Constructs a text message response to indicate an error to the user in the
# message channel they are using
constructErrorResponse: (activity, text, appendAdmins) ->
if appendAdmins
authorizedUsers = @robot.brain.get("authorizedUsers")
for userKey, isAdmin of authorizedUsers
if isAdmin
text = """#{text}
#{userKey}"""
typing =
type: 'typing'
address: activity?.address
payload =
type: 'message'
text: "#{text}"
address: activity?.address
return [typing, payload]
# Constructs a response containing a card for user input if needed or null
# if user input is not needed
maybeConstructUserInputPrompt: (event) ->
query = event.value.hubotMessage
# Remove the hubot prefix, if there is one
query = query.replace("hubot ", "")
console.log(query)
card = HubotResponseCards.maybeConstructMenuInputCard(query)
if card is null
console.log("CARD IS NULL")
return null
response =
type: 'message'
address: event?.address
attachments: [
card
]
return response
############################################################################# #############################################################################
# Helper methods for generating richer messages # Helper methods for generating richer messages
@ -256,7 +299,8 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
entities = activity?.entities || [] entities = activity?.entities || []
if not Array.isArray(entities) if not Array.isArray(entities)
entities = [entities] entities = [entities]
return entities.filter((entity) -> entity.type == "mention" && (not userId? || userId == entity.mentioned?.id)) return entities.filter((entity) -> entity.type == "mention" && \
(not userId? || userId == entity.mentioned?.id))
# Returns the provided user's userPrincipalName (UPN) or null if one cannot be found # Returns the provided user's userPrincipalName (UPN) or null if one cannot be found
getSenderUPN = (user, chatMembers) -> getSenderUPN = (user, chatMembers) ->
@ -270,14 +314,16 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
# Fixes the activity to have the proper information for Hubot # Fixes the activity to have the proper information for Hubot
# 1. Constructs the text command to send to hubot if the event is from a # 1. Constructs the text command to send to hubot if the event is from a
# submit on an adaptive card (containing the value property). # submit on an adaptive card (containing the value property).
# 2. Replaces all occurrences of the channel's bot at mention name with the configured name in hubot. # 2. Replaces all occurrences of the channel's bot at mention name with the configured
# name in hubot.
# The hubot's configured name might not be the same name that is sent from the chat service in # The hubot's configured name might not be the same name that is sent from the chat service in
# the activity's text. # the activity's text.
# 3. Replaces all occurrences of @ mentions to users with their aad object id if the user is # 3. Replaces all occurrences of @ mentions to users with their aad object id if the user is
# on the roster of chanenl members from Teams. If a mentioned user is not in the chat roster, # on the roster of chanenl members from Teams. If a mentioned user is not in the chat roster,
# the mention is replaced with their name. # the mention is replaced with their name.
# 4. Prepends hubot's name to the message for personal messages if it's not already # 4. Trims all whitespace and newlines from the beginning and end of the text.
# 5. Prepends hubot's name to the message for personal messages if it's not already
# there # there
fixActivityForHubot = (activity, robot, chatMembers) -> fixActivityForHubot = (activity, robot, chatMembers) ->
# If activity.value exists, the command is from a card and the correct # If activity.value exists, the command is from a card and the correct
@ -309,10 +355,11 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
# Set the constructed query as the text of the activity # Set the constructed query as the text of the activity
activity.text = text activity.text = text
return activity #return activity
if not activity?.text? || typeof activity.text isnt 'string' if not activity?.text? || typeof activity.text isnt 'string'
return activity return activity
myChatId = activity?.address?.bot?.id myChatId = activity?.address?.bot?.id
if not myChatId? if not myChatId?
return activity return activity
@ -332,6 +379,9 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
replacement = member.userPrincipalName replacement = member.userPrincipalName
# *** replacement = member.objectId # *** replacement = member.objectId
activity.text = activity.text.replace(mentionTextRegExp, replacement) activity.text = activity.text.replace(mentionTextRegExp, replacement)
# Remove leading/trailing whitespace and newlines
activity.text = activity.text.trim()
# prepends the robot's name for direct messages if it's not already there # prepends the robot's name for direct messages if it's not already there
if getConversationType(activity) == 'personal' && activity.text.search(robot.name) != 0 if getConversationType(activity) == 'personal' && activity.text.search(robot.name) != 0

Просмотреть файл

@ -4,15 +4,28 @@ expect = chai.expect
BotFrameworkAdapter = require '../src/adapter' BotFrameworkAdapter = require '../src/adapter'
MockRobot = require './mock-robot' MockRobot = require './mock-robot'
describe 'Main Adapter', -> describe 'Main Adapter', ->
describe 'Test Auth', -> describe 'Test Authorization Setup', ->
beforeEach -> beforeEach ->
process.env.HUBOT_TEAMS_INITIAL_ADMINS = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee,eight888-four-4444-fore-twelve121212' process.env.HUBOT_TEAMS_INITIAL_ADMINS = 'an-1_20@em.ail,authorized_user@email.la'
process.env.BOTBUILDER_APP_ID = 'botbuilder-app-id' # process.env.BOTBUILDER_APP_ID = 'botbuilder-app-id'
process.env.BOTBUILDER_APP_PASSWORD = 'botbuilder-app-password' # process.env.BOTBUILDER_APP_PASSWORD = 'botbuilder-app-password'
process.env.HUBOT_DEBUG_LEVEL = 'error'
process.env.HUBOT_TEAMS_ENABLE_AUTH = 'true' process.env.HUBOT_TEAMS_ENABLE_AUTH = 'true'
it 'should not set initial admins when auth enable is not set', ->
# Setup
delete process.env.HUBOT_TEAMS_ENABLE_AUTH
robot = new MockRobot
# Action
expect(() ->
adapter = BotFrameworkAdapter.use(robot)
).to.not.throw()
# Assert
expect(robot.brain.get("authorizedUsers")).to.be.null
it 'should not set initial admins when auth is not enabled', -> it 'should not set initial admins when auth is not enabled', ->
# Setup # Setup
process.env.HUBOT_TEAMS_ENABLE_AUTH = 'false' process.env.HUBOT_TEAMS_ENABLE_AUTH = 'false'
@ -25,6 +38,16 @@ describe 'Main Adapter', ->
# Assert # Assert
expect(robot.brain.get("authorizedUsers")).to.be.null expect(robot.brain.get("authorizedUsers")).to.be.null
it 'should throw error when auth is enabled and initial admins', ->
# Setup
delete process.env.HUBOT_TEAMS_INITIAL_ADMINS
robot = new MockRobot
# Action and Assert
expect(() ->
adapter = BotFrameworkAdapter.use(robot)
).to.throw()
it 'should set initial admins when auth is enabled', -> it 'should set initial admins when auth is enabled', ->
# Setup # Setup
@ -37,50 +60,22 @@ describe 'Main Adapter', ->
# Assert # Assert
expect(robot.brain.get("authorizedUsers")).to.eql { expect(robot.brain.get("authorizedUsers")).to.eql {
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee': true 'an-1_20@em.ail': true
'eight888-four-4444-fore-twelve121212': true 'authorized_user@email.la': true
} }
it 'should not set initial admins when auth enable is not set', ->
# Setup
delete process.env.HUBOT_TEAMS_ENABLE_AUTH
robot = new MockRobot
# Action
expect(() ->
adapter = BotFrameworkAdapter.use(robot)
).to.not.throw()
# Assert
expect(robot.brain.get("authorizedUsers")).to.be.null
it 'should allow messages from authorized users', -> describe 'Test Authorization Support for Teams Channel', ->
# Setup robot = null
adapter = null
event = null
beforeEach ->
process.env.HUBOT_TEAMS_INITIAL_ADMINS = 'an-1_20@em.ail,authorized_user@email.la'
process.env.BOTBUILDER_APP_ID = 'botbuilder-app-id'
process.env.BOTBUILDER_APP_PASSWORD = 'botbuilder-app-password'
process.env.HUBOT_TEAMS_ENABLE_AUTH = 'true'
robot = new MockRobot robot = new MockRobot
adapter = BotFrameworkAdapter.use(robot) adapter = BotFrameworkAdapter.use(robot)
robot.adapter = adapter robot.adapter = adapter
adapter.connector.fetchMembers = (serviceUrl, teamId, callback) ->
members = [
{
id: 'id-1'
objectId: 'aad-object-id-1'
name: 'user1 one'
givenName: 'user1'
surname: 'one'
email: 'one@user.one'
userPrincipalName: 'one@user.one'
},
{
id: 'user-id'
objectId: 'eight888-four-4444-fore-twelve121212'
name: 'user-name'
givenName: 'user-'
surname: 'name'
email: 'em@ai.l'
userPrincipalName: 'em@ai.l'
}
]
callback false, members
event = event =
type: 'message' type: 'message'
text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it' text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
@ -112,88 +107,44 @@ describe 'Main Adapter', ->
id: "user-id" id: "user-id"
name: "user-name" name: "user-name"
aadObjectId: "eight888-four-4444-fore-twelve121212" aadObjectId: "eight888-four-4444-fore-twelve121212"
userPrincipalName: "user-UPN"
serviceUrl: 'url-serviceUrl/a-url' serviceUrl: 'url-serviceUrl/a-url'
it 'should return authorization not supported error for non-Teams channels', ->
# Setup
event.source = 'authorization-not-supported-source'
# Action # Action
expect(() -> expect(() ->
result = adapter.handleActivity(event) adapter.handleActivity(event)
).to.not.throw() ).to.not.throw()
# Assert # Assert
expect(robot.brain.get("authorizedUsers")).to.eql { result = robot.brain.get("event")
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee': true expect(result.text).to.eql "hubot return source authorization not supported error"
'eight888-four-4444-fore-twelve121212': true
}
# it 'should overwrite the hubot command text to return an error message to unauthorized users', -> it 'should work when authorization is enabled and message is from Teams', ->
# # Setup # Setup
# robot = new Robot('../../hubot-botframework', 'botframework', false, 'hubot')
# adapter = BotFrameworkAdapter.use(robot)
# adapter.connector.fetchMembers = (serviceUrl, teamId, callback) ->
# members = [
# {
# id: 'id-1'
# objectId: 'aad-object-id-1'
# name: 'user1 one'
# givenName: 'user1'
# surname: 'one'
# email: 'one@user.one'
# userPrincipalName: 'one@user.one'
# },
# {
# id: 'user-id'
# objectId: 'eight888-four-4444-fore-twelve121212'
# name: 'user-name'
# givenName: 'user-'
# surname: 'name'
# email: 'em@ai.l'
# userPrincipalName: 'em@ai.l'
# }
# ]
# callback false, members
# event =
# type: 'message'
# text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
# agent: 'tests'
# source: 'msteams'
# entities: [
# type: "mention"
# text: "<at>Bot</at>"
# mentioned:
# id: "bot-id"
# name: "bot-name"
# ,
# type: "mention"
# text: "<at>User</at>"
# mentioned:
# id: "user-id"
# name: "user-name"
# ]
# attachments: []
# sourceEvent:
# tenant:
# id: "tenant-id"
# address:
# conversation:
# id: "19:conversation-id"
# bot:
# id: "bot-id"
# user:
# id: "id-1"
# name: "user1 one"
# aadObjectId: "aad-object-id-1"
# serviceUrl: 'url-serviceUrl/a-url'
# # Action # Action
# expect(() -> expect(() ->
# result = adapter.handleActivity(event) adapter.handleActivity(event)
# ).to.not.throw() ).to.not.throw()
# console.log("=======================================") # Assert
# console.log(adapter) result = robot.brain.get("event")
# # Assert it 'should work when message is from invoke', ->
# expect(robot.brain.get("authorizedUsers")).to.eql { # Setup
# 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee': true event.type = 'invoke'
# 'eight888-four-4444-fore-twelve121212': true event.value =
# } hubotMessage: 'hubot do something'
delete event.text
# Action
expect(() ->
adapter.sendTextToHubot(event)
).to.not.throw()
# Assert

Просмотреть файл

@ -9,7 +9,19 @@ class MockRobot
"hubot b - does something b" "hubot b - does something b"
] ]
@brain = @brain =
userForId: -> {} data: {}
userForId: (id, options) ->
user = {
id: "#{id}"
name: "a-hubot-user-name"
}
if options is null
return user
else
for key of options
user[key] = options[key]
return user
users: -> [] users: -> []
get: (key) -> get: (key) ->
if @data is undefined if @data is undefined
@ -18,16 +30,9 @@ class MockRobot
if key == storedKey if key == storedKey
return @data[storedKey] return @data[storedKey]
return null return null
set: (key, value) ->
@data[key] = value
if process.env.HUBOT_TEAMS_ENABLE_AUTH == 'true' receive: (event) ->
if process.env.HUBOT_TEAMS_INITIAL_ADMINS @brain.data["event"] = event
@brain.data = {}
authorizedUsers = {}
for admin in process.env.HUBOT_TEAMS_INITIAL_ADMINS.split(",")
authorizedUsers[admin] = true
@brain.data["authorizedUsers"] = authorizedUsers
else
throw new Error("HUBOT_TEAMS_INITIAL_ADMINS is required")
receive: -> {}
module.exports = MockRobot module.exports = MockRobot

Просмотреть файл

@ -11,15 +11,21 @@ describe 'MicrosoftTeamsMiddleware', ->
describe 'toReceivable', -> describe 'toReceivable', ->
robot = null robot = null
event = null event = null
chatMembers = null teamsChatConnector = null
cb = -> {} authEnabled = false
cb = ->
robot.receive event
beforeEach -> beforeEach ->
delete process.env.HUBOT_OFFICE365_TENANT_FILTER delete process.env.HUBOT_OFFICE365_TENANT_FILTER
robot = new MockRobot robot = new MockRobot
adapter = BotFrameworkAdapter.use(robot) options = {
robot.adapter = adapter appId: 'botframework-app-id'
appPassword: 'botframework-app-password'
}
teamsChatConnector = new MockTeamsChatConnector(options)
authEnabled = false
event = event =
type: 'message' type: 'message'
text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it' text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
@ -52,97 +58,135 @@ describe 'MicrosoftTeamsMiddleware', ->
name: "user-name" name: "user-name"
aadObjectId: 'eight888-four-4444-fore-twelve121212' aadObjectId: 'eight888-four-4444-fore-twelve121212'
it 'should allow messages without tenant id when tenant filter is empty', ->
it 'should allow messages when auth is not enabled', ->
# Setup # Setup
console.log("**************************************")
console.log(MicrosoftTeamsMiddleware.toString())
delete event.sourceEvent delete event.sourceEvent
teamsMiddleware = new MicrosoftTeamsMiddleware(robot) teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
options = {
appId: 'botframework-app-id'
appPassword: 'botframework-app-password'
}
teamsChatConnector = new MockTeamsChatConnector(options)
# Action # Action
receivable = null
expect(() -> expect(() ->
receivable = teamsMiddleware.toReceivable(event, teamsChatConnector, cb) teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
).to.not.throw() ).to.not.throw()
# Assert # Assert
expect(receivable).to.be.a('Object') result = robot.brain.get("event")
expect(result).to.be.a('Object')
# it 'should allow messages with tenant id when tenant filter is empty', -> it 'should set unauthorized error for message when user isn\'t authorized', ->
# # Setup # Setup
# teamsMiddleware = new MicrosoftTeamsMiddleware(robot) robot.brain.data["authorizedUsers"] =
'an-1_20@em.ail': true
'authorized_user@email.la': false
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
authEnabled = true
cb = (event, unauthorizedError) ->
robot.brain.data["unauthError"] = unauthorizedError
robot.receive event
# # Action # Action
# receivable = null expect(() ->
# expect(() -> teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
# receivable = teamsMiddleware.toReceivable(event, cb) ).to.not.throw()
# ).to.not.throw()
# # Assert # Assert
# expect(receivable).to.be.a('Object') result = robot.brain.get("event")
expect(result).to.be.a('Object')
expect(robot.brain.get("unauthError")).to.be.true
# it 'should allow messages from allowed tenant ids', -> it 'should allow messages without tenant id when tenant filter is empty', ->
# # Setup # Setup
# process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id delete event.sourceEvent
# teamsMiddleware = new MicrosoftTeamsMiddleware(robot) teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# # Action # Action
# receivable = null expect(() ->
# expect(() -> teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
# receivable = teamsMiddleware.toReceivable(event, cb) ).to.not.throw()
# ).to.not.throw()
# # Assert # Assert
# expect(receivable).to.be.a('Object') result = robot.brain.get("event")
expect(result).to.be.a('Object')
# it 'should block messages from unallowed tenant ids', -> it 'should allow messages with tenant id when tenant filter is empty', ->
# # Setup # Setup
# process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# event.sourceEvent.tenant.id = "different-tenant-id"
# teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# # Action # Action
# receivable = null expect(() ->
# expect(() -> teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
# receivable = teamsMiddleware.toReceivable(event, cb) ).to.not.throw()
# ).to.not.throw()
# # Assert # Assert
# expect(receivable).to.be.null result = robot.brain.get("event")
expect(result).to.be.a('Object')
# it 'return generic message when appropriate type is not found', -> it 'should allow messages from allowed tenant ids', ->
# # Setup # Setup
# event.type = 'typing' process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id
# teamsMiddleware = new MicrosoftTeamsMiddleware(robot) teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# # Action # Action
# receivable = null expect(() ->
# expect(() -> teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
# receivable = teamsMiddleware.toReceivable(event, cb) ).to.not.throw()
# ).to.not.throw()
# # Assert # Assert
# expect(receivable).to.be.not.null result = robot.brain.get("event")
expect(result).to.be.a('Object')
# it 'should work when activity text is an object', -> it 'should block messages from unallowed tenant ids', ->
# # Setup # Setup
# event.text = event process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id
# teamsMiddleware = new MicrosoftTeamsMiddleware(robot) event.sourceEvent.tenant.id = "different-tenant-id"
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# # Action # Action
# receivable = null expect(() ->
# expect(() -> teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
# receivable = teamsMiddleware.toReceivable(event, cb) ).to.not.throw()
# ).to.not.throw()
# # Assert # Assert
# expect(receivable.text).to.equal(event) result = robot.brain.get("event")
expect(result).to.be.null
it 'return generic message when appropriate type is not found', ->
# Setup
event.type = 'typing'
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# Action
expect(() ->
teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
).to.not.throw()
# Assert
result = robot.brain.get("event")
expect(result).to.be.not.null
# Test when message is from follow up button
it 'should work when activity value contains query parts', ->
# Setup
# Action
# Assert
it 'should work when activity text is an object', ->
# Setup
event.text = event
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# Action
expect(() ->
teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb)
).to.not.throw()
# Assert
result = robot.brain.get("event")
expect(result.text).to.equal(event)
# it 'should work when mentions not provided', -> # it 'should work when mentions not provided', ->
# # Setup # # Setup