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

Multi Factor Authentication #22

Open
eyebank opened this issue Apr 15, 2019 · 13 comments
Open

Multi Factor Authentication #22

eyebank opened this issue Apr 15, 2019 · 13 comments
Labels
Hacktoberfest Help Wanted Community help wanted Type/Enhancement New feature or improvement of existing feature

Comments

@eyebank
Copy link

eyebank commented Apr 15, 2019

Server 5.6.0.

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

golangbot

@hanzei
Copy link

hanzei commented Apr 24, 2019

Sorry for the delay @eyebank.

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

@crspeller
Copy link
Contributor

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

@hanzei
Copy link

hanzei commented Apr 24, 2019

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

@hanzei hanzei added Type/Enhancement New feature or improvement of existing feature Help Wanted Community help wanted labels May 8, 2019
@eyebank
Copy link
Author

eyebank commented May 9, 2019

where can I find this Client4.LoginWithMFA()?

@crspeller
Copy link
Contributor

@EbsrVlee
Copy link

EbsrVlee commented May 14, 2019

@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
Copy link
Contributor

@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
Copy link

@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
Copy link

hanzei commented May 21, 2019

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

@EbsrVlee
Copy link

EbsrVlee commented Jun 6, 2019

@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    = "[email protected]"
	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
Copy link

hanzei commented Jun 6, 2019

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
Copy link

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
Copy link

hanzei commented Jun 14, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Hacktoberfest Help Wanted Community help wanted Type/Enhancement New feature or improvement of existing feature
Projects
None yet
Development

No branches or pull requests

6 participants