2017-04-12 08:27:57 -04:00
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2017-01-30 08:30:02 -05:00
// See License.txt for license information.
package api4
import (
"fmt"
"net/http"
2017-04-26 23:11:32 +09:00
"regexp"
2017-01-30 08:30:02 -05:00
"strings"
"time"
l4g "github.com/alecthomas/log4go"
goi18n "github.com/nicksnyder/go-i18n/i18n"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
2017-01-30 08:30:02 -05:00
)
type Context struct {
2017-09-06 17:12:54 -05:00
App * app . App
2017-04-04 11:54:52 -04:00
Session model . Session
Params * ApiParams
Err * model . AppError
T goi18n . TranslateFunc
RequestId string
IpAddress string
Path string
siteURLHeader string
2017-01-30 08:30:02 -05:00
}
func ApiHandler ( h func ( * Context , http . ResponseWriter , * http . Request ) ) http . Handler {
return & handler {
handleFunc : h ,
requireSession : false ,
trustRequester : false ,
requireMfa : false ,
}
}
func ApiSessionRequired ( h func ( * Context , http . ResponseWriter , * http . Request ) ) http . Handler {
return & handler {
handleFunc : h ,
requireSession : true ,
trustRequester : false ,
requireMfa : true ,
}
}
func ApiSessionRequiredMfa ( h func ( * Context , http . ResponseWriter , * http . Request ) ) http . Handler {
return & handler {
handleFunc : h ,
requireSession : true ,
trustRequester : false ,
requireMfa : false ,
}
}
func ApiHandlerTrustRequester ( h func ( * Context , http . ResponseWriter , * http . Request ) ) http . Handler {
return & handler {
handleFunc : h ,
requireSession : false ,
trustRequester : true ,
requireMfa : false ,
}
}
func ApiSessionRequiredTrustRequester ( h func ( * Context , http . ResponseWriter , * http . Request ) ) http . Handler {
return & handler {
handleFunc : h ,
requireSession : true ,
trustRequester : true ,
requireMfa : true ,
}
}
type handler struct {
handleFunc func ( * Context , http . ResponseWriter , * http . Request )
requireSession bool
trustRequester bool
requireMfa bool
}
func ( h handler ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
now := time . Now ( )
l4g . Debug ( "%v - %v" , r . Method , r . URL . Path )
c := & Context { }
2017-09-06 17:12:54 -05:00
c . App = app . Global ( )
2017-01-30 08:30:02 -05:00
c . T , _ = utils . GetTranslationsAndLocale ( w , r )
c . RequestId = model . NewId ( )
c . IpAddress = utils . GetIpAddress ( r )
c . Params = ApiParamsFromRequest ( r )
token := ""
isTokenFromQueryString := false
// Attempt to parse token out of the header
authHeader := r . Header . Get ( model . HEADER_AUTH )
if len ( authHeader ) > 6 && strings . ToUpper ( authHeader [ 0 : 6 ] ) == model . HEADER_BEARER {
// Default session token
token = authHeader [ 7 : ]
} else if len ( authHeader ) > 5 && strings . ToLower ( authHeader [ 0 : 5 ] ) == model . HEADER_TOKEN {
// OAuth token
token = authHeader [ 6 : ]
}
// Attempt to parse the token from the cookie
if len ( token ) == 0 {
if cookie , err := r . Cookie ( model . SESSION_COOKIE_TOKEN ) ; err == nil {
token = cookie . Value
if h . requireSession && ! h . trustRequester {
if r . Header . Get ( model . HEADER_REQUESTED_WITH ) != model . HEADER_REQUESTED_WITH_XML {
2017-08-31 15:03:16 +01:00
c . Err = model . NewAppError ( "ServeHTTP" , "api.context.session_expired.app_error" , nil , "token=" + token + " Appears to be a CSRF attempt" , http . StatusUnauthorized )
2017-01-30 08:30:02 -05:00
token = ""
}
}
}
}
// Attempt to parse token out of the query string
if len ( token ) == 0 {
token = r . URL . Query ( ) . Get ( "access_token" )
isTokenFromQueryString = true
}
2017-04-04 11:54:52 -04:00
c . SetSiteURLHeader ( app . GetProtocol ( r ) + "://" + r . Host )
2017-01-30 08:30:02 -05:00
w . Header ( ) . Set ( model . HEADER_REQUEST_ID , c . RequestId )
2017-08-16 09:51:45 -07:00
w . Header ( ) . Set ( model . HEADER_VERSION_ID , fmt . Sprintf ( "%v.%v.%v.%v" , model . CurrentVersion , model . BuildNumber , utils . ClientCfgHash , utils . IsLicensed ( ) ) )
2017-01-30 08:30:02 -05:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
if r . Method == "GET" {
w . Header ( ) . Set ( "Expires" , "0" )
}
if len ( token ) != 0 {
2017-09-06 17:12:54 -05:00
session , err := app . Global ( ) . GetSession ( token )
2017-01-30 08:30:02 -05:00
if err != nil {
l4g . Error ( utils . T ( "api.context.invalid_session.error" ) , err . Error ( ) )
c . RemoveSessionCookie ( w , r )
if h . requireSession {
2017-08-31 15:03:16 +01:00
c . Err = model . NewAppError ( "ServeHTTP" , "api.context.session_expired.app_error" , nil , "token=" + token , http . StatusUnauthorized )
2017-01-30 08:30:02 -05:00
}
} else if ! session . IsOAuth && isTokenFromQueryString {
2017-08-31 15:03:16 +01:00
c . Err = model . NewAppError ( "ServeHTTP" , "api.context.token_provided.app_error" , nil , "token=" + token , http . StatusUnauthorized )
2017-01-30 08:30:02 -05:00
} else {
c . Session = * session
}
}
c . Path = r . URL . Path
if c . Err == nil && h . requireSession {
c . SessionRequired ( )
}
if c . Err == nil && h . requireMfa {
c . MfaRequired ( )
}
if c . Err == nil {
h . handleFunc ( c , w , r )
}
// Handle errors that have occured
if c . Err != nil {
c . Err . Translate ( c . T )
c . Err . RequestId = c . RequestId
c . LogError ( c . Err )
c . Err . Where = r . URL . Path
// Block out detailed error when not in developer mode
if ! * utils . Cfg . ServiceSettings . EnableDeveloper {
c . Err . DetailedError = ""
}
w . WriteHeader ( c . Err . StatusCode )
w . Write ( [ ] byte ( c . Err . ToJson ( ) ) )
2017-09-19 18:31:35 -05:00
if c . App . Metrics != nil {
c . App . Metrics . IncrementHttpError ( )
2017-01-30 08:30:02 -05:00
}
}
2017-09-19 18:31:35 -05:00
if c . App . Metrics != nil {
c . App . Metrics . IncrementHttpRequest ( )
2017-01-30 08:30:02 -05:00
2017-06-26 21:46:19 -07:00
if r . URL . Path != model . API_URL_SUFFIX + "/websocket" {
2017-01-30 08:30:02 -05:00
elapsed := float64 ( time . Since ( now ) ) / float64 ( time . Second )
2017-09-19 18:31:35 -05:00
c . App . Metrics . ObserveHttpRequestDuration ( elapsed )
2017-01-30 08:30:02 -05:00
}
}
}
func ( c * Context ) LogAudit ( extraInfo string ) {
audit := & model . Audit { UserId : c . Session . UserId , IpAddress : c . IpAddress , Action : c . Path , ExtraInfo : extraInfo , SessionId : c . Session . Id }
2017-09-06 17:12:54 -05:00
if r := <- app . Global ( ) . Srv . Store . Audit ( ) . Save ( audit ) ; r . Err != nil {
2017-01-30 08:30:02 -05:00
c . LogError ( r . Err )
}
}
func ( c * Context ) LogAuditWithUserId ( userId , extraInfo string ) {
if len ( c . Session . UserId ) > 0 {
extraInfo = strings . TrimSpace ( extraInfo + " session_user=" + c . Session . UserId )
}
audit := & model . Audit { UserId : userId , IpAddress : c . IpAddress , Action : c . Path , ExtraInfo : extraInfo , SessionId : c . Session . Id }
2017-09-06 17:12:54 -05:00
if r := <- app . Global ( ) . Srv . Store . Audit ( ) . Save ( audit ) ; r . Err != nil {
2017-01-30 08:30:02 -05:00
c . LogError ( r . Err )
}
}
func ( c * Context ) LogError ( err * model . AppError ) {
// filter out endless reconnects
if c . Path == "/api/v3/users/websocket" && err . StatusCode == 401 || err . Id == "web.check_browser_compatibility.app_error" {
c . LogDebug ( err )
} else {
2017-04-28 07:03:52 -07:00
l4g . Error ( utils . TDefault ( "api.context.log.error" ) , c . Path , err . Where , err . StatusCode ,
c . RequestId , c . Session . UserId , c . IpAddress , err . SystemMessage ( utils . TDefault ) , err . DetailedError )
2017-01-30 08:30:02 -05:00
}
}
func ( c * Context ) LogDebug ( err * model . AppError ) {
2017-04-28 07:03:52 -07:00
l4g . Debug ( utils . TDefault ( "api.context.log.error" ) , c . Path , err . Where , err . StatusCode ,
c . RequestId , c . Session . UserId , c . IpAddress , err . SystemMessage ( utils . TDefault ) , err . DetailedError )
2017-01-30 08:30:02 -05:00
}
func ( c * Context ) IsSystemAdmin ( ) bool {
return app . SessionHasPermissionTo ( c . Session , model . PERMISSION_MANAGE_SYSTEM )
}
func ( c * Context ) SessionRequired ( ) {
2017-07-31 12:59:32 -04:00
if ! * utils . Cfg . ServiceSettings . EnableUserAccessTokens && c . Session . Props [ model . SESSION_PROP_TYPE ] == model . SESSION_TYPE_USER_ACCESS_TOKEN {
c . Err = model . NewAppError ( "" , "api.context.session_expired.app_error" , nil , "UserAccessToken" , http . StatusUnauthorized )
return
}
2017-01-30 08:30:02 -05:00
if len ( c . Session . UserId ) == 0 {
2017-04-10 08:19:49 -04:00
c . Err = model . NewAppError ( "" , "api.context.session_expired.app_error" , nil , "UserRequired" , http . StatusUnauthorized )
2017-01-30 08:30:02 -05:00
return
}
}
func ( c * Context ) MfaRequired ( ) {
// Must be licensed for MFA and have it configured for enforcement
2017-08-16 09:51:45 -07:00
if ! utils . IsLicensed ( ) || ! * utils . License ( ) . Features . MFA || ! * utils . Cfg . ServiceSettings . EnableMultifactorAuthentication || ! * utils . Cfg . ServiceSettings . EnforceMultifactorAuthentication {
2017-01-30 08:30:02 -05:00
return
}
// OAuth integrations are excepted
if c . Session . IsOAuth {
return
}
2017-09-06 17:12:54 -05:00
if user , err := app . Global ( ) . GetUser ( c . Session . UserId ) ; err != nil {
2017-08-31 15:03:16 +01:00
c . Err = model . NewAppError ( "" , "api.context.session_expired.app_error" , nil , "MfaRequired" , http . StatusUnauthorized )
2017-01-30 08:30:02 -05:00
return
} else {
// Only required for email and ldap accounts
if user . AuthService != "" &&
user . AuthService != model . USER_AUTH_SERVICE_EMAIL &&
user . AuthService != model . USER_AUTH_SERVICE_LDAP {
return
}
2017-05-09 07:48:57 -05:00
// Special case to let user get themself
if c . Path == "/api/v4/users/me" {
return
}
2017-01-30 08:30:02 -05:00
if ! user . MfaActive {
2017-05-09 07:48:57 -05:00
c . Err = model . NewAppError ( "" , "api.context.mfa_required.app_error" , nil , "MfaRequired" , http . StatusForbidden )
2017-01-30 08:30:02 -05:00
return
}
}
}
func ( c * Context ) RemoveSessionCookie ( w http . ResponseWriter , r * http . Request ) {
cookie := & http . Cookie {
Name : model . SESSION_COOKIE_TOKEN ,
Value : "" ,
Path : "/" ,
MaxAge : - 1 ,
HttpOnly : true ,
}
http . SetCookie ( w , cookie )
}
func ( c * Context ) SetInvalidParam ( parameter string ) {
c . Err = NewInvalidParamError ( parameter )
}
func ( c * Context ) SetInvalidUrlParam ( parameter string ) {
c . Err = NewInvalidUrlParamError ( parameter )
}
func NewInvalidParamError ( parameter string ) * model . AppError {
2017-08-31 15:03:16 +01:00
err := model . NewAppError ( "Context" , "api.context.invalid_body_param.app_error" , map [ string ] interface { } { "Name" : parameter } , "" , http . StatusBadRequest )
2017-01-30 08:30:02 -05:00
return err
}
func NewInvalidUrlParamError ( parameter string ) * model . AppError {
2017-08-31 15:03:16 +01:00
err := model . NewAppError ( "Context" , "api.context.invalid_url_param.app_error" , map [ string ] interface { } { "Name" : parameter } , "" , http . StatusBadRequest )
2017-01-30 08:30:02 -05:00
return err
}
func ( c * Context ) SetPermissionError ( permission * model . Permission ) {
2017-08-31 15:03:16 +01:00
c . Err = model . NewAppError ( "Permissions" , "api.context.permissions.app_error" , nil , "userId=" + c . Session . UserId + ", " + "permission=" + permission . Id , http . StatusForbidden )
2017-01-30 08:30:02 -05:00
}
2017-04-04 11:54:52 -04:00
func ( c * Context ) SetSiteURLHeader ( url string ) {
c . siteURLHeader = strings . TrimRight ( url , "/" )
2017-01-30 08:30:02 -05:00
}
2017-04-04 11:54:52 -04:00
func ( c * Context ) GetSiteURLHeader ( ) string {
return c . siteURLHeader
2017-01-30 08:30:02 -05:00
}
func ( c * Context ) RequireUserId ( ) * Context {
if c . Err != nil {
return c
}
2017-03-21 18:43:16 -04:00
if c . Params . UserId == model . ME {
c . Params . UserId = c . Session . UserId
}
2017-01-30 08:30:02 -05:00
if len ( c . Params . UserId ) != 26 {
c . SetInvalidUrlParam ( "user_id" )
}
return c
}
func ( c * Context ) RequireTeamId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . TeamId ) != 26 {
c . SetInvalidUrlParam ( "team_id" )
}
return c
}
2017-06-20 09:55:43 -04:00
func ( c * Context ) RequireInviteId ( ) * Context {
if c . Err != nil {
return c
}
2017-07-14 17:06:59 -04:00
if len ( c . Params . InviteId ) == 0 {
2017-06-20 09:55:43 -04:00
c . SetInvalidUrlParam ( "invite_id" )
}
return c
}
2017-07-31 12:59:32 -04:00
func ( c * Context ) RequireTokenId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . TokenId ) != 26 {
c . SetInvalidUrlParam ( "token_id" )
}
return c
}
2017-01-30 08:30:02 -05:00
func ( c * Context ) RequireChannelId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . ChannelId ) != 26 {
c . SetInvalidUrlParam ( "channel_id" )
}
return c
}
2017-02-07 11:54:07 -05:00
2017-02-08 05:00:16 -05:00
func ( c * Context ) RequireUsername ( ) * Context {
if c . Err != nil {
return c
}
2017-02-14 10:28:08 -05:00
if ! model . IsValidUsername ( c . Params . Username ) {
c . SetInvalidParam ( "username" )
2017-02-08 05:00:16 -05:00
}
return c
}
2017-02-13 10:52:50 -05:00
func ( c * Context ) RequirePostId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . PostId ) != 26 {
c . SetInvalidUrlParam ( "post_id" )
}
return c
}
2017-04-20 09:55:02 -04:00
func ( c * Context ) RequireAppId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . AppId ) != 26 {
c . SetInvalidUrlParam ( "app_id" )
}
return c
}
2017-02-17 10:31:21 -05:00
func ( c * Context ) RequireFileId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . FileId ) != 26 {
c . SetInvalidUrlParam ( "file_id" )
}
return c
}
2017-09-01 09:00:27 -04:00
func ( c * Context ) RequirePluginId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . PluginId ) == 0 {
c . SetInvalidUrlParam ( "plugin_id" )
}
return c
}
2017-03-13 10:14:16 -04:00
func ( c * Context ) RequireReportId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . ReportId ) != 26 {
c . SetInvalidUrlParam ( "report_id" )
}
return c
}
2017-04-17 16:07:28 +02:00
func ( c * Context ) RequireEmojiId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . EmojiId ) != 26 {
c . SetInvalidUrlParam ( "emoji_id" )
}
return c
}
2017-02-14 10:28:08 -05:00
func ( c * Context ) RequireTeamName ( ) * Context {
if c . Err != nil {
return c
}
2017-02-17 10:31:21 -05:00
if ! model . IsValidTeamName ( c . Params . TeamName ) {
2017-02-14 10:28:08 -05:00
c . SetInvalidUrlParam ( "team_name" )
}
return c
}
func ( c * Context ) RequireChannelName ( ) * Context {
if c . Err != nil {
return c
}
if ! model . IsValidChannelIdentifier ( c . Params . ChannelName ) {
c . SetInvalidUrlParam ( "channel_name" )
2017-02-17 10:31:21 -05:00
}
2017-02-14 10:28:08 -05:00
return c
}
2017-02-07 11:54:07 -05:00
func ( c * Context ) RequireEmail ( ) * Context {
if c . Err != nil {
return c
}
2017-02-14 10:28:08 -05:00
if ! model . IsValidEmail ( c . Params . Email ) {
2017-02-07 11:54:07 -05:00
c . SetInvalidUrlParam ( "email" )
}
return c
}
2017-02-28 04:14:16 -05:00
func ( c * Context ) RequireCategory ( ) * Context {
if c . Err != nil {
return c
}
2017-04-22 21:52:03 +09:00
if ! model . IsValidAlphaNumHyphenUnderscore ( c . Params . Category , true ) {
2017-02-28 04:14:16 -05:00
c . SetInvalidUrlParam ( "category" )
}
return c
}
2017-04-20 09:55:02 -04:00
func ( c * Context ) RequireService ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . Service ) == 0 {
c . SetInvalidUrlParam ( "service" )
}
return c
}
2017-02-28 04:14:16 -05:00
func ( c * Context ) RequirePreferenceName ( ) * Context {
if c . Err != nil {
return c
}
2017-04-22 21:52:03 +09:00
if ! model . IsValidAlphaNumHyphenUnderscore ( c . Params . PreferenceName , true ) {
2017-02-28 04:14:16 -05:00
c . SetInvalidUrlParam ( "preference_name" )
}
return c
}
2017-03-12 04:10:56 +05:30
2017-04-22 21:52:03 +09:00
func ( c * Context ) RequireEmojiName ( ) * Context {
if c . Err != nil {
return c
}
2017-04-26 23:11:32 +09:00
validName := regexp . MustCompile ( ` ^[a-zA-Z0-9\-\+_]+$ ` )
if len ( c . Params . EmojiName ) == 0 || len ( c . Params . EmojiName ) > 64 || ! validName . MatchString ( c . Params . EmojiName ) {
2017-04-22 21:52:03 +09:00
c . SetInvalidUrlParam ( "emoji_name" )
}
return c
}
2017-03-12 04:10:56 +05:30
func ( c * Context ) RequireHookId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . HookId ) != 26 {
c . SetInvalidUrlParam ( "hook_id" )
}
return c
}
2017-04-08 02:06:09 +09:00
func ( c * Context ) RequireCommandId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . CommandId ) != 26 {
c . SetInvalidUrlParam ( "command_id" )
}
return c
}
2017-05-18 15:05:57 -04:00
func ( c * Context ) RequireJobId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . JobId ) != 26 {
c . SetInvalidUrlParam ( "job_id" )
}
return c
}
func ( c * Context ) RequireJobType ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . JobType ) == 0 || len ( c . Params . JobType ) > 32 {
c . SetInvalidUrlParam ( "job_type" )
}
return c
}
2017-08-29 16:14:59 -05:00
func ( c * Context ) RequireActionId ( ) * Context {
if c . Err != nil {
return c
}
if len ( c . Params . ActionId ) != 26 {
c . SetInvalidUrlParam ( "action_id" )
}
return c
}