github.com/wgh-/mattermost-server@v4.8.0-rc2+incompatible/api4/context.go (about) 1 // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package api4 5 6 import ( 7 "fmt" 8 "net/http" 9 "regexp" 10 "strings" 11 "time" 12 13 l4g "github.com/alecthomas/log4go" 14 goi18n "github.com/nicksnyder/go-i18n/i18n" 15 16 "github.com/mattermost/mattermost-server/app" 17 "github.com/mattermost/mattermost-server/model" 18 "github.com/mattermost/mattermost-server/utils" 19 ) 20 21 type Context struct { 22 App *app.App 23 Session model.Session 24 Params *ApiParams 25 Err *model.AppError 26 T goi18n.TranslateFunc 27 RequestId string 28 IpAddress string 29 Path string 30 siteURLHeader string 31 } 32 33 func (api *API) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 34 return &handler{ 35 app: api.App, 36 handleFunc: h, 37 requireSession: false, 38 trustRequester: false, 39 requireMfa: false, 40 } 41 } 42 43 func (api *API) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 44 return &handler{ 45 app: api.App, 46 handleFunc: h, 47 requireSession: true, 48 trustRequester: false, 49 requireMfa: true, 50 } 51 } 52 53 func (api *API) ApiSessionRequiredMfa(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 54 return &handler{ 55 app: api.App, 56 handleFunc: h, 57 requireSession: true, 58 trustRequester: false, 59 requireMfa: false, 60 } 61 } 62 63 func (api *API) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 64 return &handler{ 65 app: api.App, 66 handleFunc: h, 67 requireSession: false, 68 trustRequester: true, 69 requireMfa: false, 70 } 71 } 72 73 func (api *API) ApiSessionRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 74 return &handler{ 75 app: api.App, 76 handleFunc: h, 77 requireSession: true, 78 trustRequester: true, 79 requireMfa: true, 80 } 81 } 82 83 type handler struct { 84 app *app.App 85 handleFunc func(*Context, http.ResponseWriter, *http.Request) 86 requireSession bool 87 trustRequester bool 88 requireMfa bool 89 } 90 91 func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 92 now := time.Now() 93 l4g.Debug("%v - %v", r.Method, r.URL.Path) 94 95 c := &Context{} 96 c.App = h.app 97 c.T, _ = utils.GetTranslationsAndLocale(w, r) 98 c.RequestId = model.NewId() 99 c.IpAddress = utils.GetIpAddress(r) 100 c.Params = ApiParamsFromRequest(r) 101 102 token, tokenLocation := app.ParseAuthTokenFromRequest(r) 103 104 // CSRF Check 105 if tokenLocation == app.TokenLocationCookie && h.requireSession && !h.trustRequester { 106 if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML { 107 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) 108 token = "" 109 } 110 } 111 112 c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host) 113 114 w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) 115 w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil)) 116 117 w.Header().Set("Content-Type", "application/json") 118 119 if r.Method == "GET" { 120 w.Header().Set("Expires", "0") 121 } 122 123 if len(token) != 0 { 124 session, err := c.App.GetSession(token) 125 126 if err != nil { 127 l4g.Info(utils.T("api.context.invalid_session.error"), err.Error()) 128 c.RemoveSessionCookie(w, r) 129 if h.requireSession { 130 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) 131 } 132 } else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString { 133 c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) 134 } else { 135 c.Session = *session 136 } 137 138 // Rate limit by UserID 139 if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.Session.UserId, w) { 140 return 141 } 142 } 143 144 c.Path = r.URL.Path 145 146 if c.Err == nil && h.requireSession { 147 c.SessionRequired() 148 } 149 150 if c.Err == nil && h.requireMfa { 151 c.MfaRequired() 152 } 153 154 if c.Err == nil { 155 h.handleFunc(c, w, r) 156 } 157 158 // Handle errors that have occured 159 if c.Err != nil { 160 c.Err.Translate(c.T) 161 c.Err.RequestId = c.RequestId 162 163 if c.Err.Id == "api.context.session_expired.app_error" { 164 c.LogInfo(c.Err) 165 } else { 166 c.LogError(c.Err) 167 } 168 169 c.Err.Where = r.URL.Path 170 171 // Block out detailed error when not in developer mode 172 if !*c.App.Config().ServiceSettings.EnableDeveloper { 173 c.Err.DetailedError = "" 174 } 175 176 w.WriteHeader(c.Err.StatusCode) 177 w.Write([]byte(c.Err.ToJson())) 178 179 if c.App.Metrics != nil { 180 c.App.Metrics.IncrementHttpError() 181 } 182 } 183 184 if c.App.Metrics != nil { 185 c.App.Metrics.IncrementHttpRequest() 186 187 if r.URL.Path != model.API_URL_SUFFIX+"/websocket" { 188 elapsed := float64(time.Since(now)) / float64(time.Second) 189 c.App.Metrics.ObserveHttpRequestDuration(elapsed) 190 } 191 } 192 } 193 194 func (c *Context) LogAudit(extraInfo string) { 195 audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id} 196 if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil { 197 c.LogError(r.Err) 198 } 199 } 200 201 func (c *Context) LogAuditWithUserId(userId, extraInfo string) { 202 203 if len(c.Session.UserId) > 0 { 204 extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId) 205 } 206 207 audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id} 208 if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil { 209 c.LogError(r.Err) 210 } 211 } 212 213 func (c *Context) LogError(err *model.AppError) { 214 215 // Filter out 404s, endless reconnects and browser compatibility errors 216 if err.StatusCode == http.StatusNotFound || 217 (c.Path == "/api/v3/users/websocket" && err.StatusCode == 401) || 218 err.Id == "web.check_browser_compatibility.app_error" { 219 c.LogDebug(err) 220 } else { 221 l4g.Error(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, 222 c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) 223 } 224 } 225 226 func (c *Context) LogInfo(err *model.AppError) { 227 l4g.Info(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, 228 c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) 229 } 230 231 func (c *Context) LogDebug(err *model.AppError) { 232 l4g.Debug(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode, 233 c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError) 234 } 235 236 func (c *Context) IsSystemAdmin() bool { 237 return c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) 238 } 239 240 func (c *Context) SessionRequired() { 241 if !*c.App.Config().ServiceSettings.EnableUserAccessTokens && c.Session.Props[model.SESSION_PROP_TYPE] == model.SESSION_TYPE_USER_ACCESS_TOKEN { 242 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized) 243 return 244 } 245 246 if len(c.Session.UserId) == 0 { 247 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized) 248 return 249 } 250 } 251 252 func (c *Context) MfaRequired() { 253 // Must be licensed for MFA and have it configured for enforcement 254 if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication { 255 return 256 } 257 258 // OAuth integrations are excepted 259 if c.Session.IsOAuth { 260 return 261 } 262 263 if user, err := c.App.GetUser(c.Session.UserId); err != nil { 264 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "MfaRequired", http.StatusUnauthorized) 265 return 266 } else { 267 // Only required for email and ldap accounts 268 if user.AuthService != "" && 269 user.AuthService != model.USER_AUTH_SERVICE_EMAIL && 270 user.AuthService != model.USER_AUTH_SERVICE_LDAP { 271 return 272 } 273 274 // Special case to let user get themself 275 if c.Path == "/api/v4/users/me" { 276 return 277 } 278 279 if !user.MfaActive { 280 c.Err = model.NewAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired", http.StatusForbidden) 281 return 282 } 283 } 284 } 285 286 func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) { 287 cookie := &http.Cookie{ 288 Name: model.SESSION_COOKIE_TOKEN, 289 Value: "", 290 Path: "/", 291 MaxAge: -1, 292 HttpOnly: true, 293 } 294 295 http.SetCookie(w, cookie) 296 } 297 298 func (c *Context) SetInvalidParam(parameter string) { 299 c.Err = NewInvalidParamError(parameter) 300 } 301 302 func (c *Context) SetInvalidUrlParam(parameter string) { 303 c.Err = NewInvalidUrlParamError(parameter) 304 } 305 306 func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool { 307 metrics := c.App.Metrics 308 if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 { 309 if et == etag { 310 w.Header().Set(model.HEADER_ETAG_SERVER, etag) 311 w.WriteHeader(http.StatusNotModified) 312 if metrics != nil { 313 metrics.IncrementEtagHitCounter(routeName) 314 } 315 return true 316 } 317 } 318 319 if metrics != nil { 320 metrics.IncrementEtagMissCounter(routeName) 321 } 322 323 return false 324 } 325 326 func NewInvalidParamError(parameter string) *model.AppError { 327 err := model.NewAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest) 328 return err 329 } 330 func NewInvalidUrlParamError(parameter string) *model.AppError { 331 err := model.NewAppError("Context", "api.context.invalid_url_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest) 332 return err 333 } 334 335 func (c *Context) SetPermissionError(permission *model.Permission) { 336 c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden) 337 } 338 339 func (c *Context) SetSiteURLHeader(url string) { 340 c.siteURLHeader = strings.TrimRight(url, "/") 341 } 342 343 func (c *Context) GetSiteURLHeader() string { 344 return c.siteURLHeader 345 } 346 347 func (c *Context) RequireUserId() *Context { 348 if c.Err != nil { 349 return c 350 } 351 352 if c.Params.UserId == model.ME { 353 c.Params.UserId = c.Session.UserId 354 } 355 356 if len(c.Params.UserId) != 26 { 357 c.SetInvalidUrlParam("user_id") 358 } 359 return c 360 } 361 362 func (c *Context) RequireTeamId() *Context { 363 if c.Err != nil { 364 return c 365 } 366 367 if len(c.Params.TeamId) != 26 { 368 c.SetInvalidUrlParam("team_id") 369 } 370 return c 371 } 372 373 func (c *Context) RequireInviteId() *Context { 374 if c.Err != nil { 375 return c 376 } 377 378 if len(c.Params.InviteId) == 0 { 379 c.SetInvalidUrlParam("invite_id") 380 } 381 return c 382 } 383 384 func (c *Context) RequireTokenId() *Context { 385 if c.Err != nil { 386 return c 387 } 388 389 if len(c.Params.TokenId) != 26 { 390 c.SetInvalidUrlParam("token_id") 391 } 392 return c 393 } 394 395 func (c *Context) RequireChannelId() *Context { 396 if c.Err != nil { 397 return c 398 } 399 400 if len(c.Params.ChannelId) != 26 { 401 c.SetInvalidUrlParam("channel_id") 402 } 403 return c 404 } 405 406 func (c *Context) RequireUsername() *Context { 407 if c.Err != nil { 408 return c 409 } 410 411 if !model.IsValidUsername(c.Params.Username) { 412 c.SetInvalidParam("username") 413 } 414 415 return c 416 } 417 418 func (c *Context) RequirePostId() *Context { 419 if c.Err != nil { 420 return c 421 } 422 423 if len(c.Params.PostId) != 26 { 424 c.SetInvalidUrlParam("post_id") 425 } 426 return c 427 } 428 429 func (c *Context) RequireAppId() *Context { 430 if c.Err != nil { 431 return c 432 } 433 434 if len(c.Params.AppId) != 26 { 435 c.SetInvalidUrlParam("app_id") 436 } 437 return c 438 } 439 440 func (c *Context) RequireFileId() *Context { 441 if c.Err != nil { 442 return c 443 } 444 445 if len(c.Params.FileId) != 26 { 446 c.SetInvalidUrlParam("file_id") 447 } 448 449 return c 450 } 451 452 func (c *Context) RequireFilename() *Context { 453 if c.Err != nil { 454 return c 455 } 456 457 if len(c.Params.Filename) == 0 { 458 c.SetInvalidUrlParam("filename") 459 } 460 461 return c 462 } 463 464 func (c *Context) RequirePluginId() *Context { 465 if c.Err != nil { 466 return c 467 } 468 469 if len(c.Params.PluginId) == 0 { 470 c.SetInvalidUrlParam("plugin_id") 471 } 472 473 return c 474 } 475 476 func (c *Context) RequireReportId() *Context { 477 if c.Err != nil { 478 return c 479 } 480 481 if len(c.Params.ReportId) != 26 { 482 c.SetInvalidUrlParam("report_id") 483 } 484 return c 485 } 486 487 func (c *Context) RequireEmojiId() *Context { 488 if c.Err != nil { 489 return c 490 } 491 492 if len(c.Params.EmojiId) != 26 { 493 c.SetInvalidUrlParam("emoji_id") 494 } 495 return c 496 } 497 498 func (c *Context) RequireTeamName() *Context { 499 if c.Err != nil { 500 return c 501 } 502 503 if !model.IsValidTeamName(c.Params.TeamName) { 504 c.SetInvalidUrlParam("team_name") 505 } 506 507 return c 508 } 509 510 func (c *Context) RequireChannelName() *Context { 511 if c.Err != nil { 512 return c 513 } 514 515 if !model.IsValidChannelIdentifier(c.Params.ChannelName) { 516 c.SetInvalidUrlParam("channel_name") 517 } 518 519 return c 520 } 521 522 func (c *Context) RequireEmail() *Context { 523 if c.Err != nil { 524 return c 525 } 526 527 if !model.IsValidEmail(c.Params.Email) { 528 c.SetInvalidUrlParam("email") 529 } 530 531 return c 532 } 533 534 func (c *Context) RequireCategory() *Context { 535 if c.Err != nil { 536 return c 537 } 538 539 if !model.IsValidAlphaNumHyphenUnderscore(c.Params.Category, true) { 540 c.SetInvalidUrlParam("category") 541 } 542 543 return c 544 } 545 546 func (c *Context) RequireService() *Context { 547 if c.Err != nil { 548 return c 549 } 550 551 if len(c.Params.Service) == 0 { 552 c.SetInvalidUrlParam("service") 553 } 554 555 return c 556 } 557 558 func (c *Context) RequirePreferenceName() *Context { 559 if c.Err != nil { 560 return c 561 } 562 563 if !model.IsValidAlphaNumHyphenUnderscore(c.Params.PreferenceName, true) { 564 c.SetInvalidUrlParam("preference_name") 565 } 566 567 return c 568 } 569 570 func (c *Context) RequireEmojiName() *Context { 571 if c.Err != nil { 572 return c 573 } 574 575 validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`) 576 577 if len(c.Params.EmojiName) == 0 || len(c.Params.EmojiName) > model.EMOJI_NAME_MAX_LENGTH || !validName.MatchString(c.Params.EmojiName) { 578 c.SetInvalidUrlParam("emoji_name") 579 } 580 581 return c 582 } 583 584 func (c *Context) RequireHookId() *Context { 585 if c.Err != nil { 586 return c 587 } 588 589 if len(c.Params.HookId) != 26 { 590 c.SetInvalidUrlParam("hook_id") 591 } 592 593 return c 594 } 595 596 func (c *Context) RequireCommandId() *Context { 597 if c.Err != nil { 598 return c 599 } 600 601 if len(c.Params.CommandId) != 26 { 602 c.SetInvalidUrlParam("command_id") 603 } 604 return c 605 } 606 607 func (c *Context) RequireJobId() *Context { 608 if c.Err != nil { 609 return c 610 } 611 612 if len(c.Params.JobId) != 26 { 613 c.SetInvalidUrlParam("job_id") 614 } 615 return c 616 } 617 618 func (c *Context) RequireJobType() *Context { 619 if c.Err != nil { 620 return c 621 } 622 623 if len(c.Params.JobType) == 0 || len(c.Params.JobType) > 32 { 624 c.SetInvalidUrlParam("job_type") 625 } 626 return c 627 } 628 629 func (c *Context) RequireActionId() *Context { 630 if c.Err != nil { 631 return c 632 } 633 634 if len(c.Params.ActionId) != 26 { 635 c.SetInvalidUrlParam("action_id") 636 } 637 return c 638 }