140 lines
4.3 KiB
Go
140 lines
4.3 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package apiAuditLogs
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost/tools/mattermost-govet/facts"
|
|
"github.com/mattermost/mattermost/tools/mattermost-govet/util"
|
|
"golang.org/x/tools/go/analysis"
|
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
|
"golang.org/x/tools/go/ast/inspector"
|
|
)
|
|
|
|
const mattermostPackagePath = "github.com/mattermost/mattermost-server/v6/"
|
|
|
|
var Analyzer = &analysis.Analyzer{
|
|
Name: "apiAuditLogs",
|
|
Doc: "check that audit records are properly created in the API layer.",
|
|
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.ApiHandlerFacts},
|
|
Run: run,
|
|
}
|
|
|
|
func run(pass *analysis.Pass) (interface{}, error) {
|
|
if pass.Pkg.Path() != util.API4PkgPath {
|
|
return nil, nil
|
|
}
|
|
|
|
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
|
nodeFilter := []ast.Node{
|
|
(*ast.FuncDecl)(nil),
|
|
}
|
|
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
|
funDecl := n.(*ast.FuncDecl)
|
|
|
|
filename := pass.Fset.File(n.Pos()).Name()
|
|
if strings.HasSuffix(filename, "_test.go") || strings.HasSuffix(filename, "apitestlib.go") {
|
|
return
|
|
}
|
|
|
|
if whiteList[funDecl.Name.Name] {
|
|
return
|
|
}
|
|
|
|
if obj, ok := pass.TypesInfo.Defs[funDecl.Name].(*types.Func); ok {
|
|
var fact facts.IsApiHandler
|
|
if !pass.ImportObjectFact(obj, &fact) {
|
|
return
|
|
}
|
|
}
|
|
initializationFound := false
|
|
logCallFound := false
|
|
successCallFound := false
|
|
ast.Inspect(funDecl, func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
case *ast.CallExpr:
|
|
fun, ok := n.Fun.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
instanceType, ok := pass.TypesInfo.Types[fun.X]
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
if instanceType.Type.String() == "*"+util.ServerModulePath+"/channels/audit.Record" && fun.Sel.Name == "Success" {
|
|
successCallFound = true
|
|
}
|
|
if instanceType.Type.String() == "*"+mattermostPackagePath+"/channels/web.Context" && (fun.Sel.Name == "LogAuditRec" || fun.Sel.Name == "LogAuditRecWithLevel") {
|
|
logCallFound = true
|
|
}
|
|
|
|
if instanceType.Type.String() == "*"+mattermostPackagePath+"/channels/web.Context" && fun.Sel.Name == "MakeAuditRecord" {
|
|
initializationFound = true
|
|
if len(n.Args) < 2 {
|
|
pass.Reportf(n.Pos(), "Invalid record initialization, expected at least 2 parameters")
|
|
return true
|
|
}
|
|
|
|
arg0, ok := n.Args[0].(*ast.BasicLit)
|
|
if !ok {
|
|
pass.Reportf(n.Args[0].Pos(), "Invalid record name, expected \"%s\", found \"%v\"", funDecl.Name.Name, n.Args[0])
|
|
return true
|
|
}
|
|
|
|
arg1, ok := n.Args[1].(*ast.SelectorExpr)
|
|
if !ok {
|
|
pass.Reportf(n.Args[1].Pos(), "Invalid initial state for record, expected \"audit.Fail\", found \"%v\"", n.Args[1])
|
|
return true
|
|
}
|
|
|
|
if arg0.Kind != token.STRING {
|
|
pass.Reportf(n.Args[0].Pos(), "Invalid record name, expected \"%s\", found \"%v\"", funDecl.Name.Name, arg0)
|
|
return true
|
|
}
|
|
|
|
arg0Val, err := strconv.Unquote(arg0.Value)
|
|
if err != nil {
|
|
pass.Reportf(n.Args[0].Pos(), "Invalid record name, expected \"%s\", found \"%v\"", funDecl.Name.Name, arg0.Value)
|
|
return true
|
|
}
|
|
|
|
if arg0Val != funDecl.Name.Name {
|
|
pass.Reportf(n.Args[0].Pos(), "Invalid record name, expected \"%s\", found \"%s\"", funDecl.Name.Name, arg0Val)
|
|
return true
|
|
}
|
|
|
|
arg1X, ok := arg1.X.(*ast.Ident)
|
|
if !ok {
|
|
pass.Reportf(n.Args[1].Pos(), "Invalid initial state for record, expected \"audit.Fail\", found \"%v\"", arg1.X)
|
|
return true
|
|
}
|
|
|
|
if arg1X.Name != "audit" || arg1.Sel.Name != "Fail" {
|
|
pass.Reportf(n.Args[1].Pos(), "Invalid initial state for record, expected \"audit.Fail\", found \"%s.%s\"", arg1X.Name, arg1.Sel.Name)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
if !initializationFound {
|
|
pass.Reportf(funDecl.Pos(), "Expected audit log in this function, but not found, please add the audit logs or add the \"%s\" function to the white list", funDecl.Name.Name)
|
|
return
|
|
}
|
|
if !logCallFound {
|
|
pass.Reportf(funDecl.Pos(), "Expected LogAuditRec or LogAuditRecWithLevel call, but not found")
|
|
}
|
|
if !successCallFound {
|
|
pass.Reportf(funDecl.Pos(), "Expected Success call, but not found")
|
|
}
|
|
})
|
|
return nil, nil
|
|
}
|