From 7a7c3e271c3488f1793446c48a503de5c3518507 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Wed, 26 Oct 2022 09:43:25 -0500 Subject: [PATCH 1/4] events: add TeamAccessGranted and TeamAccessRevoked --- slackevents/inner_events.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/slackevents/inner_events.go b/slackevents/inner_events.go index eb1e8df65..e88b6cefe 100644 --- a/slackevents/inner_events.go +++ b/slackevents/inner_events.go @@ -524,6 +524,18 @@ func (e MessageEvent) IsEdited() bool { e.Message.Edited != nil } +// TeamAccessGrantedEvent is sent if access to teams was granted for your org-wide app. +type TeamAccessGrantedEvent struct { + Type string `json:"type"` + TeamIDs []string `json:"team_ids"` +} + +// TeamAccessRevokedEvent is sent if access to teams was revoked for your org-wide app. +type TeamAccessRevokedEvent struct { + Type string `json:"type"` + TeamIDs []string `json:"team_ids"` +} + type EventsAPIType string const ( @@ -599,6 +611,10 @@ const ( MessageMetadataUpdated = EventsAPIType("message_metadata_updated") // MessageMetadataPosted A message with metadata was deleted MessageMetadataDeleted = EventsAPIType("message_metadata_deleted") + // TeamAccessGranted is sent if access to teams was granted for your org-wide app. + TeamAccessGranted = EventsAPIType("team_access_granted") + // TeamAccessrevoked is sent if access to teams was revoked for your org-wide app. + TeamAccessrevoked = EventsAPIType("team_access_revoked") ) // EventsAPIInnerEventMapping maps INNER Event API events to their corresponding struct @@ -641,4 +657,6 @@ var EventsAPIInnerEventMapping = map[EventsAPIType]interface{}{ MessageMetadataPosted: MessageMetadataPostedEvent{}, MessageMetadataUpdated: MessageMetadataUpdatedEvent{}, MessageMetadataDeleted: MessageMetadataDeletedEvent{}, + TeamAccessGranted: TeamAccessGrantedEvent{}, + TeamAccessrevoked: TeamAccessRevokedEvent{}, } From 26ae199df4a511d07e2cd963d6add6d115e7c47e Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Wed, 26 Oct 2022 09:43:42 -0500 Subject: [PATCH 2/4] api/auth: add ListTeams for auth.teams.list --- auth.go | 34 ++++++++++++++++++++++++++++++++++ auth_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 auth_test.go diff --git a/auth.go b/auth.go index f4f7f003a..bf6e80d36 100644 --- a/auth.go +++ b/auth.go @@ -38,3 +38,37 @@ func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*Au return api.authRequest(ctx, "auth.revoke", values) } + +type listTeamsResponse struct { + Teams []Team `json:"teams"` + SlackResponse +} + +type ListTeamsParameters struct { + Limit int + Cursor string +} + +// ListTeams returns all workspaces a token can access. +// More info: https://api.slack.com/methods/admin.teams.list +func (api *Client) ListTeams(params ListTeamsParameters) ([]Team, string, error) { + return api.ListTeamsContext(context.Background(), params) +} + +// ListTeams returns all workspaces a token can access with a custom context. +func (api *Client) ListTeamsContext(ctx context.Context, params ListTeamsParameters) ([]Team, string, error) { + values := url.Values{ + "token": {api.token}, + } + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + + response := &listTeamsResponse{} + err := api.postMethod(ctx, "auth.teams.list", values, response) + if err != nil { + return nil, "", err + } + + return response.Teams, response.ResponseMetadata.Cursor, response.Err() +} diff --git a/auth_test.go b/auth_test.go new file mode 100644 index 000000000..5fe9900eb --- /dev/null +++ b/auth_test.go @@ -0,0 +1,51 @@ +package slack + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func getTeamList(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("Content-Type", "application/json") + response := []byte(`{ + "ok": true, + "teams": [ + { + "name": "Shinichi's workspace", + "id": "T12345678" + }, + { + "name": "Migi's workspace", + "id": "T12345679" + } + ], + "response_metadata": { + "next_cursor": "dXNlcl9pZDo5MTQyOTI5Mzkz" + } +}`) + rw.Write(response) +} + +func TestListTeams(t *testing.T) { + http.HandleFunc("/auth.teams.list", getTeamList) + + once.Do(startServer) + api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) + + teams, cursor, err := api.ListTeams(ListTeamsParameters{}) + if err != nil { + t.Errorf("Unexpected error: %s", err) + return + } + + assert.Len(t, teams, 2) + assert.Equal(t, "T12345678", teams[0].ID) + assert.Equal(t, "Shinichi's workspace", teams[0].Name) + + assert.Equal(t, "T12345679", teams[1].ID) + assert.Equal(t, "Migi's workspace", teams[1].Name) + + assert.Equal(t, "dXNlcl9pZDo5MTQyOTI5Mzkz", cursor) +} From 86690c762bd0ac470c13d07c1afc90bd991e7c0c Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Wed, 26 Oct 2022 12:07:12 -0500 Subject: [PATCH 3/4] slackevents: fix bug where enterprise id is not filled on inner events Before this change, innerEvent.EnterpriseID was always empty string. Now, it is filled when available from the outer event. --- slackevents/outer_events.go | 1 + slackevents/parsers.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/slackevents/outer_events.go b/slackevents/outer_events.go index 6277248dc..bcc85f331 100644 --- a/slackevents/outer_events.go +++ b/slackevents/outer_events.go @@ -35,6 +35,7 @@ type EventsAPICallbackEvent struct { Token string `json:"token"` TeamID string `json:"team_id"` APIAppID string `json:"api_app_id"` + EnterpriseID string `json:"enterprise_id"` InnerEvent *json.RawMessage `json:"event"` AuthedUsers []string `json:"authed_users"` AuthedTeams []string `json:"authed_teams"` diff --git a/slackevents/parsers.go b/slackevents/parsers.go index 23ba2b85b..96ba5681b 100644 --- a/slackevents/parsers.go +++ b/slackevents/parsers.go @@ -102,7 +102,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, "unmarshalling_error", e.APIAppID, - "", + e.EnterpriseID, &slack.UnmarshallingErrorEvent{ErrorObj: err}, EventsAPIInnerEvent{}, }, err @@ -114,7 +114,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, iE.Type, e.APIAppID, - "", + e.EnterpriseID, nil, EventsAPIInnerEvent{}, }, fmt.Errorf("Inner Event does not exist! %s", iE.Type) @@ -128,7 +128,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, "unmarshalling_error", e.APIAppID, - "", + e.EnterpriseID, &slack.UnmarshallingErrorEvent{ErrorObj: err}, EventsAPIInnerEvent{}, }, err @@ -138,7 +138,7 @@ func parseInnerEvent(e *EventsAPICallbackEvent) (EventsAPIEvent, error) { e.TeamID, e.Type, e.APIAppID, - "", + e.EnterpriseID, e, EventsAPIInnerEvent{iE.Type, recvEvent}, }, nil From 2d81614fa683a937e459b8b49482ceabcb3c40e2 Mon Sep 17 00:00:00 2001 From: Stephen Wan Date: Thu, 27 Oct 2022 10:22:05 -0500 Subject: [PATCH 4/4] conversation: support passing team id in CreateConversation This is a breaking change that hopefully future proofs this codepath from future breaking changes, since adding additional params to the struct should be backwards compatible. --- conversation.go | 19 ++++++++++++++----- conversation_test.go | 2 +- examples/pins/pins.go | 2 +- slacktest/handlers_test.go | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/conversation.go b/conversation.go index 07864d573..225608677 100644 --- a/conversation.go +++ b/conversation.go @@ -342,17 +342,26 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin return response.NoOp, response.AlreadyClosed, response.Err() } +type CreateConversationParams struct { + ChannelName string + IsPrivate bool + TeamID string +} + // CreateConversation initiates a public or private channel-based conversation -func (api *Client) CreateConversation(channelName string, isPrivate bool) (*Channel, error) { - return api.CreateConversationContext(context.Background(), channelName, isPrivate) +func (api *Client) CreateConversation(params CreateConversationParams) (*Channel, error) { + return api.CreateConversationContext(context.Background(), params) } // CreateConversationContext initiates a public or private channel-based conversation with a custom context -func (api *Client) CreateConversationContext(ctx context.Context, channelName string, isPrivate bool) (*Channel, error) { +func (api *Client) CreateConversationContext(ctx context.Context, params CreateConversationParams) (*Channel, error) { values := url.Values{ "token": {api.token}, - "name": {channelName}, - "is_private": {strconv.FormatBool(isPrivate)}, + "name": {params.ChannelName}, + "is_private": {strconv.FormatBool(params.IsPrivate)}, + } + if params.TeamID != "" { + values.Set("team_id", params.TeamID) } response, err := api.channelRequest(ctx, "conversations.create", values) if err != nil { diff --git a/conversation_test.go b/conversation_test.go index 61a4e92b1..adbad7c28 100644 --- a/conversation_test.go +++ b/conversation_test.go @@ -385,7 +385,7 @@ func TestCreateConversation(t *testing.T) { http.HandleFunc("/conversations.create", okChannelJsonHandler) once.Do(startServer) api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) - channel, err := api.CreateConversation("CXXXXXXXX", false) + channel, err := api.CreateConversation(CreateConversationParams{ChannelName: "CXXXXXXXX"}) if err != nil { t.Errorf("Unexpected error: %s", err) return diff --git a/examples/pins/pins.go b/examples/pins/pins.go index d13d2d2c8..5c4f67c22 100644 --- a/examples/pins/pins.go +++ b/examples/pins/pins.go @@ -43,7 +43,7 @@ func main() { postAsUserID = authTest.UserID // Create a temporary channel - channel, err := api.CreateConversation(channelName, false) + channel, err := api.CreateConversation(slack.CreateConversationParams{ChannelName: channelName}) if err != nil { // If the channel exists, that means we just need to unarchive it diff --git a/slacktest/handlers_test.go b/slacktest/handlers_test.go index 6678e7684..dec4c99be 100644 --- a/slacktest/handlers_test.go +++ b/slacktest/handlers_test.go @@ -37,7 +37,7 @@ func TestServerCreateConversationHandler(t *testing.T) { go s.Start() client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL())) - conversation, err := client.CreateConversation("test", false) + conversation, err := client.CreateConversation(slack.CreateConversationParams{ChannelName: "test"}) assert.NoError(t, err) assert.Equal(t, "C0EAQDV4Z", conversation.ID) assert.Equal(t, "U023BECGF", conversation.Creator)