github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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 goi18n "github.com/nicksnyder/go-i18n/i18n" 14 15 "github.com/mattermost/mattermost-server/app" 16 "github.com/mattermost/mattermost-server/mlog" 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 mlog.Debug(fmt.Sprintf("%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 mlog.Info(fmt.Sprintf("Invalid session err=%v", err.Error())) 128 if err.StatusCode == http.StatusInternalServerError { 129 c.Err = err 130 } else if h.requireSession { 131 c.RemoveSessionCookie(w, r) 132 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) 133 } 134 } else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString { 135 c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) 136 } else { 137 c.Session = *session 138 } 139 140 // Rate limit by UserID 141 if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.Session.UserId, w) { 142 return 143 } 144 } 145 146 c.Path = r.URL.Path 147 148 if c.Err == nil && h.requireSession { 149 c.SessionRequired() 150 } 151 152 if c.Err == nil && h.requireMfa { 153 c.MfaRequired() 154 } 155 156 if c.Err == nil { 157 h.handleFunc(c, w, r) 158 } 159 160 // Handle errors that have occurred 161 if c.Err != nil { 162 c.Err.Translate(c.T) 163 c.Err.RequestId = c.RequestId 164 165 if c.Err.Id == "api.context.session_expired.app_error" { 166 c.LogInfo(c.Err) 167 } else { 168 c.LogError(c.Err) 169 } 170 171 c.Err.Where = r.URL.Path 172 173 // Block out detailed error when not in developer mode 174 if !*c.App.Config().ServiceSettings.EnableDeveloper { 175 c.Err.DetailedError = "" 176 } 177 178 w.WriteHeader(c.Err.StatusCode) 179 w.Write([]byte(c.Err.ToJson())) 180 181 if c.App.Metrics != nil { 182 c.App.Metrics.IncrementHttpError() 183 } 184 } 185 186 if c.App.Metrics != nil { 187 c.App.Metrics.IncrementHttpRequest() 188 189 if r.URL.Path != model.API_URL_SUFFIX+"/websocket" { 190 elapsed := float64(time.Since(now)) / float64(time.Second) 191 c.App.Metrics.ObserveHttpRequestDuration(elapsed) 192 } 193 } 194 } 195 196 func (c *Context) LogAudit(extraInfo string) { 197 audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id} 198 if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil { 199 c.LogError(r.Err) 200 } 201 } 202 203 func (c *Context) LogAuditWithUserId(userId, extraInfo string) { 204 205 if len(c.Session.UserId) > 0 { 206 extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId) 207 } 208 209 audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id} 210 if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil { 211 c.LogError(r.Err) 212 } 213 } 214 215 func (c *Context) LogError(err *model.AppError) { 216 217 // Filter out 404s, endless reconnects and browser compatibility errors 218 if err.StatusCode == http.StatusNotFound || 219 (c.Path == "/api/v3/users/websocket" && err.StatusCode == 401) || 220 err.Id == "web.check_browser_compatibility.app_error" { 221 c.LogDebug(err) 222 } else { 223 mlog.Error(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, 224 c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) 225 } 226 } 227 228 func (c *Context) LogInfo(err *model.AppError) { 229 mlog.Info(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, 230 c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) 231 } 232 233 func (c *Context) LogDebug(err *model.AppError) { 234 mlog.Debug(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, 235 c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) 236 } 237 238 func (c *Context) IsSystemAdmin() bool { 239 return c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) 240 } 241 242 func (c *Context) SessionRequired() { 243 if !*c.App.Config().ServiceSettings.EnableUserAccessTokens && c.Session.Props[model.SESSION_PROP_TYPE] == model.SESSION_TYPE_USER_ACCESS_TOKEN { 244 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized) 245 return 246 } 247 248 if len(c.Session.UserId) == 0 { 249 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized) 250 return 251 } 252 } 253 254 func (c *Context) MfaRequired() { 255 // Must be licensed for MFA and have it configured for enforcement 256 if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication { 257 return 258 } 259 260 // OAuth integrations are excepted 261 if c.Session.IsOAuth { 262 return 263 } 264 265 if user, err := c.App.GetUser(c.Session.UserId); err != nil { 266 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "MfaRequired", http.StatusUnauthorized) 267 return 268 } else { 269 // Only required for email and ldap accounts 270 if user.AuthService != "" && 271 user.AuthService != model.USER_AUTH_SERVICE_EMAIL && 272 user.AuthService != model.USER_AUTH_SERVICE_LDAP { 273 return 274 } 275 276 // Special case to let user get themself 277 if c.Path == "/api/v4/users/me" { 278 return 279 } 280 281 if !user.MfaActive { 282 c.Err = model.NewAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired", http.StatusForbidden) 283 return 284 } 285 } 286 } 287 288 func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) { 289 cookie := &http.Cookie{ 290 Name: model.SESSION_COOKIE_TOKEN, 291 Value: "", 292 Path: "/", 293 MaxAge: -1, 294 HttpOnly: true, 295 } 296 297 http.SetCookie(w, cookie) 298 } 299 300 func (c *Context) SetInvalidParam(parameter string) { 301 c.Err = NewInvalidParamError(parameter) 302 } 303 304 func (c *Context) SetInvalidUrlParam(parameter string) { 305 c.Err = NewInvalidUrlParamError(parameter) 306 } 307 308 func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool { 309 metrics := c.App.Metrics 310 if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 { 311 if et == etag { 312 w.Header().Set(model.HEADER_ETAG_SERVER, etag) 313 w.WriteHeader(http.StatusNotModified) 314 if metrics != nil { 315 metrics.IncrementEtagHitCounter(routeName) 316 } 317 return true 318 } 319 } 320 321 if metrics != nil { 322 metrics.IncrementEtagMissCounter(routeName) 323 } 324 325 return false 326 } 327 328 func NewInvalidParamError(parameter string) *model.AppError { 329 err := model.NewAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest) 330 return err 331 } 332 func NewInvalidUrlParamError(parameter string) *model.AppError { 333 err := model.NewAppError("Context", "api.context.invalid_url_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest) 334 return err 335 } 336 337 func (c *Context) SetPermissionError(permission *model.Permission) { 338 c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden) 339 } 340 341 func (c *Context) SetSiteURLHeader(url string) { 342 c.siteURLHeader = strings.TrimRight(url, "/") 343 } 344 345 func (c *Context) GetSiteURLHeader() string { 346 return c.siteURLHeader 347 } 348 349 func (c *Context) RequireUserId() *Context { 350 if c.Err != nil { 351 return c 352 } 353 354 if c.Params.UserId == model.ME { 355 c.Params.UserId = c.Session.UserId 356 } 357 358 if len(c.Params.UserId) != 26 { 359 c.SetInvalidUrlParam("user_id") 360 } 361 return c 362 } 363 364 func (c *Context) RequireTeamId() *Context { 365 if c.Err != nil { 366 return c 367 } 368 369 if len(c.Params.TeamId) != 26 { 370 c.SetInvalidUrlParam("team_id") 371 } 372 return c 373 } 374 375 func (c *Context) RequireInviteId() *Context { 376 if c.Err != nil { 377 return c 378 } 379 380 if len(c.Params.InviteId) == 0 { 381 c.SetInvalidUrlParam("invite_id") 382 } 383 return c 384 } 385 386 func (c *Context) RequireTokenId() *Context { 387 if c.Err != nil { 388 return c 389 } 390 391 if len(c.Params.TokenId) != 26 { 392 c.SetInvalidUrlParam("token_id") 393 } 394 return c 395 } 396 397 func (c *Context) RequireChannelId() *Context { 398 if c.Err != nil { 399 return c 400 } 401 402 if len(c.Params.ChannelId) != 26 { 403 c.SetInvalidUrlParam("channel_id") 404 } 405 return c 406 } 407 408 func (c *Context) RequireUsername() *Context { 409 if c.Err != nil { 410 return c 411 } 412 413 if !model.IsValidUsername(c.Params.Username) { 414 c.SetInvalidParam("username") 415 } 416 417 return c 418 } 419 420 func (c *Context) RequirePostId() *Context { 421 if c.Err != nil { 422 return c 423 } 424 425 if len(c.Params.PostId) != 26 { 426 c.SetInvalidUrlParam("post_id") 427 } 428 return c 429 } 430 431 func (c *Context) RequireAppId() *Context { 432 if c.Err != nil { 433 return c 434 } 435 436 if len(c.Params.AppId) != 26 { 437 c.SetInvalidUrlParam("app_id") 438 } 439 return c 440 } 441 442 func (c *Context) RequireFileId() *Context { 443 if c.Err != nil { 444 return c 445 } 446 447 if len(c.Params.FileId) != 26 { 448 c.SetInvalidUrlParam("file_id") 449 } 450 451 return c 452 } 453 454 func (c *Context) RequireFilename() *Context { 455 if c.Err != nil { 456 return c 457 } 458 459 if len(c.Params.Filename) == 0 { 460 c.SetInvalidUrlParam("filename") 461 } 462 463 return c 464 } 465 466 func (c *Context) RequirePluginId() *Context { 467 if c.Err != nil { 468 return c 469 } 470 471 if len(c.Params.PluginId) == 0 { 472 c.SetInvalidUrlParam("plugin_id") 473 } 474 475 return c 476 } 477 478 func (c *Context) RequireReportId() *Context { 479 if c.Err != nil { 480 return c 481 } 482 483 if len(c.Params.ReportId) != 26 { 484 c.SetInvalidUrlParam("report_id") 485 } 486 return c 487 } 488 489 func (c *Context) RequireEmojiId() *Context { 490 if c.Err != nil { 491 return c 492 } 493 494 if len(c.Params.EmojiId) != 26 { 495 c.SetInvalidUrlParam("emoji_id") 496 } 497 return c 498 } 499 500 func (c *Context) RequireTeamName() *Context { 501 if c.Err != nil { 502 return c 503 } 504 505 if !model.IsValidTeamName(c.Params.TeamName) { 506 c.SetInvalidUrlParam("team_name") 507 } 508 509 return c 510 } 511 512 func (c *Context) RequireChannelName() *Context { 513 if c.Err != nil { 514 return c 515 } 516 517 if !model.IsValidChannelIdentifier(c.Params.ChannelName) { 518 c.SetInvalidUrlParam("channel_name") 519 } 520 521 return c 522 } 523 524 func (c *Context) RequireEmail() *Context { 525 if c.Err != nil { 526 return c 527 } 528 529 if !model.IsValidEmail(c.Params.Email) { 530 c.SetInvalidUrlParam("email") 531 } 532 533 return c 534 } 535 536 func (c *Context) RequireCategory() *Context { 537 if c.Err != nil { 538 return c 539 } 540 541 if !model.IsValidAlphaNumHyphenUnderscore(c.Params.Category, true) { 542 c.SetInvalidUrlParam("category") 543 } 544 545 return c 546 } 547 548 func (c *Context) RequireService() *Context { 549 if c.Err != nil { 550 return c 551 } 552 553 if len(c.Params.Service) == 0 { 554 c.SetInvalidUrlParam("service") 555 } 556 557 return c 558 } 559 560 func (c *Context) RequirePreferenceName() *Context { 561 if c.Err != nil { 562 return c 563 } 564 565 if !model.IsValidAlphaNumHyphenUnderscore(c.Params.PreferenceName, true) { 566 c.SetInvalidUrlParam("preference_name") 567 } 568 569 return c 570 } 571 572 func (c *Context) RequireEmojiName() *Context { 573 if c.Err != nil { 574 return c 575 } 576 577 validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`) 578 579 if len(c.Params.EmojiName) == 0 || len(c.Params.EmojiName) > model.EMOJI_NAME_MAX_LENGTH || !validName.MatchString(c.Params.EmojiName) { 580 c.SetInvalidUrlParam("emoji_name") 581 } 582 583 return c 584 } 585 586 func (c *Context) RequireHookId() *Context { 587 if c.Err != nil { 588 return c 589 } 590 591 if len(c.Params.HookId) != 26 { 592 c.SetInvalidUrlParam("hook_id") 593 } 594 595 return c 596 } 597 598 func (c *Context) RequireCommandId() *Context { 599 if c.Err != nil { 600 return c 601 } 602 603 if len(c.Params.CommandId) != 26 { 604 c.SetInvalidUrlParam("command_id") 605 } 606 return c 607 } 608 609 func (c *Context) RequireJobId() *Context { 610 if c.Err != nil { 611 return c 612 } 613 614 if len(c.Params.JobId) != 26 { 615 c.SetInvalidUrlParam("job_id") 616 } 617 return c 618 } 619 620 func (c *Context) RequireJobType() *Context { 621 if c.Err != nil { 622 return c 623 } 624 625 if len(c.Params.JobType) == 0 || len(c.Params.JobType) > 32 { 626 c.SetInvalidUrlParam("job_type") 627 } 628 return c 629 } 630 631 func (c *Context) RequireActionId() *Context { 632 if c.Err != nil { 633 return c 634 } 635 636 if len(c.Params.ActionId) != 26 { 637 c.SetInvalidUrlParam("action_id") 638 } 639 return c 640 } 641 642 func (c *Context) RequireRoleId() *Context { 643 if c.Err != nil { 644 return c 645 } 646 647 if len(c.Params.RoleId) != 26 { 648 c.SetInvalidUrlParam("role_id") 649 } 650 return c 651 } 652 653 func (c *Context) RequireRoleName() *Context { 654 if c.Err != nil { 655 return c 656 } 657 658 if !model.IsValidRoleName(c.Params.RoleName) { 659 c.SetInvalidUrlParam("role_name") 660 } 661 662 return c 663 }