2015-10-08 12:27:09 -04:00
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
2015-06-14 23:53:32 -08:00
// See License.txt for license information.
package web
import (
2015-08-21 11:15:21 -07:00
l4g "code.google.com/p/log4go"
2015-09-16 15:49:12 -04:00
"fmt"
2015-06-14 23:53:32 -08:00
"github.com/gorilla/mux"
"github.com/mattermost/platform/api"
"github.com/mattermost/platform/model"
2015-09-21 14:22:23 -04:00
"github.com/mattermost/platform/store"
2015-06-14 23:53:32 -08:00
"github.com/mattermost/platform/utils"
"github.com/mssola/user_agent"
"gopkg.in/fsnotify.v1"
2015-09-16 15:49:12 -04:00
"html/template"
"net/http"
2015-10-16 19:05:55 -07:00
"net/url"
2015-09-16 15:49:12 -04:00
"strconv"
"strings"
2015-06-14 23:53:32 -08:00
)
var Templates * template . Template
type HtmlTemplatePage api . Page
func NewHtmlTemplatePage ( templateName string , title string ) * HtmlTemplatePage {
if len ( title ) > 0 {
2015-09-22 01:15:41 -07:00
title = utils . Cfg . TeamSettings . SiteName + " - " + title
2015-06-14 23:53:32 -08:00
}
props := make ( map [ string ] string )
2015-09-15 18:59:14 -07:00
props [ "Title" ] = title
2015-10-16 09:10:54 -07:00
return & HtmlTemplatePage { TemplateName : templateName , Props : props , ClientCfg : utils . ClientCfg }
2015-06-14 23:53:32 -08:00
}
func ( me * HtmlTemplatePage ) Render ( c * api . Context , w http . ResponseWriter ) {
2015-10-16 19:05:55 -07:00
if me . Team != nil {
me . Team . Sanitize ( )
}
2015-10-16 09:10:54 -07:00
if me . User != nil {
me . User . Sanitize ( map [ string ] bool { } )
}
2015-10-20 04:37:51 -07:00
if len ( c . Session . Token ) > 0 {
me . SessionTokenHash = model . HashPassword ( c . Session . Token )
2015-10-16 09:10:54 -07:00
}
2015-06-14 23:53:32 -08:00
if err := Templates . ExecuteTemplate ( w , me . TemplateName , me ) ; err != nil {
c . SetUnknownError ( me . TemplateName , err . Error ( ) )
}
}
func InitWeb ( ) {
l4g . Debug ( "Initializing web routes" )
2015-07-08 11:50:10 -04:00
mainrouter := api . Srv . Router
2015-06-14 23:53:32 -08:00
staticDir := utils . FindDir ( "web/static" )
l4g . Debug ( "Using static directory at %v" , staticDir )
2015-07-08 11:50:10 -04:00
mainrouter . PathPrefix ( "/static/" ) . Handler ( http . StripPrefix ( "/static/" , http . FileServer ( http . Dir ( staticDir ) ) ) )
mainrouter . Handle ( "/" , api . AppHandlerIndependent ( root ) ) . Methods ( "GET" )
2015-09-16 15:49:12 -04:00
mainrouter . Handle ( "/oauth/authorize" , api . UserRequired ( authorizeOAuth ) ) . Methods ( "GET" )
mainrouter . Handle ( "/oauth/access_token" , api . ApiAppHandler ( getAccessToken ) ) . Methods ( "POST" )
2015-09-10 14:56:37 -07:00
2015-07-08 11:50:10 -04:00
mainrouter . Handle ( "/signup_team_complete/" , api . AppHandlerIndependent ( signupTeamComplete ) ) . Methods ( "GET" )
mainrouter . Handle ( "/signup_user_complete/" , api . AppHandlerIndependent ( signupUserComplete ) ) . Methods ( "GET" )
mainrouter . Handle ( "/signup_team_confirm/" , api . AppHandlerIndependent ( signupTeamConfirm ) ) . Methods ( "GET" )
mainrouter . Handle ( "/verify_email" , api . AppHandlerIndependent ( verifyEmail ) ) . Methods ( "GET" )
mainrouter . Handle ( "/find_team" , api . AppHandlerIndependent ( findTeam ) ) . Methods ( "GET" )
mainrouter . Handle ( "/signup_team" , api . AppHandlerIndependent ( signup ) ) . Methods ( "GET" )
2015-09-03 13:38:14 -07:00
mainrouter . Handle ( "/login/{service:[A-Za-z]+}/complete" , api . AppHandlerIndependent ( loginCompleteOAuth ) ) . Methods ( "GET" )
mainrouter . Handle ( "/signup/{service:[A-Za-z]+}/complete" , api . AppHandlerIndependent ( signupCompleteOAuth ) ) . Methods ( "GET" )
2015-09-10 14:56:37 -07:00
mainrouter . Handle ( "/admin_console" , api . UserRequired ( adminConsole ) ) . Methods ( "GET" )
2015-10-18 18:55:04 +03:00
mainrouter . Handle ( "/admin_console/" , api . UserRequired ( adminConsole ) ) . Methods ( "GET" )
mainrouter . Handle ( "/admin_console/{tab:[A-Za-z0-9-_]+}" , api . UserRequired ( adminConsole ) ) . Methods ( "GET" )
mainrouter . Handle ( "/admin_console/{tab:[A-Za-z0-9-_]+}/{team:[A-Za-z0-9-]*}" , api . UserRequired ( adminConsole ) ) . Methods ( "GET" )
2015-09-03 13:38:14 -07:00
2015-09-21 14:22:23 -04:00
mainrouter . Handle ( "/hooks/{id:[A-Za-z0-9]+}" , api . ApiAppHandler ( incomingWebhook ) ) . Methods ( "POST" )
2015-09-03 13:38:14 -07:00
// ----------------------------------------------------------------------------------------------
2015-09-16 15:49:12 -04:00
// *ANYTHING* team specific should go below this line
2015-09-03 13:38:14 -07:00
// ----------------------------------------------------------------------------------------------
mainrouter . Handle ( "/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}" , api . AppHandler ( login ) ) . Methods ( "GET" )
mainrouter . Handle ( "/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/" , api . AppHandler ( login ) ) . Methods ( "GET" )
mainrouter . Handle ( "/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login" , api . AppHandler ( login ) ) . Methods ( "GET" )
mainrouter . Handle ( "/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout" , api . AppHandler ( logout ) ) . Methods ( "GET" )
mainrouter . Handle ( "/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password" , api . AppHandler ( resetPassword ) ) . Methods ( "GET" )
2015-10-20 04:37:51 -07:00
mainrouter . Handle ( "/{team}/login/{service}" , api . AppHandler ( loginWithOAuth ) ) . Methods ( "GET" ) // Bug in gorilla.mux prevents us from using regex here.
mainrouter . Handle ( "/{team}/channels/{channelname}" , api . AppHandler ( getChannel ) ) . Methods ( "GET" ) // Bug in gorilla.mux prevents us from using regex here.
mainrouter . Handle ( "/{team}/signup/{service}" , api . AppHandler ( signupWithOAuth ) ) . Methods ( "GET" ) // Bug in gorilla.mux prevents us from using regex here.
2015-06-14 23:53:32 -08:00
watchAndParseTemplates ( )
}
func watchAndParseTemplates ( ) {
templatesDir := utils . FindDir ( "web/templates" )
l4g . Debug ( "Parsing templates at %v" , templatesDir )
var err error
if Templates , err = template . ParseGlob ( templatesDir + "*.html" ) ; err != nil {
l4g . Error ( "Failed to parse templates %v" , err )
}
watcher , err := fsnotify . NewWatcher ( )
if err != nil {
l4g . Error ( "Failed to create directory watcher %v" , err )
}
go func ( ) {
for {
select {
case event := <- watcher . Events :
if event . Op & fsnotify . Write == fsnotify . Write {
l4g . Info ( "Re-parsing templates because of modified file %v" , event . Name )
if Templates , err = template . ParseGlob ( templatesDir + "*.html" ) ; err != nil {
l4g . Error ( "Failed to parse templates %v" , err )
}
}
case err := <- watcher . Errors :
l4g . Error ( "Failed in directory watcher %v" , err )
}
}
} ( )
err = watcher . Add ( templatesDir )
if err != nil {
l4g . Error ( "Failed to add directory to watcher %v" , err )
}
}
var browsersNotSupported string = "MSIE/8;MSIE/9;Internet Explorer/8;Internet Explorer/9"
func CheckBrowserCompatability ( c * api . Context , r * http . Request ) bool {
ua := user_agent . New ( r . UserAgent ( ) )
bname , bversion := ua . Browser ( )
browsers := strings . Split ( browsersNotSupported , ";" )
for _ , browser := range browsers {
version := strings . Split ( browser , "/" )
if strings . HasPrefix ( bname , version [ 0 ] ) && strings . HasPrefix ( bversion , version [ 1 ] ) {
c . Err = model . NewAppError ( "CheckBrowserCompatability" , "Your current browser is not supported, please upgrade to one of the following browsers: Google Chrome 21 or higher, Internet Explorer 10 or higher, FireFox 14 or higher" , "" )
return false
}
}
return true
}
2015-10-16 19:05:55 -07:00
// func getTeamAndUser(c *api.Context) (*model.Team, *model.User) {
// if tr := <-api.Srv.Store.Team().Get(c.Session.TeamId); tr.Err != nil {
2015-10-16 09:10:54 -07:00
// c.Err = tr.Err
// return nil, nil
// } else {
2015-10-16 19:05:55 -07:00
// if ur := <-api.Srv.Store.User().Get(c.Session.UserId); ur.Err != nil {
2015-10-16 09:10:54 -07:00
// c.Err = ur.Err
// return nil, nil
// } else {
// return tr.Data.(*model.Team), ur.Data.(*model.User)
// }
// }
// }
2015-06-14 23:53:32 -08:00
func root ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
if ! CheckBrowserCompatability ( c , r ) {
return
}
2015-09-25 09:51:27 -07:00
if len ( c . Session . UserId ) == 0 {
page := NewHtmlTemplatePage ( "signup_team" , "Signup" )
page . Render ( c , w )
} else {
2015-10-16 19:05:55 -07:00
teamChan := api . Srv . Store . Team ( ) . Get ( c . Session . TeamId )
userChan := api . Srv . Store . User ( ) . Get ( c . Session . UserId )
var team * model . Team
if tr := <- teamChan ; tr . Err != nil {
c . Err = tr . Err
return
} else {
team = tr . Data . ( * model . Team )
}
var user * model . User
if ur := <- userChan ; ur . Err != nil {
c . Err = ur . Err
2015-10-16 09:10:54 -07:00
return
2015-10-16 19:05:55 -07:00
} else {
user = ur . Data . ( * model . User )
2015-10-16 09:10:54 -07:00
}
2015-09-25 09:51:27 -07:00
page := NewHtmlTemplatePage ( "home" , "Home" )
2015-10-16 09:10:54 -07:00
page . Team = team
page . User = user
2015-09-25 09:51:27 -07:00
page . Render ( c , w )
}
2015-06-14 23:53:32 -08:00
}
2015-07-08 11:50:10 -04:00
func signup ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
2015-06-14 23:53:32 -08:00
if ! CheckBrowserCompatability ( c , r ) {
return
}
2015-07-08 11:50:10 -04:00
page := NewHtmlTemplatePage ( "signup_team" , "Signup" )
page . Render ( c , w )
}
func login ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
if ! CheckBrowserCompatability ( c , r ) {
return
}
params := mux . Vars ( r )
teamName := params [ "team" ]
2015-06-14 23:53:32 -08:00
2015-07-08 11:50:10 -04:00
var team * model . Team
if tResult := <- api . Srv . Store . Team ( ) . GetByName ( teamName ) ; tResult . Err != nil {
2015-10-16 19:05:55 -07:00
l4g . Error ( "Couldn't find team name=%v, err=%v" , teamName , tResult . Err . Message )
2015-09-24 09:44:36 -07:00
http . Redirect ( w , r , api . GetProtocol ( r ) + "://" + r . Host , http . StatusTemporaryRedirect )
2015-07-08 11:50:10 -04:00
return
2015-06-14 23:53:32 -08:00
} else {
2015-07-08 11:50:10 -04:00
team = tResult . Data . ( * model . Team )
}
2015-06-14 23:53:32 -08:00
2015-10-01 17:52:47 -07:00
// We still might be able to switch to this team because we've logged in before
2015-10-16 19:05:55 -07:00
session := api . FindMultiSessionForTeamId ( r , team . Id )
if session != nil {
w . Header ( ) . Set ( model . HEADER_TOKEN , session . Token )
http . Redirect ( w , r , c . GetSiteURL ( ) + "/" + team . Name + "/channels/town-square" , http . StatusTemporaryRedirect )
return
2015-10-01 17:52:47 -07:00
}
2015-06-14 23:53:32 -08:00
page := NewHtmlTemplatePage ( "login" , "Login" )
2015-07-08 11:50:10 -04:00
page . Props [ "TeamDisplayName" ] = team . DisplayName
2015-10-01 17:52:47 -07:00
page . Props [ "TeamName" ] = team . Name
2015-06-14 23:53:32 -08:00
page . Render ( c , w )
}
func signupTeamConfirm ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
email := r . FormValue ( "email" )
page := NewHtmlTemplatePage ( "signup_team_confirm" , "Signup Email Sent" )
page . Props [ "Email" ] = email
page . Render ( c , w )
}
func signupTeamComplete ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
data := r . FormValue ( "d" )
hash := r . FormValue ( "h" )
2015-09-22 12:12:50 -07:00
if ! model . ComparePassword ( hash , fmt . Sprintf ( "%v:%v" , data , utils . Cfg . EmailSettings . InviteSalt ) ) {
2015-06-14 23:53:32 -08:00
c . Err = model . NewAppError ( "signupTeamComplete" , "The signup link does not appear to be valid" , "" )
return
}
props := model . MapFromJson ( strings . NewReader ( data ) )
t , err := strconv . ParseInt ( props [ "time" ] , 10 , 64 )
2015-08-21 11:15:21 -07:00
if err != nil || model . GetMillis ( ) - t > 1000 * 60 * 60 * 24 * 30 { // 30 days
2015-06-14 23:53:32 -08:00
c . Err = model . NewAppError ( "signupTeamComplete" , "The signup link has expired" , "" )
return
}
page := NewHtmlTemplatePage ( "signup_team_complete" , "Complete Team Sign Up" )
page . Props [ "Email" ] = props [ "email" ]
page . Props [ "Data" ] = data
page . Props [ "Hash" ] = hash
page . Render ( c , w )
}
func signupUserComplete ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
id := r . FormValue ( "id" )
data := r . FormValue ( "d" )
hash := r . FormValue ( "h" )
var props map [ string ] string
if len ( id ) > 0 {
props = make ( map [ string ] string )
if result := <- api . Srv . Store . Team ( ) . Get ( id ) ; result . Err != nil {
c . Err = result . Err
return
} else {
team := result . Data . ( * model . Team )
if ! ( team . Type == model . TEAM_OPEN || ( team . Type == model . TEAM_INVITE && len ( team . AllowedDomains ) > 0 ) ) {
c . Err = model . NewAppError ( "signupUserComplete" , "The team type doesn't allow open invites" , "id=" + id )
return
}
props [ "email" ] = ""
2015-07-08 11:50:10 -04:00
props [ "display_name" ] = team . DisplayName
2015-06-14 23:53:32 -08:00
props [ "name" ] = team . Name
props [ "id" ] = team . Id
data = model . MapToJson ( props )
hash = ""
}
} else {
2015-09-22 12:12:50 -07:00
if ! model . ComparePassword ( hash , fmt . Sprintf ( "%v:%v" , data , utils . Cfg . EmailSettings . InviteSalt ) ) {
2015-06-14 23:53:32 -08:00
c . Err = model . NewAppError ( "signupTeamComplete" , "The signup link does not appear to be valid" , "" )
return
}
props = model . MapFromJson ( strings . NewReader ( data ) )
t , err := strconv . ParseInt ( props [ "time" ] , 10 , 64 )
if err != nil || model . GetMillis ( ) - t > 1000 * 60 * 60 * 48 { // 48 hour
c . Err = model . NewAppError ( "signupTeamComplete" , "The signup link has expired" , "" )
return
}
}
page := NewHtmlTemplatePage ( "signup_user_complete" , "Complete User Sign Up" )
page . Props [ "Email" ] = props [ "email" ]
2015-07-08 11:50:10 -04:00
page . Props [ "TeamDisplayName" ] = props [ "display_name" ]
2015-06-14 23:53:32 -08:00
page . Props [ "TeamName" ] = props [ "name" ]
page . Props [ "TeamId" ] = props [ "id" ]
page . Props [ "Data" ] = data
page . Props [ "Hash" ] = hash
page . Render ( c , w )
}
func logout ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
api . Logout ( c , w , r )
2015-10-16 19:05:55 -07:00
http . Redirect ( w , r , c . GetTeamURL ( ) , http . StatusTemporaryRedirect )
2015-06-14 23:53:32 -08:00
}
func getChannel ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
params := mux . Vars ( r )
2015-07-08 11:50:10 -04:00
name := params [ "channelname" ]
2015-10-01 18:15:40 -07:00
teamName := params [ "team" ]
2015-10-16 19:05:55 -07:00
var team * model . Team
if result := <- api . Srv . Store . Team ( ) . GetByName ( teamName ) ; result . Err != nil {
c . Err = result . Err
2015-10-16 09:10:54 -07:00
return
2015-10-16 19:05:55 -07:00
} else {
team = result . Data . ( * model . Team )
}
// We are logged into a different team. Lets see if we have another
// session in the cookie that will give us access.
if c . Session . TeamId != team . Id {
session := api . FindMultiSessionForTeamId ( r , team . Id )
if session == nil {
// redirect to login
2015-10-20 04:37:51 -07:00
fmt . Println ( ">>>>>>>>>>forwarding" )
2015-10-16 19:05:55 -07:00
http . Redirect ( w , r , c . GetSiteURL ( ) + "/" + team . Name + "/?redirect=" + url . QueryEscape ( r . URL . Path ) , http . StatusTemporaryRedirect )
} else {
c . Session = * session
}
2015-10-16 09:10:54 -07:00
}
2015-06-14 23:53:32 -08:00
2015-10-16 19:05:55 -07:00
userChan := api . Srv . Store . User ( ) . Get ( c . Session . UserId )
2015-06-14 23:53:32 -08:00
var channelId string
if result := <- api . Srv . Store . Channel ( ) . CheckPermissionsToByName ( c . Session . TeamId , name , c . Session . UserId ) ; result . Err != nil {
c . Err = result . Err
return
} else {
channelId = result . Data . ( string )
}
2015-10-16 19:05:55 -07:00
var user * model . User
if ur := <- userChan ; ur . Err != nil {
c . Err = ur . Err
c . RemoveSessionCookie ( w , r )
l4g . Error ( "Error in getting users profile for id=%v forcing logout" , c . Session . UserId )
2015-10-01 18:15:40 -07:00
return
2015-10-16 19:05:55 -07:00
} else {
user = ur . Data . ( * model . User )
2015-10-01 18:15:40 -07:00
}
2015-06-14 23:53:32 -08:00
if len ( channelId ) == 0 {
if strings . Index ( name , "__" ) > 0 {
// It's a direct message channel that doesn't exist yet so let's create it
ids := strings . Split ( name , "__" )
otherUserId := ""
if ids [ 0 ] == c . Session . UserId {
otherUserId = ids [ 1 ]
} else {
otherUserId = ids [ 0 ]
}
2015-06-29 09:07:13 -04:00
if sc , err := api . CreateDirectChannel ( c , otherUserId ) ; err != nil {
2015-06-14 23:53:32 -08:00
api . Handle404 ( w , r )
return
} else {
channelId = sc . Id
}
} else {
2015-10-07 17:13:55 -07:00
// We will attempt to auto-join open channels
if cr := <- api . Srv . Store . Channel ( ) . GetByName ( c . Session . TeamId , name ) ; cr . Err != nil {
http . Redirect ( w , r , c . GetTeamURL ( ) + "/channels/town-square" , http . StatusFound )
} else {
channel := cr . Data . ( * model . Channel )
if channel . Type == model . CHANNEL_OPEN {
api . JoinChannel ( c , channel . Id , "" )
if c . Err != nil {
return
}
2015-07-09 13:59:19 -04:00
2015-10-07 17:13:55 -07:00
channelId = channel . Id
} else {
http . Redirect ( w , r , c . GetTeamURL ( ) + "/channels/town-square" , http . StatusFound )
}
}
2015-06-14 23:53:32 -08:00
}
}
page := NewHtmlTemplatePage ( "channel" , "" )
2015-10-16 09:10:54 -07:00
page . Props [ "Title" ] = name + " - " + team . DisplayName + " " + page . ClientCfg [ "SiteName" ]
2015-07-08 11:50:10 -04:00
page . Props [ "TeamDisplayName" ] = team . DisplayName
2015-10-01 17:52:47 -07:00
page . Props [ "TeamName" ] = team . Name
2015-06-14 23:53:32 -08:00
page . Props [ "TeamType" ] = team . Type
2015-06-18 10:40:46 -04:00
page . Props [ "TeamId" ] = team . Id
2015-06-14 23:53:32 -08:00
page . Props [ "ChannelName" ] = name
page . Props [ "ChannelId" ] = channelId
page . Props [ "UserId" ] = c . Session . UserId
2015-10-16 09:10:54 -07:00
page . Team = team
page . User = user
2015-06-14 23:53:32 -08:00
page . Render ( c , w )
}
func verifyEmail ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
resend := r . URL . Query ( ) . Get ( "resend" )
2015-09-21 17:03:08 -07:00
resendSuccess := r . URL . Query ( ) . Get ( "resend_success" )
2015-07-29 16:17:20 -04:00
name := r . URL . Query ( ) . Get ( "teamname" )
2015-06-14 23:53:32 -08:00
email := r . URL . Query ( ) . Get ( "email" )
hashedId := r . URL . Query ( ) . Get ( "hid" )
userId := r . URL . Query ( ) . Get ( "uid" )
2015-07-29 16:17:20 -04:00
var team * model . Team
if result := <- api . Srv . Store . Team ( ) . GetByName ( name ) ; result . Err != nil {
c . Err = result . Err
return
} else {
team = result . Data . ( * model . Team )
}
2015-06-14 23:53:32 -08:00
2015-07-29 16:17:20 -04:00
if resend == "true" {
if result := <- api . Srv . Store . User ( ) . GetByEmail ( team . Id , email ) ; result . Err != nil {
2015-06-14 23:53:32 -08:00
c . Err = result . Err
return
} else {
user := result . Data . ( * model . User )
2015-10-05 14:54:15 -07:00
if user . LastActivityAt > 0 {
api . FireAndForgetEmailChangeVerifyEmail ( user . Id , user . Email , team . Name , team . DisplayName , c . GetSiteURL ( ) , c . GetTeamURLFromTeam ( team ) )
} else {
api . FireAndForgetVerifyEmail ( user . Id , user . Email , team . Name , team . DisplayName , c . GetSiteURL ( ) , c . GetTeamURLFromTeam ( team ) )
}
2015-09-21 16:03:18 -07:00
2015-09-21 17:03:08 -07:00
newAddress := strings . Replace ( r . URL . String ( ) , "&resend=true" , "&resend_success=true" , - 1 )
2015-09-21 16:03:18 -07:00
http . Redirect ( w , r , newAddress , http . StatusFound )
2015-06-14 23:53:32 -08:00
return
}
}
2015-10-06 09:12:49 -04:00
if len ( userId ) == 26 && len ( hashedId ) != 0 && model . ComparePassword ( hashedId , userId ) {
2015-06-14 23:53:32 -08:00
if c . Err = ( <- api . Srv . Store . User ( ) . VerifyEmail ( userId ) ) . Err ; c . Err != nil {
return
} else {
2015-10-06 09:12:49 -04:00
c . LogAudit ( "Email Verified" )
http . Redirect ( w , r , api . GetProtocol ( r ) + "://" + r . Host + "/" + name + "/login?verified=true&email=" + email , http . StatusTemporaryRedirect )
return
2015-06-14 23:53:32 -08:00
}
}
page := NewHtmlTemplatePage ( "verify" , "Email Verified" )
2015-07-29 16:17:20 -04:00
page . Props [ "TeamURL" ] = c . GetTeamURLFromTeam ( team )
page . Props [ "UserEmail" ] = email
2015-09-21 17:03:08 -07:00
page . Props [ "ResendSuccess" ] = resendSuccess
2015-06-14 23:53:32 -08:00
page . Render ( c , w )
}
func findTeam ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
page := NewHtmlTemplatePage ( "find_team" , "Find Team" )
page . Render ( c , w )
}
func resetPassword ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
isResetLink := true
hash := r . URL . Query ( ) . Get ( "h" )
data := r . URL . Query ( ) . Get ( "d" )
2015-07-08 11:50:10 -04:00
params := mux . Vars ( r )
teamName := params [ "team" ]
2015-06-14 23:53:32 -08:00
if len ( hash ) == 0 || len ( data ) == 0 {
isResetLink = false
} else {
2015-09-22 12:12:50 -07:00
if ! model . ComparePassword ( hash , fmt . Sprintf ( "%v:%v" , data , utils . Cfg . EmailSettings . PasswordResetSalt ) ) {
2015-06-14 23:53:32 -08:00
c . Err = model . NewAppError ( "resetPassword" , "The reset link does not appear to be valid" , "" )
return
}
props := model . MapFromJson ( strings . NewReader ( data ) )
t , err := strconv . ParseInt ( props [ "time" ] , 10 , 64 )
if err != nil || model . GetMillis ( ) - t > 1000 * 60 * 60 { // one hour
c . Err = model . NewAppError ( "resetPassword" , "The signup link has expired" , "" )
return
}
}
2015-07-08 11:50:10 -04:00
teamDisplayName := "Developer/Beta"
var team * model . Team
if tResult := <- api . Srv . Store . Team ( ) . GetByName ( teamName ) ; tResult . Err != nil {
c . Err = tResult . Err
return
} else {
team = tResult . Data . ( * model . Team )
}
2015-06-14 23:53:32 -08:00
2015-07-08 11:50:10 -04:00
if team != nil {
teamDisplayName = team . DisplayName
2015-06-14 23:53:32 -08:00
}
page := NewHtmlTemplatePage ( "password_reset" , "" )
2015-10-16 09:10:54 -07:00
page . Props [ "Title" ] = "Reset Password " + page . ClientCfg [ "SiteName" ]
2015-07-08 11:50:10 -04:00
page . Props [ "TeamDisplayName" ] = teamDisplayName
2015-10-01 17:52:47 -07:00
page . Props [ "TeamName" ] = teamName
2015-06-14 23:53:32 -08:00
page . Props [ "Hash" ] = hash
page . Props [ "Data" ] = data
2015-07-08 11:50:10 -04:00
page . Props [ "TeamName" ] = teamName
2015-06-14 23:53:32 -08:00
page . Props [ "IsReset" ] = strconv . FormatBool ( isResetLink )
page . Render ( c , w )
}
2015-07-15 12:48:50 -04:00
2015-07-17 09:47:25 -04:00
func signupWithOAuth ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
params := mux . Vars ( r )
service := params [ "service" ]
2015-07-22 12:13:45 -04:00
teamName := params [ "team" ]
if len ( teamName ) == 0 {
c . Err = model . NewAppError ( "signupWithOAuth" , "Invalid team name" , "team_name=" + teamName )
c . Err . StatusCode = http . StatusBadRequest
return
}
hash := r . URL . Query ( ) . Get ( "h" )
var team * model . Team
if result := <- api . Srv . Store . Team ( ) . GetByName ( teamName ) ; result . Err != nil {
c . Err = result . Err
return
} else {
team = result . Data . ( * model . Team )
}
if api . IsVerifyHashRequired ( nil , team , hash ) {
data := r . URL . Query ( ) . Get ( "d" )
props := model . MapFromJson ( strings . NewReader ( data ) )
2015-09-22 12:12:50 -07:00
if ! model . ComparePassword ( hash , fmt . Sprintf ( "%v:%v" , data , utils . Cfg . EmailSettings . InviteSalt ) ) {
2015-07-22 15:05:20 -04:00
c . Err = model . NewAppError ( "signupWithOAuth" , "The signup link does not appear to be valid" , "" )
2015-07-22 12:13:45 -04:00
return
}
t , err := strconv . ParseInt ( props [ "time" ] , 10 , 64 )
if err != nil || model . GetMillis ( ) - t > 1000 * 60 * 60 * 48 { // 48 hours
2015-07-22 15:05:20 -04:00
c . Err = model . NewAppError ( "signupWithOAuth" , "The signup link has expired" , "" )
2015-07-22 12:13:45 -04:00
return
}
if team . Id != props [ "id" ] {
2015-07-22 15:05:20 -04:00
c . Err = model . NewAppError ( "signupWithOAuth" , "Invalid team name" , data )
2015-07-22 12:13:45 -04:00
return
}
}
2015-07-15 12:48:50 -04:00
2015-07-17 09:47:25 -04:00
redirectUri := c . GetSiteURL ( ) + "/signup/" + service + "/complete"
2015-07-15 12:48:50 -04:00
2015-08-14 08:43:49 -04:00
api . GetAuthorizationCode ( c , w , r , teamName , service , redirectUri , "" )
2015-07-15 12:48:50 -04:00
}
2015-07-17 09:47:25 -04:00
func signupCompleteOAuth ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
params := mux . Vars ( r )
service := params [ "service" ]
2015-07-15 12:48:50 -04:00
code := r . URL . Query ( ) . Get ( "code" )
state := r . URL . Query ( ) . Get ( "state" )
2015-08-14 08:43:49 -04:00
uri := c . GetSiteURL ( ) + "/signup/" + service + "/complete"
2015-07-17 09:47:25 -04:00
2015-08-14 08:43:49 -04:00
if body , team , err := api . AuthorizeOAuthUser ( service , code , state , uri ) ; err != nil {
2015-07-15 12:48:50 -04:00
c . Err = err
return
} else {
2015-07-17 09:47:25 -04:00
var user * model . User
if service == model . USER_AUTH_SERVICE_GITLAB {
glu := model . GitLabUserFromJson ( body )
user = model . UserFromGitLabUser ( glu )
}
if user == nil {
c . Err = model . NewAppError ( "signupCompleteOAuth" , "Could not create user out of " + service + " user object" , "" )
return
}
2015-08-28 08:37:55 -04:00
suchan := api . Srv . Store . User ( ) . GetByAuth ( team . Id , user . AuthData , service )
euchan := api . Srv . Store . User ( ) . GetByEmail ( team . Id , user . Email )
if team . Email == "" {
team . Email = user . Email
if result := <- api . Srv . Store . Team ( ) . Update ( team ) ; result . Err != nil {
c . Err = result . Err
return
}
} else {
found := true
count := 0
for found {
if found = api . IsUsernameTaken ( user . Username , team . Id ) ; c . Err != nil {
return
} else if found {
user . Username = user . Username + strconv . Itoa ( count )
count += 1
}
}
}
if result := <- suchan ; result . Err == nil {
2015-07-22 11:26:55 -04:00
c . Err = model . NewAppError ( "signupCompleteOAuth" , "This " + service + " account has already been used to sign up for team " + team . DisplayName , "email=" + user . Email )
return
}
2015-08-28 08:37:55 -04:00
if result := <- euchan ; result . Err == nil {
2015-07-22 11:14:51 -04:00
c . Err = model . NewAppError ( "signupCompleteOAuth" , "Team " + team . DisplayName + " already has a user with the email address attached to your " + service + " account" , "email=" + user . Email )
return
}
2015-07-22 10:12:28 -04:00
user . TeamId = team . Id
2015-09-10 10:43:14 -04:00
user . EmailVerified = true
2015-07-15 12:48:50 -04:00
2015-08-28 08:37:55 -04:00
ruser := api . CreateUser ( c , team , user )
if c . Err != nil {
return
}
api . Login ( c , w , r , ruser , "" )
if c . Err != nil {
return
}
2015-10-16 19:05:55 -07:00
page := NewHtmlTemplatePage ( "home" , "Home" )
page . Team = team
page . User = ruser
page . Render ( c , w )
2015-07-15 12:48:50 -04:00
}
}
2015-07-17 09:47:25 -04:00
func loginWithOAuth ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
params := mux . Vars ( r )
service := params [ "service" ]
2015-07-22 12:13:45 -04:00
teamName := params [ "team" ]
2015-08-14 08:43:49 -04:00
loginHint := r . URL . Query ( ) . Get ( "login_hint" )
2015-07-22 12:13:45 -04:00
if len ( teamName ) == 0 {
c . Err = model . NewAppError ( "loginWithOAuth" , "Invalid team name" , "team_name=" + teamName )
c . Err . StatusCode = http . StatusBadRequest
return
}
// Make sure team exists
if result := <- api . Srv . Store . Team ( ) . GetByName ( teamName ) ; result . Err != nil {
c . Err = result . Err
return
}
2015-07-15 12:48:50 -04:00
2015-07-17 09:47:25 -04:00
redirectUri := c . GetSiteURL ( ) + "/login/" + service + "/complete"
2015-07-15 12:48:50 -04:00
2015-08-14 08:43:49 -04:00
api . GetAuthorizationCode ( c , w , r , teamName , service , redirectUri , loginHint )
2015-07-15 12:48:50 -04:00
}
2015-07-17 09:47:25 -04:00
func loginCompleteOAuth ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
params := mux . Vars ( r )
service := params [ "service" ]
2015-07-15 12:48:50 -04:00
code := r . URL . Query ( ) . Get ( "code" )
state := r . URL . Query ( ) . Get ( "state" )
2015-08-14 08:43:49 -04:00
uri := c . GetSiteURL ( ) + "/login/" + service + "/complete"
2015-07-17 09:47:25 -04:00
2015-08-14 08:43:49 -04:00
if body , team , err := api . AuthorizeOAuthUser ( service , code , state , uri ) ; err != nil {
2015-07-15 12:48:50 -04:00
c . Err = err
return
} else {
2015-07-17 09:47:25 -04:00
authData := ""
if service == model . USER_AUTH_SERVICE_GITLAB {
glu := model . GitLabUserFromJson ( body )
authData = glu . GetAuthData ( )
}
if len ( authData ) == 0 {
c . Err = model . NewAppError ( "loginCompleteOAuth" , "Could not parse auth data out of " + service + " user object" , "" )
return
}
2015-07-15 12:48:50 -04:00
var user * model . User
2015-07-22 10:12:28 -04:00
if result := <- api . Srv . Store . User ( ) . GetByAuth ( team . Id , authData , service ) ; result . Err != nil {
2015-07-15 12:48:50 -04:00
c . Err = result . Err
return
} else {
user = result . Data . ( * model . User )
api . Login ( c , w , r , user , "" )
if c . Err != nil {
return
}
2015-10-16 19:05:55 -07:00
page := NewHtmlTemplatePage ( "home" , "Home" )
page . Team = team
page . User = user
page . Render ( c , w )
2015-07-15 12:48:50 -04:00
root ( c , w , r )
}
}
}
2015-09-03 13:38:14 -07:00
func adminConsole ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
2015-09-10 14:56:37 -07:00
2015-09-10 18:32:22 -07:00
if ! c . HasSystemAdminPermissions ( "adminConsole" ) {
2015-09-10 14:56:37 -07:00
return
}
2015-09-10 18:32:22 -07:00
2015-10-16 19:05:55 -07:00
teamChan := api . Srv . Store . Team ( ) . Get ( c . Session . TeamId )
userChan := api . Srv . Store . User ( ) . Get ( c . Session . UserId )
var team * model . Team
if tr := <- teamChan ; tr . Err != nil {
c . Err = tr . Err
2015-10-16 09:10:54 -07:00
return
2015-10-16 19:05:55 -07:00
} else {
team = tr . Data . ( * model . Team )
}
var user * model . User
if ur := <- userChan ; ur . Err != nil {
c . Err = ur . Err
return
} else {
user = ur . Data . ( * model . User )
2015-10-16 09:10:54 -07:00
}
2015-10-18 18:55:04 +03:00
params := mux . Vars ( r )
activeTab := params [ "tab" ]
teamId := params [ "team" ]
2015-09-10 18:32:22 -07:00
page := NewHtmlTemplatePage ( "admin_console" , "Admin Console" )
2015-10-19 10:29:10 -07:00
page . User = user
page . Team = team
2015-10-18 18:55:04 +03:00
page . Props [ "ActiveTab" ] = activeTab
page . Props [ "TeamId" ] = teamId
2015-09-10 18:32:22 -07:00
page . Render ( c , w )
2015-09-03 13:38:14 -07:00
}
2015-09-16 15:49:12 -04:00
func authorizeOAuth ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
if ! utils . Cfg . ServiceSettings . EnableOAuthServiceProvider {
c . Err = model . NewAppError ( "authorizeOAuth" , "The system admin has turned off OAuth service providing." , "" )
c . Err . StatusCode = http . StatusNotImplemented
return
}
if ! CheckBrowserCompatability ( c , r ) {
return
}
responseType := r . URL . Query ( ) . Get ( "response_type" )
clientId := r . URL . Query ( ) . Get ( "client_id" )
redirect := r . URL . Query ( ) . Get ( "redirect_uri" )
scope := r . URL . Query ( ) . Get ( "scope" )
state := r . URL . Query ( ) . Get ( "state" )
if len ( responseType ) == 0 || len ( clientId ) == 0 || len ( redirect ) == 0 {
c . Err = model . NewAppError ( "authorizeOAuth" , "Missing one or more of response_type, client_id, or redirect_uri" , "" )
return
}
var app * model . OAuthApp
if result := <- api . Srv . Store . OAuth ( ) . GetApp ( clientId ) ; result . Err != nil {
c . Err = result . Err
return
} else {
app = result . Data . ( * model . OAuthApp )
}
var team * model . Team
if result := <- api . Srv . Store . Team ( ) . Get ( c . Session . TeamId ) ; result . Err != nil {
c . Err = result . Err
return
} else {
team = result . Data . ( * model . Team )
}
page := NewHtmlTemplatePage ( "authorize" , "Authorize Application" )
page . Props [ "TeamName" ] = team . Name
page . Props [ "AppName" ] = app . Name
page . Props [ "ResponseType" ] = responseType
page . Props [ "ClientId" ] = clientId
page . Props [ "RedirectUri" ] = redirect
page . Props [ "Scope" ] = scope
page . Props [ "State" ] = state
page . Render ( c , w )
}
func getAccessToken ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
if ! utils . Cfg . ServiceSettings . EnableOAuthServiceProvider {
c . Err = model . NewAppError ( "getAccessToken" , "The system admin has turned off OAuth service providing." , "" )
c . Err . StatusCode = http . StatusNotImplemented
return
}
c . LogAudit ( "attempt" )
r . ParseForm ( )
grantType := r . FormValue ( "grant_type" )
if grantType != model . ACCESS_TOKEN_GRANT_TYPE {
c . Err = model . NewAppError ( "getAccessToken" , "invalid_request: Bad grant_type" , "" )
return
}
clientId := r . FormValue ( "client_id" )
if len ( clientId ) != 26 {
c . Err = model . NewAppError ( "getAccessToken" , "invalid_request: Bad client_id" , "" )
return
}
secret := r . FormValue ( "client_secret" )
if len ( secret ) == 0 {
c . Err = model . NewAppError ( "getAccessToken" , "invalid_request: Missing client_secret" , "" )
return
}
code := r . FormValue ( "code" )
if len ( code ) == 0 {
c . Err = model . NewAppError ( "getAccessToken" , "invalid_request: Missing code" , "" )
return
}
redirectUri := r . FormValue ( "redirect_uri" )
achan := api . Srv . Store . OAuth ( ) . GetApp ( clientId )
tchan := api . Srv . Store . OAuth ( ) . GetAccessDataByAuthCode ( code )
authData := api . GetAuthData ( code )
if authData == nil {
c . LogAudit ( "fail - invalid auth code" )
c . Err = model . NewAppError ( "getAccessToken" , "invalid_grant: Invalid or expired authorization code" , "" )
return
}
uchan := api . Srv . Store . User ( ) . Get ( authData . UserId )
if authData . IsExpired ( ) {
c . LogAudit ( "fail - auth code expired" )
c . Err = model . NewAppError ( "getAccessToken" , "invalid_grant: Invalid or expired authorization code" , "" )
return
}
if authData . RedirectUri != redirectUri {
c . LogAudit ( "fail - redirect uri provided did not match previous redirect uri" )
c . Err = model . NewAppError ( "getAccessToken" , "invalid_request: Supplied redirect_uri does not match authorization code redirect_uri" , "" )
return
}
if ! model . ComparePassword ( code , fmt . Sprintf ( "%v:%v:%v:%v" , clientId , redirectUri , authData . CreateAt , authData . UserId ) ) {
c . LogAudit ( "fail - auth code is invalid" )
c . Err = model . NewAppError ( "getAccessToken" , "invalid_grant: Invalid or expired authorization code" , "" )
return
}
var app * model . OAuthApp
if result := <- achan ; result . Err != nil {
c . Err = model . NewAppError ( "getAccessToken" , "invalid_client: Invalid client credentials" , "" )
return
} else {
app = result . Data . ( * model . OAuthApp )
}
if ! model . ComparePassword ( app . ClientSecret , secret ) {
c . LogAudit ( "fail - invalid client credentials" )
c . Err = model . NewAppError ( "getAccessToken" , "invalid_client: Invalid client credentials" , "" )
return
}
callback := redirectUri
if len ( callback ) == 0 {
callback = app . CallbackUrls [ 0 ]
}
if result := <- tchan ; result . Err != nil {
c . Err = model . NewAppError ( "getAccessToken" , "server_error: Encountered internal server error while accessing database" , "" )
return
} else if result . Data != nil {
c . LogAudit ( "fail - auth code has been used previously" )
accessData := result . Data . ( * model . AccessData )
// Revoke access token, related auth code, and session from DB as well as from cache
if err := api . RevokeAccessToken ( accessData . Token ) ; err != nil {
l4g . Error ( "Encountered an error revoking an access token, err=" + err . Message )
}
c . Err = model . NewAppError ( "getAccessToken" , "invalid_grant: Authorization code already exchanged for an access token" , "" )
return
}
var user * model . User
if result := <- uchan ; result . Err != nil {
c . Err = model . NewAppError ( "getAccessToken" , "server_error: Encountered internal server error while pulling user from database" , "" )
return
} else {
user = result . Data . ( * model . User )
}
session := & model . Session { UserId : user . Id , TeamId : user . TeamId , Roles : user . Roles , IsOAuth : true }
if result := <- api . Srv . Store . Session ( ) . Save ( session ) ; result . Err != nil {
c . Err = model . NewAppError ( "getAccessToken" , "server_error: Encountered internal server error while saving session to database" , "" )
return
} else {
session = result . Data . ( * model . Session )
api . AddSessionToCache ( session )
}
accessData := & model . AccessData { AuthCode : authData . Code , Token : session . Token , RedirectUri : callback }
if result := <- api . Srv . Store . OAuth ( ) . SaveAccessData ( accessData ) ; result . Err != nil {
l4g . Error ( result . Err )
c . Err = model . NewAppError ( "getAccessToken" , "server_error: Encountered internal server error while saving access token to database" , "" )
return
}
accessRsp := & model . AccessResponse { AccessToken : session . Token , TokenType : model . ACCESS_TOKEN_TYPE , ExpiresIn : model . SESSION_TIME_OAUTH_IN_SECS }
w . Header ( ) . Set ( "Content-Type" , "application/json" )
w . Header ( ) . Set ( "Cache-Control" , "no-store" )
w . Header ( ) . Set ( "Pragma" , "no-cache" )
c . LogAuditWithUserId ( user . Id , "success" )
w . Write ( [ ] byte ( accessRsp . ToJson ( ) ) )
}
2015-09-21 14:22:23 -04:00
func incomingWebhook ( c * api . Context , w http . ResponseWriter , r * http . Request ) {
2015-10-05 08:46:23 -04:00
if ! utils . Cfg . ServiceSettings . EnableIncomingWebhooks {
c . Err = model . NewAppError ( "incomingWebhook" , "Incoming webhooks have been disabled by the system admin." , "" )
c . Err . StatusCode = http . StatusNotImplemented
return
}
2015-09-21 14:22:23 -04:00
params := mux . Vars ( r )
id := params [ "id" ]
hchan := api . Srv . Store . Webhook ( ) . GetIncoming ( id )
r . ParseForm ( )
2015-09-29 08:38:26 -04:00
var props map [ string ] string
if r . Header . Get ( "Content-Type" ) == "application/json" {
props = model . MapFromJson ( r . Body )
} else {
props = model . MapFromJson ( strings . NewReader ( r . FormValue ( "payload" ) ) )
}
2015-09-21 14:22:23 -04:00
text := props [ "text" ]
if len ( text ) == 0 {
c . Err = model . NewAppError ( "incomingWebhook" , "No text specified" , "" )
return
}
channelName := props [ "channel" ]
var hook * model . IncomingWebhook
if result := <- hchan ; result . Err != nil {
c . Err = model . NewAppError ( "incomingWebhook" , "Invalid webhook" , "err=" + result . Err . Message )
return
} else {
hook = result . Data . ( * model . IncomingWebhook )
}
var channel * model . Channel
var cchan store . StoreChannel
if len ( channelName ) != 0 {
if channelName [ 0 ] == '@' {
if result := <- api . Srv . Store . User ( ) . GetByUsername ( hook . TeamId , channelName [ 1 : ] ) ; result . Err != nil {
c . Err = model . NewAppError ( "incomingWebhook" , "Couldn't find the user" , "err=" + result . Err . Message )
return
} else {
channelName = model . GetDMNameFromIds ( result . Data . ( * model . User ) . Id , hook . UserId )
}
} else if channelName [ 0 ] == '#' {
channelName = channelName [ 1 : ]
}
cchan = api . Srv . Store . Channel ( ) . GetByName ( hook . TeamId , channelName )
} else {
cchan = api . Srv . Store . Channel ( ) . Get ( hook . ChannelId )
}
2015-10-19 10:19:48 -04:00
overrideUsername := props [ "username" ]
overrideIconUrl := props [ "icon_url" ]
2015-09-21 14:22:23 -04:00
if result := <- cchan ; result . Err != nil {
c . Err = model . NewAppError ( "incomingWebhook" , "Couldn't find the channel" , "err=" + result . Err . Message )
return
} else {
channel = result . Data . ( * model . Channel )
}
pchan := api . Srv . Store . Channel ( ) . CheckPermissionsTo ( hook . TeamId , channel . Id , hook . UserId )
2015-10-19 10:19:48 -04:00
// create a mock session
c . Session = model . Session { UserId : hook . UserId , TeamId : hook . TeamId , IsOAuth : false }
2015-09-21 14:22:23 -04:00
if ! c . HasPermissionsToChannel ( pchan , "createIncomingHook" ) && channel . Type != model . CHANNEL_OPEN {
c . Err = model . NewAppError ( "incomingWebhook" , "Inappropriate channel permissions" , "" )
return
}
2015-10-19 10:19:48 -04:00
if _ , err := api . CreateWebhookPost ( c , channel . Id , text , overrideUsername , overrideIconUrl ) ; err != nil {
c . Err = err
2015-09-21 14:22:23 -04:00
return
}
w . Header ( ) . Set ( "Content-Type" , "text/plain" )
w . Write ( [ ] byte ( "ok" ) )
}