diff --git a/api/v4/source/teams.yaml b/api/v4/source/teams.yaml
index cf5ab8266cd4..293650accab0 100644
--- a/api/v4/source/teams.yaml
+++ b/api/v4/source/teams.yaml
@@ -1572,62 +1572,6 @@
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
- "/api/v4/teams/{team_id}/import":
- post:
- tags:
- - teams
- summary: Import a Team from other application
- description: >
- Import a team into a existing team. Import users, channels, posts,
- hooks.
-
- ##### Permissions
-
- Must have `permission_import_team` permission.
- operationId: ImportTeam
- parameters:
- - name: team_id
- in: path
- description: Team GUID
- required: true
- schema:
- type: string
- requestBody:
- content:
- multipart/form-data:
- schema:
- type: object
- properties:
- file:
- description: A file to be uploaded in zip format.
- type: string
- format: binary
- filesize:
- description: The size of the zip file to be imported.
- type: integer
- importFrom:
- description: String that defines from which application the team was
- exported to be imported into Mattermost.
- type: string
- required:
- - file
- - filesize
- - importFrom
- responses:
- "200":
- description: JSON object containing a base64 encoded text file of the import logs
- in its `results` property.
- content:
- application/json:
- schema:
- type: object
- properties:
- results:
- type: string
- "400":
- $ref: "#/components/responses/BadRequest"
- "403":
- $ref: "#/components/responses/Forbidden"
"/api/v4/teams/invite/{invite_id}":
get:
tags:
diff --git a/docs/main/administration-guide/comply/embedded-json-audit-log-schema.mdx b/docs/main/administration-guide/comply/embedded-json-audit-log-schema.mdx
index a62ddd703da0..612529120c88 100644
--- a/docs/main/administration-guide/comply/embedded-json-audit-log-schema.mdx
+++ b/docs/main/administration-guide/comply/embedded-json-audit-log-schema.mdx
@@ -439,10 +439,6 @@ The following tables list the comprehensive audit event types (`event_name` valu
Deleting teams |
-importTeam |
-Importing team data |
-
-
invalidateAllEmailInvites |
Invalidating all email invitations |
@@ -1484,10 +1480,6 @@ From Mattermost v11.5.0, audit log entries for posts and content access events i
scheduleExport |
Scheduling export operations |
-
-slackImport |
-Slack data import operations |
-
diff --git a/docs/main/administration-guide/manage/command-line-tools.mdx b/docs/main/administration-guide/manage/command-line-tools.mdx
index 9511d00bd05d..1dc0b5142730 100644
--- a/docs/main/administration-guide/manage/command-line-tools.mdx
+++ b/docs/main/administration-guide/manage/command-line-tools.mdx
@@ -320,29 +320,11 @@ Import data into Mattermost.
Child Command
- [mattermost import bulk](#mattermost-import-bulk) - Import a Mattermost Bulk Import File. Deprecated in favor of [mmctl import commands](/administration-guide/manage/mmctl-command-line-tool#mmctl-import).
-- [mattermost import slack](#mattermost-import-slack) - Import a team from Slack.
### mattermost import bulk
From Mattermost v6.0, this command has been deprecated in favor of [mmctl import commands](/administration-guide/manage/mmctl-command-line-tool#mmctl-import) as the supported way to import data into Mattermost.
-### mattermost import slack
-
-See the [mmctl import commands](/administration-guide/manage/mmctl-command-line-tool#mmctl-import) documentation as the preferred way to import Slack data into Mattermost.
-
-Description
-Import a team from a Slack export zip file.
-
-Format
-. code-block:: sh
-
-mattermost import slack {team} {file}
-
-Example
-``` sh
-bin/mattermost import slack myteam slack_export.zip
-```
-
------------------------------------------------------------------------------------------------------------------------
## mattermost jobserver
diff --git a/docs/main/administration-guide/onboard/advanced-permissions-backend-infrastructure.mdx b/docs/main/administration-guide/onboard/advanced-permissions-backend-infrastructure.mdx
index b6a9f77684c1..81881a5e09bf 100644
--- a/docs/main/administration-guide/onboard/advanced-permissions-backend-infrastructure.mdx
+++ b/docs/main/administration-guide/onboard/advanced-permissions-backend-infrastructure.mdx
@@ -387,12 +387,6 @@ Permissions in Mattermost are a property of the server code base and are not cre
|
-| import_team |
-system |
-Import teams in Team Settings. |
- |
-
-
| view_team |
team |
Read the Team object. |
@@ -802,7 +796,6 @@ The following built-in roles with default permissions are available:
- sysconsole_read_user_management_permissions
- manage_public_channel_members
- sysconsole_write_about
-- import_team
- sysconsole_write_user_management_system_roles
- sysconsole_read_reporting
- upload_file
@@ -1021,7 +1014,6 @@ The following built-in roles with default permissions are available:
- manage_private_channel_members
- convert_public_channel_to_private
- add_reaction
-- import_team
- add_bookmark_public_channel
- edit_bookmark_public_channel
- delete_bookmark_public_channel
diff --git a/e2e-tests/cypress/tests/support/api/default_roles_permissions.js b/e2e-tests/cypress/tests/support/api/default_roles_permissions.js
index 33aeaa48c574..89b5acdb8b05 100644
--- a/e2e-tests/cypress/tests/support/api/default_roles_permissions.js
+++ b/e2e-tests/cypress/tests/support/api/default_roles_permissions.js
@@ -12,7 +12,7 @@ export const defaultRolesPermissions = {
playbook_member: 'playbook_private_manage_members playbook_private_manage_properties playbook_private_view playbook_public_manage_members playbook_public_manage_properties playbook_public_view run_create',
run_admin: 'run_manage_members run_manage_properties',
run_member: 'run_view',
- system_admin: 'add_bookmark_private_channel add_bookmark_public_channel add_ldap_private_cert add_ldap_public_cert add_reaction add_saml_idp_cert add_saml_private_cert add_saml_public_cert add_user_to_team assign_bot assign_system_admin_role bypass_incoming_webhook_channel_lock convert_private_channel_to_public convert_public_channel_to_private create_bot create_compliance_export_job create_custom_group create_data_retention_job create_direct_channel create_elasticsearch_post_aggregation_job create_elasticsearch_post_indexing_job create_emojis create_group_channel create_ldap_sync_job create_post create_post_ephemeral create_post_public create_private_channel create_public_channel create_team create_user_access_token delete_bookmark_private_channel delete_bookmark_public_channel delete_custom_group delete_emojis delete_others_emojis delete_others_posts delete_post delete_private_channel delete_public_channel demote_to_guest download_compliance_export_result edit_bookmark_private_channel edit_bookmark_public_channel edit_brand edit_custom_group edit_file_attachment edit_other_users edit_others_posts edit_post get_analytics get_logs get_public_link get_saml_cert_status get_saml_metadata_from_idp import_team invalidate_caches invalidate_email_invite invite_guest invite_user join_private_teams join_public_channels join_public_teams list_private_teams list_public_teams list_team_channels list_users_without_team manage_bots manage_channel_access_rules manage_channel_join_requests manage_channel_roles manage_compliance_export_job manage_custom_group_members manage_data_retention_job manage_elasticsearch_post_aggregation_job manage_elasticsearch_post_indexing_job manage_jobs manage_ldap_sync_job manage_license_information manage_oauth manage_others_agent manage_others_bots manage_others_incoming_webhooks manage_others_outgoing_webhooks manage_others_slash_commands manage_outgoing_oauth_connections manage_own_agent manage_own_incoming_webhooks manage_own_outgoing_webhooks manage_own_slash_commands manage_private_channel_auto_translation manage_private_channel_banner manage_private_channel_discoverability manage_private_channel_members manage_private_channel_properties manage_public_channel_auto_translation manage_public_channel_banner manage_public_channel_members manage_public_channel_properties manage_roles manage_secure_connections manage_shared_channels manage_system manage_system_wide_oauth manage_team manage_team_access_rules manage_team_roles order_bookmark_private_channel order_bookmark_public_channel playbook_private_create playbook_private_make_public playbook_private_manage_members playbook_private_manage_properties playbook_private_manage_roles playbook_private_view playbook_public_create playbook_public_make_private playbook_public_manage_members playbook_public_manage_properties playbook_public_manage_roles playbook_public_view promote_guest purge_elasticsearch_indexes read_audits read_bots read_channel read_channel_content read_compliance_export_job read_data_retention_job read_deleted_posts read_elasticsearch_post_aggregation_job read_elasticsearch_post_indexing_job read_jobs read_ldap_sync_job read_license_information read_other_users_teams read_others_bots read_private_channel_groups read_public_channel read_public_channel_groups read_user_access_token recycle_database_connections reload_config remove_ldap_private_cert remove_ldap_public_cert remove_others_reactions remove_reaction remove_saml_idp_cert remove_saml_private_cert remove_saml_public_cert remove_user_from_team restore_custom_group revoke_user_access_token run_create run_manage_members run_manage_properties run_view sysconsole_read_about_edition_and_license sysconsole_read_ai_recaps sysconsole_read_authentication_email sysconsole_read_authentication_guest_access sysconsole_read_authentication_ldap sysconsole_read_authentication_mfa sysconsole_read_authentication_openid sysconsole_read_authentication_password sysconsole_read_authentication_saml sysconsole_read_authentication_signup sysconsole_read_billing sysconsole_read_compliance_compliance_export sysconsole_read_compliance_compliance_monitoring sysconsole_read_compliance_custom_terms_of_service sysconsole_read_compliance_data_retention_policy sysconsole_read_environment_database sysconsole_read_environment_developer sysconsole_read_environment_elasticsearch sysconsole_read_environment_file_storage sysconsole_read_environment_high_availability sysconsole_read_environment_image_proxy sysconsole_read_environment_logging sysconsole_read_environment_mobile_security sysconsole_read_environment_performance_monitoring sysconsole_read_environment_push_notification_server sysconsole_read_environment_rate_limiting sysconsole_read_environment_session_lengths sysconsole_read_environment_smtp sysconsole_read_environment_web_server sysconsole_read_experimental_feature_flags sysconsole_read_experimental_features sysconsole_read_integrations_bot_accounts sysconsole_read_integrations_cors sysconsole_read_integrations_gif sysconsole_read_integrations_integration_management sysconsole_read_plugins sysconsole_read_products_boards sysconsole_read_reporting_server_logs sysconsole_read_reporting_site_statistics sysconsole_read_reporting_team_statistics sysconsole_read_site_announcement_banner sysconsole_read_site_customization sysconsole_read_site_emoji sysconsole_read_site_file_sharing_and_downloads sysconsole_read_site_ip_filters sysconsole_read_site_localization sysconsole_read_site_notices sysconsole_read_site_notifications sysconsole_read_site_posts sysconsole_read_site_public_links sysconsole_read_site_users_and_teams sysconsole_read_user_management_channels sysconsole_read_user_management_groups sysconsole_read_user_management_permissions sysconsole_read_user_management_system_roles sysconsole_read_user_management_teams sysconsole_read_user_management_users sysconsole_write_about_edition_and_license sysconsole_write_ai_recaps sysconsole_write_authentication_email sysconsole_write_authentication_guest_access sysconsole_write_authentication_ldap sysconsole_write_authentication_mfa sysconsole_write_authentication_openid sysconsole_write_authentication_password sysconsole_write_authentication_saml sysconsole_write_authentication_signup sysconsole_write_billing sysconsole_write_compliance_compliance_export sysconsole_write_compliance_compliance_monitoring sysconsole_write_compliance_custom_terms_of_service sysconsole_write_compliance_data_retention_policy sysconsole_write_environment_database sysconsole_write_environment_developer sysconsole_write_environment_elasticsearch sysconsole_write_environment_file_storage sysconsole_write_environment_high_availability sysconsole_write_environment_image_proxy sysconsole_write_environment_logging sysconsole_write_environment_mobile_security sysconsole_write_environment_performance_monitoring sysconsole_write_environment_push_notification_server sysconsole_write_environment_rate_limiting sysconsole_write_environment_session_lengths sysconsole_write_environment_smtp sysconsole_write_environment_web_server sysconsole_write_experimental_feature_flags sysconsole_write_experimental_features sysconsole_write_integrations_bot_accounts sysconsole_write_integrations_cors sysconsole_write_integrations_gif sysconsole_write_integrations_integration_management sysconsole_write_plugins sysconsole_write_products_boards sysconsole_write_reporting_server_logs sysconsole_write_reporting_site_statistics sysconsole_write_reporting_team_statistics sysconsole_write_site_announcement_banner sysconsole_write_site_customization sysconsole_write_site_emoji sysconsole_write_site_file_sharing_and_downloads sysconsole_write_site_ip_filters sysconsole_write_site_localization sysconsole_write_site_notices sysconsole_write_site_notifications sysconsole_write_site_posts sysconsole_write_site_public_links sysconsole_write_site_users_and_teams sysconsole_write_user_management_channels sysconsole_write_user_management_groups sysconsole_write_user_management_permissions sysconsole_write_user_management_system_roles sysconsole_write_user_management_teams sysconsole_write_user_management_users test_elasticsearch test_email test_ldap test_s3 test_site_url upload_file use_channel_mentions use_group_mentions use_slash_commands view_members view_team',
+ system_admin: 'add_bookmark_private_channel add_bookmark_public_channel add_ldap_private_cert add_ldap_public_cert add_reaction add_saml_idp_cert add_saml_private_cert add_saml_public_cert add_user_to_team assign_bot assign_system_admin_role bypass_incoming_webhook_channel_lock convert_private_channel_to_public convert_public_channel_to_private create_bot create_compliance_export_job create_custom_group create_data_retention_job create_direct_channel create_elasticsearch_post_aggregation_job create_elasticsearch_post_indexing_job create_emojis create_group_channel create_ldap_sync_job create_post create_post_ephemeral create_post_public create_private_channel create_public_channel create_team create_user_access_token delete_bookmark_private_channel delete_bookmark_public_channel delete_custom_group delete_emojis delete_others_emojis delete_others_posts delete_post delete_private_channel delete_public_channel demote_to_guest download_compliance_export_result edit_bookmark_private_channel edit_bookmark_public_channel edit_brand edit_custom_group edit_file_attachment edit_other_users edit_others_posts edit_post get_analytics get_logs get_public_link get_saml_cert_status get_saml_metadata_from_idp invalidate_caches invalidate_email_invite invite_guest invite_user join_private_teams join_public_channels join_public_teams list_private_teams list_public_teams list_team_channels list_users_without_team manage_bots manage_channel_access_rules manage_channel_join_requests manage_channel_roles manage_compliance_export_job manage_custom_group_members manage_data_retention_job manage_elasticsearch_post_aggregation_job manage_elasticsearch_post_indexing_job manage_jobs manage_ldap_sync_job manage_license_information manage_oauth manage_others_agent manage_others_bots manage_others_incoming_webhooks manage_others_outgoing_webhooks manage_others_slash_commands manage_outgoing_oauth_connections manage_own_agent manage_own_incoming_webhooks manage_own_outgoing_webhooks manage_own_slash_commands manage_private_channel_auto_translation manage_private_channel_banner manage_private_channel_discoverability manage_private_channel_members manage_private_channel_properties manage_public_channel_auto_translation manage_public_channel_banner manage_public_channel_members manage_public_channel_properties manage_roles manage_secure_connections manage_shared_channels manage_system manage_system_wide_oauth manage_team manage_team_access_rules manage_team_roles order_bookmark_private_channel order_bookmark_public_channel playbook_private_create playbook_private_make_public playbook_private_manage_members playbook_private_manage_properties playbook_private_manage_roles playbook_private_view playbook_public_create playbook_public_make_private playbook_public_manage_members playbook_public_manage_properties playbook_public_manage_roles playbook_public_view promote_guest purge_elasticsearch_indexes read_audits read_bots read_channel read_channel_content read_compliance_export_job read_data_retention_job read_deleted_posts read_elasticsearch_post_aggregation_job read_elasticsearch_post_indexing_job read_jobs read_ldap_sync_job read_license_information read_other_users_teams read_others_bots read_private_channel_groups read_public_channel read_public_channel_groups read_user_access_token recycle_database_connections reload_config remove_ldap_private_cert remove_ldap_public_cert remove_others_reactions remove_reaction remove_saml_idp_cert remove_saml_private_cert remove_saml_public_cert remove_user_from_team restore_custom_group revoke_user_access_token run_create run_manage_members run_manage_properties run_view sysconsole_read_about_edition_and_license sysconsole_read_ai_recaps sysconsole_read_authentication_email sysconsole_read_authentication_guest_access sysconsole_read_authentication_ldap sysconsole_read_authentication_mfa sysconsole_read_authentication_openid sysconsole_read_authentication_password sysconsole_read_authentication_saml sysconsole_read_authentication_signup sysconsole_read_billing sysconsole_read_compliance_compliance_export sysconsole_read_compliance_compliance_monitoring sysconsole_read_compliance_custom_terms_of_service sysconsole_read_compliance_data_retention_policy sysconsole_read_environment_database sysconsole_read_environment_developer sysconsole_read_environment_elasticsearch sysconsole_read_environment_file_storage sysconsole_read_environment_high_availability sysconsole_read_environment_image_proxy sysconsole_read_environment_logging sysconsole_read_environment_mobile_security sysconsole_read_environment_performance_monitoring sysconsole_read_environment_push_notification_server sysconsole_read_environment_rate_limiting sysconsole_read_environment_session_lengths sysconsole_read_environment_smtp sysconsole_read_environment_web_server sysconsole_read_experimental_feature_flags sysconsole_read_experimental_features sysconsole_read_integrations_bot_accounts sysconsole_read_integrations_cors sysconsole_read_integrations_gif sysconsole_read_integrations_integration_management sysconsole_read_plugins sysconsole_read_products_boards sysconsole_read_reporting_server_logs sysconsole_read_reporting_site_statistics sysconsole_read_reporting_team_statistics sysconsole_read_site_announcement_banner sysconsole_read_site_customization sysconsole_read_site_emoji sysconsole_read_site_file_sharing_and_downloads sysconsole_read_site_ip_filters sysconsole_read_site_localization sysconsole_read_site_notices sysconsole_read_site_notifications sysconsole_read_site_posts sysconsole_read_site_public_links sysconsole_read_site_users_and_teams sysconsole_read_user_management_channels sysconsole_read_user_management_groups sysconsole_read_user_management_permissions sysconsole_read_user_management_system_roles sysconsole_read_user_management_teams sysconsole_read_user_management_users sysconsole_write_about_edition_and_license sysconsole_write_ai_recaps sysconsole_write_authentication_email sysconsole_write_authentication_guest_access sysconsole_write_authentication_ldap sysconsole_write_authentication_mfa sysconsole_write_authentication_openid sysconsole_write_authentication_password sysconsole_write_authentication_saml sysconsole_write_authentication_signup sysconsole_write_billing sysconsole_write_compliance_compliance_export sysconsole_write_compliance_compliance_monitoring sysconsole_write_compliance_custom_terms_of_service sysconsole_write_compliance_data_retention_policy sysconsole_write_environment_database sysconsole_write_environment_developer sysconsole_write_environment_elasticsearch sysconsole_write_environment_file_storage sysconsole_write_environment_high_availability sysconsole_write_environment_image_proxy sysconsole_write_environment_logging sysconsole_write_environment_mobile_security sysconsole_write_environment_performance_monitoring sysconsole_write_environment_push_notification_server sysconsole_write_environment_rate_limiting sysconsole_write_environment_session_lengths sysconsole_write_environment_smtp sysconsole_write_environment_web_server sysconsole_write_experimental_feature_flags sysconsole_write_experimental_features sysconsole_write_integrations_bot_accounts sysconsole_write_integrations_cors sysconsole_write_integrations_gif sysconsole_write_integrations_integration_management sysconsole_write_plugins sysconsole_write_products_boards sysconsole_write_reporting_server_logs sysconsole_write_reporting_site_statistics sysconsole_write_reporting_team_statistics sysconsole_write_site_announcement_banner sysconsole_write_site_customization sysconsole_write_site_emoji sysconsole_write_site_file_sharing_and_downloads sysconsole_write_site_ip_filters sysconsole_write_site_localization sysconsole_write_site_notices sysconsole_write_site_notifications sysconsole_write_site_posts sysconsole_write_site_public_links sysconsole_write_site_users_and_teams sysconsole_write_user_management_channels sysconsole_write_user_management_groups sysconsole_write_user_management_permissions sysconsole_write_user_management_system_roles sysconsole_write_user_management_teams sysconsole_write_user_management_users test_elasticsearch test_email test_ldap test_s3 test_site_url upload_file use_channel_mentions use_group_mentions use_slash_commands view_members view_team',
system_custom_group_admin: 'create_custom_group delete_custom_group edit_custom_group manage_custom_group_members restore_custom_group',
system_guest: 'create_direct_channel create_group_channel',
system_manager: 'add_user_to_team convert_private_channel_to_public convert_public_channel_to_private create_elasticsearch_post_aggregation_job create_elasticsearch_post_indexing_job delete_private_channel delete_public_channel edit_brand get_analytics get_logs invalidate_caches join_private_teams join_public_teams list_private_teams list_public_teams manage_channel_roles manage_elasticsearch_post_aggregation_job manage_elasticsearch_post_indexing_job manage_outgoing_oauth_connections manage_private_channel_auto_translation manage_private_channel_members manage_private_channel_properties manage_public_channel_auto_translation manage_public_channel_members manage_public_channel_properties manage_team manage_team_roles purge_elasticsearch_indexes read_channel read_elasticsearch_post_aggregation_job read_elasticsearch_post_indexing_job read_ldap_sync_job read_license_information read_private_channel_groups read_public_channel read_public_channel_groups recycle_database_connections reload_config remove_user_from_team sysconsole_read_about_edition_and_license sysconsole_read_authentication_email sysconsole_read_authentication_guest_access sysconsole_read_authentication_ldap sysconsole_read_authentication_mfa sysconsole_read_authentication_openid sysconsole_read_authentication_password sysconsole_read_authentication_saml sysconsole_read_authentication_signup sysconsole_read_environment_database sysconsole_read_environment_developer sysconsole_read_environment_elasticsearch sysconsole_read_environment_file_storage sysconsole_read_environment_high_availability sysconsole_read_environment_image_proxy sysconsole_read_environment_logging sysconsole_read_environment_performance_monitoring sysconsole_read_environment_push_notification_server sysconsole_read_environment_rate_limiting sysconsole_read_environment_session_lengths sysconsole_read_environment_smtp sysconsole_read_environment_web_server sysconsole_read_integrations_bot_accounts sysconsole_read_integrations_cors sysconsole_read_integrations_gif sysconsole_read_integrations_integration_management sysconsole_read_plugins sysconsole_read_products_boards sysconsole_read_reporting_server_logs sysconsole_read_reporting_site_statistics sysconsole_read_reporting_team_statistics sysconsole_read_site_announcement_banner sysconsole_read_site_customization sysconsole_read_site_emoji sysconsole_read_site_file_sharing_and_downloads sysconsole_read_site_localization sysconsole_read_site_notices sysconsole_read_site_notifications sysconsole_read_site_posts sysconsole_read_site_public_links sysconsole_read_site_users_and_teams sysconsole_read_user_management_channels sysconsole_read_user_management_groups sysconsole_read_user_management_permissions sysconsole_read_user_management_teams sysconsole_write_environment_database sysconsole_write_environment_developer sysconsole_write_environment_elasticsearch sysconsole_write_environment_file_storage sysconsole_write_environment_high_availability sysconsole_write_environment_image_proxy sysconsole_write_environment_logging sysconsole_write_environment_performance_monitoring sysconsole_write_environment_push_notification_server sysconsole_write_environment_rate_limiting sysconsole_write_environment_session_lengths sysconsole_write_environment_smtp sysconsole_write_environment_web_server sysconsole_write_integrations_bot_accounts sysconsole_write_integrations_cors sysconsole_write_integrations_gif sysconsole_write_integrations_integration_management sysconsole_write_products_boards sysconsole_write_site_announcement_banner sysconsole_write_site_customization sysconsole_write_site_emoji sysconsole_write_site_file_sharing_and_downloads sysconsole_write_site_localization sysconsole_write_site_notices sysconsole_write_site_notifications sysconsole_write_site_posts sysconsole_write_site_public_links sysconsole_write_site_users_and_teams sysconsole_write_user_management_channels sysconsole_write_user_management_groups sysconsole_write_user_management_permissions sysconsole_write_user_management_teams test_elasticsearch test_email test_ldap test_s3 test_site_url view_team',
@@ -23,7 +23,7 @@ export const defaultRolesPermissions = {
system_user: 'create_custom_group create_direct_channel create_emojis create_group_channel create_team delete_custom_group delete_emojis edit_custom_group join_public_teams list_public_teams manage_custom_group_members manage_own_agent restore_custom_group view_members',
system_user_access_token: 'create_user_access_token read_user_access_token revoke_user_access_token',
system_user_manager: 'add_user_to_team convert_private_channel_to_public convert_public_channel_to_private delete_private_channel delete_public_channel join_private_teams join_public_teams list_private_teams list_public_teams manage_channel_roles manage_private_channel_auto_translation manage_private_channel_members manage_private_channel_properties manage_public_channel_auto_translation manage_public_channel_members manage_public_channel_properties manage_team manage_team_roles read_channel read_ldap_sync_job read_private_channel_groups read_public_channel read_public_channel_groups remove_user_from_team sysconsole_read_authentication_email sysconsole_read_authentication_guest_access sysconsole_read_authentication_ldap sysconsole_read_authentication_mfa sysconsole_read_authentication_openid sysconsole_read_authentication_password sysconsole_read_authentication_saml sysconsole_read_authentication_signup sysconsole_read_user_management_channels sysconsole_read_user_management_groups sysconsole_read_user_management_permissions sysconsole_read_user_management_teams sysconsole_write_user_management_channels sysconsole_write_user_management_groups sysconsole_write_user_management_teams test_ldap view_team',
- team_admin: 'add_bookmark_private_channel add_bookmark_public_channel add_reaction bypass_incoming_webhook_channel_lock convert_private_channel_to_public convert_public_channel_to_private create_post delete_bookmark_private_channel delete_bookmark_public_channel delete_others_posts delete_post edit_bookmark_private_channel edit_bookmark_public_channel import_team manage_channel_access_rules manage_channel_join_requests manage_channel_roles manage_others_incoming_webhooks manage_others_outgoing_webhooks manage_others_slash_commands manage_own_incoming_webhooks manage_own_outgoing_webhooks manage_own_slash_commands manage_private_channel_auto_translation manage_private_channel_banner manage_private_channel_discoverability manage_private_channel_members manage_public_channel_auto_translation manage_public_channel_banner manage_public_channel_members manage_team manage_team_access_rules manage_team_roles order_bookmark_private_channel order_bookmark_public_channel playbook_private_manage_roles playbook_public_manage_roles read_private_channel_groups read_public_channel_groups remove_reaction remove_user_from_team upload_file use_channel_mentions use_group_mentions',
+ team_admin: 'add_bookmark_private_channel add_bookmark_public_channel add_reaction bypass_incoming_webhook_channel_lock convert_private_channel_to_public convert_public_channel_to_private create_post delete_bookmark_private_channel delete_bookmark_public_channel delete_others_posts delete_post edit_bookmark_private_channel edit_bookmark_public_channel manage_channel_access_rules manage_channel_join_requests manage_channel_roles manage_others_incoming_webhooks manage_others_outgoing_webhooks manage_others_slash_commands manage_own_incoming_webhooks manage_own_outgoing_webhooks manage_own_slash_commands manage_private_channel_auto_translation manage_private_channel_banner manage_private_channel_discoverability manage_private_channel_members manage_public_channel_auto_translation manage_public_channel_banner manage_public_channel_members manage_team manage_team_access_rules manage_team_roles order_bookmark_private_channel order_bookmark_public_channel playbook_private_manage_roles playbook_public_manage_roles read_private_channel_groups read_public_channel_groups remove_reaction remove_user_from_team upload_file use_channel_mentions use_group_mentions',
team_guest: 'view_team',
team_post_all: 'create_post upload_file use_channel_mentions use_group_mentions',
team_post_all_public: 'create_post_public use_channel_mentions use_group_mentions',
diff --git a/server/.golangci.yml b/server/.golangci.yml
index 0476dcc59ce1..3f5521be8cc1 100644
--- a/server/.golangci.yml
+++ b/server/.golangci.yml
@@ -228,7 +228,6 @@ linters:
platform/services/sharedchannel/permalink_test.go|\
platform/services/sharedchannel/sync_recv.go|\
platform/services/sharedchannel/sync_send.go|\
- platform/services/slackimport/slackimport.go|\
platform/services/telemetry/telemetry.go|\
platform/services/telemetry/telemetry_test.go|\
platform/services/upgrader/upgrader_linux.go|\
diff --git a/server/channels/api4/team.go b/server/channels/api4/team.go
index 31cb486da8e2..1ee579bd07d2 100644
--- a/server/channels/api4/team.go
+++ b/server/channels/api4/team.go
@@ -4,8 +4,6 @@
package api4
import (
- "bytes"
- "encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -19,9 +17,8 @@ import (
)
const (
- MaxAddMembersBatch = 256
- MaximumBulkImportSize = 10 * 1024 * 1024
- groupIDsParamPattern = "[^a-zA-Z0-9,]*"
+ MaxAddMembersBatch = 256
+ groupIDsParamPattern = "[^a-zA-Z0-9,]*"
)
var groupIDsQueryParamRegex *regexp.Regexp
@@ -68,7 +65,6 @@ func (api *API) InitTeam() {
api.BaseRoutes.TeamByName.Handle("/exists", api.APISessionRequired(teamExists)).Methods(http.MethodGet)
api.BaseRoutes.TeamMember.Handle("/roles", api.APISessionRequired(updateTeamMemberRoles)).Methods(http.MethodPut)
api.BaseRoutes.TeamMember.Handle("/schemeRoles", api.APISessionRequired(updateTeamMemberSchemeRoles)).Methods(http.MethodPut)
- api.BaseRoutes.Team.Handle("/import", api.APISessionRequired(importTeam)).Methods(http.MethodPost)
api.BaseRoutes.Team.Handle("/invite/email", api.APISessionRequired(inviteUsersToTeam)).Methods(http.MethodPost)
api.BaseRoutes.Team.Handle("/invite-guests/email", api.APISessionRequired(inviteGuestsToChannels)).Methods(http.MethodPost)
api.BaseRoutes.Teams.Handle("/invites/email", api.APISessionRequired(invalidateAllEmailInvites)).Methods(http.MethodDelete)
@@ -1657,97 +1653,6 @@ func teamExists(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
- if c.App.Channels().License().IsCloud() {
- c.Err = model.NewAppError("importTeam", "api.restricted_system_admin", nil, "", http.StatusForbidden)
- return
- }
-
- c.RequireTeamId()
- if c.Err != nil {
- return
- }
-
- if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionImportTeam) {
- c.SetPermissionError(model.PermissionImportTeam)
- return
- }
-
- if err := r.ParseMultipartForm(MaximumBulkImportSize); err != nil {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.parse.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
- return
- }
-
- importFromArray, ok := r.MultipartForm.Value["importFrom"]
- if !ok || len(importFromArray) < 1 {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.no_import_from.app_error", nil, "", http.StatusBadRequest)
- return
- }
- importFrom := importFromArray[0]
-
- fileSizeStr, ok := r.MultipartForm.Value["filesize"]
- if !ok || len(fileSizeStr) < 1 {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.unavailable.app_error", nil, "", http.StatusBadRequest)
- return
- }
-
- fileSize, err := strconv.ParseInt(fileSizeStr[0], 10, 64)
- if err != nil {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.integer.app_error", nil, "", http.StatusBadRequest)
- return
- }
-
- fileInfoArray, ok := r.MultipartForm.File["file"]
- if !ok {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.no_file.app_error", nil, "", http.StatusBadRequest)
- return
- }
-
- if len(fileInfoArray) <= 0 {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.array.app_error", nil, "", http.StatusBadRequest)
- return
- }
-
- auditRec := c.MakeAuditRecord(model.AuditEventImportTeam, model.AuditStatusFail)
- defer c.LogAuditRec(auditRec)
- model.AddEventParameterToAuditRec(auditRec, "team_id", c.Params.TeamId)
-
- fileInfo := fileInfoArray[0]
-
- fileData, err := fileInfo.Open()
- if err != nil {
- c.Err = model.NewAppError("importTeam", "api.team.import_team.open.app_error", nil, "", http.StatusBadRequest).Wrap(err)
- return
- }
- defer fileData.Close()
- model.AddEventParameterToAuditRec(auditRec, "filename", fileInfo.Filename)
- model.AddEventParameterToAuditRec(auditRec, "filesize", fileSize)
- model.AddEventParameterToAuditRec(auditRec, "from", importFrom)
-
- var log *bytes.Buffer
- data := map[string]string{}
- switch importFrom {
- case "slack":
- var err *model.AppError
- if err, log = c.App.SlackImport(c.AppContext, fileData, fileSize, c.Params.TeamId); err != nil {
- c.Err = err
- c.Err.StatusCode = http.StatusBadRequest
- }
- data["results"] = base64.StdEncoding.EncodeToString(log.Bytes())
- default:
- c.Err = model.NewAppError("importTeam", "api.team.import_team.unknown_import_from.app_error", nil, "", http.StatusBadRequest)
- }
-
- if c.Err != nil {
- w.WriteHeader(c.Err.StatusCode)
- return
- }
- auditRec.Success()
- if _, err := w.Write([]byte(model.MapToJSON(data))); err != nil {
- c.Logger.Warn("Error while writing response", mlog.Err(err))
- }
-}
-
func inviteUsersToTeam(c *Context, w http.ResponseWriter, r *http.Request) {
graceful := r.URL.Query().Get("graceful") != ""
diff --git a/server/channels/api4/team_test.go b/server/channels/api4/team_test.go
index 0a799c815ad4..735b4b86d31e 100644
--- a/server/channels/api4/team_test.go
+++ b/server/channels/api4/team_test.go
@@ -5,8 +5,6 @@ package api4
import (
"context"
- "encoding/base64"
- "encoding/binary"
"encoding/json"
"fmt"
"net/http"
@@ -4131,97 +4129,6 @@ func TestTeamExists(t *testing.T) {
})
}
-func TestImportTeam(t *testing.T) {
- mainHelper.Parallel(t)
- th := Setup(t).InitBasic(t)
-
- th.TestForAllClients(t, func(T *testing.T, c *model.Client4) {
- data, err := testutils.ReadTestFile("Fake_Team_Import.zip")
-
- require.False(t, err != nil && len(data) == 0, "Error while reading the test file.")
- _, resp, err := th.SystemAdminClient.ImportTeam(context.Background(), data, binary.Size(data), "XYZ", "Fake_Team_Import.zip", th.BasicTeam.Id)
- require.Error(t, err)
- CheckBadRequestStatus(t, resp)
-
- _, resp, err = th.SystemAdminClient.ImportTeam(context.Background(), data, binary.Size(data), "", "Fake_Team_Import.zip", th.BasicTeam.Id)
- require.Error(t, err)
- CheckBadRequestStatus(t, resp)
- }, "Import from unknown and source")
-
- t.Run("ImportTeam", func(t *testing.T) {
- var data []byte
- var err error
- data, err = testutils.ReadTestFile("Fake_Team_Import.zip")
-
- require.False(t, err != nil && len(data) == 0, "Error while reading the test file.")
-
- // Import the channels/users/posts
- fileResp, _, err := th.SystemAdminClient.ImportTeam(context.Background(), data, binary.Size(data), "slack", "Fake_Team_Import.zip", th.BasicTeam.Id)
- require.NoError(t, err)
-
- fileData, err := base64.StdEncoding.DecodeString(fileResp["results"])
- require.NoError(t, err, "failed to decode base64 results data")
-
- fileReturned := string(fileData)
- require.Truef(t, strings.Contains(fileReturned, "darth.vader@stardeath.com"), "failed to report the user was imported, fileReturned: %s", fileReturned)
-
- // Checking the imported users
- importedUser, _, err := th.SystemAdminClient.GetUserByUsername(context.Background(), "bot_test", "")
- require.NoError(t, err)
- require.Equal(t, importedUser.Username, "bot_test", "username should match with the imported user")
-
- importedUser, _, err = th.SystemAdminClient.GetUserByUsername(context.Background(), "lordvader", "")
- require.NoError(t, err)
- require.Equal(t, importedUser.Username, "lordvader", "username should match with the imported user")
-
- // Checking the imported Channels
- importedChannel, _, err := th.SystemAdminClient.GetChannelByName(context.Background(), "testchannel", th.BasicTeam.Id, "")
- require.NoError(t, err)
- require.Equal(t, importedChannel.Name, "testchannel", "names did not match expected: testchannel")
-
- importedChannel, _, err = th.SystemAdminClient.GetChannelByName(context.Background(), "general", th.BasicTeam.Id, "")
- require.NoError(t, err)
- require.Equal(t, importedChannel.Name, "general", "names did not match expected: general")
-
- posts, _, err := th.SystemAdminClient.GetPostsForChannel(context.Background(), importedChannel.Id, 0, 60, "", false, false)
- require.NoError(t, err)
- require.Equal(t, posts.Posts[posts.Order[3]].Message, "This is a test post to test the import process", "missing posts in the import process")
- })
-
- t.Run("Cloud Forbidden", func(t *testing.T) {
- var data []byte
- var err error
- data, err = testutils.ReadTestFile("Fake_Team_Import.zip")
-
- require.False(t, err != nil && len(data) == 0, "Error while reading the test file.")
- th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
-
- // Import the channels/users/posts
- _, resp, err := th.SystemAdminClient.ImportTeam(context.Background(), data, binary.Size(data), "slack", "Fake_Team_Import.zip", th.BasicTeam.Id)
- require.Error(t, err)
- CheckForbiddenStatus(t, resp)
- th.App.Srv().SetLicense(nil)
- })
-
- t.Run("MissingFile", func(t *testing.T) {
- _, resp, err := th.SystemAdminClient.ImportTeam(context.Background(), nil, 4343, "slack", "Fake_Team_Import.zip", th.BasicTeam.Id)
- require.Error(t, err)
- CheckBadRequestStatus(t, resp)
- })
-
- t.Run("WrongPermission", func(t *testing.T) {
- var data []byte
- var err error
- data, err = testutils.ReadTestFile("Fake_Team_Import.zip")
- require.False(t, err != nil && len(data) == 0, "Error while reading the test file.")
-
- // Import the channels/users/posts
- _, resp, err := th.Client.ImportTeam(context.Background(), data, binary.Size(data), "slack", "Fake_Team_Import.zip", th.BasicTeam.Id)
- require.Error(t, err)
- CheckForbiddenStatus(t, resp)
- })
-}
-
func TestValidateUserPermissionsOnChannels(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
diff --git a/server/channels/app/app_test.go b/server/channels/app/app_test.go
index 03792e4acf92..a504abdbcb43 100644
--- a/server/channels/app/app_test.go
+++ b/server/channels/app/app_test.go
@@ -175,7 +175,6 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
"team_admin": {
model.PermissionRemoveUserFromTeam.Id,
model.PermissionManageTeam.Id,
- model.PermissionImportTeam.Id,
model.PermissionManageTeamRoles.Id,
model.PermissionManageTeamAccessRules.Id,
model.PermissionManageChannelRoles.Id,
diff --git a/server/channels/app/permissions_migrations.go b/server/channels/app/permissions_migrations.go
index 620129dccd9d..c8ef2f62f8f3 100644
--- a/server/channels/app/permissions_migrations.go
+++ b/server/channels/app/permissions_migrations.go
@@ -1187,6 +1187,15 @@ func (a *App) removeGetAnalyticsPermissionMigration() (permissionsMap, error) {
return transformations, nil
}
+func (a *App) removeImportTeamPermissionMigration() (permissionsMap, error) {
+ return permissionsMap{
+ permissionTransformation{
+ On: permissionExists("import_team"),
+ Remove: []string{"import_team"},
+ },
+ }, nil
+}
+
func (a *App) addSysConsoleMobileSecurityPermission() (permissionsMap, error) {
transformations := []permissionTransformation{}
@@ -1408,6 +1417,7 @@ func (s *Server) doPermissionsMigrations() error {
{Key: model.MigrationKeyAddManageAgentPermissions, Migration: a.getAddManageAgentPermissionsMigration},
{Key: model.MigrationKeyAddEditFileAttachmentPermission, Migration: a.getAddEditFileAttachmentPermissionMigration},
{Key: model.MigrationKeyAddDiscoverableChannelPermissions, Migration: a.getAddDiscoverableChannelPermissionsMigration},
+ {Key: model.MigrationRemoveImportTeamPermission, Migration: a.removeImportTeamPermissionMigration},
}
roles, err := s.Store().Role().GetAll()
diff --git a/server/channels/app/slack.go b/server/channels/app/slack.go
index 0f93f4396ff1..aeeae285ede8 100644
--- a/server/channels/app/slack.go
+++ b/server/channels/app/slack.go
@@ -4,79 +4,15 @@
package app
import (
- "bytes"
"fmt"
- "image"
- "mime/multipart"
"regexp"
"strings"
- "time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
- "github.com/mattermost/mattermost/server/v8/platform/services/slackimport"
)
-func (a *App) SlackImport(rctx request.CTX, fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer) {
- actions := slackimport.Actions{
- UpdateActive: func(user *model.User, active bool) (*model.User, *model.AppError) {
- return a.UpdateActive(rctx, user, active)
- },
- AddUserToChannel: a.AddUserToChannel,
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- return a.JoinUserToTeam(rctx, team, user, userRequestorId)
- },
- CreateDirectChannel: a.createDirectChannel,
- CreateGroupChannel: a.createGroupChannel,
- CreateChannel: func(channel *model.Channel, addMember bool) (*model.Channel, *model.AppError) {
- return a.CreateChannel(rctx, channel, addMember)
- },
- DoUploadFile: func(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) {
- return a.DoUploadFile(rctx, now, rawTeamId, rawChannelId, rawUserId, rawFilename, data, true)
- },
- GenerateThumbnailImage: a.generateThumbnailImage,
- GeneratePreviewImage: a.generatePreviewImage,
- InvalidateAllCaches: func() *model.AppError { return a.ch.srv.platform.InvalidateAllCaches() },
- MaxPostSize: func() int { return a.ch.srv.platform.MaxPostSize() },
- SendPasswordReset: func(email string) (bool, *model.AppError) {
- sent, err := a.SendPasswordReset(rctx, email, a.GetSiteURL())
- if err != nil {
- return false, err
- }
-
- return sent, nil
- },
- PrepareImage: func(fileData []byte) (image.Image, string, func(), error) {
- img, imgType, release, err := prepareImage(rctx, a.ch.imgDecoder, bytes.NewReader(fileData))
- if err != nil {
- return nil, "", nil, err
- }
- return img, imgType, release, err
- },
- }
-
- // Determine if this is an Admin import:
- // mattermost cmd imports (no session) are treated as admin imports since only server admins can run them
- // Web imports (include mmctl calls) check the actual user's role
- isAdminImport := false
-
- if rctx.Session() == nil {
- // no session means it's being run directly on the server and only
- // server admins can run CLI commands, so treat as admin import
- isAdminImport = true
- rctx.Logger().Info("Slack import initiated via CLI, treating as admin import")
- } else if rctx.Session().UserId != "" {
- // Web API + mmctl import - check if the user is a system admin
- if user, err := a.GetUser(rctx.Session().UserId); err == nil {
- isAdminImport = user.IsSystemAdmin()
- }
- }
-
- importer := slackimport.NewWithAdminFlag(a.Srv().Store(), actions, a.Config(), isAdminImport)
- return importer.SlackImport(rctx, fileData, fileSize, teamID)
-}
-
func (a *App) ProcessSlackText(rctx request.CTX, text string) string {
text = expandAnnouncement(text)
text = replaceUserIds(rctx, a.Srv().Store().User(), text)
diff --git a/server/channels/testlib/store.go b/server/channels/testlib/store.go
index 82a910d2d7ad..aa28de161c6a 100644
--- a/server/channels/testlib/store.go
+++ b/server/channels/testlib/store.go
@@ -105,6 +105,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
systemStore.On("GetByName", model.MigrationKeyAddManageAgentPermissions).Return(&model.System{Name: model.MigrationKeyAddManageAgentPermissions, Value: "true"}, nil)
systemStore.On("GetByName", model.MigrationKeyAddEditFileAttachmentPermission).Return(&model.System{Name: model.MigrationKeyAddEditFileAttachmentPermission, Value: "true"}, nil)
systemStore.On("GetByName", model.MigrationKeyAddDiscoverableChannelPermissions).Return(&model.System{Name: model.MigrationKeyAddDiscoverableChannelPermissions, Value: "true"}, nil)
+ systemStore.On("GetByName", model.MigrationRemoveImportTeamPermission).Return(&model.System{Name: model.MigrationRemoveImportTeamPermission, Value: "true"}, nil)
systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once()
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
diff --git a/server/cmd/mattermost/commands/import.go b/server/cmd/mattermost/commands/import.go
index 85448c62e817..99aee90eabf1 100644
--- a/server/cmd/mattermost/commands/import.go
+++ b/server/cmd/mattermost/commands/import.go
@@ -12,7 +12,6 @@ import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
- "github.com/mattermost/mattermost/server/v8/channels/app"
)
var ImportCmd = &cobra.Command{
@@ -20,14 +19,6 @@ var ImportCmd = &cobra.Command{
Short: "Import data.",
}
-var SlackImportCmd = &cobra.Command{
- Use: "slack [team] [file]",
- Short: "Import a team from Slack.",
- Long: "Import a team from a Slack export zip file.",
- Example: " import slack myteam slack_export.zip",
- RunE: slackImportCmdF,
-}
-
var BulkImportCmd = &cobra.Command{
Use: "bulk [file]",
Short: "Import bulk data.",
@@ -44,63 +35,10 @@ func init() {
ImportCmd.AddCommand(
BulkImportCmd,
- SlackImportCmd,
)
RootCmd.AddCommand(ImportCmd)
}
-func slackImportCmdF(command *cobra.Command, args []string) error {
- a, err := InitDBCommandContextCobra(command)
- if err != nil {
- return err
- }
- defer a.Srv().Shutdown()
-
- rctx := request.EmptyContext(a.Log())
-
- if len(args) != 2 {
- return errors.New("Incorrect number of arguments.")
- }
-
- team := getTeamFromTeamArg(a, args[0])
- if team == nil {
- return errors.New("Unable to find team '" + args[0] + "'")
- }
-
- fileReader, err := os.Open(args[1])
- if err != nil {
- return err
- }
- defer fileReader.Close()
-
- fileInfo, err := fileReader.Stat()
- if err != nil {
- return err
- }
-
- CommandPrettyPrintln("Running Slack Import. This may take a long time for large teams or teams with many messages.")
-
- importErr, log := a.SlackImport(rctx, fileReader, fileInfo.Size(), team.Id)
-
- if importErr != nil {
- return err
- }
-
- CommandPrettyPrintln("")
- CommandPrintln(log.String())
- CommandPrettyPrintln("")
-
- CommandPrettyPrintln("Finished Slack Import.")
- CommandPrettyPrintln("")
-
- auditRec := a.MakeAuditRecord(rctx, model.AuditEventSlackImport, model.AuditStatusSuccess)
- auditRec.AddMeta("team", team)
- auditRec.AddMeta("file", args[1])
- a.LogAuditRec(rctx, auditRec, nil)
-
- return nil
-}
-
func bulkImportCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
@@ -174,16 +112,3 @@ func bulkImportCmdF(command *cobra.Command, args []string) error {
return nil
}
-
-func getTeamFromTeamArg(a *app.App, teamArg string) *model.Team {
- var team *model.Team
- team, err := a.Srv().Store().Team().GetByName(teamArg)
-
- if err != nil {
- var t *model.Team
- if t, err = a.Srv().Store().Team().Get(teamArg); err == nil {
- team = t
- }
- }
- return team
-}
diff --git a/server/i18n/en.json b/server/i18n/en.json
index 9b6bfc1ab1c4..60e09e381242 100644
--- a/server/i18n/en.json
+++ b/server/i18n/en.json
@@ -3654,98 +3654,6 @@
"id": "api.shared_channel.uninvite_remote_to_channel_error",
"translation": "Could not uninvite remote to channel"
},
- {
- "id": "api.slackimport.slack_add_bot_user.email",
- "translation": "The Integration/Slack Bot user with email {{.Email}} has been imported.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_bot_user.unable_import",
- "translation": "Unable to import the Integration/Slack Bot user {{.Username}}.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_channels.added",
- "translation": "\r\nChannels added:\r\n"
- },
- {
- "id": "api.slackimport.slack_add_channels.failed_to_add_user",
- "translation": "Unable to add Slack user {{.Username}} to channel.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_channels.import_failed",
- "translation": "Unable to import Slack channel {{.DisplayName}}.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_channels.merge",
- "translation": "The Slack channel {{.DisplayName}} already exists as an active Mattermost channel. Both channels have been merged.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.created",
- "translation": "\r\nUsers created:\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.email",
- "translation": "Slack user with email {{.Email}} has been imported.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.merge_existing",
- "translation": "Slack user merged with an existing Mattermost user with matching email {{.Email}} and username {{.Username}}.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.merge_existing_failed",
- "translation": "Slack user merged with an existing Mattermost user with matching email {{.Email}} and username {{.Username}}, but was unable to add the user to their team.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.merge_existing_skipped_non_admin",
- "translation": "Slack user with email {{.Email}} matches existing Mattermost user {{.Username}}. Merging into an existing Mattermost account is only available for imports run by a system admin, so this user's posts and channel memberships will not be imported. Re-run the import as a system admin to merge them.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.missing_email_address",
- "translation": "User {{.Username}} does not have an email address in the Slack export. Used {{.Email}} as a placeholder. The user should update their email address once logged in to the system.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.send_reset_email_failed",
- "translation": "Unable to send password reset email to {{.Username}} at {{.Email}}.\r\n"
- },
- {
- "id": "api.slackimport.slack_add_users.unable_import",
- "translation": "Unable to import Slack user: {{.Username}}.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.log",
- "translation": "Mattermost Slack Import Log\r\n"
- },
- {
- "id": "api.slackimport.slack_import.note1",
- "translation": "- Some messages may not have been imported because they were not supported by this importer.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.note2",
- "translation": "- Slack bot messages are currently not supported.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.note3",
- "translation": "- Additional errors may be found in the server logs.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.notes",
- "translation": "\r\nNotes:\r\n"
- },
- {
- "id": "api.slackimport.slack_import.open.app_error",
- "translation": "Unable to open the file: {{.Filename}}.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.team_fail",
- "translation": "Unable to get the team to import into.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.zip.app_error",
- "translation": "Unable to open the Slack export zip file.\r\n"
- },
- {
- "id": "api.slackimport.slack_import.zip.file_too_large",
- "translation": "{{.Filename}} in zip archive too large to process for Slack import\r\n"
- },
{
"id": "api.status.user_not_found.app_error",
"translation": "User not found."
@@ -3850,38 +3758,6 @@
"id": "api.team.get_team_icon.read_file.app_error",
"translation": "Unable to read the team icon file."
},
- {
- "id": "api.team.import_team.array.app_error",
- "translation": "Empty array under 'file' in request."
- },
- {
- "id": "api.team.import_team.integer.app_error",
- "translation": "Filesize not an integer."
- },
- {
- "id": "api.team.import_team.no_file.app_error",
- "translation": "No file under 'file' in request."
- },
- {
- "id": "api.team.import_team.no_import_from.app_error",
- "translation": "Malformed request: importFrom field is not present."
- },
- {
- "id": "api.team.import_team.open.app_error",
- "translation": "Could not open file."
- },
- {
- "id": "api.team.import_team.parse.app_error",
- "translation": "Could not parse multipart form."
- },
- {
- "id": "api.team.import_team.unavailable.app_error",
- "translation": "Malformed request: filesize field is not present."
- },
- {
- "id": "api.team.import_team.unknown_import_from.app_error",
- "translation": "Unknown import source."
- },
{
"id": "api.team.invalidate_all_email_invites.app_error",
"translation": "Error invalidating email invites."
diff --git a/server/platform/services/slackimport/converters.go b/server/platform/services/slackimport/converters.go
deleted file mode 100644
index d6e820ab67a7..000000000000
--- a/server/platform/services/slackimport/converters.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-package slackimport
-
-import (
- "regexp"
- "strconv"
- "strings"
-
- "github.com/mattermost/mattermost/server/public/shared/mlog"
-)
-
-func slackConvertTimeStamp(ts string) int64 {
- timeString := strings.SplitN(ts, ".", 2)[0]
-
- timeStamp, err := strconv.ParseInt(timeString, 10, 64)
- if err != nil {
- mlog.Warn("Slack Import: Bad timestamp detected.")
- return 1
- }
- return timeStamp * 1000 // Convert to milliseconds
-}
-
-func slackConvertChannelName(channelName string, channelId string) string {
- newName := strings.Trim(channelName, "_-")
- if len(newName) == 1 {
- return "slack-channel-" + newName
- }
-
- if isValidChannelNameCharacters(newName) {
- return newName
- }
- return strings.ToLower(channelId)
-}
-
-func slackConvertUserMentions(users []slackUser, posts map[string][]slackPost) map[string][]slackPost {
- var regexes = make(map[string]*regexp.Regexp, len(users))
- for _, user := range users {
- r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>")
- if err != nil {
- mlog.Warn("Slack Import: Unable to compile the @mention, matching regular expression for the Slack user.", mlog.String("user_name", user.Username), mlog.String("user_id", user.Id))
- continue
- }
- regexes["@"+user.Username] = r
- }
-
- // Special cases.
- regexes["@here"], _ = regexp.Compile(``)
- regexes["@channel"], _ = regexp.Compile("")
- regexes["@all"], _ = regexp.Compile("")
-
- for channelName, channelPosts := range posts {
- for postIdx, post := range channelPosts {
- for mention, r := range regexes {
- post.Text = r.ReplaceAllString(post.Text, mention)
- posts[channelName][postIdx] = post
- }
- }
- }
-
- return posts
-}
-
-func slackConvertChannelMentions(channels []slackChannel, posts map[string][]slackPost) map[string][]slackPost {
- var regexes = make(map[string]*regexp.Regexp, len(channels))
- for _, channel := range channels {
- r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>")
- if err != nil {
- mlog.Warn("Slack Import: Unable to compile the !channel, matching regular expression for the Slack channel.", mlog.String("channel_id", channel.Id), mlog.String("channel_name", channel.Name))
- continue
- }
- regexes["~"+channel.Name] = r
- }
-
- for channelName, channelPosts := range posts {
- for postIdx, post := range channelPosts {
- for channelReplace, r := range regexes {
- post.Text = r.ReplaceAllString(post.Text, channelReplace)
- posts[channelName][postIdx] = post
- }
- }
- }
-
- return posts
-}
-
-func slackConvertPostsMarkup(posts map[string][]slackPost) map[string][]slackPost {
- regexReplaceAllString := []struct {
- regex *regexp.Regexp
- rpl string
- }{
- // URL
- {
- regexp.MustCompile(`<([^|<>]+)\|([^|<>]+)>`),
- "[$2]($1)",
- },
- // bold
- {
- regexp.MustCompile(`(^|[\s.;,])\*(\S[^*\n]+)\*`),
- "$1**$2**",
- },
- // strikethrough
- {
- regexp.MustCompile(`(^|[\s.;,])\~(\S[^~\n]+)\~`),
- "$1~~$2~~",
- },
- // single paragraph blockquote
- // Slack converts > character to >
- {
- regexp.MustCompile(`(?sm)^>`),
- ">",
- },
- }
-
- regexReplaceAllStringFunc := []struct {
- regex *regexp.Regexp
- fn func(string) string
- }{
- // multiple paragraphs blockquotes
- {
- regexp.MustCompile(`(?sm)^>>>(.+)$`),
- func(src string) string {
- // remove >>> prefix, might have leading \n
- prefixRegexp := regexp.MustCompile(`^([\n])?>>>(.*)`)
- src = prefixRegexp.ReplaceAllString(src, "$1$2")
- // append > to start of line
- appendRegexp := regexp.MustCompile(`(?m)^`)
- return appendRegexp.ReplaceAllString(src, ">$0")
- },
- },
- }
-
- for channelName, channelPosts := range posts {
- for postIdx, post := range channelPosts {
- result := post.Text
-
- for _, rule := range regexReplaceAllString {
- result = rule.regex.ReplaceAllString(result, rule.rpl)
- }
-
- for _, rule := range regexReplaceAllStringFunc {
- result = rule.regex.ReplaceAllStringFunc(result, rule.fn)
- }
- posts[channelName][postIdx].Text = result
- }
- }
-
- return posts
-}
diff --git a/server/platform/services/slackimport/main_test.go b/server/platform/services/slackimport/main_test.go
deleted file mode 100644
index ad8ff61f405b..000000000000
--- a/server/platform/services/slackimport/main_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-package slackimport
-
-import (
- "fmt"
- "os"
- "testing"
-)
-
-func TestMain(m *testing.M) {
- prevDir, err := os.Getwd()
- if err != nil {
- panic("Failed to get current working directory: " + err.Error())
- }
-
- err = os.Chdir("../..")
- if err != nil {
- panic(fmt.Sprintf("Failed to set current working directory to %s: %s", "../..", err.Error()))
- }
-
- defer func() {
- err := os.Chdir(prevDir)
- if err != nil {
- panic(fmt.Sprintf("Failed to restore current working directory to %s: %s", prevDir, err.Error()))
- }
- }()
-
- os.Exit(m.Run())
-}
diff --git a/server/platform/services/slackimport/parsers.go b/server/platform/services/slackimport/parsers.go
deleted file mode 100644
index eeab1219fdb8..000000000000
--- a/server/platform/services/slackimport/parsers.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-package slackimport
-
-import (
- "encoding/json"
- "io"
-
- "github.com/mattermost/mattermost/server/public/model"
- "github.com/mattermost/mattermost/server/public/shared/mlog"
-)
-
-func slackParseChannels(data io.Reader, channelType model.ChannelType) ([]slackChannel, error) {
- decoder := json.NewDecoder(data)
-
- var channels []slackChannel
- if err := decoder.Decode(&channels); err != nil {
- mlog.Warn("Slack Import: Error occurred when parsing some Slack channels. Import may work anyway.", mlog.Err(err))
- return channels, err
- }
-
- for i := range channels {
- channels[i].Type = channelType
- }
-
- return channels, nil
-}
-
-func slackParseUsers(data io.Reader) ([]slackUser, error) {
- decoder := json.NewDecoder(data)
-
- var users []slackUser
- err := decoder.Decode(&users)
- // This actually returns errors that are ignored.
- // In this case it is erroring because of a null that Slack
- // introduced. So we just return the users here.
- return users, err
-}
-
-func slackParsePosts(data io.Reader) ([]slackPost, error) {
- decoder := json.NewDecoder(data)
-
- var posts []slackPost
- if err := decoder.Decode(&posts); err != nil {
- mlog.Warn("Slack Import: Error occurred when parsing some Slack posts. Import may work anyway.", mlog.Err(err))
- return posts, err
- }
- return posts, nil
-}
diff --git a/server/platform/services/slackimport/slackimport.go b/server/platform/services/slackimport/slackimport.go
deleted file mode 100644
index 1a5fad551e38..000000000000
--- a/server/platform/services/slackimport/slackimport.go
+++ /dev/null
@@ -1,873 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-package slackimport
-
-import (
- "archive/zip"
- "bytes"
- "errors"
- "image"
- "io"
- "mime/multipart"
- "net/http"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
- "time"
- "unicode/utf8"
-
- "github.com/mattermost/mattermost/server/public/model"
- "github.com/mattermost/mattermost/server/public/shared/i18n"
- "github.com/mattermost/mattermost/server/public/shared/mlog"
- "github.com/mattermost/mattermost/server/public/shared/request"
- "github.com/mattermost/mattermost/server/v8/channels/store"
- "github.com/mattermost/mattermost/server/v8/channels/utils"
-)
-
-type slackChannel struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Creator string `json:"creator"`
- Members []string `json:"members"`
- Purpose slackChannelSub `json:"purpose"`
- Topic slackChannelSub `json:"topic"`
- Type model.ChannelType
-}
-
-type slackChannelSub struct {
- Value string `json:"value"`
-}
-
-type slackProfile struct {
- FirstName string `json:"first_name"`
- LastName string `json:"last_name"`
- Email string `json:"email"`
-}
-
-type slackUser struct {
- Id string `json:"id"`
- Username string `json:"name"`
- Profile slackProfile `json:"profile"`
-}
-
-type slackFile struct {
- Id string `json:"id"`
- Title string `json:"title"`
-}
-
-type slackPost struct {
- User string `json:"user"`
- BotId string `json:"bot_id"`
- BotUsername string `json:"username"`
- Text string `json:"text"`
- TimeStamp string `json:"ts"`
- ThreadTS string `json:"thread_ts"`
- Type string `json:"type"`
- SubType string `json:"subtype"`
- Comment *slackComment `json:"comment"`
- Upload bool `json:"upload"`
- File *slackFile `json:"file"`
- Files []*slackFile `json:"files"`
- Attachments []*model.MessageAttachment `json:"attachments"`
-}
-
-var isValidChannelNameCharacters = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`).MatchString
-
-const slackImportMaxFileSize = 1024 * 1024 * 70
-
-type slackComment struct {
- User string `json:"user"`
- Comment string `json:"comment"`
-}
-
-// Actions provides the actions that needs to be used for import slack data
-type Actions struct {
- UpdateActive func(*model.User, bool) (*model.User, *model.AppError)
- AddUserToChannel func(request.CTX, *model.User, *model.Channel, bool) (*model.ChannelMember, *model.AppError)
- JoinUserToTeam func(*model.Team, *model.User, string) (*model.TeamMember, *model.AppError)
- CreateDirectChannel func(request.CTX, string, string, ...model.ChannelOption) (*model.Channel, *model.AppError)
- CreateGroupChannel func(request.CTX, []string, string, ...model.ChannelOption) (*model.Channel, *model.AppError)
- CreateChannel func(*model.Channel, bool) (*model.Channel, *model.AppError)
- DoUploadFile func(time.Time, string, string, string, string, []byte) (*model.FileInfo, *model.AppError)
- GenerateThumbnailImage func(request.CTX, image.Image, string, string)
- GeneratePreviewImage func(request.CTX, image.Image, string, string)
- InvalidateAllCaches func() *model.AppError
- MaxPostSize func() int
- SendPasswordReset func(string) (bool, *model.AppError)
- PrepareImage func(fileData []byte) (image.Image, string, func(), error)
-}
-
-// SlackImporter is a service that allows to import slack dumps into mattermost
-type SlackImporter struct {
- store store.Store
- actions Actions
- config *model.Config
- isAdminImport bool
-}
-
-// New creates a new SlackImporter service instance. It receive a store, a set of actions and the current config.
-// It is expected to be used right away and discarded after that
-func New(store store.Store, actions Actions, config *model.Config) *SlackImporter {
- return &SlackImporter{
- store: store,
- actions: actions,
- config: config,
- }
-}
-
-// NewWithAdminFlag creates a new SlackImporter service instance with information about whether this is an admin import.
-// This allows for enhanced security controls based on the importing user's role.
-func NewWithAdminFlag(store store.Store, actions Actions, config *model.Config, isAdminImport bool) *SlackImporter {
- return &SlackImporter{
- store: store,
- actions: actions,
- config: config,
- isAdminImport: isAdminImport,
- }
-}
-
-func (si *SlackImporter) SlackImport(rctx request.CTX, fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer) {
- // Create log file
- log := bytes.NewBufferString(i18n.T("api.slackimport.slack_import.log"))
-
- zipreader, err := zip.NewReader(fileData, fileSize)
- if err != nil || zipreader.File == nil {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.app_error"))
- return model.NewAppError("SlackImport", "api.slackimport.slack_import.zip.app_error", nil, "", http.StatusBadRequest).Wrap(err), log
- }
-
- var channels []slackChannel
- var publicChannels []slackChannel
- var privateChannels []slackChannel
- var groupChannels []slackChannel
- var directChannels []slackChannel
-
- var users []slackUser
- posts := make(map[string][]slackPost)
- uploads := make(map[string]*zip.File)
- for _, file := range zipreader.File {
- fileReader, err := file.Open()
- if err != nil {
- log.WriteString(i18n.T("api.slackimport.slack_import.open.app_error", map[string]any{"Filename": file.Name}))
- return model.NewAppError("SlackImport", "api.slackimport.slack_import.open.app_error", map[string]any{"Filename": file.Name}, "", http.StatusInternalServerError).Wrap(err), log
- }
- defer fileReader.Close()
-
- reader := utils.NewLimitedReaderWithError(fileReader, slackImportMaxFileSize)
- if file.Name == "channels.json" {
- publicChannels, err = slackParseChannels(reader, model.ChannelTypeOpen)
- if errors.Is(err, utils.ErrSizeLimitExceeded) {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.file_too_large", map[string]any{"Filename": file.Name}))
- continue
- }
- channels = append(channels, publicChannels...)
- } else if file.Name == "dms.json" {
- directChannels, err = slackParseChannels(reader, model.ChannelTypeDirect)
- if errors.Is(err, utils.ErrSizeLimitExceeded) {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.file_too_large", map[string]any{"Filename": file.Name}))
- continue
- }
- channels = append(channels, directChannels...)
- } else if file.Name == "groups.json" {
- privateChannels, err = slackParseChannels(reader, model.ChannelTypePrivate)
- if errors.Is(err, utils.ErrSizeLimitExceeded) {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.file_too_large", map[string]any{"Filename": file.Name}))
- continue
- }
- channels = append(channels, privateChannels...)
- } else if file.Name == "mpims.json" {
- groupChannels, err = slackParseChannels(reader, model.ChannelTypeGroup)
- if errors.Is(err, utils.ErrSizeLimitExceeded) {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.file_too_large", map[string]any{"Filename": file.Name}))
- continue
- }
- channels = append(channels, groupChannels...)
- } else if file.Name == "users.json" {
- users, err = slackParseUsers(reader)
- if errors.Is(err, utils.ErrSizeLimitExceeded) {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.file_too_large", map[string]any{"Filename": file.Name}))
- continue
- }
- } else {
- spl := strings.Split(file.Name, "/")
- if len(spl) == 2 && strings.HasSuffix(spl[1], ".json") {
- newposts, err := slackParsePosts(reader)
- if errors.Is(err, utils.ErrSizeLimitExceeded) {
- log.WriteString(i18n.T("api.slackimport.slack_import.zip.file_too_large", map[string]any{"Filename": file.Name}))
- continue
- }
- channel := spl[0]
- if _, ok := posts[channel]; !ok {
- posts[channel] = newposts
- } else {
- posts[channel] = append(posts[channel], newposts...)
- }
- } else if len(spl) == 3 && spl[0] == "__uploads" {
- uploads[spl[1]] = file
- }
- }
- }
-
- posts = slackConvertUserMentions(users, posts)
- posts = slackConvertChannelMentions(channels, posts)
- posts = slackConvertPostsMarkup(posts)
-
- addedUsers := si.slackAddUsers(rctx, teamID, users, log)
- botUser := si.slackAddBotUser(rctx, teamID, log)
-
- si.slackAddChannels(rctx, teamID, channels, posts, addedUsers, uploads, botUser, log)
-
- if botUser != nil {
- si.deactivateSlackBotUser(rctx, botUser)
- }
-
- if err := si.actions.InvalidateAllCaches(); err != nil {
- return err, log
- }
-
- log.WriteString(i18n.T("api.slackimport.slack_import.notes"))
- log.WriteString("=======\r\n\r\n")
-
- log.WriteString(i18n.T("api.slackimport.slack_import.note1"))
- log.WriteString(i18n.T("api.slackimport.slack_import.note2"))
- log.WriteString(i18n.T("api.slackimport.slack_import.note3"))
-
- return nil, log
-}
-
-func truncateRunes(s string, i int) string {
- runes := []rune(s)
- if len(runes) > i {
- return string(runes[:i])
- }
- return s
-}
-
-func (si *SlackImporter) slackAddUsers(rctx request.CTX, teamId string, slackusers []slackUser, importerLog *bytes.Buffer) map[string]*model.User {
- // Log header
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.created"))
- importerLog.WriteString("===============\r\n\r\n")
-
- addedUsers := make(map[string]*model.User)
-
- // Need the team
- team, err := si.store.Team().Get(teamId)
- if err != nil {
- importerLog.WriteString(i18n.T("api.slackimport.slack_import.team_fail"))
- return addedUsers
- }
-
- for _, sUser := range slackusers {
- firstName := sUser.Profile.FirstName
- lastName := sUser.Profile.LastName
- email := sUser.Profile.Email
- if email == "" {
- email = sUser.Username + "@example.com"
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.missing_email_address", map[string]any{"Email": email, "Username": sUser.Username}))
- rctx.Logger().Warn("Slack Import: User does not have an email address in the Slack export. Used username as a placeholder. The user should update their email address once logged in to the system.", mlog.String("user_email", email), mlog.String("user_name", sUser.Username))
- }
-
- // Check for email conflict; only admin imports may merge into an existing account.
- if existingUser, err := si.store.User().GetByEmail(email); err == nil {
- if si.isAdminImport {
- addedUsers[sUser.Id] = existingUser
- if _, err := si.actions.JoinUserToTeam(team, addedUsers[sUser.Id], ""); err != nil {
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.merge_existing_failed", map[string]any{"Email": existingUser.Email, "Username": existingUser.Username}))
- } else {
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.merge_existing", map[string]any{"Email": existingUser.Email, "Username": existingUser.Username}))
- }
- continue
- }
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.merge_existing_skipped_non_admin", map[string]any{"Email": existingUser.Email, "Username": existingUser.Username}))
- continue
- }
-
- email = strings.ToLower(email)
- newUser := model.User{
- Username: sUser.Username,
- FirstName: firstName,
- LastName: lastName,
- Email: email,
- Password: "",
- }
-
- mUser := si.oldImportUser(rctx, team, &newUser)
- if mUser == nil {
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.unable_import", map[string]any{"Username": sUser.Username}))
- continue
- }
-
- sent, err := si.actions.SendPasswordReset(email)
- if err != nil {
- rctx.Logger().Warn("Slack Import: Cannot send password reset email to user. An admin should update their email address once logged in to the system.", mlog.String("user_email", email), mlog.String("user_name", sUser.Username))
- }
-
- if !sent {
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.send_reset_email_failed", map[string]any{"Username": sUser.Username, "Email": newUser.Email}))
- }
-
- addedUsers[sUser.Id] = mUser
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_users.email", map[string]any{"Email": newUser.Email}))
- }
-
- return addedUsers
-}
-
-func (si *SlackImporter) slackAddBotUser(rctx request.CTX, teamId string, log *bytes.Buffer) *model.User {
- team, err := si.store.Team().Get(teamId)
- if err != nil {
- log.WriteString(i18n.T("api.slackimport.slack_import.team_fail"))
- return nil
- }
-
- password := model.NewId()
- username := "slackimportuser_" + model.NewId()
- email := username + "@localhost"
-
- botUser := model.User{
- Username: username,
- FirstName: "",
- LastName: "",
- Email: email,
- Password: password,
- }
-
- mUser := si.oldImportUser(rctx, team, &botUser)
- if mUser == nil {
- log.WriteString(i18n.T("api.slackimport.slack_add_bot_user.unable_import", map[string]any{"Username": username}))
- return nil
- }
-
- log.WriteString(i18n.T("api.slackimport.slack_add_bot_user.email", map[string]any{"Email": botUser.Email}))
- return mUser
-}
-
-func (si *SlackImporter) slackAddPosts(rctx request.CTX, teamId string, channel *model.Channel, posts []slackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User) {
- sort.Slice(posts, func(i, j int) bool {
- return slackConvertTimeStamp(posts[i].TimeStamp) < slackConvertTimeStamp(posts[j].TimeStamp)
- })
- threads := make(map[string]string)
- for _, sPost := range posts {
- switch {
- case sPost.Type == "message" && (sPost.SubType == "" || sPost.SubType == "file_share"):
- if sPost.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
- newPost := model.Post{
- UserId: users[sPost.User].Id,
- ChannelId: channel.Id,
- Message: sPost.Text,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- }
- if sPost.Upload {
- if sPost.File != nil {
- if fileInfo, ok := si.slackUploadFile(rctx, sPost.File, uploads, teamId, newPost.ChannelId, newPost.UserId, sPost.TimeStamp); ok {
- newPost.FileIds = append(newPost.FileIds, fileInfo.Id)
- }
- } else if sPost.Files != nil {
- for _, file := range sPost.Files {
- if fileInfo, ok := si.slackUploadFile(rctx, file, uploads, teamId, newPost.ChannelId, newPost.UserId, sPost.TimeStamp); ok {
- newPost.FileIds = append(newPost.FileIds, fileInfo.Id)
- }
- }
- }
- }
- // If post in thread
- if sPost.ThreadTS != "" && sPost.ThreadTS != sPost.TimeStamp {
- newPost.RootId = threads[sPost.ThreadTS]
- }
- postId := si.oldImportPost(rctx, &newPost)
- // If post is thread starter
- if sPost.ThreadTS == sPost.TimeStamp {
- threads[sPost.ThreadTS] = postId
- }
- case sPost.Type == "message" && sPost.SubType == "file_comment":
- if sPost.Comment == nil {
- rctx.Logger().Debug("Slack Import: Unable to import the message as it has no comments.")
- continue
- }
- if sPost.Comment.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.Comment.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
- newPost := model.Post{
- UserId: users[sPost.Comment.User].Id,
- ChannelId: channel.Id,
- Message: sPost.Comment.Comment,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- }
- si.oldImportPost(rctx, &newPost)
- case sPost.Type == "message" && sPost.SubType == "bot_message":
- if botUser == nil {
- rctx.Logger().Warn("Slack Import: Unable to import the bot message as the bot user does not exist.")
- continue
- }
- if sPost.BotId == "" {
- rctx.Logger().Warn("Slack Import: Unable to import bot message as the BotId field is missing.")
- continue
- }
-
- props := make(model.StringInterface)
- props[model.PostPropsOverrideUsername] = sPost.BotUsername
- if len(sPost.Attachments) > 0 {
- props[model.PostPropsAttachments] = sPost.Attachments
- }
-
- post := &model.Post{
- UserId: botUser.Id,
- ChannelId: channel.Id,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- Message: sPost.Text,
- Type: model.PostTypeMessageAttachment,
- }
-
- postId := si.oldImportIncomingWebhookPost(rctx, post, props)
- // If post is thread starter
- if sPost.ThreadTS == sPost.TimeStamp {
- threads[sPost.ThreadTS] = postId
- }
- case sPost.Type == "message" && (sPost.SubType == "channel_join" || sPost.SubType == "channel_leave"):
- if sPost.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
-
- var postType string
- if sPost.SubType == "channel_join" {
- postType = model.PostTypeJoinChannel
- } else {
- postType = model.PostTypeLeaveChannel
- }
-
- newPost := model.Post{
- UserId: users[sPost.User].Id,
- ChannelId: channel.Id,
- Message: sPost.Text,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- Type: postType,
- Props: model.StringInterface{
- "username": users[sPost.User].Username,
- },
- }
- si.oldImportPost(rctx, &newPost)
- case sPost.Type == "message" && sPost.SubType == "me_message":
- if sPost.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
- newPost := model.Post{
- UserId: users[sPost.User].Id,
- ChannelId: channel.Id,
- Message: "*" + sPost.Text + "*",
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- }
- postId := si.oldImportPost(rctx, &newPost)
- // If post is thread starter
- if sPost.ThreadTS == sPost.TimeStamp {
- threads[sPost.ThreadTS] = postId
- }
- case sPost.Type == "message" && sPost.SubType == "channel_topic":
- if sPost.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
- newPost := model.Post{
- UserId: users[sPost.User].Id,
- ChannelId: channel.Id,
- Message: sPost.Text,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- Type: model.PostTypeHeaderChange,
- }
- si.oldImportPost(rctx, &newPost)
- case sPost.Type == "message" && sPost.SubType == "channel_purpose":
- if sPost.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
- newPost := model.Post{
- UserId: users[sPost.User].Id,
- ChannelId: channel.Id,
- Message: sPost.Text,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- Type: model.PostTypePurposeChange,
- }
- si.oldImportPost(rctx, &newPost)
- case sPost.Type == "message" && sPost.SubType == "channel_name":
- if sPost.User == "" {
- rctx.Logger().Debug("Slack Import: Unable to import the message as the user field is missing.")
- continue
- }
- if users[sPost.User] == nil {
- rctx.Logger().Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
- continue
- }
- newPost := model.Post{
- UserId: users[sPost.User].Id,
- ChannelId: channel.Id,
- Message: sPost.Text,
- CreateAt: slackConvertTimeStamp(sPost.TimeStamp),
- Type: model.PostTypeDisplaynameChange,
- }
- si.oldImportPost(rctx, &newPost)
- default:
- rctx.Logger().Warn(
- "Slack Import: Unable to import the message as its type is not supported",
- mlog.String("post_type", sPost.Type),
- mlog.String("post_subtype", sPost.SubType),
- )
- }
- }
-}
-
-func (si *SlackImporter) slackUploadFile(rctx request.CTX, slackPostFile *slackFile, uploads map[string]*zip.File, teamId string, channelId string, userId string, slackTimestamp string) (*model.FileInfo, bool) {
- if slackPostFile == nil {
- rctx.Logger().Warn("Slack Import: Unable to attach the file to the post as the latter has no file section present in Slack export.")
- return nil, false
- }
- file, ok := uploads[slackPostFile.Id]
- if !ok {
- rctx.Logger().Warn("Slack Import: Unable to import file as the file is missing from the Slack export zip file.", mlog.String("file_id", slackPostFile.Id))
- return nil, false
- }
- openFile, err := file.Open()
- if err != nil {
- rctx.Logger().Warn("Slack Import: Unable to open the file from the Slack export.", mlog.String("file_id", slackPostFile.Id), mlog.Err(err))
- return nil, false
- }
- defer openFile.Close()
-
- // since this is an attachment, we should treat it as a file and apply according limits
- reader := utils.NewLimitedReaderWithError(openFile, *si.config.FileSettings.MaxFileSize)
- timestamp := utils.TimeFromMillis(slackConvertTimeStamp(slackTimestamp))
- uploadedFile, err := si.oldImportFile(rctx, timestamp, reader, teamId, channelId, userId, filepath.Base(file.Name))
- if err != nil {
- rctx.Logger().Warn("Slack Import: An error occurred when uploading file.", mlog.String("file_id", slackPostFile.Id), mlog.Err(err))
- return nil, false
- }
-
- return uploadedFile, true
-}
-
-func (si *SlackImporter) deactivateSlackBotUser(rctx request.CTX, user *model.User) {
- if _, err := si.actions.UpdateActive(user, false); err != nil {
- rctx.Logger().Warn("Slack Import: Unable to deactivate the user account used for the bot.")
- }
-}
-
-func (si *SlackImporter) addSlackUsersToChannel(rctx request.CTX, members []string, users map[string]*model.User, channel *model.Channel, log *bytes.Buffer) {
- for _, member := range members {
- user, ok := users[member]
- if !ok {
- log.WriteString(i18n.T("api.slackimport.slack_add_channels.failed_to_add_user", map[string]any{"Username": "?"}))
- continue
- }
- if _, err := si.actions.AddUserToChannel(rctx, user, channel, false); err != nil {
- log.WriteString(i18n.T("api.slackimport.slack_add_channels.failed_to_add_user", map[string]any{"Username": user.Username}))
- }
- }
-}
-
-func slackSanitiseChannelProperties(rctx request.CTX, channel model.Channel) model.Channel {
- if utf8.RuneCountInString(channel.DisplayName) > model.ChannelDisplayNameMaxRunes {
- rctx.Logger().Warn("Slack Import: Channel display name exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
- channel.DisplayName = truncateRunes(channel.DisplayName, model.ChannelDisplayNameMaxRunes)
- }
-
- if len(channel.Name) > model.ChannelNameMaxLength {
- rctx.Logger().Warn("Slack Import: Channel handle exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
- channel.Name = channel.Name[0:model.ChannelNameMaxLength]
- }
-
- if utf8.RuneCountInString(channel.Purpose) > model.ChannelPurposeMaxRunes {
- rctx.Logger().Warn("Slack Import: Channel purpose exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
- channel.Purpose = truncateRunes(channel.Purpose, model.ChannelPurposeMaxRunes)
- }
-
- if utf8.RuneCountInString(channel.Header) > model.ChannelHeaderMaxRunes {
- rctx.Logger().Warn("Slack Import: Channel header exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
- channel.Header = truncateRunes(channel.Header, model.ChannelHeaderMaxRunes)
- }
-
- return channel
-}
-
-func (si *SlackImporter) slackAddChannels(rctx request.CTX, teamId string, slackchannels []slackChannel, posts map[string][]slackPost, users map[string]*model.User, uploads map[string]*zip.File, botUser *model.User, importerLog *bytes.Buffer) map[string]*model.Channel {
- // Write Header
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_channels.added"))
- importerLog.WriteString("=================\r\n\r\n")
-
- addedChannels := make(map[string]*model.Channel)
- for _, sChannel := range slackchannels {
- newChannel := model.Channel{
- TeamId: teamId,
- Type: sChannel.Type,
- DisplayName: sChannel.Name,
- Name: slackConvertChannelName(sChannel.Name, sChannel.Id),
- Purpose: sChannel.Purpose.Value,
- Header: sChannel.Topic.Value,
- }
-
- // Direct message channels in Slack don't have a name so we set the id as name or else the messages won't get imported.
- if newChannel.Type == model.ChannelTypeDirect {
- sChannel.Name = sChannel.Id
- }
-
- newChannel = slackSanitiseChannelProperties(rctx, newChannel)
-
- var mChannel *model.Channel
- var err error
- if mChannel, err = si.store.Channel().GetByName(teamId, sChannel.Name, true); err == nil {
- // The channel already exists as an active channel. Merge with the existing one.
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_channels.merge", map[string]any{"DisplayName": newChannel.DisplayName}))
- } else if _, nErr := si.store.Channel().GetDeletedByName(teamId, sChannel.Name); nErr == nil {
- // The channel already exists but has been deleted. Generate a random string for the handle instead.
- newChannel.Name = model.NewId()
- newChannel = slackSanitiseChannelProperties(rctx, newChannel)
- }
-
- if mChannel == nil {
- // Haven't found an existing channel to merge with. Try importing it as a new one.
- mChannel = si.oldImportChannel(rctx, &newChannel, sChannel, users)
- if mChannel == nil {
- rctx.Logger().Warn("Slack Import: Unable to import Slack channel.", mlog.String("channel_display_name", newChannel.DisplayName))
- importerLog.WriteString(i18n.T("api.slackimport.slack_add_channels.import_failed", map[string]any{"DisplayName": newChannel.DisplayName}))
- continue
- }
- }
-
- // Members for direct and group channels are added during the creation of the channel in the oldImportChannel function
- if sChannel.Type == model.ChannelTypeOpen || sChannel.Type == model.ChannelTypePrivate {
- si.addSlackUsersToChannel(rctx, sChannel.Members, users, mChannel, importerLog)
- }
- importerLog.WriteString(newChannel.DisplayName + "\r\n")
- addedChannels[sChannel.Id] = mChannel
- si.slackAddPosts(rctx, teamId, mChannel, posts[sChannel.Name], users, uploads, botUser)
- }
-
- return addedChannels
-}
-
-//
-// -- Old SlackImport Functions --
-// Import functions are suitable for entering posts and users into the database without
-// some of the usual checks. (IsValid is still run)
-//
-
-func (si *SlackImporter) oldImportPost(rctx request.CTX, post *model.Post) string {
- // Workaround for empty messages, which may be the case if they are webhook posts.
- firstIteration := true
- firstPostId := ""
- if post.RootId != "" {
- firstPostId = post.RootId
- }
- maxPostSize := si.actions.MaxPostSize()
- for messageRuneCount := utf8.RuneCountInString(post.Message); messageRuneCount > 0 || firstIteration; messageRuneCount = utf8.RuneCountInString(post.Message) {
- var remainder string
- if messageRuneCount > maxPostSize {
- remainder = string(([]rune(post.Message))[maxPostSize:])
- post.Message = truncateRunes(post.Message, maxPostSize)
- } else {
- remainder = ""
- }
-
- post.Hashtags, _ = model.ParseHashtags(post.Message)
-
- post.RootId = firstPostId
-
- _, err := si.store.Post().Save(rctx, post)
- if err != nil {
- rctx.Logger().Debug("Error saving post.", mlog.String("user_id", post.UserId), mlog.String("message", post.Message))
- }
-
- if firstIteration {
- if firstPostId == "" {
- firstPostId = post.Id
- }
- for _, fileId := range post.FileIds {
- if err := si.store.FileInfo().AttachToPost(rctx, fileId, post.Id, post.ChannelId, post.UserId); err != nil {
- rctx.Logger().Error(
- "Error attaching files to post.",
- mlog.String("post_id", post.Id),
- mlog.String("file_ids", strings.Join(post.FileIds, ",")),
- mlog.String("user_id", post.UserId),
- mlog.Err(err),
- )
- }
- }
- post.FileIds = nil
- }
-
- post.Id = ""
- post.CreateAt++
- post.Message = remainder
- firstIteration = false
- }
- return firstPostId
-}
-
-func (si *SlackImporter) oldImportUser(rctx request.CTX, team *model.Team, user *model.User) *model.User {
- user.MakeNonNil()
-
- user.Roles = model.SystemUserRoleId
-
- ruser, nErr := si.store.User().Save(rctx, user)
- if nErr != nil {
- rctx.Logger().Debug("Error saving user.", mlog.Err(nErr))
- return nil
- }
-
- // Only system admins can automatically verify emails during import
- if si.isAdminImport {
- if _, err := si.store.User().VerifyEmail(ruser.Id, ruser.Email); err != nil {
- rctx.Logger().Warn("Failed to set email verified for admin import.", mlog.Err(err))
- }
- } else {
- // Non-admin users: emails remain unverified
- rctx.Logger().Debug("Email verification skipped for non-admin import.",
- mlog.String("user_email", ruser.Email))
- }
-
- if _, err := si.actions.JoinUserToTeam(team, user, ""); err != nil {
- rctx.Logger().Warn("Failed to join team when importing.", mlog.Err(err))
- }
-
- return ruser
-}
-
-func (si *SlackImporter) oldImportChannel(rctx request.CTX, channel *model.Channel, sChannel slackChannel, users map[string]*model.User) *model.Channel {
- switch {
- case channel.Type == model.ChannelTypeDirect:
- if len(sChannel.Members) < 2 {
- return nil
- }
- u1 := users[sChannel.Members[0]]
- u2 := users[sChannel.Members[1]]
- if u1 == nil || u2 == nil {
- rctx.Logger().Warn("Either or both of user ids not found in users.json. Ignoring.", mlog.String("id1", sChannel.Members[0]), mlog.String("id2", sChannel.Members[1]))
- return nil
- }
- sc, err := si.actions.CreateDirectChannel(rctx, u1.Id, u2.Id)
- if err != nil {
- return nil
- }
-
- return sc
- // check if direct channel has less than 8 members and if not import as private channel instead
- case channel.Type == model.ChannelTypeGroup && len(sChannel.Members) < 8:
- members := make([]string, len(sChannel.Members))
-
- for i := range sChannel.Members {
- u := users[sChannel.Members[i]]
- if u == nil {
- rctx.Logger().Warn("User not found in users.json. Ignoring.", mlog.String("id", sChannel.Members[i]))
- continue
- }
- members[i] = u.Id
- }
-
- creator := users[sChannel.Creator]
- if creator == nil {
- return nil
- }
- sc, err := si.actions.CreateGroupChannel(rctx, members, "")
- if err != nil {
- return nil
- }
-
- return sc
- case channel.Type == model.ChannelTypeGroup:
- channel.Type = model.ChannelTypePrivate
- sc, err := si.actions.CreateChannel(channel, false)
- if err != nil {
- return nil
- }
-
- return sc
- }
-
- sc, err := si.store.Channel().Save(rctx, channel, *si.config.TeamSettings.MaxChannelsPerTeam)
- if err != nil {
- return nil
- }
-
- return sc
-}
-
-func (si *SlackImporter) oldImportFile(rctx request.CTX, timestamp time.Time, file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) {
- buf := bytes.NewBuffer(nil)
- _, err := io.Copy(buf, file)
- if err != nil {
- return nil, err
- }
-
- data := buf.Bytes()
-
- fileInfo, appErr := si.actions.DoUploadFile(timestamp, teamId, channelId, userId, fileName, data)
- if appErr != nil {
- return nil, appErr
- }
-
- if fileInfo.IsImage() && !fileInfo.IsSvg() {
- img, imgType, release, err := si.actions.PrepareImage(data)
- if err != nil {
- return nil, err
- }
- defer release()
- si.actions.GenerateThumbnailImage(rctx, img, imgType, fileInfo.ThumbnailPath)
- si.actions.GeneratePreviewImage(rctx, img, imgType, fileInfo.PreviewPath)
- }
-
- return fileInfo, nil
-}
-
-func (si *SlackImporter) oldImportIncomingWebhookPost(rctx request.CTX, post *model.Post, props model.StringInterface) string {
- linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
- post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})")
-
- post.AddProp(model.PostPropsFromWebhook, "true")
-
- if _, ok := props[model.PostPropsOverrideUsername]; !ok {
- post.AddProp(model.PostPropsOverrideUsername, model.DefaultWebhookUsername)
- }
-
- if len(props) > 0 {
- for key, val := range props {
- if key == model.PostPropsAttachments {
- if attachments, success := val.([]*model.MessageAttachment); success {
- model.ParseMessageAttachment(post, attachments)
- }
- } else if key != model.PostPropsFromWebhook {
- post.AddProp(key, val)
- }
- }
- }
-
- return si.oldImportPost(rctx, post)
-}
diff --git a/server/platform/services/slackimport/slackimport_test.go b/server/platform/services/slackimport/slackimport_test.go
deleted file mode 100644
index dbeeb33b9bc1..000000000000
--- a/server/platform/services/slackimport/slackimport_test.go
+++ /dev/null
@@ -1,1042 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-package slackimport
-
-import (
- "archive/zip"
- "bytes"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost/server/public/model"
- "github.com/mattermost/mattermost/server/public/shared/request"
- "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
-)
-
-func TestSlackConvertTimeStamp(t *testing.T) {
- assert.EqualValues(t, slackConvertTimeStamp("1469785419.000033"), 1469785419000)
-}
-
-func TestSlackConvertChannelName(t *testing.T) {
- for _, tc := range []struct {
- nameInput string
- idInput string
- output string
- }{
- {"test-channel", "C0G08DLQH", "test-channel"},
- {"_test_channel_", "C0G04DLQH", "test_channel"},
- {"__test", "C0G07DLQH", "test"},
- {"-t", "C0G06DLQH", "slack-channel-t"},
- {"a", "C0G05DLQH", "slack-channel-a"},
- {"случайный", "C0G05DLQD", "c0g05dlqd"},
- } {
- assert.Equal(t, slackConvertChannelName(tc.nameInput, tc.idInput), tc.output, "nameInput = %v", tc.nameInput)
- }
-}
-
-func TestSlackConvertUserMentions(t *testing.T) {
- users := []slackUser{
- {Id: "U00000A0A", Username: "firstuser"},
- {Id: "U00000B1B", Username: "seconduser"},
- }
-
- posts := map[string][]slackPost{
- "test-channel": {
- {
- Text: ": Hi guys.",
- },
- {
- Text: "Calling .",
- },
- {
- Text: "Yo .",
- },
- {
- Text: "Regular user test <@U00000B1B|seconduser> and <@U00000A0A>.",
- },
- },
- }
-
- expectedPosts := map[string][]slackPost{
- "test-channel": {
- {
- Text: "@channel: Hi guys.",
- },
- {
- Text: "Calling @here.",
- },
- {
- Text: "Yo @all.",
- },
- {
- Text: "Regular user test @seconduser and @firstuser.",
- },
- },
- }
-
- assert.Equal(t, expectedPosts, slackConvertUserMentions(users, posts))
-}
-
-func TestSlackConvertChannelMentions(t *testing.T) {
- channels := []slackChannel{
- {Id: "C000AA00A", Name: "one"},
- {Id: "C000BB11B", Name: "two"},
- }
-
- posts := map[string][]slackPost{
- "test-channel": {
- {
- Text: "Go to <#C000AA00A>.",
- },
- {
- User: "U00000A0A",
- Text: "Try <#C000BB11B|two> for this.",
- },
- },
- }
-
- expectedPosts := map[string][]slackPost{
- "test-channel": {
- {
- Text: "Go to ~one.",
- },
- {
- User: "U00000A0A",
- Text: "Try ~two for this.",
- },
- },
- }
-
- assert.Equal(t, expectedPosts, slackConvertChannelMentions(channels, posts))
-}
-
-func openTestFile(t *testing.T, filename string) (*os.File, error) {
- working, err := os.Getwd()
- if err != nil {
- return nil, err
- }
- t.Log("working directory:", working)
-
- path := filepath.Join("../tests", filename)
- return os.Open(path)
-}
-
-func TestSlackParseChannels(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-channels.json")
- require.NoError(t, err)
- defer file.Close()
-
- channels, err := slackParseChannels(file, model.ChannelTypeOpen)
- require.NoError(t, err)
- assert.Equal(t, 6, len(channels))
-}
-
-func TestSlackParseDirectMessages(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-direct-messages.json")
- require.NoError(t, err)
- defer file.Close()
-
- channels, err := slackParseChannels(file, model.ChannelTypeDirect)
- require.NoError(t, err)
- assert.Equal(t, 4, len(channels))
-}
-
-func TestSlackParsePrivateChannels(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-private-channels.json")
- require.NoError(t, err)
- defer file.Close()
-
- channels, err := slackParseChannels(file, model.ChannelTypePrivate)
- require.NoError(t, err)
- assert.Equal(t, 1, len(channels))
-}
-
-func TestSlackParseGroupDirectMessages(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-group-direct-messages.json")
- require.NoError(t, err)
- defer file.Close()
-
- channels, err := slackParseChannels(file, model.ChannelTypeGroup)
- require.NoError(t, err)
- assert.Equal(t, 3, len(channels))
-}
-
-func TestSlackParseUsers(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-users.json")
- require.NoError(t, err)
- defer file.Close()
-
- users, err := slackParseUsers(file)
- require.NoError(t, err)
- assert.Equal(t, 11, len(users))
-}
-
-func TestSlackParsePosts(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-posts.json")
- require.NoError(t, err)
- defer file.Close()
-
- posts, err := slackParsePosts(file)
- require.NoError(t, err)
- assert.Equal(t, 9, len(posts))
-}
-
-func TestSlackParseMultipleAttachments(t *testing.T) {
- file, err := openTestFile(t, "slack-import-test-posts.json")
- require.NoError(t, err)
- defer file.Close()
-
- posts, err := slackParsePosts(file)
- require.NoError(t, err)
- assert.Equal(t, 2, len(posts[8].Files))
-}
-
-func TestSlackSanitiseChannelProperties(t *testing.T) {
- rctx := request.TestContext(t)
-
- c1 := model.Channel{
- DisplayName: "display-name",
- Name: "name",
- Purpose: "The channel purpose",
- Header: "The channel header",
- }
-
- c1s := slackSanitiseChannelProperties(rctx, c1)
- assert.Equal(t, c1, c1s)
-
- c2 := model.Channel{
- DisplayName: strings.Repeat("abcdefghij", 7),
- Name: strings.Repeat("abcdefghij", 7),
- Purpose: strings.Repeat("0123456789", 30),
- Header: strings.Repeat("0123456789", 120),
- }
-
- c2s := slackSanitiseChannelProperties(rctx, c2)
- assert.Equal(t, model.Channel{
- DisplayName: strings.Repeat("abcdefghij", 6) + "abcd",
- Name: strings.Repeat("abcdefghij", 6) + "abcd",
- Purpose: strings.Repeat("0123456789", 25),
- Header: strings.Repeat("0123456789", 102) + "0123",
- }, c2s)
-}
-
-func TestSlackConvertPostsMarkup(t *testing.T) {
- input := make(map[string][]slackPost)
- input["test"] = []slackPost{
- {
- Text: "This message contains a link to .",
- },
- {
- Text: "This message contains a mailto link to in it.",
- },
- {
- Text: "This message contains a *bold* word.",
- },
- {
- Text: "This is not a * bold * word.",
- },
- {
- Text: `There is *no bold word
-in this*.`,
- },
- {
- Text: "*This* is not a*bold* word.*This* is a bold word, *and* this; *and* this too.",
- },
- {
- Text: "This message contains a ~strikethrough~ word.",
- },
- {
- Text: "This is not a ~ strikethrough ~ word.",
- },
- {
- Text: `There is ~no strikethrough word
-in this~.`,
- },
- {
- Text: "~This~ is not a~strikethrough~ word.~This~ is a strikethrough word, ~and~ this; ~and~ this too.",
- },
- {
- Text: `This message contains multiple paragraphs blockquotes
->>>first
-second
-third`,
- },
- {
- Text: `This message contains single paragraph blockquotes
->something
->another thing`,
- },
- {
- Text: "This message has no > block quote",
- },
- }
-
- expectedOutput := make(map[string][]slackPost)
- expectedOutput["test"] = []slackPost{
- {
- Text: "This message contains a link to [Google](https://google.com).",
- },
- {
- Text: "This message contains a mailto link to [me@example.com](mailto:me@example.com) in it.",
- },
- {
- Text: "This message contains a **bold** word.",
- },
- {
- Text: "This is not a * bold * word.",
- },
- {
- Text: `There is *no bold word
-in this*.`,
- },
- {
- Text: "**This** is not a*bold* word.**This** is a bold word, **and** this; **and** this too.",
- },
- {
- Text: "This message contains a ~~strikethrough~~ word.",
- },
- {
- Text: "This is not a ~ strikethrough ~ word.",
- },
- {
- Text: `There is ~no strikethrough word
-in this~.`,
- },
- {
- Text: "~~This~~ is not a~strikethrough~ word.~~This~~ is a strikethrough word, ~~and~~ this; ~~and~~ this too.",
- },
- {
- Text: `This message contains multiple paragraphs blockquotes
->first
->second
->third`,
- },
- {
- Text: `This message contains single paragraph blockquotes
->something
->another thing`,
- },
- {
- Text: "This message has no > block quote",
- },
- }
-
- assert.Equal(t, expectedOutput, slackConvertPostsMarkup(input))
-}
-
-func TestOldImportChannel(t *testing.T) {
- u1 := &model.User{
- Id: model.NewId(),
- Username: "test-user-1",
- }
- u2 := &model.User{
- Id: model.NewId(),
- Username: "test-user-2",
- }
- store := &mocks.Store{}
- config := &model.Config{}
- config.SetDefaults()
- rctx := request.TestContext(t)
-
- t.Run("No panic on direct channel", func(t *testing.T) {
- // ch := th.CreateDmChannel(u1)
- ch := &model.Channel{
- Type: model.ChannelTypeDirect,
- Name: model.GetDMNameFromIds(u1.Id, u2.Id),
- }
- users := map[string]*model.User{
- u2.Id: u2,
- }
- sCh := slackChannel{
- Id: "someid",
- Members: []string{u1.Id, "randomID"},
- Creator: "randomID2",
- }
-
- actions := Actions{}
-
- importer := New(store, actions, config)
- _ = importer.oldImportChannel(rctx, ch, sCh, users)
- })
-
- t.Run("No panic on direct channel with 1 member", func(t *testing.T) {
- ch := &model.Channel{
- Type: model.ChannelTypeDirect,
- Name: model.GetDMNameFromIds(u1.Id, u1.Id),
- }
- users := map[string]*model.User{
- u1.Id: u1,
- }
- sCh := slackChannel{
- Id: "someid",
- Members: []string{u1.Id},
- Creator: "randomID2",
- }
-
- actions := Actions{}
-
- importer := New(store, actions, config)
- _ = importer.oldImportChannel(rctx, ch, sCh, users)
- })
-
- t.Run("No panic on group channel", func(t *testing.T) {
- ch := &model.Channel{
- Type: model.ChannelTypeGroup,
- Name: "test-channel",
- }
- users := map[string]*model.User{
- u1.Id: u1,
- }
- sCh := slackChannel{
- Id: "someid",
- Members: []string{u1.Id},
- Creator: "randomID2",
- }
- actions := Actions{}
-
- importer := New(store, actions, config)
- _ = importer.oldImportChannel(rctx, ch, sCh, users)
- })
-}
-
-func TestSlackUploadFile(t *testing.T) {
- store := &mocks.Store{}
- config := &model.Config{}
- config.SetDefaults()
- defaultLimit := *config.FileSettings.MaxFileSize
-
- rctx := request.TestContext(t)
-
- sf := &slackFile{
- Id: "testfile",
- Title: "test-file",
- }
-
- buf := new(bytes.Buffer)
- zipWriter := zip.NewWriter(buf)
- writer, err := zipWriter.Create("testfile")
- require.NoError(t, err)
-
- _, err = writer.Write([]byte(strings.Repeat("a", 100)))
- require.NoError(t, err)
-
- err = zipWriter.Close()
- require.NoError(t, err)
-
- zipReader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
- require.NoError(t, err)
-
- uploads := map[string]*zip.File{
- "testfile": zipReader.File[0],
- }
-
- t.Run("Should not fail when file is in limits", func(t *testing.T) {
- importer := New(store, Actions{
- DoUploadFile: func(_ time.Time, _, _, _, _ string, _ []byte) (*model.FileInfo, *model.AppError) {
- return &model.FileInfo{}, nil
- },
- }, config)
- _, ok := importer.slackUploadFile(rctx, sf, uploads, "team-id", "channel-id", "user-id", time.Now().String())
- require.True(t, ok)
- })
-
- t.Run("Should fail when file size exceeded", func(t *testing.T) {
- defer func() {
- config.FileSettings.MaxFileSize = new(defaultLimit)
- }()
-
- config.FileSettings.MaxFileSize = new(int64(10))
-
- importer := New(store, Actions{}, config)
- _, ok := importer.slackUploadFile(rctx, sf, uploads, "team-id", "channel-id", "user-id", time.Now().String())
- require.False(t, ok)
- })
-}
-
-func TestOldImportUserEmailVerificationIsNotAutomatic(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- userStore := &mocks.UserStore{}
- store.On("User").Return(userStore)
-
- // Track if VerifyEmail is called (it should NOT be called)
- verifyEmailCalled := false
- userStore.On("VerifyEmail", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("user-id", nil).Run(func(args mock.Arguments) {
- verifyEmailCalled = true
- })
-
- savedUser := &model.User{
- Id: "test-user-id",
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- EmailVerified: false, // Must remain false after import
- Roles: model.SystemUserRoleId,
- }
- userStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.User")).Return(savedUser, nil)
-
- joinTeamCalled := false
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinTeamCalled = true
- return &model.TeamMember{}, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
-
- importer := New(store, actions, config)
-
- team := &model.Team{
- Id: "test-team-id",
- Name: "test-team",
- }
-
- user := &model.User{
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- FirstName: "Test",
- LastName: "User",
- }
-
- result := importer.oldImportUser(rctx, team, user)
-
- require.NotNil(t, result, "User import should succeed")
- assert.Equal(t, "test-user-id", result.Id, "Should return the saved user")
- assert.False(t, verifyEmailCalled, "SECURITY: VerifyEmail should NOT be called - this prevents domain bypass vulnerability")
- assert.True(t, joinTeamCalled, "User should still be joined to the team")
-
- // Verify the user was saved with unverified email (VerifyEmail should not have been called)
- userStore.AssertCalled(t, "Save", mock.AnythingOfType("*request.Context"), mock.MatchedBy(func(u *model.User) bool {
- return u.Email == "testuser@restricted-domain.com" && !u.EmailVerified
- }))
-}
-
-// TestSlackImportEnhancedSecurityAdminCanVerifyEmails tests that system admins can automatically verify emails
-func TestSlackImportEnhancedSecurityAdminCanVerifyEmails(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- userStore := &mocks.UserStore{}
- store.On("User").Return(userStore)
-
- // Track if VerifyEmail is called (it SHOULD be called for admin imports)
- verifyEmailCalled := false
- userStore.On("VerifyEmail", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("user-id", nil).Run(func(args mock.Arguments) {
- verifyEmailCalled = true
- })
-
- savedUser := &model.User{
- Id: "test-user-id",
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- EmailVerified: false, // Will be verified by admin import
- Roles: model.SystemUserRoleId,
- }
- userStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.User")).Return(savedUser, nil)
-
- joinTeamCalled := false
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinTeamCalled = true
- return &model.TeamMember{}, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
-
- // Pass true to indicate this is an admin import
- importer := NewWithAdminFlag(store, actions, config, true)
-
- team := &model.Team{
- Id: "test-team-id",
- Name: "test-team",
- }
-
- user := &model.User{
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- FirstName: "Test",
- LastName: "User",
- }
-
- result := importer.oldImportUser(rctx, team, user)
-
- require.NotNil(t, result, "User import should succeed")
- assert.Equal(t, "test-user-id", result.Id, "Should return the saved user")
- assert.True(t, verifyEmailCalled, "ADMIN IMPORT: VerifyEmail SHOULD be called for system admin imports")
- assert.True(t, joinTeamCalled, "User should still be joined to the team")
-
- // Verify VerifyEmail was called with correct parameters
- userStore.AssertCalled(t, "VerifyEmail", "test-user-id", "testuser@restricted-domain.com")
-}
-
-// TestSlackImportEnhancedSecurityNonAdminCannotVerifyEmails tests that non-admin users cannot automatically verify emails
-func TestSlackImportEnhancedSecurityNonAdminCannotVerifyEmails(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- userStore := &mocks.UserStore{}
- store.On("User").Return(userStore)
-
- // Track if VerifyEmail is called (it should NOT be called for non-admin imports)
- verifyEmailCalled := false
- userStore.On("VerifyEmail", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("user-id", nil).Run(func(args mock.Arguments) {
- verifyEmailCalled = true
- })
-
- savedUser := &model.User{
- Id: "test-user-id",
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- EmailVerified: false, // Should remain false for non-admin import
- Roles: model.SystemUserRoleId,
- }
- userStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.User")).Return(savedUser, nil)
-
- joinTeamCalled := false
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinTeamCalled = true
- return &model.TeamMember{}, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
-
- // Pass false to indicate this is NOT an admin import
- importer := NewWithAdminFlag(store, actions, config, false)
-
- team := &model.Team{
- Id: "test-team-id",
- Name: "test-team",
- }
-
- user := &model.User{
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- FirstName: "Test",
- LastName: "User",
- }
-
- result := importer.oldImportUser(rctx, team, user)
-
- require.NotNil(t, result, "User import should succeed")
- assert.Equal(t, "test-user-id", result.Id, "Should return the saved user")
- assert.False(t, verifyEmailCalled, "NON-ADMIN IMPORT: VerifyEmail should NOT be called for non-admin imports")
- assert.True(t, joinTeamCalled, "User should still be joined to the team")
-
- // Verify VerifyEmail was NOT called
- userStore.AssertNotCalled(t, "VerifyEmail")
-}
-
-// TestSlackImportEnhancedSecurityNoImportingUser tests behavior when no importing user is provided
-func TestSlackImportEnhancedSecurityNoImportingUser(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- userStore := &mocks.UserStore{}
- store.On("User").Return(userStore)
-
- // Track if VerifyEmail is called (it should NOT be called when no importing user)
- verifyEmailCalled := false
- userStore.On("VerifyEmail", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("user-id", nil).Run(func(args mock.Arguments) {
- verifyEmailCalled = true
- })
-
- savedUser := &model.User{
- Id: "test-user-id",
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- EmailVerified: false, // Should remain false when no importing user
- Roles: model.SystemUserRoleId,
- }
- userStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.User")).Return(savedUser, nil)
-
- joinTeamCalled := false
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinTeamCalled = true
- return &model.TeamMember{}, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
-
- // Pass false to indicate no admin privileges (default secure behavior)
- importer := NewWithAdminFlag(store, actions, config, false)
-
- team := &model.Team{
- Id: "test-team-id",
- Name: "test-team",
- }
-
- user := &model.User{
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- FirstName: "Test",
- LastName: "User",
- }
-
- result := importer.oldImportUser(rctx, team, user)
-
- require.NotNil(t, result, "User import should succeed")
- assert.Equal(t, "test-user-id", result.Id, "Should return the saved user")
- assert.False(t, verifyEmailCalled, "NO IMPORTING USER: VerifyEmail should NOT be called when no importing user is provided")
- assert.True(t, joinTeamCalled, "User should still be joined to the team")
-
- // Verify VerifyEmail was NOT called
- userStore.AssertNotCalled(t, "VerifyEmail")
-}
-
-// TestSlackImportEnhancedSecurityBackwardsCompatibility tests that the old New() constructor still works
-func TestSlackImportEnhancedSecurityBackwardsCompatibility(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- userStore := &mocks.UserStore{}
- store.On("User").Return(userStore)
-
- // Track if VerifyEmail is called (it should NOT be called with old constructor)
- verifyEmailCalled := false
- userStore.On("VerifyEmail", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return("user-id", nil).Run(func(args mock.Arguments) {
- verifyEmailCalled = true
- })
-
- savedUser := &model.User{
- Id: "test-user-id",
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- EmailVerified: false, // Should remain false with old constructor
- Roles: model.SystemUserRoleId,
- }
- userStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.User")).Return(savedUser, nil)
-
- joinTeamCalled := false
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinTeamCalled = true
- return &model.TeamMember{}, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
-
- // Use the old constructor (backwards compatibility)
- importer := New(store, actions, config)
-
- team := &model.Team{
- Id: "test-team-id",
- Name: "test-team",
- }
-
- user := &model.User{
- Username: "testuser",
- Email: "testuser@restricted-domain.com",
- FirstName: "Test",
- LastName: "User",
- }
-
- result := importer.oldImportUser(rctx, team, user)
-
- require.NotNil(t, result, "User import should succeed")
- assert.Equal(t, "test-user-id", result.Id, "Should return the saved user")
- assert.False(t, verifyEmailCalled, "BACKWARDS COMPATIBILITY: VerifyEmail should NOT be called with old constructor")
- assert.True(t, joinTeamCalled, "User should still be joined to the team")
-
- // Verify VerifyEmail was NOT called
- userStore.AssertNotCalled(t, "VerifyEmail")
-}
-
-type slackAddUsersTestSetup struct {
- store *mocks.Store
- teamStore *mocks.TeamStore
- userStore *mocks.UserStore
- team *model.Team
- savedUser *model.User
-}
-
-func newSlackAddUsersTestSetup(t *testing.T) *slackAddUsersTestSetup {
- t.Helper()
-
- s := &slackAddUsersTestSetup{}
- s.store = &mocks.Store{}
- s.teamStore = &mocks.TeamStore{}
- s.userStore = &mocks.UserStore{}
- s.store.On("Team").Return(s.teamStore)
- s.store.On("User").Return(s.userStore)
-
- s.team = &model.Team{Id: "test-team-id", Name: "test-team"}
- s.teamStore.On("Get", "test-team-id").Return(s.team, nil)
- s.userStore.On("GetByEmail", mock.AnythingOfType("string")).Return(nil, fmt.Errorf("not found"))
-
- s.savedUser = &model.User{Id: "test-user-id", Username: "testuser", Email: "testuser@example.com"}
- s.userStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.User")).Return(s.savedUser, nil)
-
- return s
-}
-
-func (s *slackAddUsersTestSetup) newImporter(actions Actions) *SlackImporter {
- config := &model.Config{}
- config.SetDefaults()
- return New(s.store, actions, config)
-}
-
-func (s *slackAddUsersTestSetup) newImporterWithAdminFlag(actions Actions, isAdminImport bool) *SlackImporter {
- config := &model.Config{}
- config.SetDefaults()
- return NewWithAdminFlag(s.store, actions, config, isAdminImport)
-}
-
-func (s *slackAddUsersTestSetup) defaultActions() Actions {
- return Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- return &model.TeamMember{}, nil
- },
- SendPasswordReset: func(email string) (bool, *model.AppError) {
- return true, nil
- },
- }
-}
-
-func defaultSlackUsers() []slackUser {
- return []slackUser{
- {Id: "U001", Username: "testuser", Profile: slackProfile{FirstName: "Test", LastName: "User", Email: "testuser@example.com"}},
- }
-}
-
-func TestSlackAddUsersLogContainsProperUserCreationMessage(t *testing.T) {
- rctx := request.TestContext(t)
- s := newSlackAddUsersTestSetup(t)
-
- importer := s.newImporter(s.defaultActions())
- importerLog := new(bytes.Buffer)
- importer.slackAddUsers(rctx, "test-team-id", defaultSlackUsers(), importerLog)
-
- logOutput := importerLog.String()
- assert.Contains(t, logOutput, "api.slackimport.slack_add_users.email", "import log should contain the user creation message")
- assert.NotContains(t, logOutput, "api.slackimport.slack_add_users.email_pwd", "import log must not use the old user creation message")
-}
-
-func TestSlackAddUsersLogsSendResetEmailFailure(t *testing.T) {
- rctx := request.TestContext(t)
- s := newSlackAddUsersTestSetup(t)
-
- actions := s.defaultActions()
- actions.SendPasswordReset = func(email string) (bool, *model.AppError) {
- return false, nil
- }
-
- importer := s.newImporter(actions)
- importerLog := new(bytes.Buffer)
- importer.slackAddUsers(rctx, "test-team-id", defaultSlackUsers(), importerLog)
-
- assert.Contains(t, importerLog.String(), "api.slackimport.slack_add_users.send_reset_email_failed")
-}
-
-func TestSlackAddUsersGeneratesUserWithEmptyPassword(t *testing.T) {
- rctx := request.TestContext(t)
- s := newSlackAddUsersTestSetup(t)
-
- importer := s.newImporter(s.defaultActions())
- importerLog := new(bytes.Buffer)
- importer.slackAddUsers(rctx, "test-team-id", defaultSlackUsers(), importerLog)
-
- s.userStore.AssertCalled(t, "Save", mock.AnythingOfType("*request.Context"), mock.MatchedBy(func(u *model.User) bool {
- return u.Password == ""
- }))
-}
-
-func TestSlackAddUsersTriggersPasswordResetFlow(t *testing.T) {
- rctx := request.TestContext(t)
- s := newSlackAddUsersTestSetup(t)
-
- passwordResetCalled := false
- actions := s.defaultActions()
- actions.SendPasswordReset = func(email string) (bool, *model.AppError) {
- passwordResetCalled = true
- return true, nil
- }
-
- importer := s.newImporter(actions)
- importerLog := new(bytes.Buffer)
- importer.slackAddUsers(rctx, "test-team-id", defaultSlackUsers(), importerLog)
-
- assert.True(t, passwordResetCalled, "SendPasswordReset should be called for each imported user")
-}
-
-// TestSlackAddUsersNonAdminImportDoesNotMergeExistingUser tests that a non-admin import skips
-// a Slack user whose email matches an existing Mattermost account that is not already on the
-// team, rather than force-enrolling that account into the team.
-func TestSlackAddUsersNonAdminImportDoesNotMergeExistingUser(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- teamStore := &mocks.TeamStore{}
- userStore := &mocks.UserStore{}
- store.On("Team").Return(teamStore)
- store.On("User").Return(userStore)
-
- team := &model.Team{Id: "test-team-id", Name: "test-team"}
- teamStore.On("Get", "test-team-id").Return(team, nil)
-
- existingUser := &model.User{Id: "existing-user-id", Username: "existinguser", Email: "shared@example.com"}
- userStore.On("GetByEmail", "shared@example.com").Return(existingUser, nil)
-
- var joinedUserIDs []string
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinedUserIDs = append(joinedUserIDs, user.Id)
- return &model.TeamMember{}, nil
- },
- SendPasswordReset: func(email string) (bool, *model.AppError) {
- return true, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
- importer := NewWithAdminFlag(store, actions, config, false)
-
- slackUsers := []slackUser{
- {Id: "U001", Username: "slackuser", Profile: slackProfile{FirstName: "Slack", LastName: "User", Email: "shared@example.com"}},
- }
-
- importerLog := new(bytes.Buffer)
- addedUsers := importer.slackAddUsers(rctx, "test-team-id", slackUsers, importerLog)
-
- assert.NotContains(t, addedUsers, "U001", "user should be skipped entirely on email conflict")
- assert.Empty(t, joinedUserIDs, "no account should be joined to the team")
- userStore.AssertNotCalled(t, "Save", mock.Anything, mock.Anything)
- assert.Contains(t, importerLog.String(), "api.slackimport.slack_add_users.merge_existing_skipped_non_admin")
- assert.NotContains(t, importerLog.String(), "api.slackimport.slack_add_users.email")
- assert.NotContains(t, importerLog.String(), "api.slackimport.slack_add_users.unable_import")
-}
-
-// TestSlackAddUsersNonAdminImportSkipsExistingTeamMember tests that a non-admin import skips
-// a Slack user whose email matches an existing Mattermost account even when that account is
-// already a team member, preventing post authorship forgery.
-func TestSlackAddUsersNonAdminImportSkipsExistingTeamMember(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- teamStore := &mocks.TeamStore{}
- userStore := &mocks.UserStore{}
- store.On("Team").Return(teamStore)
- store.On("User").Return(userStore)
-
- team := &model.Team{Id: "test-team-id", Name: "test-team"}
- teamStore.On("Get", "test-team-id").Return(team, nil)
-
- existingUser := &model.User{Id: "existing-user-id", Username: "existinguser", Email: "shared@example.com"}
- userStore.On("GetByEmail", "shared@example.com").Return(existingUser, nil)
-
- var joinedUserIDs []string
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinedUserIDs = append(joinedUserIDs, user.Id)
- return &model.TeamMember{}, nil
- },
- SendPasswordReset: func(email string) (bool, *model.AppError) {
- return true, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
- importer := NewWithAdminFlag(store, actions, config, false)
-
- slackUsers := []slackUser{
- {Id: "U001", Username: "slackuser", Profile: slackProfile{FirstName: "Slack", LastName: "User", Email: "shared@example.com"}},
- }
-
- importerLog := new(bytes.Buffer)
- addedUsers := importer.slackAddUsers(rctx, "test-team-id", slackUsers, importerLog)
-
- assert.NotContains(t, addedUsers, "U001", "existing account should be skipped even when already on the team")
- assert.Empty(t, joinedUserIDs, "no account should be joined to the team")
- userStore.AssertNotCalled(t, "Save", mock.Anything, mock.Anything)
- assert.Contains(t, importerLog.String(), "api.slackimport.slack_add_users.merge_existing_skipped_non_admin")
-}
-
-// TestSlackAddUsersAdminImportMergesExistingUser tests that an admin import preserves the
-// existing behavior of adopting an existing Mattermost account that shares an email with a
-// Slack user.
-func TestSlackAddUsersAdminImportMergesExistingUser(t *testing.T) {
- rctx := request.TestContext(t)
-
- store := &mocks.Store{}
- teamStore := &mocks.TeamStore{}
- userStore := &mocks.UserStore{}
- store.On("Team").Return(teamStore)
- store.On("User").Return(userStore)
-
- team := &model.Team{Id: "test-team-id", Name: "test-team"}
- teamStore.On("Get", "test-team-id").Return(team, nil)
-
- existingUser := &model.User{Id: "existing-user-id", Username: "existinguser", Email: "shared@example.com"}
- userStore.On("GetByEmail", "shared@example.com").Return(existingUser, nil)
-
- var joinedUserIDs []string
- actions := Actions{
- JoinUserToTeam: func(team *model.Team, user *model.User, userRequestorId string) (*model.TeamMember, *model.AppError) {
- joinedUserIDs = append(joinedUserIDs, user.Id)
- return &model.TeamMember{}, nil
- },
- SendPasswordReset: func(email string) (bool, *model.AppError) {
- return true, nil
- },
- }
-
- config := &model.Config{}
- config.SetDefaults()
- importer := NewWithAdminFlag(store, actions, config, true)
-
- slackUsers := []slackUser{
- {Id: "U001", Username: "slackuser", Profile: slackProfile{FirstName: "Slack", LastName: "User", Email: "shared@example.com"}},
- }
-
- importerLog := new(bytes.Buffer)
- addedUsers := importer.slackAddUsers(rctx, "test-team-id", slackUsers, importerLog)
-
- require.Contains(t, addedUsers, "U001")
- assert.Equal(t, "existing-user-id", addedUsers["U001"].Id, "admin import should still adopt the existing account")
- assert.Contains(t, joinedUserIDs, "existing-user-id")
-
- userStore.AssertNotCalled(t, "Save", mock.Anything, mock.Anything)
-
- expectedLog := "api.slackimport.slack_add_users.created" +
- "===============\r\n\r\n" +
- "api.slackimport.slack_add_users.merge_existing"
- assert.Equal(t, expectedLog, importerLog.String())
-}
-
-// TestSlackAddUsersCreatesNewAccountWhenNoExistingMatch tests that when no existing account
-// matches the Slack user's email, a new account is created and joined to the team, regardless
-// of whether the import is an admin import.
-func TestSlackAddUsersCreatesNewAccountWhenNoExistingMatch(t *testing.T) {
- rctx := request.TestContext(t)
- s := newSlackAddUsersTestSetup(t)
-
- importer := s.newImporterWithAdminFlag(s.defaultActions(), false)
- importerLog := new(bytes.Buffer)
- addedUsers := importer.slackAddUsers(rctx, "test-team-id", defaultSlackUsers(), importerLog)
-
- require.Contains(t, addedUsers, "U001")
- assert.Equal(t, s.savedUser.Id, addedUsers["U001"].Id)
- assert.Contains(t, importerLog.String(), "api.slackimport.slack_add_users.email")
-}
diff --git a/server/public/model/audit_events.go b/server/public/model/audit_events.go
index d0c0bed1d4e6..2e9732eb4ec3 100644
--- a/server/public/model/audit_events.go
+++ b/server/public/model/audit_events.go
@@ -237,7 +237,6 @@ const (
const (
AuditEventBulkImport = "bulkImport" // bulk import data from a file
AuditEventDeleteImport = "deleteImport" // delete import file
- AuditEventSlackImport = "slackImport" // import data from Slack
)
// Jobs
@@ -433,7 +432,6 @@ const (
AuditEventAddUserToTeamFromInvite = "addUserToTeamFromInvite" // add user to team using invitation link
AuditEventCreateTeam = "createTeam" // create team
AuditEventDeleteTeam = "deleteTeam" // delete team
- AuditEventImportTeam = "importTeam" // import team data from external source
AuditEventInvalidateAllEmailInvites = "invalidateAllEmailInvites" // invalidate all pending email invitations
AuditEventInviteGuestsToChannels = "inviteGuestsToChannels" // invite guest users to specific channels
AuditEventInviteUsersToTeam = "inviteUsersToTeam" // invite users to team
diff --git a/server/public/model/client4.go b/server/public/model/client4.go
index f13f4827f9c2..f54b5dada2b7 100644
--- a/server/public/model/client4.go
+++ b/server/public/model/client4.go
@@ -269,10 +269,6 @@ func (c *Client4) teamStatsRoute(teamId string) clientRoute {
return c.teamRoute(teamId).Join("stats")
}
-func (c *Client4) teamImportRoute(teamId string) clientRoute {
- return c.teamRoute(teamId).Join("import")
-}
-
func (c *Client4) channelsRoute() clientRoute {
return newClientRoute("channels")
}
@@ -2629,50 +2625,6 @@ func (c *Client4) GetTeamUnread(ctx context.Context, teamId, userId string) (*Te
return DecodeJSONFromResponse[*TeamUnread](r)
}
-// ImportTeam will import an exported team from other app into a existing team.
-func (c *Client4) ImportTeam(ctx context.Context, data []byte, filesize int, importFrom, filename, teamId string) (map[string]string, *Response, error) {
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
-
- part, err := writer.CreateFormFile("file", filename)
- if err != nil {
- return nil, nil, err
- }
-
- if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
- return nil, nil, err
- }
-
- part, err = writer.CreateFormField("filesize")
- if err != nil {
- return nil, nil, err
- }
-
- if _, err = io.Copy(part, strings.NewReader(strconv.Itoa(filesize))); err != nil {
- return nil, nil, err
- }
-
- part, err = writer.CreateFormField("importFrom")
- if err != nil {
- return nil, nil, err
- }
-
- if _, err = io.Copy(part, strings.NewReader(importFrom)); err != nil {
- return nil, nil, err
- }
-
- if err = writer.Close(); err != nil {
- return nil, nil, err
- }
-
- r, err := c.doAPIRequestReaderRoute(ctx, http.MethodPost, c.teamImportRoute(teamId), writer.FormDataContentType(), body, nil)
- if err != nil {
- return nil, BuildResponse(r), err
- }
- defer closeBody(r)
- return DecodeJSONFromResponse[map[string]string](r)
-}
-
// InviteUsersToTeam invite users by email to the team.
func (c *Client4) InviteUsersToTeam(ctx context.Context, teamId string, userEmails []string) (*Response, error) {
r, err := c.doAPIPostJSON(ctx, c.teamRoute(teamId).Join("invite", "email"), userEmails)
diff --git a/server/public/model/migration.go b/server/public/model/migration.go
index 7ece8d257ba8..065062067e9b 100644
--- a/server/public/model/migration.go
+++ b/server/public/model/migration.go
@@ -66,4 +66,5 @@ const (
MigrationKeyAddManageAgentPermissions = "add_manage_agent_permissions"
MigrationKeyAddEditFileAttachmentPermission = "add_edit_file_attachment_permission"
MigrationKeyAddDiscoverableChannelPermissions = "add_discoverable_channel_permissions"
+ MigrationRemoveImportTeamPermission = "remove_import_team_permission"
)
diff --git a/server/public/model/permission.go b/server/public/model/permission.go
index 9a89b665473c..abb69c3b54a6 100644
--- a/server/public/model/permission.go
+++ b/server/public/model/permission.go
@@ -99,7 +99,6 @@ var PermissionDeleteOthersPosts *Permission
var PermissionRemoveUserFromTeam *Permission
var PermissionCreateTeam *Permission
var PermissionManageTeam *Permission
-var PermissionImportTeam *Permission
var PermissionViewTeam *Permission
var PermissionListUsersWithoutTeam *Permission
var PermissionReadJobs *Permission
@@ -1190,12 +1189,6 @@ func initializePermissions() {
"authentication.permissions.manage_team.description",
PermissionScopeTeam,
}
- PermissionImportTeam = &Permission{
- "import_team",
- "authentication.permissions.import_team.name",
- "authentication.permissions.import_team.description",
- PermissionScopeTeam,
- }
PermissionViewTeam = &Permission{
"view_team",
"authentication.permissions.view_team.name",
@@ -2611,7 +2604,6 @@ func initializePermissions() {
PermissionDeleteOthersEmojis,
PermissionRemoveUserFromTeam,
PermissionManageTeam,
- PermissionImportTeam,
PermissionViewTeam,
PermissionViewMembers,
PermissionInviteGuest,
diff --git a/server/public/model/role.go b/server/public/model/role.go
index d4a392849069..e0327e8fac31 100644
--- a/server/public/model/role.go
+++ b/server/public/model/role.go
@@ -1067,7 +1067,6 @@ func MakeDefaultRoles() map[string]*Role {
Permissions: []string{
PermissionRemoveUserFromTeam.Id,
PermissionManageTeam.Id,
- PermissionImportTeam.Id,
PermissionManageTeamRoles.Id,
PermissionManageTeamAccessRules.Id,
PermissionManageChannelRoles.Id,
diff --git a/server/tests/slack-import-test-channels.json b/server/tests/slack-import-test-channels.json
deleted file mode 100644
index c8a2b5ec375c..000000000000
--- a/server/tests/slack-import-test-channels.json
+++ /dev/null
@@ -1,165 +0,0 @@
-[
- {
- "id": "C13CFUSDV",
- "name": "artwork",
- "created": "1461575251",
- "creator": "U07Q4MHCP",
- "is_archived": false,
- "is_general": false,
- "members": [
- "U07Q4MHCP",
- "U13C5JZ7W",
- "U13CLGTKK",
- "U13CSKE2E"
- ],
- "topic": {
- "value": "",
- "creator": "",
- "last_set": "0"
- },
- "purpose": {
- "value": "Discussing art",
- "creator": "U07Q4MHCP",
- "last_set": "1461575252"
- }
- },
- {
- "id": "C13CN0Q0H",
- "name": "eating-out",
- "created": "1461575308",
- "creator": "U07Q4MHCP",
- "is_archived": false,
- "is_general": false,
- "members": [
- "U07Q4MHCP",
- "U13C5JZ7W",
- "U13CLGTKK",
- "U13CSKE2E"
- ],
- "topic": {
- "value": "",
- "creator": "",
- "last_set": "0"
- },
- "purpose": {
- "value": "Restaurants around town",
- "creator": "U07Q4MHCP",
- "last_set": "1461575309"
- }
- },
- {
- "id": "C07Q4SBL3",
- "name": "general",
- "created": "1437124885",
- "creator": "U07Q4MHCP",
- "is_archived": false,
- "is_general": true,
- "members": [
- "U07Q4MHCP",
- "U13C5JZ7W",
- "U13CLGTKK",
- "U13CSKE2E",
- "U15F73RL4",
- "U15F922BY",
- "U15FHKEKX",
- "U15FHR92R",
- "U15FUR1QS",
- "U1THQPEMV",
- "U24CG47QU"
- ],
- "topic": {
- "value": "Company-wide announcements and work-based matters",
- "creator": "",
- "last_set": "0"
- },
- "purpose": {
- "value": "This channel is for team-wide communication and announcements. All team members are in this channel.",
- "creator": "",
- "last_set": "0"
- }
- },
- {
- "id": "C13B104PM",
- "name": "movies",
- "created": "1461575333",
- "creator": "U07Q4MHCP",
- "is_archived": false,
- "is_general": false,
- "members": [
- "U07Q4MHCP",
- "U13C5JZ7W",
- "U13CLGTKK",
- "U13CSKE2E"
- ],
- "topic": {
- "value": "",
- "creator": "",
- "last_set": "0"
- },
- "purpose": {
- "value": "New movies",
- "creator": "U07Q4MHCP",
- "last_set": "1461575334"
- }
- },
- {
- "id": "C15FGPREU",
- "name": "outings",
- "created": "1462216386",
- "creator": "U15FUR1QS",
- "is_archived": false,
- "is_general": false,
- "members": [
- "U07Q4MHCP",
- "U13C5JZ7W",
- "U13CLGTKK",
- "U13CSKE2E",
- "U15F73RL4",
- "U15F922BY",
- "U15FHKEKX",
- "U15FHR92R",
- "U15FUR1QS"
- ],
- "topic": {
- "value": "",
- "creator": "",
- "last_set": "0"
- },
- "purpose": {
- "value": "Let's discuss our next monthly outing",
- "creator": "U15FUR1QS",
- "last_set": "1462216387"
- }
- },
- {
- "id": "C07Q4MBTL",
- "name": "random",
- "created": "1437124885",
- "creator": "U07Q4MHCP",
- "is_archived": false,
- "is_general": false,
- "members": [
- "U07Q4MHCP",
- "U13C5JZ7W",
- "U13CLGTKK",
- "U13CSKE2E",
- "U15F73RL4",
- "U15F922BY",
- "U15FHKEKX",
- "U15FHR92R",
- "U15FUR1QS",
- "U1THQPEMV",
- "U24CG47QU"
- ],
- "topic": {
- "value": "Non-work banter and water cooler conversation",
- "creator": "",
- "last_set": "0"
- },
- "purpose": {
- "value": "A place for non-work-related flimflam, faffing, hodge-podge or jibber-jabber you'd prefer to keep out of more focused work-related channels.",
- "creator": "",
- "last_set": "0"
- }
- }
-]
\ No newline at end of file
diff --git a/server/tests/slack-import-test-direct-messages.json b/server/tests/slack-import-test-direct-messages.json
deleted file mode 100644
index c13fe1b3363a..000000000000
--- a/server/tests/slack-import-test-direct-messages.json
+++ /dev/null
@@ -1,34 +0,0 @@
-[
- {
- "id": "D24TJMP61",
- "created": "1472131245",
- "members": [
- "U1VVY70HK",
- "U1W4XLR62"
- ]
- },
- {
- "id": "D24TJNXUG",
- "created": "1472132335",
- "members": [
- "U1XG9JUH1",
- "U20APGU90"
- ]
- },
- {
- "id": "D24TLE5V5",
- "created": "1472131113",
- "members": [
- "U1TQ3A3L7",
- "U1URR19QV"
- ]
- },
- {
- "id": "D24TM4MPT",
- "created": "1472131021",
- "members": [
- "U1V47GAM9",
- "U1XVNP6DT"
- ]
- }
-]
\ No newline at end of file
diff --git a/server/tests/slack-import-test-group-direct-messages.json b/server/tests/slack-import-test-group-direct-messages.json
deleted file mode 100644
index 4e00c78c8a23..000000000000
--- a/server/tests/slack-import-test-group-direct-messages.json
+++ /dev/null
@@ -1,69 +0,0 @@
-[
- {
- "id": "G1XSSNBTP",
- "name": "mpdm-mcauly--sutton--barker--lankford-1",
- "created": 1470239933,
- "creator": "U1FFMLSKC",
- "is_archived": false,
- "members": [
- "U1934896U",
- "U1TTSPAGP",
- "U1FFMLSKC",
- "U1U234D4P"
- ],
- "topic": {
- "value": "Group messaging",
- "creator": "U1FFMLSKC",
- "last_set": "1470239933"
- },
- "purpose": {
- "value": "Group messaging with: @mcauly @sutton @barker @lankford",
- "creator": "U1FFMLSKC",
- "last_set": "1470239933"
- }
- },
- {
- "id": "G22KV02G2",
- "name": "mpdm-mcauly--wolfgram--weddington-1",
- "created": 1471532476,
- "creator": "U1K9SEY9K",
- "is_archived": false,
- "members": [
- "U1934896U",
- "U1K9SEY9K",
- "U1YTGR5CP"
- ],
- "topic": {
- "value": "Group messaging",
- "creator": "U1K9SEY9K",
- "last_set": "1471532476"
- },
- "purpose": {
- "value": "Group messaging with: @mcauly @wolfgram @weddington",
- "creator": "U1K9SEY9K",
- "last_set": "1471532476"
- }
- },
- {
- "id": "G2436812A",
- "name": "mpdm-mcauly--lovell--buck-1",
- "created": 1471964085,
- "creator": "U1934896U",
- "is_archived": false,
- "members": [
- "U1934896U",
- "U1YYZNDJP",
- "U1JSZSPR7"
- ],
- "topic": {
- "value": "Group messaging",
- "creator": "U1934896U",
- "last_set": "1471964085"
- },
- "purpose": {
- "value": "Group messaging with: @mcauly @lovell @buck",
- "creator": "U1934896U",
- "last_set": "1471964085"
- }
- }
-]
\ No newline at end of file
diff --git a/server/tests/slack-import-test-posts.json b/server/tests/slack-import-test-posts.json
deleted file mode 100644
index 07b8c65d5bac..000000000000
--- a/server/tests/slack-import-test-posts.json
+++ /dev/null
@@ -1,288 +0,0 @@
-[
- {
- "type": "message",
- "user": "U07Q4MHCP",
- "text": "<@U24CG47QU>",
- "ts": "1472932439.000002"
- },
- {
- "user": "U07Q4MHCP",
- "text": "<@U07Q4MHCP|lindy> has left the channel",
- "type": "message",
- "subtype": "channel_leave",
- "ts": "1469440454.000002"
- },
- {
- "user": "U15FUR1QS",
- "purpose": "Let's discuss our next monthly outing",
- "type": "message",
- "subtype": "channel_purpose",
- "text": "<@U15FUR1QS|igor> set the channel purpose: Let's discuss our next monthly outing",
- "ts": "1462216387.000003"
- },
- {
- "type": "message",
- "subtype": "file_share",
- "text": "<@U07Q4MHCP|lindy> uploaded a file: ",
- "file": {
- "id": "F1WB5FH7W",
- "created": 1469782519,
- "timestamp": 1469782519,
- "name": "Pdf.pdf",
- "title": "Pdf.pdf",
- "mimetype": "application\/pdf",
- "filetype": "pdf",
- "pretty_type": "PDF",
- "user": "U07Q4MHCP",
- "editable": false,
- "size": 457717,
- "mode": "hosted",
- "is_external": false,
- "external_type": "",
- "is_public": true,
- "public_url_shared": false,
- "display_as_bot": false,
- "username": "",
- "url_private": "https:\/\/files.slack.com\/files-pri\/T07Q4MHC7-F1WB5FH7W\/pdf.pdf?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "url_private_download": "https:\/\/files.slack.com\/files-pri\/T07Q4MHC7-F1WB5FH7W\/download\/pdf.pdf?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "permalink": "https:\/\/slackexportteam.slack.com\/files\/lindy\/F1WB5FH7W\/pdf.pdf",
- "permalink_public": "https:\/\/slack-files.com\/T07Q4MHC7-F1WB5FH7W-8560bd68a4",
- "channels": [
- "C07Q4MBTL"
- ],
- "groups": [
-
- ],
- "ims": [
-
- ],
- "comments_count": 0
- },
- "user": "U07Q4MHCP",
- "upload": true,
- "display_as_bot": false,
- "username": "<@U07Q4MHCP|lindy>",
- "bot_id": null,
- "ts": "1469782519.000002"
- },
- {
- "user": "U24CG47QU",
- "text": "<@U24CG47QU|lindy.invite> has joined the channel",
- "type": "message",
- "subtype": "channel_join",
- "ts": "1472028157.000002"
- },
- {
- "bot_id": "B13CH9S5V",
- "attachments": [
- {
- "fallback": ": M2 informatique PLS et EID2 on vous retrouve sur le du master, les invitations partiront lundi (ou demander en MP).",
- "ts": 1474722287,
- "author_name": "Master 2 PLS",
- "author_link": "https:\/\/twitter.com\/m2pls\/status\/779667900600844288",
- "author_icon": "https:\/\/pbs.twimg.com\/profile_images\/649829889890086912\/ULpVHs9S_normal.jpg",
- "author_subname": "@m2pls",
- "pretext": "",
- "text": "M2 informatique PLS et EID2 on vous retrouve sur le du master, les invitations partiront lundi (ou demander en MP).",
- "service_name": "twitter",
- "service_url": "https:\/\/twitter.com\/",
- "from_url": "https:\/\/twitter.com\/m2pls\/status\/779667900600844288",
- "id": 1,
- "footer": "Twitter",
- "footer_icon": "https:\/\/a.slack-edge.com\/6e067\/img\/services\/twitter_pixel_snapped_32.png"
- }
- ],
- "text": null,
- "type": "message",
- "subtype": "bot_message",
- "ts": "1474722288.637240"
- },
- {
- "type": "message",
- "user": "U07Q4MHCP",
- "text": "",
- "attachments": [
- {
- "from_url": "https:\/\/slackexportteam.slack.com\/archives\/general\/p1469781868000005",
- "fallback": "[July 29th, 2016 1:44 AM] lindy: permalink?",
- "ts": "1469781868.000005",
- "author_subname": "lindy",
- "channel_id": "C07Q4SBL3",
- "channel_name": "general",
- "is_msg_unfurl": true,
- "text": "permalink?",
- "author_link": "https:\/\/slackexportteam.slack.com\/team\/lindy",
- "author_icon": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F0180%2Fimg%2Favatars%2Fava_0001-48.png",
- "mrkdwn_in": [
- "text"
- ],
- "id": 1,
- "footer": "Posted in #general"
- }
- ],
- "ts": "1469781880.000006"
- },
- {
- "type": "message",
- "subtype": "file_share",
- "text": "<@U07Q4MHCP|lindy> uploaded a file: ",
- "file": {
- "id": "F1WBDJW93",
- "created": 1469782076,
- "timestamp": 1469782076,
- "name": "fb-wow.gif",
- "title": "fb-wow.gif",
- "mimetype": "image\/gif",
- "filetype": "gif",
- "pretty_type": "GIF",
- "user": "U07Q4MHCP",
- "editable": false,
- "size": 27235,
- "mode": "hosted",
- "is_external": false,
- "external_type": "",
- "is_public": true,
- "public_url_shared": false,
- "display_as_bot": false,
- "username": "",
- "url_private": "https:\/\/files.slack.com\/files-pri\/T07Q4MHC7-F1WBDJW93\/fb-wow.gif?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "url_private_download": "https:\/\/files.slack.com\/files-pri\/T07Q4MHC7-F1WBDJW93\/download\/fb-wow.gif?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "thumb_64": "https:\/\/files.slack.com\/files-tmb\/T07Q4MHC7-F1WBDJW93-7031b90b93\/fb-wow_64.png?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "thumb_80": "https:\/\/files.slack.com\/files-tmb\/T07Q4MHC7-F1WBDJW93-7031b90b93\/fb-wow_80.png?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "thumb_360": "https:\/\/files.slack.com\/files-tmb\/T07Q4MHC7-F1WBDJW93-7031b90b93\/fb-wow_360.png?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "thumb_360_w": 59,
- "thumb_360_h": 59,
- "thumb_160": "https:\/\/files.slack.com\/files-tmb\/T07Q4MHC7-F1WBDJW93-7031b90b93\/fb-wow_160.png?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "image_exif_rotation": 1,
- "original_w": 59,
- "original_h": 59,
- "deanimate_gif": "https:\/\/files.slack.com\/files-tmb\/T07Q4MHC7-F1WBDJW93-7031b90b93\/fb-wow_deanimate_gif.png?t=xoxe-7820731415-83616398485-83620610359-b95751e3ea",
- "permalink": "https:\/\/slackexportteam.slack.com\/files\/lindy\/F1WBDJW93\/fb-wow.gif",
- "permalink_public": "https:\/\/slack-files.com\/T07Q4MHC7-F1WBDJW93-718081d960",
- "channels": [
- "C07Q4SBL3"
- ],
- "groups": [
-
- ],
- "ims": [
-
- ],
- "comments_count": 0
- },
- "user": "U07Q4MHCP",
- "upload": true,
- "display_as_bot": false,
- "username": "<@U07Q4MHCP|lindy>",
- "bot_id": null,
- "ts": "1469782077.000010"
- },
- {
- "type": "message",
- "text": "multiple_attachments",
- "files": [
- {
- "id": "FL9GSN60L",
- "created": 1562758810,
- "timestamp": 1562758810,
- "name": "Screenshot from 2019-07-09 17-08-24.png",
- "title": "Screenshot from 2019-07-09 17-08-24.png",
- "mimetype": "image\/png",
- "filetype": "png",
- "pretty_type": "PNG",
- "user": "UKW2XKH7E",
- "editable": false,
- "size": 79513,
- "mode": "hosted",
- "is_external": false,
- "external_type": "",
- "is_public": true,
- "public_url_shared": false,
- "display_as_bot": false,
- "username": "",
- "url_private": "https:\/\/files.slack.com\/files-pri\/TKK4L637U-FL9GSN60L\/screenshot_from_2019-07-09_17-08-24.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "url_private_download": "https:\/\/files.slack.com\/files-pri\/TKK4L637U-FL9GSN60L\/download\/screenshot_from_2019-07-09_17-08-24.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_64": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_64.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_80": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_80.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_360": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_360.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_360_w": 197,
- "thumb_360_h": 360,
- "thumb_480": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_480.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_480_w": 263,
- "thumb_480_h": 480,
- "thumb_160": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_160.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_720": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_720.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_720_w": 395,
- "thumb_720_h": 720,
- "thumb_800": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_800.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_800_w": 439,
- "thumb_800_h": 800,
- "thumb_960": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_960.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_960_w": 526,
- "thumb_960_h": 960,
- "thumb_1024": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL9GSN60L-e70b98a77f\/screenshot_from_2019-07-09_17-08-24_1024.png?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_1024_w": 561,
- "thumb_1024_h": 1024,
- "image_exif_rotation": 1,
- "original_w": 632,
- "original_h": 1153,
- "permalink": "https:\/\/svenstestteam.slack.com\/files\/UKW2XKH7E\/FL9GSN60L\/screenshot_from_2019-07-09_17-08-24.png",
- "permalink_public": "https:\/\/slack-files.com\/TKK4L637U-FL9GSN60L-19fdfd655a",
- "has_rich_preview": false
- },
- {
- "id": "FL3LBGRT3",
- "created": 1562758810,
- "timestamp": 1562758810,
- "name": "vscode.jpg",
- "title": "vscode.jpg",
- "mimetype": "image\/jpeg",
- "filetype": "jpg",
- "pretty_type": "JPEG",
- "user": "UKW2XKH7E",
- "editable": false,
- "size": 549439,
- "mode": "hosted",
- "is_external": false,
- "external_type": "",
- "is_public": true,
- "public_url_shared": false,
- "display_as_bot": false,
- "username": "",
- "url_private": "https:\/\/files.slack.com\/files-pri\/TKK4L637U-FL3LBGRT3\/vscode.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "url_private_download": "https:\/\/files.slack.com\/files-pri\/TKK4L637U-FL3LBGRT3\/download\/vscode.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_64": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_64.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_80": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_80.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_360": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_360.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_360_w": 360,
- "thumb_360_h": 203,
- "thumb_480": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_480.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_480_w": 480,
- "thumb_480_h": 270,
- "thumb_160": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_160.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_720": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_720.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_720_w": 720,
- "thumb_720_h": 405,
- "thumb_800": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_800.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_800_w": 800,
- "thumb_800_h": 450,
- "thumb_960": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_960.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_960_w": 960,
- "thumb_960_h": 540,
- "thumb_1024": "https:\/\/files.slack.com\/files-tmb\/TKK4L637U-FL3LBGRT3-ece28edc37\/vscode_1024.jpg?t=xoxe-665156207266-683692183393-689571362948-acb8000145d9929cba3d5c38249a6673",
- "thumb_1024_w": 1024,
- "thumb_1024_h": 576,
- "image_exif_rotation": 1,
- "original_w": 1920,
- "original_h": 1080,
- "permalink": "https:\/\/svenstestteam.slack.com\/files\/UKW2XKH7E\/FL3LBGRT3\/vscode.jpg",
- "permalink_public": "https:\/\/slack-files.com\/TKK4L637U-FL3LBGRT3-ffd5da7911",
- "has_rich_preview": false
- }
- ],
- "upload": true,
- "user": "UKW2XKH7E",
- "ts": "1562758814.000100",
- "client_msg_id": "bf2a1192-6b57-4dd3-90f9-8e833fcad758"
- }
-]
diff --git a/server/tests/slack-import-test-private-channels.json b/server/tests/slack-import-test-private-channels.json
deleted file mode 100644
index 1e5adf991d4d..000000000000
--- a/server/tests/slack-import-test-private-channels.json
+++ /dev/null
@@ -1,25 +0,0 @@
-[
- {
- "id": "G1T7B1T9N",
- "name": "csi-team-chat",
- "created": 1468959839,
- "creator": "U17G57PB6",
- "is_archived": false,
- "members": [
- "U15CML9C1",
- "U1934896U",
- "U17G57PB6",
- "U1T7A2J56"
- ],
- "topic": {
- "value": "\"A constantly evolving landscape of hills\"",
- "creator": "U1934896U",
- "last_set": "1469017133"
- },
- "purpose": {
- "value": "CSI team chat",
- "creator": "U17G57PB6",
- "last_set": "1468972624"
- }
- }
-]
\ No newline at end of file
diff --git a/server/tests/slack-import-test-users.json b/server/tests/slack-import-test-users.json
deleted file mode 100644
index 40b53a45b578..000000000000
--- a/server/tests/slack-import-test-users.json
+++ /dev/null
@@ -1,363 +0,0 @@
-[
- {
- "id": "U13CLGTKK",
- "team_id": "T07Q4MHC7",
- "name": "alice",
- "deleted": false,
- "status": null,
- "color": "4bbe2e",
- "real_name": "Alice",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Alice",
- "last_name": "",
- "avatar_hash": "g2280143ab5d",
- "real_name": "Alice",
- "real_name_normalized": "Alice",
- "email": "lindy+1@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/2280143ab5dd760d0a951b74bd06185a.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F0180%2Fimg%2Favatars%2Fava_0021-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/2280143ab5dd760d0a951b74bd06185a.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0021-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/2280143ab5dd760d0a951b74bd06185a.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F3654%2Fimg%2Favatars%2Fava_0021-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/2280143ab5dd760d0a951b74bd06185a.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0021-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/2280143ab5dd760d0a951b74bd06185a.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0021-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/2280143ab5dd760d0a951b74bd06185a.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0021-512.png",
- "fields": null
- },
- "is_admin": true,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U15F73RL4",
- "team_id": "T07Q4MHC7",
- "name": "brett",
- "deleted": false,
- "status": null,
- "color": "e96699",
- "real_name": "Brett",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Brett",
- "last_name": "",
- "avatar_hash": "g16de52e9c3d",
- "real_name": "Brett",
- "real_name_normalized": "Brett",
- "email": "lindy+13@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/16de52e9c3dd5917b72a327f9b36a7db.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0001-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/16de52e9c3dd5917b72a327f9b36a7db.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0001-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/16de52e9c3dd5917b72a327f9b36a7db.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F0180%2Fimg%2Favatars%2Fava_0001-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/16de52e9c3dd5917b72a327f9b36a7db.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F3654%2Fimg%2Favatars%2Fava_0001-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/16de52e9c3dd5917b72a327f9b36a7db.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0001-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/16de52e9c3dd5917b72a327f9b36a7db.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0001-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U15FUR1QS",
- "team_id": "T07Q4MHC7",
- "name": "igor",
- "deleted": false,
- "status": null,
- "color": "5b89d5",
- "real_name": "Igor",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Igor",
- "last_name": "",
- "avatar_hash": "gd2d8b6603c7",
- "real_name": "Igor",
- "real_name_normalized": "Igor",
- "email": "lindy+12@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/d2d8b6603c7e7eb443ccc103f50852c5.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0019-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/d2d8b6603c7e7eb443ccc103f50852c5.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0019-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/d2d8b6603c7e7eb443ccc103f50852c5.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0019-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/d2d8b6603c7e7eb443ccc103f50852c5.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0019-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/d2d8b6603c7e7eb443ccc103f50852c5.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0019-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/d2d8b6603c7e7eb443ccc103f50852c5.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0019-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U15FHR92R",
- "team_id": "T07Q4MHC7",
- "name": "karen",
- "deleted": false,
- "status": null,
- "color": "684b6c",
- "real_name": "Karen",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Karen",
- "last_name": "",
- "avatar_hash": "g6fd11d3e252",
- "real_name": "Karen",
- "real_name_normalized": "Karen",
- "email": "lindy+10@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/6fd11d3e252e571d7371c7a5bbeb54ff.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0007-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/6fd11d3e252e571d7371c7a5bbeb54ff.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0007-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/6fd11d3e252e571d7371c7a5bbeb54ff.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0007-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/6fd11d3e252e571d7371c7a5bbeb54ff.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0007-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/6fd11d3e252e571d7371c7a5bbeb54ff.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0007-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/6fd11d3e252e571d7371c7a5bbeb54ff.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0007-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U1THQPEMV",
- "team_id": "T07Q4MHC7",
- "name": "lindsay",
- "deleted": false,
- "status": null,
- "color": "2b6836",
- "real_name": "Lindsay B",
- "tz": "America\/New_York",
- "tz_label": "Eastern Daylight Time",
- "tz_offset": -14400,
- "profile": {
- "first_name": "Lindsay",
- "last_name": "B",
- "avatar_hash": "g59bda23e5db",
- "real_name": "Lindsay B",
- "real_name_normalized": "Lindsay B",
- "email": "lindsay@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/59bda23e5dbe5930c3941c7bb90c3e2a.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0000-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/59bda23e5dbe5930c3941c7bb90c3e2a.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0000-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/59bda23e5dbe5930c3941c7bb90c3e2a.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0000-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/59bda23e5dbe5930c3941c7bb90c3e2a.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0000-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/59bda23e5dbe5930c3941c7bb90c3e2a.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0000-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/59bda23e5dbe5930c3941c7bb90c3e2a.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0000-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U07Q4MHCP",
- "team_id": "T07Q4MHC7",
- "name": "lindy",
- "deleted": false,
- "status": null,
- "color": "9f69e7",
- "real_name": "",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "avatar_hash": "g822d051544b",
- "real_name": "",
- "real_name_normalized": "",
- "email": "lindy@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0001-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0001-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F0180%2Fimg%2Favatars%2Fava_0001-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F3654%2Fimg%2Favatars%2Fava_0001-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0001-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/822d051544b1f50f1e9f015cd2990edc.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0001-512.png",
- "fields": null
- },
- "is_admin": true,
- "is_owner": true,
- "is_primary_owner": true,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U24CG47QU",
- "team_id": "T07Q4MHC7",
- "name": "lindy.invite",
- "deleted": false,
- "status": null,
- "color": "99a949",
- "real_name": "lindy invite",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "lindy",
- "last_name": "invite",
- "avatar_hash": "g1c46f51413d",
- "real_name": "lindy invite",
- "real_name_normalized": "lindy invite",
- "email": "lindy+invite@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/1c46f51413d2af8048dfaadcf43f5a03.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0015-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/1c46f51413d2af8048dfaadcf43f5a03.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0015-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/1c46f51413d2af8048dfaadcf43f5a03.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0015-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/1c46f51413d2af8048dfaadcf43f5a03.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0015-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/1c46f51413d2af8048dfaadcf43f5a03.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0015-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/1c46f51413d2af8048dfaadcf43f5a03.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0015-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U15FHKEKX",
- "team_id": "T07Q4MHC7",
- "name": "peter",
- "deleted": false,
- "status": null,
- "color": "e0a729",
- "real_name": "Peter",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Peter",
- "last_name": "",
- "avatar_hash": "g5f5c8dacc18",
- "real_name": "Peter",
- "real_name_normalized": "Peter",
- "email": "lindy+14@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/5f5c8dacc18f616a0dcdec904fdd5cea.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0014-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/5f5c8dacc18f616a0dcdec904fdd5cea.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0014-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/5f5c8dacc18f616a0dcdec904fdd5cea.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0014-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/5f5c8dacc18f616a0dcdec904fdd5cea.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0014-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/5f5c8dacc18f616a0dcdec904fdd5cea.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0014-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/5f5c8dacc18f616a0dcdec904fdd5cea.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0014-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U15F922BY",
- "team_id": "T07Q4MHC7",
- "name": "sam",
- "deleted": false,
- "status": null,
- "color": "674b1b",
- "real_name": "Sam",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Sam",
- "last_name": "",
- "avatar_hash": "gef41b9b8176",
- "real_name": "Sam",
- "real_name_normalized": "Sam",
- "email": "lindy+11@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/ef41b9b8176fba6eaffdfe04415df8be.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F0180%2Fimg%2Favatars%2Fava_0017-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/ef41b9b8176fba6eaffdfe04415df8be.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0017-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/ef41b9b8176fba6eaffdfe04415df8be.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0017-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/ef41b9b8176fba6eaffdfe04415df8be.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0017-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/ef41b9b8176fba6eaffdfe04415df8be.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0017-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/ef41b9b8176fba6eaffdfe04415df8be.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0017-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U13CSKE2E",
- "team_id": "T07Q4MHC7",
- "name": "sandy",
- "deleted": false,
- "status": null,
- "color": "3c989f",
- "real_name": "Sandy",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Sandy",
- "last_name": "",
- "avatar_hash": "g7bb3eebada3",
- "real_name": "Sandy",
- "real_name_normalized": "Sandy",
- "email": "lindy+3@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/7bb3eebada3d8ee1251980df86904ec9.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0013-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/7bb3eebada3d8ee1251980df86904ec9.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F0180%2Fimg%2Favatars%2Fava_0013-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/7bb3eebada3d8ee1251980df86904ec9.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0013-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/7bb3eebada3d8ee1251980df86904ec9.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0013-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/7bb3eebada3d8ee1251980df86904ec9.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0013-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/7bb3eebada3d8ee1251980df86904ec9.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0013-512.png",
- "fields": null
- },
- "is_admin": false,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- },
- {
- "id": "U13C5JZ7W",
- "team_id": "T07Q4MHC7",
- "name": "ted",
- "deleted": false,
- "status": null,
- "color": "e7392d",
- "real_name": "Ted",
- "tz": "Africa\/Cairo",
- "tz_label": "Eastern European Time",
- "tz_offset": 7200,
- "profile": {
- "first_name": "Ted",
- "last_name": "",
- "avatar_hash": "g5b31933276f",
- "real_name": "Ted",
- "real_name_normalized": "Ted",
- "email": "lindy+2@example.com",
- "image_24": "https:\/\/secure.gravatar.com\/avatar\/5b31933276f699aa8ff55a8fe4a2e7cb.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0022-24.png",
- "image_32": "https:\/\/secure.gravatar.com\/avatar\/5b31933276f699aa8ff55a8fe4a2e7cb.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0022-32.png",
- "image_48": "https:\/\/secure.gravatar.com\/avatar\/5b31933276f699aa8ff55a8fe4a2e7cb.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0022-48.png",
- "image_72": "https:\/\/secure.gravatar.com\/avatar\/5b31933276f699aa8ff55a8fe4a2e7cb.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0022-72.png",
- "image_192": "https:\/\/secure.gravatar.com\/avatar\/5b31933276f699aa8ff55a8fe4a2e7cb.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0022-192.png",
- "image_512": "https:\/\/secure.gravatar.com\/avatar\/5b31933276f699aa8ff55a8fe4a2e7cb.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0022-512.png",
- "fields": null
- },
- "is_admin": true,
- "is_owner": false,
- "is_primary_owner": false,
- "is_restricted": false,
- "is_ultra_restricted": false,
- "is_bot": false
- }
-]
\ No newline at end of file
diff --git a/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx b/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx
index 0fea18e18927..cd3f9e807699 100644
--- a/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx
+++ b/webapp/channels/src/components/admin_console/permission_schemes_settings/strings/permissions.tsx
@@ -185,16 +185,6 @@ export const permissionRolesStrings: Record