1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package web 5 6import ( 7 "net/http" 8 "path" 9 "regexp" 10 "strings" 11 12 "github.com/mattermost/mattermost-server/v6/app" 13 "github.com/mattermost/mattermost-server/v6/app/request" 14 "github.com/mattermost/mattermost-server/v6/audit" 15 "github.com/mattermost/mattermost-server/v6/model" 16 "github.com/mattermost/mattermost-server/v6/shared/i18n" 17 "github.com/mattermost/mattermost-server/v6/shared/mlog" 18 "github.com/mattermost/mattermost-server/v6/utils" 19) 20 21type Context struct { 22 App app.AppIface 23 AppContext *request.Context 24 Logger *mlog.Logger 25 Params *Params 26 Err *model.AppError 27 siteURLHeader string 28} 29 30// LogAuditRec logs an audit record using default LevelAPI. 31func (c *Context) LogAuditRec(rec *audit.Record) { 32 c.LogAuditRecWithLevel(rec, app.LevelAPI) 33} 34 35// LogAuditRec logs an audit record using specified Level. 36// If the context is flagged with a permissions error then `level` 37// is ignored and the audit record is emitted with `LevelPerms`. 38func (c *Context) LogAuditRecWithLevel(rec *audit.Record, level mlog.Level) { 39 if rec == nil { 40 return 41 } 42 if c.Err != nil { 43 rec.AddMeta("err", c.Err.Id) 44 rec.AddMeta("code", c.Err.StatusCode) 45 if c.Err.Id == "api.context.permissions.app_error" { 46 level = app.LevelPerms 47 } 48 rec.Fail() 49 } 50 c.App.Srv().Audit.LogRecord(level, *rec) 51} 52 53// MakeAuditRecord creates a audit record pre-populated with data from this context. 54func (c *Context) MakeAuditRecord(event string, initialStatus string) *audit.Record { 55 rec := &audit.Record{ 56 APIPath: c.AppContext.Path(), 57 Event: event, 58 Status: initialStatus, 59 UserID: c.AppContext.Session().UserId, 60 SessionID: c.AppContext.Session().Id, 61 Client: c.AppContext.UserAgent(), 62 IPAddress: c.AppContext.IPAddress(), 63 Meta: audit.Meta{audit.KeyClusterID: c.App.GetClusterId()}, 64 } 65 rec.AddMetaTypeConverter(model.AuditModelTypeConv) 66 67 return rec 68} 69 70func (c *Context) LogAudit(extraInfo string) { 71 audit := &model.Audit{UserId: c.AppContext.Session().UserId, IpAddress: c.AppContext.IPAddress(), Action: c.AppContext.Path(), ExtraInfo: extraInfo, SessionId: c.AppContext.Session().Id} 72 if err := c.App.Srv().Store.Audit().Save(audit); err != nil { 73 appErr := model.NewAppError("LogAudit", "app.audit.save.saving.app_error", nil, err.Error(), http.StatusInternalServerError) 74 c.LogErrorByCode(appErr) 75 } 76} 77 78func (c *Context) LogAuditWithUserId(userId, extraInfo string) { 79 if c.AppContext.Session().UserId != "" { 80 extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.AppContext.Session().UserId) 81 } 82 83 audit := &model.Audit{UserId: userId, IpAddress: c.AppContext.IPAddress(), Action: c.AppContext.Path(), ExtraInfo: extraInfo, SessionId: c.AppContext.Session().Id} 84 if err := c.App.Srv().Store.Audit().Save(audit); err != nil { 85 appErr := model.NewAppError("LogAuditWithUserId", "app.audit.save.saving.app_error", nil, err.Error(), http.StatusInternalServerError) 86 c.LogErrorByCode(appErr) 87 } 88} 89 90func (c *Context) LogErrorByCode(err *model.AppError) { 91 code := err.StatusCode 92 msg := err.SystemMessage(i18n.TDefault) 93 fields := []mlog.Field{ 94 mlog.String("err_where", err.Where), 95 mlog.Int("http_code", err.StatusCode), 96 mlog.String("err_details", err.DetailedError), 97 } 98 switch { 99 case (code >= http.StatusBadRequest && code < http.StatusInternalServerError) || 100 err.Id == "web.check_browser_compatibility.app_error": 101 c.Logger.Debug(msg, fields...) 102 case code == http.StatusNotImplemented: 103 c.Logger.Info(msg, fields...) 104 default: 105 c.Logger.Error(msg, fields...) 106 } 107} 108 109func (c *Context) IsSystemAdmin() bool { 110 return c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) 111} 112 113func (c *Context) SessionRequired() { 114 if !*c.App.Config().ServiceSettings.EnableUserAccessTokens && 115 c.AppContext.Session().Props[model.SessionPropType] == model.SessionTypeUserAccessToken && 116 c.AppContext.Session().Props[model.SessionPropIsBot] != model.SessionPropIsBotValue { 117 118 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized) 119 return 120 } 121 122 if c.AppContext.Session().UserId == "" { 123 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized) 124 return 125 } 126} 127 128func (c *Context) CloudKeyRequired() { 129 if license := c.App.Srv().License(); license == nil || !*license.Features.Cloud || c.AppContext.Session().Props[model.SessionPropType] != model.SessionTypeCloudKey { 130 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "TokenRequired", http.StatusUnauthorized) 131 return 132 } 133} 134 135func (c *Context) RemoteClusterTokenRequired() { 136 if license := c.App.Srv().License(); license == nil || !*license.Features.RemoteClusterService || c.AppContext.Session().Props[model.SessionPropType] != model.SessionTypeRemoteclusterToken { 137 c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "TokenRequired", http.StatusUnauthorized) 138 return 139 } 140} 141 142func (c *Context) MfaRequired() { 143 // Must be licensed for MFA and have it configured for enforcement 144 if license := c.App.Srv().License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication { 145 return 146 } 147 148 // OAuth integrations are excepted 149 if c.AppContext.Session().IsOAuth { 150 return 151 } 152 153 user, err := c.App.GetUser(c.AppContext.Session().UserId) 154 if err != nil { 155 c.Err = model.NewAppError("MfaRequired", "api.context.get_user.app_error", nil, err.Error(), http.StatusUnauthorized) 156 return 157 } 158 159 if user.IsGuest() && !*c.App.Config().GuestAccountsSettings.EnforceMultifactorAuthentication { 160 return 161 } 162 // Only required for email and ldap accounts 163 if user.AuthService != "" && 164 user.AuthService != model.UserAuthServiceEmail && 165 user.AuthService != model.UserAuthServiceLdap { 166 return 167 } 168 169 // Special case to let user get themself 170 subpath, _ := utils.GetSubpathFromConfig(c.App.Config()) 171 if c.AppContext.Path() == path.Join(subpath, "/api/v4/users/me") { 172 return 173 } 174 175 // Bots are exempt 176 if user.IsBot { 177 return 178 } 179 180 if !user.MfaActive { 181 c.Err = model.NewAppError("MfaRequired", "api.context.mfa_required.app_error", nil, "", http.StatusForbidden) 182 return 183 } 184} 185 186// ExtendSessionExpiryIfNeeded will update Session.ExpiresAt based on session lengths in config. 187// Session cookies will be resent to the client with updated max age. 188func (c *Context) ExtendSessionExpiryIfNeeded(w http.ResponseWriter, r *http.Request) { 189 if ok := c.App.ExtendSessionExpiryIfNeeded(c.AppContext.Session()); ok { 190 c.App.AttachSessionCookies(c.AppContext, w, r) 191 } 192} 193 194func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) { 195 subpath, _ := utils.GetSubpathFromConfig(c.App.Config()) 196 197 cookie := &http.Cookie{ 198 Name: model.SessionCookieToken, 199 Value: "", 200 Path: subpath, 201 MaxAge: -1, 202 HttpOnly: true, 203 } 204 205 http.SetCookie(w, cookie) 206} 207 208func (c *Context) SetInvalidParam(parameter string) { 209 c.Err = NewInvalidParamError(parameter) 210} 211 212func (c *Context) SetInvalidURLParam(parameter string) { 213 c.Err = NewInvalidURLParamError(parameter) 214} 215 216func (c *Context) SetServerBusyError() { 217 c.Err = NewServerBusyError() 218} 219 220func (c *Context) SetInvalidRemoteIdError(id string) { 221 c.Err = NewInvalidRemoteIdError(id) 222} 223 224func (c *Context) SetInvalidRemoteClusterTokenError() { 225 c.Err = NewInvalidRemoteClusterTokenError() 226} 227 228func (c *Context) SetJSONEncodingError() { 229 c.Err = NewJSONEncodingError() 230} 231 232func (c *Context) SetCommandNotFoundError() { 233 c.Err = model.NewAppError("GetCommand", "store.sql_command.save.get.app_error", nil, "", http.StatusNotFound) 234} 235 236func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool { 237 metrics := c.App.Metrics() 238 if et := r.Header.Get(model.HeaderEtagClient); etag != "" { 239 if et == etag { 240 w.Header().Set(model.HeaderEtagServer, etag) 241 w.WriteHeader(http.StatusNotModified) 242 if metrics != nil { 243 metrics.IncrementEtagHitCounter(routeName) 244 } 245 return true 246 } 247 } 248 249 if metrics != nil { 250 metrics.IncrementEtagMissCounter(routeName) 251 } 252 253 return false 254} 255 256func NewInvalidParamError(parameter string) *model.AppError { 257 err := model.NewAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest) 258 return err 259} 260func NewInvalidURLParamError(parameter string) *model.AppError { 261 err := model.NewAppError("Context", "api.context.invalid_url_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest) 262 return err 263} 264func NewServerBusyError() *model.AppError { 265 err := model.NewAppError("Context", "api.context.server_busy.app_error", nil, "", http.StatusServiceUnavailable) 266 return err 267} 268 269func NewInvalidRemoteIdError(parameter string) *model.AppError { 270 err := model.NewAppError("Context", "api.context.remote_id_invalid.app_error", map[string]interface{}{"RemoteId": parameter}, "", http.StatusBadRequest) 271 return err 272} 273 274func NewInvalidRemoteClusterTokenError() *model.AppError { 275 err := model.NewAppError("Context", "api.context.remote_id_invalid.app_error", nil, "", http.StatusUnauthorized) 276 return err 277} 278 279func NewJSONEncodingError() *model.AppError { 280 err := model.NewAppError("Context", "api.context.json_encoding.app_error", nil, "", http.StatusInternalServerError) 281 return err 282} 283 284func (c *Context) SetPermissionError(permissions ...*model.Permission) { 285 c.Err = c.App.MakePermissionError(c.AppContext.Session(), permissions) 286} 287 288func (c *Context) SetSiteURLHeader(url string) { 289 c.siteURLHeader = strings.TrimRight(url, "/") 290} 291 292func (c *Context) GetSiteURLHeader() string { 293 return c.siteURLHeader 294} 295 296func (c *Context) RequireUserId() *Context { 297 if c.Err != nil { 298 return c 299 } 300 301 if c.Params.UserId == model.Me { 302 c.Params.UserId = c.AppContext.Session().UserId 303 } 304 305 if !model.IsValidId(c.Params.UserId) { 306 c.SetInvalidURLParam("user_id") 307 } 308 return c 309} 310 311func (c *Context) RequireTeamId() *Context { 312 if c.Err != nil { 313 return c 314 } 315 316 if !model.IsValidId(c.Params.TeamId) { 317 c.SetInvalidURLParam("team_id") 318 } 319 return c 320} 321 322func (c *Context) RequireCategoryId() *Context { 323 if c.Err != nil { 324 return c 325 } 326 327 if !model.IsValidCategoryId(c.Params.CategoryId) { 328 c.SetInvalidURLParam("category_id") 329 } 330 return c 331} 332 333func (c *Context) RequireInviteId() *Context { 334 if c.Err != nil { 335 return c 336 } 337 338 if c.Params.InviteId == "" { 339 c.SetInvalidURLParam("invite_id") 340 } 341 return c 342} 343 344func (c *Context) RequireTokenId() *Context { 345 if c.Err != nil { 346 return c 347 } 348 349 if !model.IsValidId(c.Params.TokenId) { 350 c.SetInvalidURLParam("token_id") 351 } 352 return c 353} 354 355func (c *Context) RequireThreadId() *Context { 356 if c.Err != nil { 357 return c 358 } 359 360 if !model.IsValidId(c.Params.ThreadId) { 361 c.SetInvalidURLParam("thread_id") 362 } 363 return c 364} 365 366func (c *Context) RequireTimestamp() *Context { 367 if c.Err != nil { 368 return c 369 } 370 371 if c.Params.Timestamp == 0 { 372 c.SetInvalidURLParam("timestamp") 373 } 374 return c 375} 376 377func (c *Context) RequireChannelId() *Context { 378 if c.Err != nil { 379 return c 380 } 381 382 if !model.IsValidId(c.Params.ChannelId) { 383 c.SetInvalidURLParam("channel_id") 384 } 385 return c 386} 387 388func (c *Context) RequireUsername() *Context { 389 if c.Err != nil { 390 return c 391 } 392 393 if !model.IsValidUsername(c.Params.Username) { 394 c.SetInvalidParam("username") 395 } 396 397 return c 398} 399 400func (c *Context) RequirePostId() *Context { 401 if c.Err != nil { 402 return c 403 } 404 405 if !model.IsValidId(c.Params.PostId) { 406 c.SetInvalidURLParam("post_id") 407 } 408 return c 409} 410 411func (c *Context) RequirePolicyId() *Context { 412 if c.Err != nil { 413 return c 414 } 415 416 if !model.IsValidId(c.Params.PolicyId) { 417 c.SetInvalidURLParam("policy_id") 418 } 419 return c 420} 421 422func (c *Context) RequireAppId() *Context { 423 if c.Err != nil { 424 return c 425 } 426 427 if !model.IsValidId(c.Params.AppId) { 428 c.SetInvalidURLParam("app_id") 429 } 430 return c 431} 432 433func (c *Context) RequireFileId() *Context { 434 if c.Err != nil { 435 return c 436 } 437 438 if !model.IsValidId(c.Params.FileId) { 439 c.SetInvalidURLParam("file_id") 440 } 441 442 return c 443} 444 445func (c *Context) RequireUploadId() *Context { 446 if c.Err != nil { 447 return c 448 } 449 450 if !model.IsValidId(c.Params.UploadId) { 451 c.SetInvalidURLParam("upload_id") 452 } 453 454 return c 455} 456 457func (c *Context) RequireFilename() *Context { 458 if c.Err != nil { 459 return c 460 } 461 462 if c.Params.Filename == "" { 463 c.SetInvalidURLParam("filename") 464 } 465 466 return c 467} 468 469func (c *Context) RequirePluginId() *Context { 470 if c.Err != nil { 471 return c 472 } 473 474 if c.Params.PluginId == "" { 475 c.SetInvalidURLParam("plugin_id") 476 } 477 478 return c 479} 480 481func (c *Context) RequireReportId() *Context { 482 if c.Err != nil { 483 return c 484 } 485 486 if !model.IsValidId(c.Params.ReportId) { 487 c.SetInvalidURLParam("report_id") 488 } 489 return c 490} 491 492func (c *Context) RequireEmojiId() *Context { 493 if c.Err != nil { 494 return c 495 } 496 497 if !model.IsValidId(c.Params.EmojiId) { 498 c.SetInvalidURLParam("emoji_id") 499 } 500 return c 501} 502 503func (c *Context) RequireTeamName() *Context { 504 if c.Err != nil { 505 return c 506 } 507 508 if !model.IsValidTeamName(c.Params.TeamName) { 509 c.SetInvalidURLParam("team_name") 510 } 511 512 return c 513} 514 515func (c *Context) RequireChannelName() *Context { 516 if c.Err != nil { 517 return c 518 } 519 520 if !model.IsValidChannelIdentifier(c.Params.ChannelName) { 521 c.SetInvalidURLParam("channel_name") 522 } 523 524 return c 525} 526 527func (c *Context) SanitizeEmail() *Context { 528 if c.Err != nil { 529 return c 530 } 531 c.Params.Email = strings.ToLower(c.Params.Email) 532 if !model.IsValidEmail(c.Params.Email) { 533 c.SetInvalidURLParam("email") 534 } 535 536 return c 537} 538 539func (c *Context) RequireCategory() *Context { 540 if c.Err != nil { 541 return c 542 } 543 544 if !model.IsValidAlphaNumHyphenUnderscore(c.Params.Category, true) { 545 c.SetInvalidURLParam("category") 546 } 547 548 return c 549} 550 551func (c *Context) RequireService() *Context { 552 if c.Err != nil { 553 return c 554 } 555 556 if c.Params.Service == "" { 557 c.SetInvalidURLParam("service") 558 } 559 560 return c 561} 562 563func (c *Context) RequirePreferenceName() *Context { 564 if c.Err != nil { 565 return c 566 } 567 568 if !model.IsValidAlphaNumHyphenUnderscore(c.Params.PreferenceName, true) { 569 c.SetInvalidURLParam("preference_name") 570 } 571 572 return c 573} 574 575func (c *Context) RequireEmojiName() *Context { 576 if c.Err != nil { 577 return c 578 } 579 580 validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`) 581 582 if c.Params.EmojiName == "" || len(c.Params.EmojiName) > model.EmojiNameMaxLength || !validName.MatchString(c.Params.EmojiName) { 583 c.SetInvalidURLParam("emoji_name") 584 } 585 586 return c 587} 588 589func (c *Context) RequireHookId() *Context { 590 if c.Err != nil { 591 return c 592 } 593 594 if !model.IsValidId(c.Params.HookId) { 595 c.SetInvalidURLParam("hook_id") 596 } 597 598 return c 599} 600 601func (c *Context) RequireCommandId() *Context { 602 if c.Err != nil { 603 return c 604 } 605 606 if !model.IsValidId(c.Params.CommandId) { 607 c.SetInvalidURLParam("command_id") 608 } 609 return c 610} 611 612func (c *Context) RequireJobId() *Context { 613 if c.Err != nil { 614 return c 615 } 616 617 if !model.IsValidId(c.Params.JobId) { 618 c.SetInvalidURLParam("job_id") 619 } 620 return c 621} 622 623func (c *Context) RequireJobType() *Context { 624 if c.Err != nil { 625 return c 626 } 627 628 if c.Params.JobType == "" || len(c.Params.JobType) > 32 { 629 c.SetInvalidURLParam("job_type") 630 } 631 return c 632} 633 634func (c *Context) RequireRoleId() *Context { 635 if c.Err != nil { 636 return c 637 } 638 639 if !model.IsValidId(c.Params.RoleId) { 640 c.SetInvalidURLParam("role_id") 641 } 642 return c 643} 644 645func (c *Context) RequireSchemeId() *Context { 646 if c.Err != nil { 647 return c 648 } 649 650 if !model.IsValidId(c.Params.SchemeId) { 651 c.SetInvalidURLParam("scheme_id") 652 } 653 return c 654} 655 656func (c *Context) RequireRoleName() *Context { 657 if c.Err != nil { 658 return c 659 } 660 661 if !model.IsValidRoleName(c.Params.RoleName) { 662 c.SetInvalidURLParam("role_name") 663 } 664 665 return c 666} 667 668func (c *Context) RequireGroupId() *Context { 669 if c.Err != nil { 670 return c 671 } 672 673 if !model.IsValidId(c.Params.GroupId) { 674 c.SetInvalidURLParam("group_id") 675 } 676 return c 677} 678 679func (c *Context) RequireRemoteId() *Context { 680 if c.Err != nil { 681 return c 682 } 683 684 if c.Params.RemoteId == "" { 685 c.SetInvalidURLParam("remote_id") 686 } 687 return c 688} 689 690func (c *Context) RequireSyncableId() *Context { 691 if c.Err != nil { 692 return c 693 } 694 695 if !model.IsValidId(c.Params.SyncableId) { 696 c.SetInvalidURLParam("syncable_id") 697 } 698 return c 699} 700 701func (c *Context) RequireSyncableType() *Context { 702 if c.Err != nil { 703 return c 704 } 705 706 if c.Params.SyncableType != model.GroupSyncableTypeTeam && c.Params.SyncableType != model.GroupSyncableTypeChannel { 707 c.SetInvalidURLParam("syncable_type") 708 } 709 return c 710} 711 712func (c *Context) RequireBotUserId() *Context { 713 if c.Err != nil { 714 return c 715 } 716 717 if !model.IsValidId(c.Params.BotUserId) { 718 c.SetInvalidURLParam("bot_user_id") 719 } 720 return c 721} 722 723func (c *Context) RequireInvoiceId() *Context { 724 if c.Err != nil { 725 return c 726 } 727 728 if len(c.Params.InvoiceId) != 27 { 729 c.SetInvalidURLParam("invoice_id") 730 } 731 732 return c 733} 734 735func (c *Context) GetRemoteID(r *http.Request) string { 736 return r.Header.Get(model.HeaderRemoteclusterId) 737} 738