From a4892f29edaf383d644b5b290083424aa301d1f7 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 7 Nov 2023 17:52:08 +0900 Subject: [PATCH] Add apps.manifest.* & tooling.tokens.rotate API support --- .gitignore | 1 + .../samples/api/apps.manifest.create.json | 26 +++ .../samples/api/apps.manifest.delete.json | 6 + .../samples/api/apps.manifest.export.json | 87 ++++++++++ .../samples/api/apps.manifest.update.json | 8 + .../samples/api/apps.manifest.validate.json | 18 +++ json-logs/samples/api/auth.test.json | 3 +- .../samples/api/tooling.tokens.rotate.json | 17 ++ metadata/web-api/rate_limit_tiers.json | 6 + .../slack/api/methods/AsyncMethodsClient.java | 37 +++++ .../java/com/slack/api/methods/Methods.java | 12 ++ .../com/slack/api/methods/MethodsClient.java | 37 +++++ .../slack/api/methods/MethodsRateLimits.java | 8 + .../slack/api/methods/RequestFormBuilder.java | 56 +++++++ .../methods/impl/AsyncMethodsClientImpl.java | 64 ++++++++ .../api/methods/impl/MethodsClientImpl.java | 64 ++++++++ .../manifest/AppsManifestCreateRequest.java | 16 ++ .../manifest/AppsManifestDeleteRequest.java | 15 ++ .../manifest/AppsManifestExportRequest.java | 14 ++ .../manifest/AppsManifestUpdateRequest.java | 17 ++ .../manifest/AppsManifestValidateRequest.java | 17 ++ .../tokens/ToolingTokensRotateRequest.java | 14 ++ .../manifest/AppsManifestCreateResponse.java | 34 ++++ .../manifest/AppsManifestDeleteResponse.java | 21 +++ .../manifest/AppsManifestExportResponse.java | 24 +++ .../manifest/AppsManifestUpdateResponse.java | 24 +++ .../AppsManifestValidateResponse.java | 29 ++++ .../response/auth/AuthTestResponse.java | 1 + .../tokens/ToolingTokensRotateResponse.java | 27 ++++ .../token_rotation/tooling/ToolingToken.java | 24 +++ .../tooling/ToolingTokenRotator.java | 54 +++++++ .../tooling/ToolingTokenStore.java | 11 ++ .../tooling/store/FileToolingTokenStore.java | 83 ++++++++++ .../src/test/java/config/Constants.java | 5 + .../java/test_locally/api/MethodsTest.java | 7 - .../test_locally/api/methods/AppsTest.java | 19 ++- .../methods/apps_manifest_Test.java | 123 ++++++++++++++ .../src/test/java/util/MockSlackApi.java | 2 +- .../JsonDataRecorder.java | 23 +++ .../sample_json_generation/SampleObjects.java | 28 ++++ .../com/slack/api/model/AppCredentials.java | 11 ++ .../java/com/slack/api/model/AppManifest.java | 151 ++++++++++++++++++ .../api/model/AppManifestTest.java | 107 +++++++++++++ 43 files changed, 1336 insertions(+), 15 deletions(-) create mode 100644 json-logs/samples/api/apps.manifest.create.json create mode 100644 json-logs/samples/api/apps.manifest.delete.json create mode 100644 json-logs/samples/api/apps.manifest.export.json create mode 100644 json-logs/samples/api/apps.manifest.update.json create mode 100644 json-logs/samples/api/apps.manifest.validate.json create mode 100644 json-logs/samples/api/tooling.tokens.rotate.json create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestCreateRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestDeleteRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestExportRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestUpdateRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestValidateRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/request/tooling/tokens/ToolingTokensRotateRequest.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestCreateResponse.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestDeleteResponse.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestExportResponse.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestUpdateResponse.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestValidateResponse.java create mode 100644 slack-api-client/src/main/java/com/slack/api/methods/response/tooling/tokens/ToolingTokensRotateResponse.java create mode 100644 slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingToken.java create mode 100644 slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenRotator.java create mode 100644 slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenStore.java create mode 100644 slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/store/FileToolingTokenStore.java create mode 100644 slack-api-client/src/test/java/test_with_remote_apis/methods/apps_manifest_Test.java create mode 100644 slack-api-model/src/main/java/com/slack/api/model/AppCredentials.java create mode 100644 slack-api-model/src/main/java/com/slack/api/model/AppManifest.java create mode 100644 slack-api-model/src/test/java/test_locally/api/model/AppManifestTest.java diff --git a/.gitignore b/.gitignore index 8cda4cfbc..691a4faec 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ typescript-types/dist/ logs/ json-logs/raw* +slack-api-client/tmp/ .DS_Store diff --git a/json-logs/samples/api/apps.manifest.create.json b/json-logs/samples/api/apps.manifest.create.json new file mode 100644 index 000000000..f46896155 --- /dev/null +++ b/json-logs/samples/api/apps.manifest.create.json @@ -0,0 +1,26 @@ +{ + "ok": false, + "error": "", + "response_metadata": { + "messages": [ + "" + ] + }, + "needed": "", + "provided": "", + "errors": [ + { + "code": "", + "message": "", + "pointer": "" + } + ], + "app_id": "A00000000", + "credentials": { + "client_id": "0000000000.000000", + "client_secret": "", + "verification_token": "", + "signing_secret": "" + }, + "oauth_authorize_url": "https://www.example.com/" +} \ No newline at end of file diff --git a/json-logs/samples/api/apps.manifest.delete.json b/json-logs/samples/api/apps.manifest.delete.json new file mode 100644 index 000000000..1b3fc766f --- /dev/null +++ b/json-logs/samples/api/apps.manifest.delete.json @@ -0,0 +1,6 @@ +{ + "ok": false, + "error": "", + "needed": "", + "provided": "" +} \ No newline at end of file diff --git a/json-logs/samples/api/apps.manifest.export.json b/json-logs/samples/api/apps.manifest.export.json new file mode 100644 index 000000000..f34b32a0b --- /dev/null +++ b/json-logs/samples/api/apps.manifest.export.json @@ -0,0 +1,87 @@ +{ + "ok": false, + "manifest": { + "_metadata": { + "major_version": "", + "minor_version": "" + }, + "display_information": { + "name": "", + "long_description": "", + "description": "", + "background_color": "" + }, + "settings": { + "description": "", + "long_description": "", + "background_color": "", + "event_subscriptions": { + "bot_events": [ + "" + ], + "user_events": [ + "" + ], + "request_url": "" + }, + "interactivity": { + "is_enabled": false, + "request_url": "", + "message_menu_options_url": "" + }, + "allowed_ip_address_ranges": [ + "" + ], + "org_deploy_enabled": false, + "socket_mode_enabled": false, + "token_rotation_enabled": false + }, + "features": { + "app_home": { + "home_tab_enabled": false, + "messages_tab_enabled": false, + "messages_tab_read_only_enabled": false + }, + "bot_user": { + "display_name": "", + "always_online": false + }, + "shortcuts": [ + { + "type": "", + "callback_id": "", + "name": "", + "description": "" + } + ], + "slash_commands": [ + { + "command": "", + "description": "", + "usage_hint": "", + "url": "", + "should_escape": false + } + ], + "unfurl_domains": [ + "" + ] + }, + "oauth_config": { + "scopes": { + "bot": [ + "" + ], + "user": [ + "" + ] + }, + "redirect_urls": [ + "" + ] + } + }, + "error": "", + "needed": "", + "provided": "" +} \ No newline at end of file diff --git a/json-logs/samples/api/apps.manifest.update.json b/json-logs/samples/api/apps.manifest.update.json new file mode 100644 index 000000000..60c7bee9b --- /dev/null +++ b/json-logs/samples/api/apps.manifest.update.json @@ -0,0 +1,8 @@ +{ + "ok": false, + "app_id": "A00000000", + "permissions_updated": false, + "error": "", + "needed": "", + "provided": "" +} \ No newline at end of file diff --git a/json-logs/samples/api/apps.manifest.validate.json b/json-logs/samples/api/apps.manifest.validate.json new file mode 100644 index 000000000..e25b2d2d2 --- /dev/null +++ b/json-logs/samples/api/apps.manifest.validate.json @@ -0,0 +1,18 @@ +{ + "ok": false, + "error": "", + "response_metadata": { + "messages": [ + "" + ] + }, + "needed": "", + "provided": "", + "errors": [ + { + "code": "", + "message": "", + "pointer": "" + } + ] +} \ No newline at end of file diff --git a/json-logs/samples/api/auth.test.json b/json-logs/samples/api/auth.test.json index 5c4eb1854..78b8c9c0c 100644 --- a/json-logs/samples/api/auth.test.json +++ b/json-logs/samples/api/auth.test.json @@ -12,5 +12,6 @@ "enterprise_id": "E00000000", "error": "", "needed": "", - "provided": "" + "provided": "", + "expires_in": 12345 } \ No newline at end of file diff --git a/json-logs/samples/api/tooling.tokens.rotate.json b/json-logs/samples/api/tooling.tokens.rotate.json new file mode 100644 index 000000000..4eb16d9a3 --- /dev/null +++ b/json-logs/samples/api/tooling.tokens.rotate.json @@ -0,0 +1,17 @@ +{ + "ok": false, + "error": "", + "response_metadata": { + "messages": [ + "" + ] + }, + "needed": "", + "provided": "", + "token": "", + "refresh_token": "", + "team_id": "T00000000", + "user_id": "U00000000", + "iat": 12345, + "exp": 12345 +} \ No newline at end of file diff --git a/metadata/web-api/rate_limit_tiers.json b/metadata/web-api/rate_limit_tiers.json index ce3d2f162..0c3a24b4b 100644 --- a/metadata/web-api/rate_limit_tiers.json +++ b/metadata/web-api/rate_limit_tiers.json @@ -100,6 +100,11 @@ "api.test": "Tier4", "apps.connections.open": "Tier1", "apps.event.authorizations.list": "Tier4", + "apps.manifest.create": "Tier1", + "apps.manifest.delete": "Tier1", + "apps.manifest.export": "Tier3", + "apps.manifest.update": "Tier1", + "apps.manifest.validate": "Tier3", "apps.permissions.info": "Tier2", "apps.permissions.request": "Tier2", "apps.permissions.resources.list": "Tier2", @@ -253,6 +258,7 @@ "team.integrationLogs": "Tier2", "team.preferences.list": "Tier3", "team.profile.get": "Tier3", + "tooling.tokens.rotate": "Tier1", "usergroups.create": "Tier2", "usergroups.disable": "Tier2", "usergroups.enable": "Tier2", diff --git a/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java b/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java index 3ff9a83ba..ed35ff06f 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/AsyncMethodsClient.java @@ -42,6 +42,7 @@ import com.slack.api.methods.request.apps.AppsUninstallRequest; import com.slack.api.methods.request.apps.connections.AppsConnectionsOpenRequest; import com.slack.api.methods.request.apps.event.authorizations.AppsEventAuthorizationsListRequest; +import com.slack.api.methods.request.apps.manifest.*; import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; @@ -89,6 +90,7 @@ import com.slack.api.methods.request.stars.StarsRemoveRequest; import com.slack.api.methods.request.team.*; import com.slack.api.methods.request.team.profile.TeamProfileGetRequest; +import com.slack.api.methods.request.tooling.tokens.ToolingTokensRotateRequest; import com.slack.api.methods.request.usergroups.*; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersListRequest; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersUpdateRequest; @@ -143,6 +145,7 @@ import com.slack.api.methods.response.apps.AppsUninstallResponse; import com.slack.api.methods.response.apps.connections.AppsConnectionsOpenResponse; import com.slack.api.methods.response.apps.event.authorizations.AppsEventAuthorizationsListResponse; +import com.slack.api.methods.response.apps.manifest.*; import com.slack.api.methods.response.auth.AuthRevokeResponse; import com.slack.api.methods.response.auth.AuthTestResponse; import com.slack.api.methods.response.auth.teams.AuthTeamsListResponse; @@ -190,6 +193,7 @@ import com.slack.api.methods.response.stars.StarsRemoveResponse; import com.slack.api.methods.response.team.*; import com.slack.api.methods.response.team.profile.TeamProfileGetResponse; +import com.slack.api.methods.response.tooling.tokens.ToolingTokensRotateResponse; import com.slack.api.methods.response.usergroups.*; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersListResponse; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersUpdateResponse; @@ -747,6 +751,31 @@ CompletableFuture CompletableFuture appsEventAuthorizationsList(RequestConfigurator req); + // ------------------------------ + // apps.manifest + // ------------------------------ + + CompletableFuture appsManifestCreate(AppsManifestCreateRequest req); + + CompletableFuture appsManifestCreate(RequestConfigurator req); + + + CompletableFuture appsManifestDelete(AppsManifestDeleteRequest req); + + CompletableFuture appsManifestDelete(RequestConfigurator req); + + CompletableFuture appsManifestExport(AppsManifestExportRequest req); + + CompletableFuture appsManifestExport(RequestConfigurator req); + + CompletableFuture appsManifestUpdate(AppsManifestUpdateRequest req); + + CompletableFuture appsManifestUpdate(RequestConfigurator req); + + CompletableFuture appsManifestValidate(AppsManifestValidateRequest req); + + CompletableFuture appsManifestValidate(RequestConfigurator req); + // ------------------------------ // auth // ------------------------------ @@ -1266,6 +1295,14 @@ CompletableFuture CompletableFuture teamPreferencesList(RequestConfigurator req); + // ------------------------------ + // tooling.tokens + // ------------------------------ + + CompletableFuture toolingTokensRotate(ToolingTokensRotateRequest req); + + CompletableFuture toolingTokensRotate(RequestConfigurator req); + // ------------------------------ // usergroups // ------------------------------ diff --git a/slack-api-client/src/main/java/com/slack/api/methods/Methods.java b/slack-api-client/src/main/java/com/slack/api/methods/Methods.java index cb3296af9..d621318c7 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/Methods.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/Methods.java @@ -236,6 +236,18 @@ private Methods() { public static final String APPS_CONNECTIONS_OPEN = "apps.connections.open"; + // ------------------------------ + // apps.manifest + // ------------------------------ + + public static final String APPS_MANIFEST_CREATE = "apps.manifest.create"; + public static final String APPS_MANIFEST_DELETE = "apps.manifest.delete"; + public static final String APPS_MANIFEST_EXPORT = "apps.manifest.export"; + public static final String APPS_MANIFEST_UPDATE = "apps.manifest.update"; + public static final String APPS_MANIFEST_VALIDATE = "apps.manifest.validate"; + public static final String TOOLING_TOKENS_ROTATE = "tooling.tokens.rotate"; + + // ------------------------------ // apps.event.authorizations // ------------------------------ diff --git a/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java b/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java index aeddb1040..57ce2a89d 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/MethodsClient.java @@ -42,6 +42,7 @@ import com.slack.api.methods.request.apps.AppsUninstallRequest; import com.slack.api.methods.request.apps.connections.AppsConnectionsOpenRequest; import com.slack.api.methods.request.apps.event.authorizations.AppsEventAuthorizationsListRequest; +import com.slack.api.methods.request.apps.manifest.*; import com.slack.api.methods.request.apps.permissions.AppsPermissionsInfoRequest; import com.slack.api.methods.request.apps.permissions.AppsPermissionsRequestRequest; import com.slack.api.methods.request.apps.permissions.resources.AppsPermissionsResourcesListRequest; @@ -102,6 +103,7 @@ import com.slack.api.methods.request.stars.StarsRemoveRequest; import com.slack.api.methods.request.team.*; import com.slack.api.methods.request.team.profile.TeamProfileGetRequest; +import com.slack.api.methods.request.tooling.tokens.ToolingTokensRotateRequest; import com.slack.api.methods.request.usergroups.*; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersListRequest; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersUpdateRequest; @@ -156,6 +158,7 @@ import com.slack.api.methods.response.apps.AppsUninstallResponse; import com.slack.api.methods.response.apps.connections.AppsConnectionsOpenResponse; import com.slack.api.methods.response.apps.event.authorizations.AppsEventAuthorizationsListResponse; +import com.slack.api.methods.response.apps.manifest.*; import com.slack.api.methods.response.apps.permissions.AppsPermissionsInfoResponse; import com.slack.api.methods.response.apps.permissions.AppsPermissionsRequestResponse; import com.slack.api.methods.response.apps.permissions.resources.AppsPermissionsResourcesListResponse; @@ -216,6 +219,7 @@ import com.slack.api.methods.response.stars.StarsRemoveResponse; import com.slack.api.methods.response.team.*; import com.slack.api.methods.response.team.profile.TeamProfileGetResponse; +import com.slack.api.methods.response.tooling.tokens.ToolingTokensRotateResponse; import com.slack.api.methods.response.usergroups.*; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersListResponse; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersUpdateResponse; @@ -836,6 +840,31 @@ AdminUsergroupsRemoveChannelsResponse adminUsergroupsRemoveChannels( AppsEventAuthorizationsListResponse appsEventAuthorizationsList(RequestConfigurator req) throws IOException, SlackApiException; + // ------------------------------ + // apps.manifest + // ------------------------------ + + AppsManifestCreateResponse appsManifestCreate(AppsManifestCreateRequest req) throws IOException, SlackApiException; + + AppsManifestCreateResponse appsManifestCreate(RequestConfigurator req) throws IOException, SlackApiException; + + + AppsManifestDeleteResponse appsManifestDelete(AppsManifestDeleteRequest req) throws IOException, SlackApiException; + + AppsManifestDeleteResponse appsManifestDelete(RequestConfigurator req) throws IOException, SlackApiException; + + AppsManifestExportResponse appsManifestExport(AppsManifestExportRequest req) throws IOException, SlackApiException; + + AppsManifestExportResponse appsManifestExport(RequestConfigurator req) throws IOException, SlackApiException; + + AppsManifestUpdateResponse appsManifestUpdate(AppsManifestUpdateRequest req) throws IOException, SlackApiException; + + AppsManifestUpdateResponse appsManifestUpdate(RequestConfigurator req) throws IOException, SlackApiException; + + AppsManifestValidateResponse appsManifestValidate(AppsManifestValidateRequest req) throws IOException, SlackApiException; + + AppsManifestValidateResponse appsManifestValidate(RequestConfigurator req) throws IOException, SlackApiException; + // ------------------------------ // apps.permissions // ------------------------------ @@ -1807,6 +1836,14 @@ AdminUsergroupsRemoveChannelsResponse adminUsergroupsRemoveChannels( TeamPreferencesListResponse teamPreferencesList(RequestConfigurator req) throws IOException, SlackApiException; + // ------------------------------ + // tooling.tokens + // ------------------------------ + + ToolingTokensRotateResponse toolingTokensRotate(ToolingTokensRotateRequest req) throws IOException, SlackApiException; + + ToolingTokensRotateResponse toolingTokensRotate(RequestConfigurator req) throws IOException, SlackApiException; + // ------------------------------ // usergroups // ------------------------------ diff --git a/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java index 7bcd24dbc..9aa6b8647 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/MethodsRateLimits.java @@ -214,6 +214,14 @@ public static void setRateLimitTier(String methodName, MethodsRateLimitTier tier // Public APIs // -------------------------- + + setRateLimitTier(APPS_MANIFEST_CREATE, Tier1); + setRateLimitTier(APPS_MANIFEST_DELETE, Tier1); + setRateLimitTier(APPS_MANIFEST_EXPORT, Tier3); + setRateLimitTier(APPS_MANIFEST_UPDATE, Tier1); + setRateLimitTier(APPS_MANIFEST_VALIDATE, Tier3); + setRateLimitTier(TOOLING_TOKENS_ROTATE, Tier1); // TODO: change this when the "special" tier is clearly explained in the document + setRateLimitTier(API_TEST, Tier4); setRateLimitTier(APPS_CONNECTIONS_OPEN, Tier1); setRateLimitTier(APPS_UNINSTALL, Tier1); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java b/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java index 6bb10c744..6b25e2b25 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java @@ -40,6 +40,7 @@ import com.slack.api.methods.request.apps.AppsUninstallRequest; import com.slack.api.methods.request.apps.connections.AppsConnectionsOpenRequest; import com.slack.api.methods.request.apps.event.authorizations.AppsEventAuthorizationsListRequest; +import com.slack.api.methods.request.apps.manifest.*; import com.slack.api.methods.request.apps.permissions.AppsPermissionsInfoRequest; import com.slack.api.methods.request.apps.permissions.AppsPermissionsRequestRequest; import com.slack.api.methods.request.apps.permissions.resources.AppsPermissionsResourcesListRequest; @@ -98,6 +99,7 @@ import com.slack.api.methods.request.stars.StarsRemoveRequest; import com.slack.api.methods.request.team.*; import com.slack.api.methods.request.team.profile.TeamProfileGetRequest; +import com.slack.api.methods.request.tooling.tokens.ToolingTokensRotateRequest; import com.slack.api.methods.request.usergroups.*; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersListRequest; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersUpdateRequest; @@ -1016,6 +1018,50 @@ public static FormBody.Builder toForm(AppsEventAuthorizationsListRequest req) { return form; } + public static FormBody.Builder toForm(AppsManifestCreateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + if (req.getManifestAsString() != null) { + setIfNotNull("manifest", req.getManifestAsString(), form); + } else if (req.getManifest() != null) { + setIfNotNull("manifest", GSON.toJson(req.getManifest()), form); + } + return form; + } + + public static FormBody.Builder toForm(AppsManifestDeleteRequest req) { + FormBody.Builder form = new FormBody.Builder(); + setIfNotNull("app_id", req.getAppId(), form); + return form; + } + + public static FormBody.Builder toForm(AppsManifestUpdateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + if (req.getManifestAsString() != null) { + setIfNotNull("manifest", req.getManifestAsString(), form); + } else if (req.getManifest() != null) { + setIfNotNull("manifest", GSON.toJson(req.getManifest()), form); + } + setIfNotNull("app_id", req.getAppId(), form); + return form; + } + + public static FormBody.Builder toForm(AppsManifestExportRequest req) { + FormBody.Builder form = new FormBody.Builder(); + setIfNotNull("app_id", req.getAppId(), form); + return form; + } + + public static FormBody.Builder toForm(AppsManifestValidateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + if (req.getManifestAsString() != null) { + setIfNotNull("manifest", req.getManifestAsString(), form); + } else if (req.getManifest() != null) { + setIfNotNull("manifest", GSON.toJson(req.getManifest()), form); + } + setIfNotNull("app_id", req.getAppId(), form); + return form; + } + public static FormBody.Builder toForm(AppsPermissionsRequestRequest req) { FormBody.Builder form = new FormBody.Builder(); setIfNotNull("trigger_id", req.getTriggerId(), form); @@ -2461,6 +2507,16 @@ public static FormBody.Builder toForm(TeamPreferencesListRequest req) { return form; } + public static FormBody.Builder toForm(ToolingTokensRotateRequest req) { + FormBody.Builder form = new FormBody.Builder(); + String token = req.getRefreshToken(); + if (token == null) { + token = req.getToken(); + } + setIfNotNull("refresh_token", token, form); + return form; + } + public static FormBody.Builder toForm(UsergroupsCreateRequest req) { FormBody.Builder form = new FormBody.Builder(); setIfNotNull("name", req.getName(), form); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java b/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java index 46a7fe783..2b881af00 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/impl/AsyncMethodsClientImpl.java @@ -46,6 +46,7 @@ import com.slack.api.methods.request.apps.AppsUninstallRequest; import com.slack.api.methods.request.apps.connections.AppsConnectionsOpenRequest; import com.slack.api.methods.request.apps.event.authorizations.AppsEventAuthorizationsListRequest; +import com.slack.api.methods.request.apps.manifest.*; import com.slack.api.methods.request.auth.AuthRevokeRequest; import com.slack.api.methods.request.auth.AuthTestRequest; import com.slack.api.methods.request.auth.teams.AuthTeamsListRequest; @@ -93,6 +94,7 @@ import com.slack.api.methods.request.stars.StarsRemoveRequest; import com.slack.api.methods.request.team.*; import com.slack.api.methods.request.team.profile.TeamProfileGetRequest; +import com.slack.api.methods.request.tooling.tokens.ToolingTokensRotateRequest; import com.slack.api.methods.request.usergroups.*; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersListRequest; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersUpdateRequest; @@ -147,6 +149,7 @@ import com.slack.api.methods.response.apps.AppsUninstallResponse; import com.slack.api.methods.response.apps.connections.AppsConnectionsOpenResponse; import com.slack.api.methods.response.apps.event.authorizations.AppsEventAuthorizationsListResponse; +import com.slack.api.methods.response.apps.manifest.*; import com.slack.api.methods.response.auth.AuthRevokeResponse; import com.slack.api.methods.response.auth.AuthTestResponse; import com.slack.api.methods.response.auth.teams.AuthTeamsListResponse; @@ -194,6 +197,7 @@ import com.slack.api.methods.response.stars.StarsRemoveResponse; import com.slack.api.methods.response.team.*; import com.slack.api.methods.response.team.profile.TeamProfileGetResponse; +import com.slack.api.methods.response.tooling.tokens.ToolingTokensRotateResponse; import com.slack.api.methods.response.usergroups.*; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersListResponse; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersUpdateResponse; @@ -1271,6 +1275,56 @@ public CompletableFuture appsEventAuthoriza return appsEventAuthorizationsList(req.configure(AppsEventAuthorizationsListRequest.builder()).build()); } + @Override + public CompletableFuture appsManifestCreate(AppsManifestCreateRequest req) { + return executor.execute(APPS_MANIFEST_CREATE, toMap(req), () -> methods.appsManifestCreate(req)); + } + + @Override + public CompletableFuture appsManifestCreate(RequestConfigurator req) { + return appsManifestCreate(req.configure(AppsManifestCreateRequest.builder()).build()); + } + + @Override + public CompletableFuture appsManifestDelete(AppsManifestDeleteRequest req) { + return executor.execute(APPS_MANIFEST_DELETE, toMap(req), () -> methods.appsManifestDelete(req)); + } + + @Override + public CompletableFuture appsManifestDelete(RequestConfigurator req) { + return appsManifestDelete(req.configure(AppsManifestDeleteRequest.builder()).build()); + } + + @Override + public CompletableFuture appsManifestExport(AppsManifestExportRequest req) { + return executor.execute(APPS_MANIFEST_EXPORT, toMap(req), () -> methods.appsManifestExport(req)); + } + + @Override + public CompletableFuture appsManifestExport(RequestConfigurator req) { + return appsManifestExport(req.configure(AppsManifestExportRequest.builder()).build()); + } + + @Override + public CompletableFuture appsManifestUpdate(AppsManifestUpdateRequest req) { + return executor.execute(APPS_MANIFEST_UPDATE, toMap(req), () -> methods.appsManifestUpdate(req)); + } + + @Override + public CompletableFuture appsManifestUpdate(RequestConfigurator req) { + return appsManifestUpdate(req.configure(AppsManifestUpdateRequest.builder()).build()); + } + + @Override + public CompletableFuture appsManifestValidate(AppsManifestValidateRequest req) { + return executor.execute(APPS_MANIFEST_VALIDATE, toMap(req), () -> methods.appsManifestValidate(req)); + } + + @Override + public CompletableFuture appsManifestValidate(RequestConfigurator req) { + return appsManifestValidate(req.configure(AppsManifestValidateRequest.builder()).build()); + } + @Override public CompletableFuture authRevoke(AuthRevokeRequest req) { return executor.execute(AUTH_REVOKE, toMap(req), () -> methods.authRevoke(req)); @@ -2316,6 +2370,16 @@ public CompletableFuture teamPreferencesList(Reques return teamPreferencesList(req.configure(TeamPreferencesListRequest.builder()).build()); } + @Override + public CompletableFuture toolingTokensRotate(ToolingTokensRotateRequest req) { + return executor.execute(TOOLING_TOKENS_ROTATE, toMap(req), () -> methods.toolingTokensRotate(req)); + } + + @Override + public CompletableFuture toolingTokensRotate(RequestConfigurator req) { + return toolingTokensRotate(req.configure(ToolingTokensRotateRequest.builder()).build()); + } + @Override public CompletableFuture usergroupsCreate(UsergroupsCreateRequest req) { return executor.execute(USERGROUPS_CREATE, toMap(req), () -> methods.usergroupsCreate(req)); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java b/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java index 9101b3017..78c4b89d2 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/impl/MethodsClientImpl.java @@ -44,6 +44,7 @@ import com.slack.api.methods.request.apps.AppsUninstallRequest; import com.slack.api.methods.request.apps.connections.AppsConnectionsOpenRequest; import com.slack.api.methods.request.apps.event.authorizations.AppsEventAuthorizationsListRequest; +import com.slack.api.methods.request.apps.manifest.*; import com.slack.api.methods.request.apps.permissions.AppsPermissionsInfoRequest; import com.slack.api.methods.request.apps.permissions.AppsPermissionsRequestRequest; import com.slack.api.methods.request.apps.permissions.resources.AppsPermissionsResourcesListRequest; @@ -104,6 +105,7 @@ import com.slack.api.methods.request.stars.StarsRemoveRequest; import com.slack.api.methods.request.team.*; import com.slack.api.methods.request.team.profile.TeamProfileGetRequest; +import com.slack.api.methods.request.tooling.tokens.ToolingTokensRotateRequest; import com.slack.api.methods.request.usergroups.*; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersListRequest; import com.slack.api.methods.request.usergroups.users.UsergroupsUsersUpdateRequest; @@ -158,6 +160,7 @@ import com.slack.api.methods.response.apps.AppsUninstallResponse; import com.slack.api.methods.response.apps.connections.AppsConnectionsOpenResponse; import com.slack.api.methods.response.apps.event.authorizations.AppsEventAuthorizationsListResponse; +import com.slack.api.methods.response.apps.manifest.*; import com.slack.api.methods.response.apps.permissions.AppsPermissionsInfoResponse; import com.slack.api.methods.response.apps.permissions.AppsPermissionsRequestResponse; import com.slack.api.methods.response.apps.permissions.resources.AppsPermissionsResourcesListResponse; @@ -218,6 +221,7 @@ import com.slack.api.methods.response.stars.StarsRemoveResponse; import com.slack.api.methods.response.team.*; import com.slack.api.methods.response.team.profile.TeamProfileGetResponse; +import com.slack.api.methods.response.tooling.tokens.ToolingTokensRotateResponse; import com.slack.api.methods.response.usergroups.*; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersListResponse; import com.slack.api.methods.response.usergroups.users.UsergroupsUsersUpdateResponse; @@ -1312,6 +1316,56 @@ public AppsEventAuthorizationsListResponse appsEventAuthorizationsList(RequestCo return appsEventAuthorizationsList(req.configure(AppsEventAuthorizationsListRequest.builder()).build()); } + @Override + public AppsManifestCreateResponse appsManifestCreate(AppsManifestCreateRequest req) throws IOException, SlackApiException { + return postFormWithTokenAndParseResponse(toForm(req), Methods.APPS_MANIFEST_CREATE, getToken(req), AppsManifestCreateResponse.class); + } + + @Override + public AppsManifestCreateResponse appsManifestCreate(RequestConfigurator req) throws IOException, SlackApiException { + return appsManifestCreate(req.configure(AppsManifestCreateRequest.builder()).build()); + } + + @Override + public AppsManifestDeleteResponse appsManifestDelete(AppsManifestDeleteRequest req) throws IOException, SlackApiException { + return postFormWithTokenAndParseResponse(toForm(req), Methods.APPS_MANIFEST_DELETE, getToken(req), AppsManifestDeleteResponse.class); + } + + @Override + public AppsManifestDeleteResponse appsManifestDelete(RequestConfigurator req) throws IOException, SlackApiException { + return appsManifestDelete(req.configure(AppsManifestDeleteRequest.builder()).build()); + } + + @Override + public AppsManifestExportResponse appsManifestExport(AppsManifestExportRequest req) throws IOException, SlackApiException { + return postFormWithTokenAndParseResponse(toForm(req), Methods.APPS_MANIFEST_EXPORT, getToken(req), AppsManifestExportResponse.class); + } + + @Override + public AppsManifestExportResponse appsManifestExport(RequestConfigurator req) throws IOException, SlackApiException { + return appsManifestExport(req.configure(AppsManifestExportRequest.builder()).build()); + } + + @Override + public AppsManifestUpdateResponse appsManifestUpdate(AppsManifestUpdateRequest req) throws IOException, SlackApiException { + return postFormWithTokenAndParseResponse(toForm(req), Methods.APPS_MANIFEST_UPDATE, getToken(req), AppsManifestUpdateResponse.class); + } + + @Override + public AppsManifestUpdateResponse appsManifestUpdate(RequestConfigurator req) throws IOException, SlackApiException { + return appsManifestUpdate(req.configure(AppsManifestUpdateRequest.builder()).build()); + } + + @Override + public AppsManifestValidateResponse appsManifestValidate(AppsManifestValidateRequest req) throws IOException, SlackApiException { + return postFormWithTokenAndParseResponse(toForm(req), Methods.APPS_MANIFEST_VALIDATE, getToken(req), AppsManifestValidateResponse.class); + } + + @Override + public AppsManifestValidateResponse appsManifestValidate(RequestConfigurator req) throws IOException, SlackApiException { + return appsManifestValidate(req.configure(AppsManifestValidateRequest.builder()).build()); + } + @Override public AppsPermissionsInfoResponse appsPermissionsInfo(AppsPermissionsInfoRequest req) throws IOException, SlackApiException { return postFormWithTokenAndParseResponse(toForm(req), Methods.APPS_PERMISSIONS_INFO, getToken(req), AppsPermissionsInfoResponse.class); @@ -3006,6 +3060,16 @@ public TeamPreferencesListResponse teamPreferencesList(RequestConfigurator req) throws IOException, SlackApiException { + return toolingTokensRotate(req.configure(ToolingTokensRotateRequest.builder()).build()); + } + @Override public UsergroupsCreateResponse usergroupsCreate(UsergroupsCreateRequest req) throws IOException, SlackApiException { return postFormWithTokenAndParseResponse(toForm(req), Methods.USERGROUPS_CREATE, getToken(req), UsergroupsCreateResponse.class); diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestCreateRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestCreateRequest.java new file mode 100644 index 000000000..a71403551 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestCreateRequest.java @@ -0,0 +1,16 @@ +package com.slack.api.methods.request.apps.manifest; + +import com.slack.api.methods.SlackApiRequest; +import com.slack.api.model.AppManifest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AppsManifestCreateRequest implements SlackApiRequest { + + private String token; + + private AppManifest manifest; + private String manifestAsString; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestDeleteRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestDeleteRequest.java new file mode 100644 index 000000000..a45699d5e --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestDeleteRequest.java @@ -0,0 +1,15 @@ +package com.slack.api.methods.request.apps.manifest; + +import com.slack.api.methods.SlackApiRequest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AppsManifestDeleteRequest implements SlackApiRequest { + + private String token; + + private String appId; + +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestExportRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestExportRequest.java new file mode 100644 index 000000000..1a57e3af8 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestExportRequest.java @@ -0,0 +1,14 @@ +package com.slack.api.methods.request.apps.manifest; + +import com.slack.api.methods.SlackApiRequest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AppsManifestExportRequest implements SlackApiRequest { + + private String token; + + private String appId; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestUpdateRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestUpdateRequest.java new file mode 100644 index 000000000..7330d11a4 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestUpdateRequest.java @@ -0,0 +1,17 @@ +package com.slack.api.methods.request.apps.manifest; + +import com.slack.api.methods.SlackApiRequest; +import com.slack.api.model.AppManifest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AppsManifestUpdateRequest implements SlackApiRequest { + + private String token; + + private AppManifest manifest; + private String manifestAsString; + private String appId; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestValidateRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestValidateRequest.java new file mode 100644 index 000000000..94d4000e8 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/apps/manifest/AppsManifestValidateRequest.java @@ -0,0 +1,17 @@ +package com.slack.api.methods.request.apps.manifest; + +import com.slack.api.methods.SlackApiRequest; +import com.slack.api.model.AppManifest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AppsManifestValidateRequest implements SlackApiRequest { + + private String token; + + private AppManifest manifest; + private String manifestAsString; + private String appId; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/tooling/tokens/ToolingTokensRotateRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/tooling/tokens/ToolingTokensRotateRequest.java new file mode 100644 index 000000000..6e534fa56 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/tooling/tokens/ToolingTokensRotateRequest.java @@ -0,0 +1,14 @@ +package com.slack.api.methods.request.tooling.tokens; + +import com.slack.api.methods.SlackApiRequest; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ToolingTokensRotateRequest implements SlackApiRequest { + + private String token; + private String refreshToken; + +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestCreateResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestCreateResponse.java new file mode 100644 index 000000000..2d6232438 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestCreateResponse.java @@ -0,0 +1,34 @@ +package com.slack.api.methods.response.apps.manifest; + +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.AppCredentials; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class AppsManifestCreateResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private String appId; + private AppCredentials credentials; + private String oauthAuthorizeUrl; + + private ResponseMetadata responseMetadata; + private List errors; + + @Data + public static class Error { + private String code; + private String message; + private String pointer; + } +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestDeleteResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestDeleteResponse.java new file mode 100644 index 000000000..5c8c68098 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestDeleteResponse.java @@ -0,0 +1,21 @@ +package com.slack.api.methods.response.apps.manifest; + +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class AppsManifestDeleteResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private ResponseMetadata responseMetadata; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestExportResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestExportResponse.java new file mode 100644 index 000000000..797712429 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestExportResponse.java @@ -0,0 +1,24 @@ +package com.slack.api.methods.response.apps.manifest; + +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.AppManifest; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class AppsManifestExportResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private AppManifest manifest; + + private ResponseMetadata responseMetadata; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestUpdateResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestUpdateResponse.java new file mode 100644 index 000000000..d7535a743 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestUpdateResponse.java @@ -0,0 +1,24 @@ +package com.slack.api.methods.response.apps.manifest; + +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class AppsManifestUpdateResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private String appId; + private boolean permissionsUpdated; + + private ResponseMetadata responseMetadata; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestValidateResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestValidateResponse.java new file mode 100644 index 000000000..58212b9b2 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/apps/manifest/AppsManifestValidateResponse.java @@ -0,0 +1,29 @@ +package com.slack.api.methods.response.apps.manifest; + +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class AppsManifestValidateResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private ResponseMetadata responseMetadata; + private List errors; + + @Data + public static class Error { + private String code; + private String message; + private String pointer; + } +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/auth/AuthTestResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/auth/AuthTestResponse.java index 936c3ddff..ec358cd82 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/response/auth/AuthTestResponse.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/auth/AuthTestResponse.java @@ -26,4 +26,5 @@ public class AuthTestResponse implements SlackApiTextResponse { private String appId; // only for app-level tokens private String appName; // only for app-level tokens private boolean isEnterpriseInstall; + private Integer expiresIn; // only for tooling tokens } \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/methods/response/tooling/tokens/ToolingTokensRotateResponse.java b/slack-api-client/src/main/java/com/slack/api/methods/response/tooling/tokens/ToolingTokensRotateResponse.java new file mode 100644 index 000000000..d61c44f23 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/methods/response/tooling/tokens/ToolingTokensRotateResponse.java @@ -0,0 +1,27 @@ +package com.slack.api.methods.response.tooling.tokens; + +import com.slack.api.methods.SlackApiTextResponse; +import com.slack.api.model.ResponseMetadata; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class ToolingTokensRotateResponse implements SlackApiTextResponse { + + private boolean ok; + private String warning; + private String error; + private String needed; + private String provided; + private transient Map> httpResponseHeaders; + + private String token; + private String refreshToken; + private String teamId; + private String userId; + private Integer iat; + private Integer exp; + private ResponseMetadata responseMetadata; +} \ No newline at end of file diff --git a/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingToken.java b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingToken.java new file mode 100644 index 000000000..d509afcf9 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingToken.java @@ -0,0 +1,24 @@ +package com.slack.api.token_rotation.tooling; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolingToken { + private String teamId; + private String userId; + private String refreshToken; // xoxe-... + private String accessToken; // xoxe.xoxp-... + private Integer expireAt; // epoch time (seconds) + + public boolean isExpired() { + if (this.getExpireAt() == null) return true; + return this.getExpireAt() < System.currentTimeMillis() / 1000; + } +} diff --git a/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenRotator.java b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenRotator.java new file mode 100644 index 000000000..b86dbbf75 --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenRotator.java @@ -0,0 +1,54 @@ +package com.slack.api.token_rotation.tooling; + +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiException; +import com.slack.api.methods.response.tooling.tokens.ToolingTokensRotateResponse; + +import java.io.IOException; +import java.util.Optional; + +public class ToolingTokenRotator { + private final ToolingTokenStore store; + private final MethodsClient client; + + public ToolingTokenRotator(ToolingTokenStore store) { + this(store, Slack.getInstance().methods()); + } + + public ToolingTokenRotator(ToolingTokenStore store, MethodsClient client) { + this.store = store; + this.client = client; + } + + public Optional find(String teamId, String userId) throws SlackApiException, IOException { + Optional maybeToken = this.store.find(teamId, userId); + if (maybeToken.isPresent()) { + ToolingToken token = maybeToken.get(); + if (token.isExpired()) { + ToolingTokensRotateResponse response = this.client.toolingTokensRotate(r -> r + .token(token.getAccessToken()) + .refreshToken(token.getRefreshToken()) + ); + if (!response.isOk()) { + String error = "Failed to rotate a tooling token due to " + response.getError(); + throw new IllegalStateException(error); + } + ToolingToken refreshed = ToolingToken.builder() + .accessToken(response.getToken()) + .refreshToken(response.getRefreshToken()) + .teamId(teamId) + .userId(userId) + .expireAt(response.getExp()) + .build(); + this.store.save(refreshed); + return Optional.ofNullable(refreshed); + } + } + return maybeToken; + } + + public void save(ToolingToken token) { + this.store.save(token); + } +} diff --git a/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenStore.java b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenStore.java new file mode 100644 index 000000000..69b8fdc3a --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/ToolingTokenStore.java @@ -0,0 +1,11 @@ +package com.slack.api.token_rotation.tooling; + +import java.util.Optional; + +public interface ToolingTokenStore { + + void save(ToolingToken token); + + Optional find(String teamId, String userId); + +} diff --git a/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/store/FileToolingTokenStore.java b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/store/FileToolingTokenStore.java new file mode 100644 index 000000000..789cdfd2f --- /dev/null +++ b/slack-api-client/src/main/java/com/slack/api/token_rotation/tooling/store/FileToolingTokenStore.java @@ -0,0 +1,83 @@ +package com.slack.api.token_rotation.tooling.store; + +import com.google.gson.Gson; +import com.slack.api.SlackConfig; +import com.slack.api.token_rotation.tooling.ToolingToken; +import com.slack.api.token_rotation.tooling.ToolingTokenStore; +import com.slack.api.util.json.GsonFactory; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import static java.util.stream.Collectors.joining; + +@Slf4j +public class FileToolingTokenStore implements ToolingTokenStore { + + public static final String DEFAULT_BASE_DIR = + System.getProperty("user.home") + File.separator + ".slack-tooling-token"; + + private static final Gson GSON; + + static { + SlackConfig config = new SlackConfig(); + config.setPrettyResponseLoggingEnabled(true); + GSON = GsonFactory.createSnakeCase(config); + } + + private final String baseDir; + + public FileToolingTokenStore() { + this(DEFAULT_BASE_DIR); + } + + + private String toFilepath(String teamId, String userId) { + return baseDir + File.separator + teamId + "-" + userId + ".json"; + } + + private String toFilepath(ToolingToken token) { + return baseDir + File.separator + token.getTeamId() + "-" + token.getUserId() + ".json"; + } + + private static String loadFileContent(String filepath) throws IOException { + String content = Files.readAllLines(Paths.get(filepath)) + .stream() + .collect(joining()); + if (content == null || content.trim().isEmpty() || content.trim().equals("null")) { + return null; + } + return content; + } + + public FileToolingTokenStore(String baseDir) { + this.baseDir = baseDir; + } + + @Override + public void save(ToolingToken token) { + String path = toFilepath(token); + String content = GSON.toJson(token); + try { + Files.write(Paths.get(path), content.getBytes()); + } catch (IOException e) { + log.warn("Failed to load a config file (path: {}, content: {})", path, content, e); + throw new RuntimeException(e); + } + } + + @Override + public Optional find(String teamId, String userId) { + try { + String rawData = loadFileContent(toFilepath(teamId, userId)); + return Optional.ofNullable(GSON.fromJson(rawData, ToolingToken.class)); + } catch (IOException e) { + log.warn("Failed to load a config file (team_id: {}, user_id: {})", teamId, userId, e); + return Optional.empty(); + } + } +} diff --git a/slack-api-client/src/test/java/config/Constants.java b/slack-api-client/src/test/java/config/Constants.java index 42d166e12..4ff66db84 100644 --- a/slack-api-client/src/test/java/config/Constants.java +++ b/slack-api-client/src/test/java/config/Constants.java @@ -53,4 +53,9 @@ private Constants() { public static final String SLACK_SDK_TEST_EMAIL_ADDRESS = "SLACK_SDK_TEST_EMAIL_ADDRESS"; public static final String SLACK_SDK_TEST_REDIS_ENABLED = "SLACK_SDK_TEST_REDIS_ENABLED"; + + // For tooling.tokens.rotate + apps.manifest.* APIs + public static final String SLACK_SDK_TEST_APP_MANIFEST_API_TEAM_ID = "SLACK_SDK_TEST_APP_MANIFEST_API_TEAM_ID"; + public static final String SLACK_SDK_TEST_APP_MANIFEST_API_USER_ID = "SLACK_SDK_TEST_APP_MANIFEST_API_USER_ID"; + } diff --git a/slack-api-client/src/test/java/test_locally/api/MethodsTest.java b/slack-api-client/src/test/java/test_locally/api/MethodsTest.java index 18f4f11bc..e5e2ca5e5 100644 --- a/slack-api-client/src/test/java/test_locally/api/MethodsTest.java +++ b/slack-api-client/src/test/java/test_locally/api/MethodsTest.java @@ -36,13 +36,6 @@ public void verifyTheCoverage() { String[] allMethodNames = methods.split(","); List excludedMethodNames = Arrays.asList( - // TODO: Add apps.manifest.* APIs - "apps.manifest.create", - "apps.manifest.delete", - "apps.manifest.export", - "apps.manifest.update", - "apps.manifest.validate", - "tooling.tokens.rotate", // These next-generation platform related APIs work only on the automation platform. // Thus, we don't have short-term plans to add them to this Java SDK. "apps.auth.external.delete", diff --git a/slack-api-client/src/test/java/test_locally/api/methods/AppsTest.java b/slack-api-client/src/test/java/test_locally/api/methods/AppsTest.java index ee1512928..9bfb5b950 100644 --- a/slack-api-client/src/test/java/test_locally/api/methods/AppsTest.java +++ b/slack-api-client/src/test/java/test_locally/api/methods/AppsTest.java @@ -2,6 +2,7 @@ import com.slack.api.Slack; import com.slack.api.SlackConfig; +import com.slack.api.methods.metrics.MemoryMetricsDatastore; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -29,15 +30,21 @@ public void tearDown() throws Exception { } @Test - public void apiTest() throws Exception { - assertThat(slack.methods(ValidToken).appsUninstall(r -> r.clientId("x").clientSecret("y")) - .isOk(), is(true)); + public void appsUninstall_async() throws Exception { + assertThat(slack.methodsAsync(ValidToken).appsUninstall(r -> r.clientId("x").clientSecret("y")) + .get().isOk(), is(true)); } @Test - public void apiTest_async() throws Exception { - assertThat(slack.methodsAsync(ValidToken).appsUninstall(r -> r.clientId("x").clientSecret("y")) - .get().isOk(), is(true)); + public void appsManifest_async() throws Exception { + slack.getConfig().getMethodsConfig().setMetricsDatastore(new MemoryMetricsDatastore(1)); + String token = ValidToken + "-2222"; + assertThat(slack.methodsAsync(token).appsManifestCreate(r -> r).get().isOk(), is(true)); + assertThat(slack.methodsAsync(token).appsManifestDelete(r -> r).get().isOk(), is(true)); + assertThat(slack.methodsAsync(token).appsManifestExport(r -> r).get().isOk(), is(true)); + assertThat(slack.methodsAsync(token).appsManifestValidate(r -> r).get().isOk(), is(true)); + assertThat(slack.methodsAsync(token).appsManifestUpdate(r -> r).get().isOk(), is(true)); + assertThat(slack.methodsAsync(token).toolingTokensRotate(r -> r).get().isOk(), is(true)); } } diff --git a/slack-api-client/src/test/java/test_with_remote_apis/methods/apps_manifest_Test.java b/slack-api-client/src/test/java/test_with_remote_apis/methods/apps_manifest_Test.java new file mode 100644 index 000000000..4a4800b89 --- /dev/null +++ b/slack-api-client/src/test/java/test_with_remote_apis/methods/apps_manifest_Test.java @@ -0,0 +1,123 @@ +package test_with_remote_apis.methods; + +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiException; +import com.slack.api.methods.response.apps.manifest.*; +import com.slack.api.model.AppManifest; +import com.slack.api.token_rotation.tooling.ToolingToken; +import com.slack.api.token_rotation.tooling.ToolingTokenRotator; +import com.slack.api.token_rotation.tooling.store.FileToolingTokenStore; +import config.Constants; +import config.SlackTestConfig; +import lombok.extern.slf4j.Slf4j; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@Slf4j +public class apps_manifest_Test { + + static SlackTestConfig testConfig = SlackTestConfig.getInstance(); + static Slack slack = Slack.getInstance(testConfig.getConfig()); + + String teamId = System.getenv(Constants.SLACK_SDK_TEST_APP_MANIFEST_API_TEAM_ID); + String userId = System.getenv(Constants.SLACK_SDK_TEST_APP_MANIFEST_API_USER_ID); + + @BeforeClass + public static void setUp() throws Exception { + SlackTestConfig.initializeRawJSONDataFiles("apps.manifest.*"); + SlackTestConfig.initializeRawJSONDataFiles("tooling.tokens.*"); + } + + @AfterClass + public static void tearDown() throws InterruptedException { + SlackTestConfig.awaitCompletion(testConfig); + } + + @Test + public void manifestOperations() throws IOException, SlackApiException { + // To grab your first refresh token, visit https://api.slack.com/reference/manifests#config-tokens + // Create a new directory slack-api-client/tmp/ and place your JSON data as {team_id}-{user_id}.json (e.g., T03E94MJU-U03E94MK0.json) + // The content should be something like this: + // {"access_token": "xoxe.xoxp-1-....","refresh_token": "xoxe-1-...","team_id": "T03E94MJU","user_id": "U03E94MK0","expire_at": 1699361653} + ToolingTokenRotator tokenRotator = new ToolingTokenRotator(new FileToolingTokenStore("tmp/")); + Optional maybeToken = tokenRotator.find(teamId, userId); + assertThat(maybeToken.isPresent(), is(true)); + ToolingToken token = maybeToken.get(); + MethodsClient client = slack.methods(token.getAccessToken()); + + AppManifest invalidManifest = AppManifest.builder() +// .displayInformation(AppManifest.DisplayInformation.builder() +// .name("manifest-test-app") +// .build()) + .features(AppManifest.Features.builder() + .botUser(AppManifest.BotUser.builder().displayName("test-bot").build()) + .build()) + .settings(AppManifest.Settings.builder() + .socketModeEnabled(true) + .build()) + .oauthConfig(AppManifest.OAuthConfig.builder() + .scopes(AppManifest.Scopes.builder().bot(Arrays.asList("commands")).build()) + .build()) + .build(); + + AppManifest manifest = AppManifest.builder() + .displayInformation(AppManifest.DisplayInformation.builder() + .name("manifest-test-app") + .build()) + .features(AppManifest.Features.builder() + .botUser(AppManifest.BotUser.builder().displayName("test-bot").build()) + .build()) + .settings(AppManifest.Settings.builder() + .socketModeEnabled(true) + .build()) + .oauthConfig(AppManifest.OAuthConfig.builder() + .scopes(AppManifest.Scopes.builder().bot(Arrays.asList("commands")).build()) + .build()) + .build(); + + AppsManifestValidateResponse validation = client.appsManifestValidate(r -> r.manifest(invalidManifest)); + assertThat(validation.getError(), is("invalid_manifest")); + validation = client.appsManifestValidate(r -> r.manifest(manifest)); + assertThat(validation.getError(), is(nullValue())); + + AppsManifestCreateResponse creation = null; + try { + creation = client.appsManifestCreate(r -> r.manifest(manifest)); + assertThat(creation.getError(), is(nullValue())); + String appId = creation.getAppId(); + + validation = client.appsManifestValidate(r -> r.manifest(manifest).appId(appId)); + assertThat(validation.getError(), is(nullValue())); + + AppsManifestUpdateResponse modification = client.appsManifestUpdate(r -> r.appId(appId).manifest(manifest)); + assertThat(modification.getError(), is(nullValue())); + + manifest.getDisplayInformation().setName("manifest-test-app-2"); + manifest.getOauthConfig().getScopes().setBot(Arrays.asList("commands", "chat:write")); + modification = client.appsManifestUpdate(r -> r.appId(appId).manifest(manifest)); + assertThat(modification.getError(), is(nullValue())); + + AppsManifestExportResponse deletion = client.appsManifestExport(r -> r.appId(appId)); + assertThat(deletion.getError(), is(nullValue())); + + } finally { + if (creation != null && creation.isOk()) { + String appId = creation.getAppId(); + AppsManifestDeleteResponse deletion = client.appsManifestDelete(r -> r.appId(appId)); + assertThat(deletion.getError(), is(nullValue())); + } + } + } + + +} diff --git a/slack-api-client/src/test/java/util/MockSlackApi.java b/slack-api-client/src/test/java/util/MockSlackApi.java index d5ef26965..e5363fa09 100644 --- a/slack-api-client/src/test/java/util/MockSlackApi.java +++ b/slack-api-client/src/test/java/util/MockSlackApi.java @@ -43,7 +43,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I resp.getWriter().write("{\"ok\":false,\"error\":\"not_authed\"}"); resp.setContentType("application/json"); return; - } else if (!authorizationHeader.equals("Bearer " + ValidToken)) { + } else if (!authorizationHeader.startsWith("Bearer " + ValidToken)) { resp.setStatus(200); if (authorizationHeader.equals("Bearer " + ExpiredToken)) { resp.getWriter().write("{\"ok\":false,\"error\":\"token_expired\"}"); diff --git a/slack-api-client/src/test/java/util/sample_json_generation/JsonDataRecorder.java b/slack-api-client/src/test/java/util/sample_json_generation/JsonDataRecorder.java index 36c5f5520..a5c647045 100644 --- a/slack-api-client/src/test/java/util/sample_json_generation/JsonDataRecorder.java +++ b/slack-api-client/src/test/java/util/sample_json_generation/JsonDataRecorder.java @@ -699,6 +699,29 @@ private void scanToNormalizeValues(String path, JsonElement parent, String name, } return; } + if (name != null && name.equals("manifest") + && path.startsWith("/api/apps.manifest.")) { + JsonObject manifest = element.getAsJsonObject(); + try { + // To avoid concurrent modification of the underlying objects + List oldKeys = new ArrayList<>(); + manifest.keySet().iterator().forEachRemaining(oldKeys::add); + for (String key : oldKeys) { + manifest.remove(key); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + try { + JsonObject manifestObj = GsonFactory.createSnakeCase().toJsonTree(AppManifestObject).getAsJsonObject(); + for (String newKey : manifestObj.keySet()) { + manifest.add(newKey, manifestObj.get(newKey)); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return; + } List> entries = new ArrayList<>(element.getAsJsonObject().entrySet()); if (entries.size() > 0) { if (entries.get(0).getKey().matches("^[A-Z].{8,10}$")) { diff --git a/slack-api-client/src/test/java/util/sample_json_generation/SampleObjects.java b/slack-api-client/src/test/java/util/sample_json_generation/SampleObjects.java index 472fd1dea..75ee15a3a 100644 --- a/slack-api-client/src/test/java/util/sample_json_generation/SampleObjects.java +++ b/slack-api-client/src/test/java/util/sample_json_generation/SampleObjects.java @@ -350,6 +350,34 @@ public static File initFileObject() { public static Message Message = new Message(); public static Map RoomPendingInvitees = new HashMap<>(); + + public static AppManifest AppManifestObject = initProperties(AppManifest.builder() + .metadata(initProperties(AppManifest.Metadata.builder().build())) + .displayInformation(initProperties(AppManifest.DisplayInformation.builder().build())) + .features(initProperties(AppManifest.Features.builder() + .appHome(initProperties(AppManifest.AppHome.builder().build())) + .botUser(initProperties(AppManifest.BotUser.builder().build())) + .shortcuts(Arrays.asList(initProperties(AppManifest.Shortcut.builder().build()))) + .slashCommands(Arrays.asList(initProperties(AppManifest.SlashCommand.builder().build()))) + .unfurlDomains(Arrays.asList("")) + .build())) + .settings(initProperties(AppManifest.Settings.builder() + .allowedIpAddressRanges(Arrays.asList("")) + .interactivity(initProperties(AppManifest.Interactivity.builder().build())) + .eventSubscriptions(initProperties(AppManifest.EventSubscriptions.builder() + .botEvents(Arrays.asList("")) + .userEvents(Arrays.asList("")) + .build())) + .build())) + .oauthConfig(initProperties(AppManifest.OAuthConfig.builder() + .scopes(initProperties(AppManifest.Scopes.builder() + .bot(Arrays.asList("")) + .user(Arrays.asList("")) + .build())) + .redirectUrls(Arrays.asList("")) + .build())) + .build()); + public static Room Room = initProperties(com.slack.api.model.Room.builder() .attachedFileIds(Arrays.asList("")) .channels(Arrays.asList("")) diff --git a/slack-api-model/src/main/java/com/slack/api/model/AppCredentials.java b/slack-api-model/src/main/java/com/slack/api/model/AppCredentials.java new file mode 100644 index 000000000..939757b75 --- /dev/null +++ b/slack-api-model/src/main/java/com/slack/api/model/AppCredentials.java @@ -0,0 +1,11 @@ +package com.slack.api.model; + +import lombok.Data; + +@Data +public class AppCredentials { + private String clientId; + private String clientSecret; + private String verificationToken; + private String signingSecret; +} diff --git a/slack-api-model/src/main/java/com/slack/api/model/AppManifest.java b/slack-api-model/src/main/java/com/slack/api/model/AppManifest.java new file mode 100644 index 000000000..97f51942d --- /dev/null +++ b/slack-api-model/src/main/java/com/slack/api/model/AppManifest.java @@ -0,0 +1,151 @@ +package com.slack.api.model; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AppManifest { + + @SerializedName("_metadata") + private Metadata metadata; + private DisplayInformation displayInformation; + private Settings settings; + private Features features; + private OAuthConfig oauthConfig; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Metadata { + private String majorVersion; + private String minorVersion; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class DisplayInformation { + private String name; + private String longDescription; + private String description; + private String backgroundColor; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Settings { + private String description; + private String longDescription; + private String backgroundColor; + private EventSubscriptions eventSubscriptions; + private Interactivity interactivity; + private List allowedIpAddressRanges; + private Boolean orgDeployEnabled; + private Boolean socketModeEnabled; + private Boolean tokenRotationEnabled; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Interactivity { + private Boolean isEnabled; + private String requestUrl; + private String messageMenuOptionsUrl; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class EventSubscriptions { + private List botEvents; + private List userEvents; + private String requestUrl; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Features { + private AppHome appHome; + private BotUser botUser; + private List shortcuts; + private List slashCommands; + private List unfurlDomains; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class AppHome { + private Boolean homeTabEnabled; + private Boolean messagesTabEnabled; + private Boolean messagesTabReadOnlyEnabled; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class BotUser { + private String displayName; + private Boolean alwaysOnline; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Shortcut { + private String type; // message / global + private String callbackId; + private String name; + private String description; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SlashCommand { + private String command; + private String description; + private String usageHint; + private String url; + private Boolean shouldEscape; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class OAuthConfig { + private Scopes scopes; + private List redirectUrls; + } + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Scopes { + private List bot; + private List user; + } +} diff --git a/slack-api-model/src/test/java/test_locally/api/model/AppManifestTest.java b/slack-api-model/src/test/java/test_locally/api/model/AppManifestTest.java new file mode 100644 index 000000000..6c515b003 --- /dev/null +++ b/slack-api-model/src/test/java/test_locally/api/model/AppManifestTest.java @@ -0,0 +1,107 @@ +package test_locally.api.model; + +import com.slack.api.model.AppManifest; +import com.slack.api.model.ConversationType; +import org.junit.Test; +import test_locally.unit.GsonFactory; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AppManifestTest { + + @Test + public void parser() { + String json = "{\n" + + " \"display_information\": {\n" + + " \"name\": \"manifest-sandbox\"\n" + + " },\n" + + " \"features\": {\n" + + " \"app_home\": {\n" + + " \"home_tab_enabled\": true,\n" + + " \"messages_tab_enabled\": false,\n" + + " \"messages_tab_read_only_enabled\": false\n" + + " },\n" + + " \"bot_user\": {\n" + + " \"display_name\": \"manifest-sandbox\",\n" + + " \"always_online\": true\n" + + " },\n" + + " \"shortcuts\": [\n" + + " {\n" + + " \"name\": \"message one\",\n" + + " \"type\": \"message\",\n" + + " \"callback_id\": \"m\",\n" + + " \"description\": \"message\"\n" + + " },\n" + + " {\n" + + " \"name\": \"global one\",\n" + + " \"type\": \"global\",\n" + + " \"callback_id\": \"g\",\n" + + " \"description\": \"global\"\n" + + " }\n" + + " ],\n" + + " \"slash_commands\": [\n" + + " {\n" + + " \"command\": \"/hey\",\n" + + " \"url\": \"https://www.example.com/\",\n" + + " \"description\": \"What's up?\",\n" + + " \"usage_hint\": \"What's up?\",\n" + + " \"should_escape\": true\n" + + " }\n" + + " ],\n" + + " \"unfurl_domains\": [\n" + + " \"example.com\"\n" + + " ]\n" + + " },\n" + + " \"oauth_config\": {\n" + + " \"redirect_urls\": [\n" + + " \"https://www.example.com/foo\"\n" + + " ],\n" + + " \"scopes\": {\n" + + " \"user\": [\n" + + " \"search:read\",\n" + + " \"channels:read\",\n" + + " \"groups:read\",\n" + + " \"mpim:read\"\n" + + " ],\n" + + " \"bot\": [\n" + + " \"commands\",\n" + + " \"incoming-webhook\",\n" + + " \"app_mentions:read\",\n" + + " \"links:read\"\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"settings\": {\n" + + " \"allowed_ip_address_ranges\": [\n" + + " \"123.123.123.123/32\"\n" + + " ],\n" + + " \"event_subscriptions\": {\n" + + " \"request_url\": \"https://www.example.com/slack/events\",\n" + + " \"user_events\": [\n" + + " \"member_joined_channel\"\n" + + " ],\n" + + " \"bot_events\": [\n" + + " \"app_mention\",\n" + + " \"link_shared\"\n" + + " ]\n" + + " },\n" + + " \"interactivity\": {\n" + + " \"is_enabled\": true,\n" + + " \"request_url\": \"https://www.example.com/\",\n" + + " \"message_menu_options_url\": \"https://www.example.com/\"\n" + + " },\n" + + " \"org_deploy_enabled\": true,\n" + + " \"socket_mode_enabled\": false,\n" + + " \"token_rotation_enabled\": true\n" + + " }\n" + + "}"; + + AppManifest appManifest = GsonFactory.createSnakeCase(true, true) + .fromJson(json, AppManifest.class); + assertThat(appManifest.getSettings(), is(notNullValue())); + } +}