diff --git a/src/adapter-middleware.coffee b/src/adapter-middleware.coffee index 9a1ce94..a14e5f0 100644 --- a/src/adapter-middleware.coffee +++ b/src/adapter-middleware.coffee @@ -43,6 +43,15 @@ class TextMiddleware extends BaseMiddleware 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 supportsAuth: () -> return false diff --git a/src/adapter.coffee b/src/adapter.coffee index 063d3ff..76f2d12 100644 --- a/src/adapter.coffee +++ b/src/adapter.coffee @@ -23,7 +23,7 @@ class BotFrameworkAdapter extends Adapter constructor: (robot) -> super robot @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" @enableAuth = process.env.HUBOT_TEAMS_ENABLE_AUTH || 'false' 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.onInvoke (events, cb) => @sendTextToHubot events, cb + @connector.onInvoke (events, cb) => @menuCardInvoke events, cb - sendTextToHubot: (invokeEvent, cb) -> - invokeEvent.text = invokeEvent.value.hubotMessage - delete invokeEvent.value - @handleActivity(invokeEvent) + + # If the command for the invoke doesn't need user input, handle the command + # normally. If it does need user input, return a prompt for user input. + 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) -> MiddlewareClass = Middleware.middlewareFor(name) @@ -68,7 +77,8 @@ class BotFrameworkAdapter extends Adapter handleActivity: (activity) -> console.log("handle 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 middleware = @using(activity.source) @@ -79,17 +89,32 @@ class BotFrameworkAdapter extends Adapter # the text middleware, otherwise use the Teams middleware if not middleware.supportsAuth() if @enableAuth == 'true' - @robot.logger.info "#{LogPrefix} Message source doesn't support authorization" - activity.text = "hubot return source authorization not supported error" - # This redundant section is included if we do the short circuit sendPayload thing - event = middleware.toReceivable activity - if event? - @robot.receive event + # @robot.logger.info "#{LogPrefix} Message source doesn't support authorization" + # activity.text = "hubot return source authorization not supported error" + # # This redundant section is included if we do the short circuit sendPayload thing + # event = middleware.toReceivable activity + # if 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 event = middleware.toReceivable activity if event? @robot.receive event 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 teamsConnector = new BotBuilderTeams.TeamsChatConnector { appId: @robot.adapter.appId @@ -99,43 +124,18 @@ class BotFrameworkAdapter extends Adapter if event? console.log("********************************") console.log(event) - # If unauthorized error occurred, overwrite the text + 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 - # # 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...) -> @robot.logger.info "#{LogPrefix} send" @reply context, messages... diff --git a/src/hubot-response-cards.coffee b/src/hubot-response-cards.coffee index 7edae24..83a6e9a 100644 --- a/src/hubot-response-cards.coffee +++ b/src/hubot-response-cards.coffee @@ -3,12 +3,12 @@ HubotQueryParts = require './hubot-query-parts' -maybeConstructCard = (response, query) -> +maybeConstructResponseCard = (response, query) -> # Check if the response is from a list commands follow up button press. # If so, construct the needed input card and return it - index = query.search("generate input card") - if (index != -1) - return constructMenuInputCard(query.replace("generate input card", ""), response.text) + # index = query.search("generate input card") + # if (index != -1) + # return maybeConstructMenuInputCard(response.text) # Check if response.text matches one of the reg exps in the LUT for regex of HubotResponseCards @@ -20,10 +20,19 @@ maybeConstructCard = (response, query) -> return card return null -# Constructs an input card -constructMenuInputCard = (query, text) -> - card = initializeAdaptiveCard(query) - queryParts = HubotQueryParts[text] +# Constructs an input card if needed or returns null if the +# query doesn't need user input +maybeConstructMenuInputCard = (query) -> + 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 for i in [0 ... queryParts.inputParts.length] @@ -38,7 +47,8 @@ constructMenuInputCard = (query, text) -> # Create selector 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 else card.content.body.push(addTextInput(query + " - input" + "#{i}", inputPart)) @@ -126,13 +136,7 @@ addTextInput = (id, inputPart) -> getFollowUpButtons = (query, regex) -> actions = [] for followUpQuery in HubotResponseCards[regex] - - # 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) + shortQuery = constructShortQuery(followUpQuery) action = { 'title': shortQuery } @@ -246,6 +250,15 @@ appendCardActions = (card1, card2) -> 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 # queries stored as strings HubotResponseCards = { @@ -285,7 +298,8 @@ HubotResponseCards = { } module.exports = { - maybeConstructCard, + maybeConstructResponseCard, + maybeConstructMenuInputCard, appendCardBody, appendCardActions } \ No newline at end of file diff --git a/src/msteams-middleware.coffee b/src/msteams-middleware.coffee index 40e5690..7504e89 100644 --- a/src/msteams-middleware.coffee +++ b/src/msteams-middleware.coffee @@ -25,7 +25,7 @@ BotBuilder = require 'botbuilder' BotBuilderTeams = require 'botbuilder-teams' HubotResponseCards = require './hubot-response-cards' -#MicrosoftGraph = require '@microsoft/microsoft-graph-client' +HubotQueryParts = require './hubot-query-parts' { Robot, TextMessage, Message, User } = require 'hubot' { BaseMiddleware, registerMiddleware } = require './adapter-middleware' LogPrefix = "hubot-msteams:" @@ -41,7 +41,8 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware @allowedTenants = [] if process.env.HUBOT_OFFICE365_TENANT_FILTER? @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) -> @robot.logger.info "#{LogPrefix} toReceivable" @@ -54,45 +55,43 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware # Get the user user = getUser(activity) 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.room = getRoomId(activity) - if activity.type != 'message' && activity.type != 'invoke' - return new Message(user) - else - # Fetch the roster of members to do authorization based on UPN - teamsConnector.fetchMembers activity?.address?.serviceUrl, activity?.address?.conversation?.id, (err, chatMembers) => - if err - 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 + # Fetch the roster of members to do authorization based on UPN + teamsConnector.fetchMembers activity?.address?.serviceUrl, \ + activity?.address?.conversation?.id, (err, chatMembers) => + if err + console.log("YUP AN ERR") + return - activity = fixActivityForHubot(activity, @robot, chatMembers) - message = new TextMessage(user, activity.text, activity.address.id) - cb(message, unauthorizedError) + # 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") + # 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) -> @robot.logger.info "#{LogPrefix} toSendable" @@ -109,7 +108,7 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware # If the query sent by the user should trigger a card, # construct the card to attach to the response # and remove sentQuery from the brain - card = HubotResponseCards.maybeConstructCard(response, activity.text) + card = HubotResponseCards.maybeConstructResponseCard(response, activity.text) if card != null delete response.text response.attachments = [card] @@ -156,7 +155,7 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware return true # 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 # first response. combineResponses: (storedPayload, newPayload) -> @@ -197,9 +196,53 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware if attachment.contentType != "application/vnd.microsoft.card.adaptive" storedMessage.attachments.push(attachment) else - storedCard = HubotResponseCards.appendCardBody(storedCard, attachment) - storedCard = HubotResponseCards.appendCardActions(storedCard, attachment) + storedCard = HubotResponseCards.appendCardBody(storedCard, \ + 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 @@ -256,7 +299,8 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware entities = activity?.entities || [] if not Array.isArray(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 getSenderUPN = (user, chatMembers) -> @@ -270,14 +314,16 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware # 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 - # 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. + # 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. # The hubot's configured name might not be the same name that is sent from the chat service in # the activity's text. # 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, # 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 fixActivityForHubot = (activity, robot, chatMembers) -> # 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 activity.text = text - return activity + #return activity if not activity?.text? || typeof activity.text isnt 'string' return activity + myChatId = activity?.address?.bot?.id if not myChatId? return activity @@ -332,6 +379,9 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware replacement = member.userPrincipalName # *** replacement = member.objectId 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 if getConversationType(activity) == 'personal' && activity.text.search(robot.name) != 0 diff --git a/test/adapter.test.coffee b/test/adapter.test.coffee index 3d1b82f..fdf5a14 100644 --- a/test/adapter.test.coffee +++ b/test/adapter.test.coffee @@ -4,15 +4,28 @@ expect = chai.expect BotFrameworkAdapter = require '../src/adapter' MockRobot = require './mock-robot' + describe 'Main Adapter', -> - describe 'Test Auth', -> + describe 'Test Authorization Setup', -> beforeEach -> - process.env.HUBOT_TEAMS_INITIAL_ADMINS = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee,eight888-four-4444-fore-twelve121212' - process.env.BOTBUILDER_APP_ID = 'botbuilder-app-id' - process.env.BOTBUILDER_APP_PASSWORD = 'botbuilder-app-password' - process.env.HUBOT_DEBUG_LEVEL = 'error' + 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' + 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', -> # Setup process.env.HUBOT_TEAMS_ENABLE_AUTH = 'false' @@ -25,6 +38,16 @@ describe 'Main Adapter', -> # Assert 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', -> # Setup @@ -37,50 +60,22 @@ describe 'Main Adapter', -> # Assert expect(robot.brain.get("authorizedUsers")).to.eql { - 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee': true - 'eight888-four-4444-fore-twelve121212': true + 'an-1_20@em.ail': 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', -> - # Setup + describe 'Test Authorization Support for Teams Channel', -> + 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 adapter = BotFrameworkAdapter.use(robot) 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 = type: 'message' text: 'Bot do something Bot and tell User about it' @@ -112,88 +107,44 @@ describe 'Main Adapter', -> id: "user-id" name: "user-name" aadObjectId: "eight888-four-4444-fore-twelve121212" + userPrincipalName: "user-UPN" serviceUrl: 'url-serviceUrl/a-url' + it 'should return authorization not supported error for non-Teams channels', -> + # Setup + event.source = 'authorization-not-supported-source' + # Action expect(() -> - result = adapter.handleActivity(event) + adapter.handleActivity(event) ).to.not.throw() # Assert - expect(robot.brain.get("authorizedUsers")).to.eql { - 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee': true - 'eight888-four-4444-fore-twelve121212': true - } + result = robot.brain.get("event") + expect(result.text).to.eql "hubot return source authorization not supported error" - # it 'should overwrite the hubot command text to return an error message to unauthorized users', -> - # # 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: 'Bot do something Bot and tell User about it' - # agent: 'tests' - # source: 'msteams' - # entities: [ - # type: "mention" - # text: "Bot" - # mentioned: - # id: "bot-id" - # name: "bot-name" - # , - # type: "mention" - # text: "User" - # 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' + it 'should work when authorization is enabled and message is from Teams', -> + # Setup - # # Action - # expect(() -> - # result = adapter.handleActivity(event) - # ).to.not.throw() + # Action + expect(() -> + adapter.handleActivity(event) + ).to.not.throw() - # console.log("=======================================") - # console.log(adapter) + # Assert + result = robot.brain.get("event") - # # Assert - # expect(robot.brain.get("authorizedUsers")).to.eql { - # 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee': true - # 'eight888-four-4444-fore-twelve121212': true - # } \ No newline at end of file + it 'should work when message is from invoke', -> + # Setup + event.type = 'invoke' + event.value = + hubotMessage: 'hubot do something' + delete event.text + + # Action + expect(() -> + adapter.sendTextToHubot(event) + ).to.not.throw() + + # Assert + \ No newline at end of file diff --git a/test/mock-robot.coffee b/test/mock-robot.coffee index 9686c12..702598f 100644 --- a/test/mock-robot.coffee +++ b/test/mock-robot.coffee @@ -9,7 +9,19 @@ class MockRobot "hubot b - does something b" ] @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: -> [] get: (key) -> if @data is undefined @@ -18,16 +30,9 @@ class MockRobot if key == storedKey return @data[storedKey] return null + set: (key, value) -> + @data[key] = value - if process.env.HUBOT_TEAMS_ENABLE_AUTH == 'true' - if process.env.HUBOT_TEAMS_INITIAL_ADMINS - @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: -> {} + receive: (event) -> + @brain.data["event"] = event module.exports = MockRobot diff --git a/test/msteams-middleware.test.coffee b/test/msteams-middleware.test.coffee index 3339057..8e854ab 100644 --- a/test/msteams-middleware.test.coffee +++ b/test/msteams-middleware.test.coffee @@ -11,15 +11,21 @@ describe 'MicrosoftTeamsMiddleware', -> describe 'toReceivable', -> robot = null event = null - chatMembers = null - cb = -> {} + teamsChatConnector = null + authEnabled = false + cb = -> + robot.receive event beforeEach -> delete process.env.HUBOT_OFFICE365_TENANT_FILTER - robot = new MockRobot - adapter = BotFrameworkAdapter.use(robot) - robot.adapter = adapter + options = { + appId: 'botframework-app-id' + appPassword: 'botframework-app-password' + } + teamsChatConnector = new MockTeamsChatConnector(options) + + authEnabled = false event = type: 'message' text: 'Bot do something Bot and tell User about it' @@ -52,97 +58,135 @@ describe 'MicrosoftTeamsMiddleware', -> name: "user-name" 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 - console.log("**************************************") - console.log(MicrosoftTeamsMiddleware.toString()) delete event.sourceEvent teamsMiddleware = new MicrosoftTeamsMiddleware(robot) - options = { - appId: 'botframework-app-id' - appPassword: 'botframework-app-password' - } - teamsChatConnector = new MockTeamsChatConnector(options) - # Action - receivable = null expect(() -> - receivable = teamsMiddleware.toReceivable(event, teamsChatConnector, cb) + teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb) ).to.not.throw() # 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', -> - # # Setup - # teamsMiddleware = new MicrosoftTeamsMiddleware(robot) + it 'should set unauthorized error for message when user isn\'t authorized', -> + # Setup + 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 - # receivable = null - # expect(() -> - # receivable = teamsMiddleware.toReceivable(event, cb) - # ).to.not.throw() + # Action + expect(() -> + teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb) + ).to.not.throw() - # # Assert - # expect(receivable).to.be.a('Object') + # Assert + 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', -> - # # Setup - # process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id - # teamsMiddleware = new MicrosoftTeamsMiddleware(robot) + it 'should allow messages without tenant id when tenant filter is empty', -> + # Setup + delete event.sourceEvent + teamsMiddleware = new MicrosoftTeamsMiddleware(robot) - # # Action - # receivable = null - # expect(() -> - # receivable = teamsMiddleware.toReceivable(event, cb) - # ).to.not.throw() + # Action + expect(() -> + teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb) + ).to.not.throw() - # # Assert - # expect(receivable).to.be.a('Object') + # Assert + result = robot.brain.get("event") + expect(result).to.be.a('Object') - # it 'should block messages from unallowed tenant ids', -> - # # Setup - # process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id - # event.sourceEvent.tenant.id = "different-tenant-id" - # teamsMiddleware = new MicrosoftTeamsMiddleware(robot) + it 'should allow messages with tenant id when tenant filter is empty', -> + # Setup + teamsMiddleware = new MicrosoftTeamsMiddleware(robot) - # # Action - # receivable = null - # expect(() -> - # receivable = teamsMiddleware.toReceivable(event, cb) - # ).to.not.throw() + # Action + expect(() -> + teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb) + ).to.not.throw() - # # Assert - # expect(receivable).to.be.null + # Assert + result = robot.brain.get("event") + expect(result).to.be.a('Object') - # it 'return generic message when appropriate type is not found', -> - # # Setup - # event.type = 'typing' - # teamsMiddleware = new MicrosoftTeamsMiddleware(robot) + it 'should allow messages from allowed tenant ids', -> + # Setup + process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id + teamsMiddleware = new MicrosoftTeamsMiddleware(robot) - # # Action - # receivable = null - # expect(() -> - # receivable = teamsMiddleware.toReceivable(event, cb) - # ).to.not.throw() + # Action + expect(() -> + teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb) + ).to.not.throw() - # # Assert - # expect(receivable).to.be.not.null + # Assert + result = robot.brain.get("event") + expect(result).to.be.a('Object') - # it 'should work when activity text is an object', -> - # # Setup - # event.text = event - # teamsMiddleware = new MicrosoftTeamsMiddleware(robot) + it 'should block messages from unallowed tenant ids', -> + # Setup + process.env.HUBOT_OFFICE365_TENANT_FILTER = event.sourceEvent.tenant.id + event.sourceEvent.tenant.id = "different-tenant-id" + teamsMiddleware = new MicrosoftTeamsMiddleware(robot) - # # Action - # receivable = null - # expect(() -> - # receivable = teamsMiddleware.toReceivable(event, cb) - # ).to.not.throw() + # Action + expect(() -> + teamsMiddleware.toReceivable(event, teamsChatConnector, authEnabled, cb) + ).to.not.throw() - # # Assert - # expect(receivable.text).to.equal(event) + # Assert + 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', -> # # Setup