Repository: slack-go/slack
Pull request #847 (2020-11-16 21:23:07)

Added coverage for audit API endpoint
Includes structs and functions for using the [Slack Audit endpoint](https://api.slack.com/admins/audit-logs).

Test is provided based on the example output in the documentation,


Patch:
diff --git a/audit.go b/audit.go
new file mode 100644
index 0000000..98d4823
--- /dev/null
+++ b/audit.go
@@ -0,0 +1,144 @@
+package slack
+
+import (
+	"context"
+	"net/url"
+	"strconv"
+)
+
+type AuditLogResponse struct {
+	Entries []AuditEntry `json:"entries"`
+	SlackResponse
+}
+
+type AuditEntry struct {
+	ID         string `json:"id"`
+	DateCreate int    `json:"date_create"`
+	Action     string `json:"action"`
+	Actor      struct {
+		Type string    `json:"type"`
+		User AuditUser `json:"user"`
+	} `json:"actor"`
+	Entity struct {
+		Type string `json:"type"`
+		// Only one of the below will be completed, based on the value of Type a user, a channel, a file, an app, a workspace, or an enterprise
+		User       AuditUser       `json:"user"`
+		Channel    AuditChannel    `json:"channel"`
+		File       AuditFile       `json:"file"`
+		App        AuditApp        `json:"app"`
+		Workspace  AuditWorkspace  `json:"workspace"`
+		Enterprise AuditEnterprise `json:"enterprise"`
+	} `json:"entity"`
+	Context struct {
+		Location struct {
+			Type   string `json:"type"`
+			ID     string `json:"id"`
+			Name   string `json:"name"`
+			Domain string `json:"domain"`
+		} `json:"location"`
+		UA        string `json:"ua"`
+		IPAddress string `json:"ip_address"`
+	} `json:"context"`
+}
+
+type AuditUser struct {
+	ID    string `json:"id"`
+	Name  string `json:"name"`
+	Email string `json:"email"`
+	Team  string `json:"team"`
+}
+
+type AuditChannel struct {
+	ID          string `json:"id"`
+	Name        string `json:"name"`
+	Privacy     string `json:"privacy"`
+	IsShared    bool   `json:"is_shared"`
+	IsOrgShared bool   `json:"is_org_shared"`
+}
+
+type AuditFile struct {
+	ID       string `json:"id"`
+	Name     string `json:"name"`
+	Filetype string `json:"filetype"`
+	Title    string `json:"title"`
+}
+
+type AuditApp struct {
+	ID                  string   `json:"id"`
+	Name                string   `json:"name"`
+	IsDistributed       bool     `json:"is_distributed"`
+	IsDirectoryApproved bool     `json:"is_directory_approved"`
+	IsWorkflowApp       bool     `json:"is_workflow_app"`
+	Scopes              []string `json:"scopes"`
+}
+
+type AuditWorkspace struct {
+	ID     string `json:"id"`
+	Name   string `json:"name"`
+	Domain string `json:"domain"`
+}
+
+type AuditEnterprise struct {
+	ID     string `json:"id"`
+	Name   string `json:"name"`
+	Domain string `json:"domain"`
+}
+
+// AuditLogParameters contains all the parameters necessary (including the optional ones) for a GetAuditLogs() request
+type AuditLogParameters struct {
+	Limit  int
+	Cursor string
+	Latest int
+	Oldest int
+	Action string
+	Actor  string
+	Entity string
+}
+
+func (api *Client) auditLogsRequest(ctx context.Context, path string, values url.Values) (*AuditLogResponse, error) {
+	response := &AuditLogResponse{}
+	err := api.postMethod(ctx, path, values, response)
+	if err != nil {
+		return nil, err
+	}
+	return response, response.Err()
+}
+
+// GetAuditLogs retrieves a page of audit entires according to the parameters given
+func (api *Client) GetAuditLogs(params AuditLogParameters) (entries []AuditEntry, nextCursor string, err error) {
+	return api.GetAuditLogsContext(context.Background(), params)
+}
+
+// GetAuditLogsContext retrieves a page of audit entries according to the parameters given with a custom context
+func (api *Client) GetAuditLogsContext(ctx context.Context, params AuditLogParameters) (entries []AuditEntry, nextCursor string, err error) {
+	values := url.Values{
+		"token": {api.token},
+	}
+	if params.Limit != 0 {
+		values.Add("count", strconv.Itoa(params.Limit))
+	}
+	if params.Oldest != 0 {
+		values.Add("oldest", strconv.Itoa(params.Oldest))
+	}
+	if params.Latest != 0 {
+		values.Add("latest", strconv.Itoa(params.Latest))
+	}
+	if params.Cursor != "" {
+		values.Add("cursor", params.Cursor)
+	}
+	if params.Action != "" {
+		values.Add("action", params.Action)
+	}
+	if params.Actor != "" {
+		values.Add("actor", params.Actor)
+	}
+	if params.Entity != "" {
+		values.Add("entity", params.Entity)
+	}
+
+	response, err := api.auditLogsRequest(ctx, "audit/v1/logs", values)
+	if err != nil {
+		return nil, "", err
+	}
+	return response.Entries, response.ResponseMetadata.Cursor, response.Err()
+}


Test patch:
diff --git a/audit_test.go b/audit_test.go
new file mode 100644
index 0000000..631b631
--- /dev/null
+++ b/audit_test.go
@@ -0,0 +1,85 @@
+package slack
+
+import (
+	"net/http"
+	"testing"
+)
+
+func getAuditLogs(rw http.ResponseWriter, r *http.Request) {
+	rw.Header().Set("Content-Type", "application/json")
+	response := []byte(`{"entries": [
+			    {
+			      "id": "0123a45b-6c7d-8900-e12f-3456789gh0i1",
+			      "date_create": 1521214343,
+			      "action": "user_login",
+			      "actor": {
+			        "type": "user",
+			        "user": {
+			          "id": "W123AB456",
+			          "name": "Charlie Parker",
+			          "email": "bird@slack.com"
+			        }
+			      },
+			      "entity": {
+			        "type": "user",
+			        "user": {
+			          "id": "W123AB456",
+			          "name": "Charlie Parker",
+			          "email": "bird@slack.com"
+			        }
+			      },
+			      "context": {
+			        "location": {
+			          "type": "enterprise",
+			          "id": "E1701NCCA",
+			          "name": "Birdland",
+			          "domain": "birdland"
+			        },
+			        "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
+			        "ip_address": "1.23.45.678"
+			      }
+			    }
+			  ]
+  }`)
+	rw.Write(response)
+}
+
+func TestGetAuditLogs(t *testing.T) {
+	http.HandleFunc("/audit/v1/logs", getAuditLogs)
+
+	once.Do(startServer)
+	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
+
+	events, nextCursor, err := api.GetAuditLogs(AuditLogParameters{})
+	if err != nil {
+		t.Errorf("Unexpected error: %s", err)
+		return
+	}
+
+	if len(events) != 1 {
+		t.Fatal("Should have been 1 event")
+	}
+
+	// test the first login
+	event1 := events[0]
+
+	if event1.Action != "user_login" {
+		t.Fatal(ErrIncorrectResponse)
+	}
+	if event1.Entity.User.Email != "bird@slack.com" {
+		t.Fatal(ErrIncorrectResponse)
+	}
+	if event1.Context.Location.Domain != "birdland" {
+		t.Fatal(ErrIncorrectResponse)
+	}
+	if event1.DateCreate != 1521214343 {
+		t.Fatal(ErrIncorrectResponse)
+	}
+	if event1.Context.IPAddress != "1.23.45.678" {
+		t.Fatal(ErrIncorrectResponse)
+	}
+
+	if nextCursor != "" {
+		t.Fatal(ErrIncorrectResponse)
+	}
+}