mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Implement compliance endpoints for APIv4 (#5683)
* Implement compliance endpoints for APIv4 * Add paging to get reports endpoint
This commit is contained in:
@@ -142,7 +142,7 @@ func testEmail(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 {
|
||||
c.Err = err
|
||||
return
|
||||
|
||||
@@ -65,6 +65,8 @@ type Routes struct {
|
||||
|
||||
Admin *mux.Router // 'api/v4/admin'
|
||||
|
||||
Compliance *mux.Router // 'api/v4/compliance'
|
||||
|
||||
System *mux.Router // 'api/v4/system'
|
||||
|
||||
Preferences *mux.Router // 'api/v4/preferences'
|
||||
@@ -134,6 +136,7 @@ func InitApi(full bool) {
|
||||
BaseRoutes.SAML = BaseRoutes.ApiRoot.PathPrefix("/saml").Subrouter()
|
||||
BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter()
|
||||
BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter()
|
||||
BaseRoutes.Compliance = BaseRoutes.ApiRoot.PathPrefix("/compliance").Subrouter()
|
||||
BaseRoutes.System = BaseRoutes.ApiRoot.PathPrefix("/system").Subrouter()
|
||||
BaseRoutes.Preferences = BaseRoutes.User.PathPrefix("/preferences").Subrouter()
|
||||
BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
|
||||
@@ -153,6 +156,7 @@ func InitApi(full bool) {
|
||||
InitWebhook()
|
||||
InitPreference()
|
||||
InitSaml()
|
||||
InitCompliance()
|
||||
|
||||
app.Srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404))
|
||||
|
||||
|
||||
127
api4/compliance.go
Normal file
127
api4/compliance.go
Normal file
@@ -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
|
||||
}
|
||||
|
||||
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 {
|
||||
if c.Err != nil {
|
||||
return c
|
||||
|
||||
@@ -24,6 +24,7 @@ type ApiParams struct {
|
||||
FileId string
|
||||
CommandId string
|
||||
HookId string
|
||||
ReportId string
|
||||
EmojiId string
|
||||
Email string
|
||||
Username string
|
||||
@@ -68,6 +69,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams {
|
||||
params.HookId = val
|
||||
}
|
||||
|
||||
if val, ok := props["report_id"]; ok {
|
||||
params.ReportId = val
|
||||
}
|
||||
|
||||
if val, ok := props["emoji_id"]; ok {
|
||||
params.EmojiId = val
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
"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 {
|
||||
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
|
||||
} else {
|
||||
return result.Data.(model.Compliances), nil
|
||||
|
||||
@@ -83,6 +83,10 @@
|
||||
"id": "api.admin.init.debug",
|
||||
"translation": "Initializing admin API routes"
|
||||
},
|
||||
{
|
||||
"id": "api.compliance.init.debug",
|
||||
"translation": "Initializing compliance API routes"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.recycle_db_end.warn",
|
||||
"translation": "Finished recycling the database connection"
|
||||
|
||||
@@ -154,6 +154,14 @@ func (c *Client4) GetIncomingWebhookRoute(hookID string) string {
|
||||
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 {
|
||||
return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences")
|
||||
}
|
||||
@@ -1276,3 +1284,60 @@ func (c *Client4) GetSamlCertificateStatus() (*SamlCertificateStatus, *Response)
|
||||
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
|
||||
}
|
||||
|
||||
func (s SqlComplianceStore) GetAll() StoreChannel {
|
||||
func (s SqlComplianceStore) GetAll(offset, limit int) StoreChannel {
|
||||
|
||||
storeChannel := make(StoreChannel, 1)
|
||||
|
||||
go func() {
|
||||
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
|
||||
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())
|
||||
} else {
|
||||
result.Data = compliances
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestSqlComplianceStore(t *testing.T) {
|
||||
Must(store.Compliance().Save(compliance2))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
c := store.Compliance().GetAll()
|
||||
c := store.Compliance().GetAll(0, 1000)
|
||||
result := <-c
|
||||
compliances := result.Data.(model.Compliances)
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestSqlComplianceStore(t *testing.T) {
|
||||
compliance2.Status = model.COMPLIANCE_STATUS_FAILED
|
||||
Must(store.Compliance().Update(compliance2))
|
||||
|
||||
c = store.Compliance().GetAll()
|
||||
c = store.Compliance().GetAll(0, 1000)
|
||||
result = <-c
|
||||
compliances = result.Data.(model.Compliances)
|
||||
|
||||
@@ -39,6 +39,22 @@ func TestSqlComplianceStore(t *testing.T) {
|
||||
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)
|
||||
if rc2.Status != compliance2.Status {
|
||||
t.Fatal()
|
||||
|
||||
@@ -228,7 +228,7 @@ type ComplianceStore interface {
|
||||
Save(compliance *model.Compliance) StoreChannel
|
||||
Update(compliance *model.Compliance) StoreChannel
|
||||
Get(id string) StoreChannel
|
||||
GetAll() StoreChannel
|
||||
GetAll(offset, limit int) StoreChannel
|
||||
ComplianceExport(compliance *model.Compliance) StoreChannel
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user