mattermost-community / mattermost-bot-sample-golang

Apache License 2.0
180 stars 82 forks source link

Multi Factor Authentication #22

Open eyebank opened 5 years ago

eyebank commented 5 years ago

Server 5.6.0.

Attempting to run golang after configuring google MFA. Startup fails. Any thoughts,

golangbot

hanzei commented 5 years ago

Sorry for the delay @eyebank.

@crspeller Do you know if Client4.Login() work if MFA is enabled?

crspeller commented 5 years ago

You need to use Client4.LoginWithMFA() if MFA is enabled.

hanzei commented 5 years ago

@eyebank Would you be open to submitting a PR to fix this?

eyebank commented 5 years ago

where can I find this Client4.LoginWithMFA()?

crspeller commented 5 years ago

@eyebank You can find it in the golang client: https://github.com/mattermost/mattermost-server/blob/master/model/client4.go#L638

EbsrVlee commented 5 years ago

@crspeller Looking at the link you provided. Where would I put the following code for the golang bot to be able to work using mfa?

Thanks!

// LoginWithMFA logs a user in with a MFA token
func (c *Client4) LoginWithMFA(loginId, password, mfaToken string) (*User, *Response) {
    m := make(map[string]string)
    m["login_id"] = loginId
    m["password"] = password
    m["token"] = mfaToken
    return c.login(m)
}

func (c *Client4) login(m map[string]string) (*User, *Response) {
    r, err := c.DoApiPost("/users/login", MapToJson(m))
    if err != nil {
        return nil, BuildErrorResponse(r, err)
    }
    defer closeBody(r)
    c.AuthToken = r.Header.Get(HEADER_TOKEN)
    c.AuthType = HEADER_BEARER
    return UserFromJson(r.Body), BuildResponse(r)
}
crspeller commented 5 years ago

@EbsrVlee You replace the current login function with that one. https://github.com/mattermost/mattermost-bot-sample-golang/blob/master/bot_sample.go#L99

EbsrVlee commented 5 years ago

@crspeller Thank you! I've replaced my current login function with the one above. When I attempt to run my golang bot I get the errors undefined: Client 4 undefined: User undefined: Response Makefille:15: recipe for target 'run' failed make: *** [run] Error 2

What am I missing?

Thank you again for all your help!

hanzei commented 5 years ago

@EbsrVlee Would you be open on sharing the code you currently have?

EbsrVlee commented 5 years ago

@hanzei

Sure. (I have removed username/password/email from for obvious reasons)

package main

import (
    "os"
    "os/signal"
    "regexp"
    "strings"
    "time"

    "github.com/mattermost/mattermost-server/model"
)

const (
    SAMPLE_NAME = "**ChatBot**"

    USER_EMAIL    = "chatbot@email.org"
    USER_PASSWORD = "password"
    USER_NAME     = "Chatbot"
    USER_FIRST    = "Chatbot"
    USER_LAST     = "Chatbot"

    TEAM_NAME        = "aibot"
    CHANNEL_LOG_NAME = "chat"
)

var client *model.Client4
var webSocketClient *model.WebSocketClient

var botUser *model.User
var botTeam *model.Team
var debuggingChannel *model.Channel

// Documentation for the Go driver can be found
// at https://godoc.org/github.com/mattermost/platform/model#Client
func main() {
    println(SAMPLE_NAME)

    SetupGracefulShutdown()

    client = model.NewAPIv4Client("http://localhost:8065")

    // Lets test to see if the mattermost server is up and running
    MakeSureServerIsRunning()

    // lets attempt to login to the Mattermost server as the bot user
    // This will set the token required for all future calls
    // You can get this token with client.AuthToken
    LoginAsTheBotUser()

    // If the bot user doesn't have the correct information lets update his profile
    UpdateTheBotUserIfNeeded()

    // Lets find our bot team
    FindBotTeam()

    // This is an important step.  Lets make sure we use the botTeam
    // for all future web service requests that require a team.
    //client.SetTeamId(botTeam.Id)

    // Lets create a bot channel for logging debug messages into
    CreateBotDebuggingChannelIfNeeded()
    SendMsgToDebuggingChannel("_"+SAMPLE_NAME+" has **started** running_", "")

    // Lets start listening to some channels via the websocket!
    webSocketClient, err := model.NewWebSocketClient4("ws://localhost:8065", client.AuthToken)
    if err != nil {
        println("We failed to connect to the web socket")
        PrintError(err)
    }

    webSocketClient.Listen()

    go func() {
        for {
            select {
            case resp := <-webSocketClient.EventChannel:
                HandleWebSocketResponse(resp)
            }
        }
    }()

    // You can block forever with
    select {}
}

func MakeSureServerIsRunning() {
    if props, resp := client.GetOldClientConfig(""); resp.Error != nil {
        println("There was a problem pinging the Mattermost server.  Are you sure it's running?")
        PrintError(resp.Error)
        os.Exit(1)
    } else {
        println("Server detected and is running version " + props["Version"])
    }
}

// LoginWithMFA logs a user in with a MFA token
func (c *Client4) LoginWithMFA(loginId, password, mfaToken string) (*User, *Response) {
    m := make(map[string]string)
    m["login_id"] = Chatbot
    m["password"] = password
    m["token"] = 123456
    return c.login(m)
}

func (c *Client4) login(m map[string]string) (*User, *Response) {
    r, err := c.DoApiPost("/users/login", MapToJson(m))
    if err != nil {
        return nil, BuildErrorResponse(r, err)
    }
    defer closeBody(r)
    c.AuthToken = r.Header.Get(HEADER_TOKEN)
    c.AuthType = HEADER_BEARER
    return UserFromJson(r.Body), BuildResponse(r)
}

//func LoginAsTheBotUser() {
//  if user, resp := client.Login(USER_EMAIL, USER_PASSWORD); resp.Error != nil {
//      println("There was a problem logging into the Mattermost server.  Are you sure ran the setup steps from the README.md?")
//      PrintError(resp.Error)
//      os.Exit(1)
//  } else {
//      botUser = user
//  }
//}

func UpdateTheBotUserIfNeeded() {
    if botUser.FirstName != USER_FIRST || botUser.LastName != USER_LAST || botUser.Username != USER_NAME {
        botUser.FirstName = USER_FIRST
        botUser.LastName = USER_LAST
        botUser.Username = USER_NAME

        if user, resp := client.UpdateUser(botUser); resp.Error != nil {
            println("We failed to update the Sample Bot user")
            PrintError(resp.Error)
            os.Exit(1)
        } else {
            botUser = user
            println("Looks like this might be the first run so we've updated the bots account settings")
        }
    }
}

func FindBotTeam() {
    if team, resp := client.GetTeamByName(TEAM_NAME, ""); resp.Error != nil {
        println("We failed to get the initial load")
        println("or we do not appear to be a member of the team '" + TEAM_NAME + "'")
        PrintError(resp.Error)
        os.Exit(1)
    } else {
        botTeam = team
    }
}

func CreateBotDebuggingChannelIfNeeded() {
    if rchannel, resp := client.GetChannelByName(CHANNEL_LOG_NAME, botTeam.Id, ""); resp.Error != nil {
        println("We failed to get the channels")
        PrintError(resp.Error)
    } else {
        debuggingChannel = rchannel
        return
    }

    // Looks like we need to create the logging channel
    channel := &model.Channel{}
    channel.Name = CHANNEL_LOG_NAME
    channel.DisplayName = "Town"
    channel.Purpose = "This is used as a test channel for logging bot debug messages"
    channel.Type = model.CHANNEL_OPEN
    channel.TeamId = botTeam.Id
    if rchannel, resp := client.CreateChannel(channel); resp.Error != nil {
        println("We failed to create the channel " + CHANNEL_LOG_NAME)
        PrintError(resp.Error)
    } else {
        debuggingChannel = rchannel
        println("Looks like this might be the first run so we've created the channel " + CHANNEL_LOG_NAME)
    }
}

func SendMsgToDebuggingChannel(msg string, replyToId string) {
    post := &model.Post{}
    post.ChannelId = debuggingChannel.Id
    post.Message = msg

    post.RootId = replyToId

    if _, resp := client.CreatePost(post); resp.Error != nil {
        println("We failed to send a message to the logging channel")
        PrintError(resp.Error)
    }
}

func HandleWebSocketResponse(event *model.WebSocketEvent) {
    HandleMsgFromDebuggingChannel(event)
}

func HandleMsgFromDebuggingChannel(event *model.WebSocketEvent) {
    // If this isn't the debugging channel then lets ingore it
    if event.Broadcast.ChannelId != debuggingChannel.Id {
        return
    }

    // Lets only reponded to messaged posted events
    if event.Event != model.WEBSOCKET_EVENT_POSTED {
        return
    }

    println("responding to debugging channel msg")

    post := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
    if post != nil {

        // ignore my events
        if post.UserId == botUser.Id {
            return
        }

        // if you see any word matching 'alive' then respond
        if matched, _ := regexp.MatchString(`(?:^|\W)alive(?:$|\W)`, post.Message); matched {
            SendMsgToDebuggingChannel("Yes I'm running", post.Id)
            return
        }

        // if you see any word matching 'up' then respond
        if matched, _ := regexp.MatchString(`(?:^|\W)up(?:$|\W)`, post.Message); matched {
            SendMsgToDebuggingChannel("Yes I'm running", post.Id)
            return
        }
// if you see any word matching 'running' then respond
        if matched, _ := regexp.MatchString(`(?:^|\W)running(?:$|\W)`, post.Message); matched {
            SendMsgToDebuggingChannel("Yes I'm running", post.Id)
            return
        }

        // if you see any word matching 'hello' then respond
        if matched, _ := regexp.MatchString(`(?:^|\W)hello(?:$|\W)`, post.Message); matched {
            SendMsgToDebuggingChannel("Hi! How are you? :smile:", post.Id)
            return
        }
}

    SendMsgToDebuggingChannel("I did not understand you! Please try the help command.", post.Id)
}

func PrintError(err *model.AppError) {
    println("\tError Details:")
    println("\t\t" + err.Message)
    println("\t\t" + err.Id)
    println("\t\t" + err.DetailedError)
}

func SetupGracefulShutdown() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    go func() {
        for _ = range c {
            if webSocketClient != nil {
                webSocketClient.Close()
            }

            SendMsgToDebuggingChannel("_"+SAMPLE_NAME+" has **stopped** running_", "")
            os.Exit(0)
        }
    }()
}
hanzei commented 5 years ago

Hey @EbsrVlee,

You have to explicitly select the packages you want to use. E.g. if you want to use the struct User from github.com/mattermost/mattermost-server/model, you have to select it via model.User.

EbsrVlee commented 5 years ago

Hi @hanzei , thank you for the advice. I'm fairly new to golang. Could you give me an example of how to select it from model.user?

Thank you again.

hanzei commented 5 years ago

I would advice to read and play through "A Tour of Go" first. This is a tutorial made by the Go team targeting new gophers.