Implement compliance endpoints for APIv4 (#5683)
* Implement compliance endpoints for APIv4 * Add paging to get reports endpoint
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
a284cd8c18
Коммит
3ebfb36953
@@ -142,7 +142,7 @@ func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
crs, err := app.GetComplianceReports()
|
crs, err := app.GetComplianceReports(0, 10000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ type Routes struct {
|
|||||||
|
|
||||||
Admin *mux.Router // 'api/v4/admin'
|
Admin *mux.Router // 'api/v4/admin'
|
||||||
|
|
||||||
|
Compliance *mux.Router // 'api/v4/compliance'
|
||||||
|
|
||||||
System *mux.Router // 'api/v4/system'
|
System *mux.Router // 'api/v4/system'
|
||||||
|
|
||||||
Preferences *mux.Router // 'api/v4/preferences'
|
Preferences *mux.Router // 'api/v4/preferences'
|
||||||
@@ -134,6 +136,7 @@ func InitApi(full bool) {
|
|||||||
BaseRoutes.SAML = BaseRoutes.ApiRoot.PathPrefix("/saml").Subrouter()
|
BaseRoutes.SAML = BaseRoutes.ApiRoot.PathPrefix("/saml").Subrouter()
|
||||||
BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter()
|
BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter()
|
||||||
BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter()
|
BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter()
|
||||||
|
BaseRoutes.Compliance = BaseRoutes.ApiRoot.PathPrefix("/compliance").Subrouter()
|
||||||
BaseRoutes.System = BaseRoutes.ApiRoot.PathPrefix("/system").Subrouter()
|
BaseRoutes.System = BaseRoutes.ApiRoot.PathPrefix("/system").Subrouter()
|
||||||
BaseRoutes.Preferences = BaseRoutes.User.PathPrefix("/preferences").Subrouter()
|
BaseRoutes.Preferences = BaseRoutes.User.PathPrefix("/preferences").Subrouter()
|
||||||
BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
|
BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
|
||||||
@@ -153,6 +156,7 @@ func InitApi(full bool) {
|
|||||||
InitWebhook()
|
InitWebhook()
|
||||||
InitPreference()
|
InitPreference()
|
||||||
InitSaml()
|
InitSaml()
|
||||||
|
InitCompliance()
|
||||||
|
|
||||||
app.Srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404))
|
app.Srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404))
|
||||||
|
|
||||||
|
|||||||
127
api4/compliance.go
Обычный файл
127
api4/compliance.go
Обычный файл
@@ -0,0 +1,127 @@
|
|||||||
|
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
l4g "github.com/alecthomas/log4go"
|
||||||
|
"github.com/mattermost/platform/app"
|
||||||
|
"github.com/mattermost/platform/model"
|
||||||
|
"github.com/mattermost/platform/utils"
|
||||||
|
"github.com/mssola/user_agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitCompliance() {
|
||||||
|
l4g.Debug(utils.T("api.compliance.init.debug"))
|
||||||
|
|
||||||
|
BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(createComplianceReport)).Methods("POST")
|
||||||
|
BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(getComplianceReports)).Methods("GET")
|
||||||
|
BaseRoutes.Compliance.Handle("/reports/{report_id:[A-Za-z0-9]+}", ApiSessionRequired(getComplianceReport)).Methods("GET")
|
||||||
|
BaseRoutes.Compliance.Handle("/reports/{report_id:[A-Za-z0-9]+}/download", ApiSessionRequired(downloadComplianceReport)).Methods("GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
job := model.ComplianceFromJson(r.Body)
|
||||||
|
if job == nil {
|
||||||
|
c.SetInvalidParam("compliance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job.UserId = c.Session.UserId
|
||||||
|
|
||||||
|
rjob, err := app.SaveComplianceReport(job)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.LogAudit("")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
w.Write([]byte(rjob.ToJson()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
crs, err := app.GetComplianceReports(c.Params.Page, c.Params.PerPage)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(crs.ToJson()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireReportId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job, err := app.GetComplianceReport(c.Params.ReportId)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(job.ToJson()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.RequireReportId()
|
||||||
|
if c.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job, err := app.GetComplianceReport(c.Params.ReportId)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reportBytes, err := app.GetComplianceFile(job)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.LogAudit("downloaded " + job.Desc)
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "max-age=2592000, public")
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(len(reportBytes)))
|
||||||
|
w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
|
||||||
|
|
||||||
|
// attach extra headers to trigger a download on IE, Edge, and Safari
|
||||||
|
ua := user_agent.New(r.UserAgent())
|
||||||
|
bname, _ := ua.Browser()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"")
|
||||||
|
|
||||||
|
if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" {
|
||||||
|
// trim off anything before the final / so we just get the file's name
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(reportBytes)
|
||||||
|
}
|
||||||
@@ -396,6 +396,17 @@ func (c *Context) RequireFileId() *Context {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) RequireReportId() *Context {
|
||||||
|
if c.Err != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Params.ReportId) != 26 {
|
||||||
|
c.SetInvalidUrlParam("report_id")
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) RequireTeamName() *Context {
|
func (c *Context) RequireTeamName() *Context {
|
||||||
if c.Err != nil {
|
if c.Err != nil {
|
||||||
return c
|
return c
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type ApiParams struct {
|
|||||||
FileId string
|
FileId string
|
||||||
CommandId string
|
CommandId string
|
||||||
HookId string
|
HookId string
|
||||||
|
ReportId string
|
||||||
EmojiId string
|
EmojiId string
|
||||||
Email string
|
Email string
|
||||||
Username string
|
Username string
|
||||||
@@ -68,6 +69,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams {
|
|||||||
params.HookId = val
|
params.HookId = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, ok := props["report_id"]; ok {
|
||||||
|
params.ReportId = val
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := props["emoji_id"]; ok {
|
if val, ok := props["emoji_id"]; ok {
|
||||||
params.EmojiId = val
|
params.EmojiId = val
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetComplianceReports() (model.Compliances, *model.AppError) {
|
func GetComplianceReports(page, perPage int) (model.Compliances, *model.AppError) {
|
||||||
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance {
|
if !*utils.Cfg.ComplianceSettings.Enable || !utils.IsLicensed || !*utils.License.Features.Compliance {
|
||||||
return nil, model.NewLocAppError("GetComplianceReports", "ent.compliance.licence_disable.app_error", nil, "")
|
return nil, model.NewLocAppError("GetComplianceReports", "ent.compliance.licence_disable.app_error", nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.Compliance().GetAll(); result.Err != nil {
|
if result := <-Srv.Store.Compliance().GetAll(page*perPage, perPage); result.Err != nil {
|
||||||
return nil, result.Err
|
return nil, result.Err
|
||||||
} else {
|
} else {
|
||||||
return result.Data.(model.Compliances), nil
|
return result.Data.(model.Compliances), nil
|
||||||
|
|||||||
@@ -83,6 +83,10 @@
|
|||||||
"id": "api.admin.init.debug",
|
"id": "api.admin.init.debug",
|
||||||
"translation": "Initializing admin API routes"
|
"translation": "Initializing admin API routes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.compliance.init.debug",
|
||||||
|
"translation": "Initializing compliance API routes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.admin.recycle_db_end.warn",
|
"id": "api.admin.recycle_db_end.warn",
|
||||||
"translation": "Finished recycling the database connection"
|
"translation": "Finished recycling the database connection"
|
||||||
|
|||||||
@@ -154,6 +154,14 @@ func (c *Client4) GetIncomingWebhookRoute(hookID string) string {
|
|||||||
return fmt.Sprintf(c.GetIncomingWebhooksRoute()+"/%v", hookID)
|
return fmt.Sprintf(c.GetIncomingWebhooksRoute()+"/%v", hookID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client4) GetComplianceReportsRoute() string {
|
||||||
|
return fmt.Sprintf("/compliance/reports")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client4) GetComplianceReportRoute(reportId string) string {
|
||||||
|
return fmt.Sprintf("/compliance/reports/%v", reportId)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client4) GetPreferencesRoute(userId string) string {
|
func (c *Client4) GetPreferencesRoute(userId string) string {
|
||||||
return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences")
|
return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences")
|
||||||
}
|
}
|
||||||
@@ -1276,3 +1284,60 @@ func (c *Client4) GetSamlCertificateStatus() (*SamlCertificateStatus, *Response)
|
|||||||
return SamlCertificateStatusFromJson(r.Body), BuildResponse(r)
|
return SamlCertificateStatusFromJson(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compliance Section
|
||||||
|
|
||||||
|
// CreateComplianceReport creates an incoming webhook for a channel.
|
||||||
|
func (c *Client4) CreateComplianceReport(report *Compliance) (*Compliance, *Response) {
|
||||||
|
if r, err := c.DoApiPost(c.GetComplianceReportsRoute(), report.ToJson()); err != nil {
|
||||||
|
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return ComplianceFromJson(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetComplianceReports returns list of compliance reports.
|
||||||
|
func (c *Client4) GetComplianceReports(page, perPage int) (Compliances, *Response) {
|
||||||
|
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
|
||||||
|
if r, err := c.DoApiGet(c.GetComplianceReportsRoute()+query, ""); err != nil {
|
||||||
|
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return CompliancesFromJson(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetComplianceReport returns a compliance report.
|
||||||
|
func (c *Client4) GetComplianceReport(reportId string) (*Compliance, *Response) {
|
||||||
|
if r, err := c.DoApiGet(c.GetComplianceReportRoute(reportId), ""); err != nil {
|
||||||
|
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return ComplianceFromJson(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadComplianceReport returns a full compliance report as a file.
|
||||||
|
func (c *Client4) DownloadComplianceReport(reportId string) ([]byte, *Response) {
|
||||||
|
var rq *http.Request
|
||||||
|
rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetComplianceReportRoute(reportId), nil)
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
|
if len(c.AuthToken) > 0 {
|
||||||
|
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||||
|
return nil, &Response{Error: NewAppError("DownloadComplianceReport", "model.client.connecting.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||||
|
} else if rp.StatusCode >= 300 {
|
||||||
|
defer rp.Body.Close()
|
||||||
|
return nil, &Response{StatusCode: rp.StatusCode, Error: AppErrorFromJson(rp.Body)}
|
||||||
|
} else if data, err := ioutil.ReadAll(rp.Body); err != nil {
|
||||||
|
defer closeBody(rp)
|
||||||
|
return nil, &Response{StatusCode: rp.StatusCode, Error: NewAppError("DownloadComplianceReport", "model.client.read_file.app_error", nil, err.Error(), rp.StatusCode)}
|
||||||
|
} else {
|
||||||
|
defer closeBody(rp)
|
||||||
|
return data, BuildResponse(rp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,17 +87,17 @@ func (us SqlComplianceStore) Update(compliance *model.Compliance) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlComplianceStore) GetAll() StoreChannel {
|
func (s SqlComplianceStore) GetAll(offset, limit int) StoreChannel {
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
query := "SELECT * FROM Compliances ORDER BY CreateAt DESC"
|
query := "SELECT * FROM Compliances ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset"
|
||||||
|
|
||||||
var compliances model.Compliances
|
var compliances model.Compliances
|
||||||
if _, err := s.GetReplica().Select(&compliances, query); err != nil {
|
if _, err := s.GetReplica().Select(&compliances, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlComplianceStore.Get", "store.sql_compliance.get.finding.app_error", nil, err.Error())
|
result.Err = model.NewLocAppError("SqlComplianceStore.Get", "store.sql_compliance.get.finding.app_error", nil, err.Error())
|
||||||
} else {
|
} else {
|
||||||
result.Data = compliances
|
result.Data = compliances
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestSqlComplianceStore(t *testing.T) {
|
|||||||
Must(store.Compliance().Save(compliance2))
|
Must(store.Compliance().Save(compliance2))
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
c := store.Compliance().GetAll()
|
c := store.Compliance().GetAll(0, 1000)
|
||||||
result := <-c
|
result := <-c
|
||||||
compliances := result.Data.(model.Compliances)
|
compliances := result.Data.(model.Compliances)
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ func TestSqlComplianceStore(t *testing.T) {
|
|||||||
compliance2.Status = model.COMPLIANCE_STATUS_FAILED
|
compliance2.Status = model.COMPLIANCE_STATUS_FAILED
|
||||||
Must(store.Compliance().Update(compliance2))
|
Must(store.Compliance().Update(compliance2))
|
||||||
|
|
||||||
c = store.Compliance().GetAll()
|
c = store.Compliance().GetAll(0, 1000)
|
||||||
result = <-c
|
result = <-c
|
||||||
compliances = result.Data.(model.Compliances)
|
compliances = result.Data.(model.Compliances)
|
||||||
|
|
||||||
@@ -39,6 +39,22 @@ func TestSqlComplianceStore(t *testing.T) {
|
|||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c = store.Compliance().GetAll(0, 1)
|
||||||
|
result = <-c
|
||||||
|
compliances = result.Data.(model.Compliances)
|
||||||
|
|
||||||
|
if len(compliances) != 1 {
|
||||||
|
t.Fatal("should only have returned 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
c = store.Compliance().GetAll(1, 1)
|
||||||
|
result = <-c
|
||||||
|
compliances = result.Data.(model.Compliances)
|
||||||
|
|
||||||
|
if len(compliances) != 1 {
|
||||||
|
t.Fatal("should only have returned 1")
|
||||||
|
}
|
||||||
|
|
||||||
rc2 := (<-store.Compliance().Get(compliance2.Id)).Data.(*model.Compliance)
|
rc2 := (<-store.Compliance().Get(compliance2.Id)).Data.(*model.Compliance)
|
||||||
if rc2.Status != compliance2.Status {
|
if rc2.Status != compliance2.Status {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ type ComplianceStore interface {
|
|||||||
Save(compliance *model.Compliance) StoreChannel
|
Save(compliance *model.Compliance) StoreChannel
|
||||||
Update(compliance *model.Compliance) StoreChannel
|
Update(compliance *model.Compliance) StoreChannel
|
||||||
Get(id string) StoreChannel
|
Get(id string) StoreChannel
|
||||||
GetAll() StoreChannel
|
GetAll(offset, limit int) StoreChannel
|
||||||
ComplianceExport(compliance *model.Compliance) StoreChannel
|
ComplianceExport(compliance *model.Compliance) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user