mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
serve the API at /v1/workspace
The base moves off /v1/team for two reasons that point the same way. It collided: /v1/team on hanzo.team is a carve-out routed to the older cloud service, so the moment this image shipped its own API would have been answered by something else -- a 404 on our own endpoints, on the host we serve from. And workspace is the better noun. It is what the product calls itself in its own words, and it does not read as though the API belongs to one team among several. No /api/ anywhere in a path we own. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -89,7 +89,7 @@ export async function loginByAPI(loginId: string, password: string, token = '',
|
||||
}
|
||||
|
||||
// Log in via API
|
||||
await requestContext.post(`${testConfig.baseURL}/v1/team/users/login`, {
|
||||
await requestContext.post(`${testConfig.baseURL}/v1/workspace/users/login`, {
|
||||
data,
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ const axios = require('axios');
|
||||
|
||||
module.exports = async ({sender, message, channelId, rootId, createAt = 0, baseUrl}) => {
|
||||
const loginResponse = await axios({
|
||||
url: `${baseUrl}/v1/team/users/login`,
|
||||
url: `${baseUrl}/v1/workspace/users/login`,
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
method: 'post',
|
||||
data: {login_id: sender.username, password: sender.password},
|
||||
@@ -21,7 +21,7 @@ module.exports = async ({sender, message, channelId, rootId, createAt = 0, baseU
|
||||
let response;
|
||||
try {
|
||||
response = await axios({
|
||||
url: `${baseUrl}/v1/team/posts`,
|
||||
url: `${baseUrl}/v1/workspace/posts`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
|
||||
@@ -143,7 +143,7 @@ function getCompleteOauth(req, res) {
|
||||
|
||||
async function postOAuthMessage(req, res) {
|
||||
const {channelId, message, rootId, createAt} = req.body;
|
||||
const apiUrl = `${baseUrl}/v1/team/posts`;
|
||||
const apiUrl = `${baseUrl}/v1/workspace/posts`;
|
||||
authedUser.sign({
|
||||
method: 'post',
|
||||
url: apiUrl,
|
||||
@@ -280,7 +280,7 @@ async function openDialog(dialog) {
|
||||
try {
|
||||
await axios({
|
||||
method: 'post',
|
||||
url: `${baseUrl}/v1/team/actions/dialogs/open`,
|
||||
url: `${baseUrl}/v1/workspace/actions/dialogs/open`,
|
||||
data: dialog,
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -50,7 +50,7 @@ function structuralEnv(): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
// Readiness requires both the /v1/team/system/ping health check AND the permissions-migration job
|
||||
// Readiness requires both the /v1/workspace/system/ping health check AND the permissions-migration job
|
||||
// scheduler's "All migrations are complete." log line (scheduler.go, jobs/migrations package).
|
||||
// Ping alone isn't enough: MigrationKeyAdvancedPermissionsPhase2 runs as an async job whose
|
||||
// scheduler deliberately delays its first tick 60s after startup — a real window a spec's very
|
||||
@@ -86,7 +86,7 @@ export async function startMattermostContainer(
|
||||
.withStartupTimeout(5 * 60_000)
|
||||
.withWaitStrategy(
|
||||
Wait.forAll([
|
||||
Wait.forHttp('/v1/team/system/ping', MATTERMOST_PORT).forStatusCode(200),
|
||||
Wait.forHttp('/v1/workspace/system/ping', MATTERMOST_PORT).forStatusCode(200),
|
||||
Wait.forLogMessage(/All migrations are complete\./, 1),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -290,7 +290,7 @@ export async function deletePolicy(page: Page, policyName: string): Promise<void
|
||||
/**
|
||||
* Click the "Run Sync Job" button and return the new job ID immediately.
|
||||
*
|
||||
* Intercepts the POST /v1/team/jobs response so the caller gets the exact job ID
|
||||
* Intercepts the POST /v1/workspace/jobs response so the caller gets the exact job ID
|
||||
* without a polling round-trip. Pass the returned ID to waitForLatestSyncJob as
|
||||
* the `jobId` argument to skip Phase 1 and poll the specific job directly.
|
||||
*
|
||||
@@ -302,7 +302,7 @@ export async function runSyncJob(page: Page): Promise<string | null> {
|
||||
// Do NOT filter by resp.ok() here — capture the response regardless of status
|
||||
// so we can surface API errors explicitly instead of silently swallowing them.
|
||||
const jobResponsePromise = page.waitForResponse(
|
||||
(resp) => resp.url().includes('/v1/team/jobs') && resp.request().method() === 'POST',
|
||||
(resp) => resp.url().includes('/v1/workspace/jobs') && resp.request().method() === 'POST',
|
||||
{timeout: 10000},
|
||||
);
|
||||
// "Run Channel Sync" when team ABAC is on, else "Run Sync Job".
|
||||
@@ -310,15 +310,15 @@ export async function runSyncJob(page: Page): Promise<string | null> {
|
||||
try {
|
||||
const response = await jobResponsePromise;
|
||||
if (!response.ok()) {
|
||||
throw new Error(`POST /v1/team/jobs failed with status ${response.status()}`);
|
||||
throw new Error(`POST /v1/workspace/jobs failed with status ${response.status()}`);
|
||||
}
|
||||
const job = await response.json();
|
||||
if (!job.id) {
|
||||
throw new Error('POST /v1/team/jobs response missing id field');
|
||||
throw new Error('POST /v1/workspace/jobs response missing id field');
|
||||
}
|
||||
return job.id as string;
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.startsWith('POST /v1/team/jobs')) {
|
||||
if (err instanceof Error && err.message.startsWith('POST /v1/workspace/jobs')) {
|
||||
throw err;
|
||||
}
|
||||
// Interception timed out — callers fall back to list-based polling in Phase 1.
|
||||
|
||||
@@ -456,17 +456,17 @@ const defaultServerConfig: AdminConfig = {
|
||||
AllowCustomThemes: true,
|
||||
AllowedThemes: [],
|
||||
},
|
||||
GitLabSettings: {
|
||||
HanzoSettings: {
|
||||
Enable: false,
|
||||
Secret: '',
|
||||
Id: '',
|
||||
Scope: '',
|
||||
AuthEndpoint: '',
|
||||
TokenEndpoint: '',
|
||||
UserAPIEndpoint: '',
|
||||
Scope: 'openid email profile',
|
||||
AuthEndpoint: 'https://hanzo.id/v1/iam/oauth/authorize',
|
||||
TokenEndpoint: 'https://hanzo.id/v1/iam/oauth/token',
|
||||
UserAPIEndpoint: 'https://hanzo.id/v1/iam/oauth/userinfo',
|
||||
DiscoveryEndpoint: '',
|
||||
ButtonText: '',
|
||||
ButtonColor: '',
|
||||
ButtonColor: '#000000',
|
||||
UsePreferredUsername: false,
|
||||
},
|
||||
GoogleSettings: {
|
||||
|
||||
@@ -113,7 +113,7 @@ export default class ChannelsPostCreate {
|
||||
files && files.length > 0
|
||||
? page.waitForResponse(
|
||||
(r) =>
|
||||
r.url().includes('/v1/team/files') &&
|
||||
r.url().includes('/v1/workspace/files') &&
|
||||
r.request().method() === 'POST' &&
|
||||
r.status() >= 200 &&
|
||||
r.status() < 300,
|
||||
|
||||
+2
-2
@@ -172,7 +172,7 @@ export default class SystemProperties {
|
||||
/**
|
||||
* Click Save and wait for the server round-trip to complete.
|
||||
*
|
||||
* Monitors the actual /v1/team/custom_profile_attributes/fields network
|
||||
* Monitors the actual /v1/workspace/custom_profile_attributes/fields network
|
||||
* requests rather than relying on the coarse `networkidle` heuristic,
|
||||
* then asserts the button returns to disabled (no pending changes).
|
||||
*/
|
||||
@@ -181,7 +181,7 @@ export default class SystemProperties {
|
||||
|
||||
const saveResponsePromise = this.page.waitForResponse(
|
||||
(resp) =>
|
||||
resp.url().includes('/v1/team/custom_profile_attributes/fields') && resp.request().method() !== 'GET',
|
||||
resp.url().includes('/v1/workspace/custom_profile_attributes/fields') && resp.request().method() !== 'GET',
|
||||
);
|
||||
|
||||
await this.saveButton.click();
|
||||
|
||||
+2
-2
@@ -9,14 +9,14 @@ import {UserActionMenu} from './user_action_menu';
|
||||
const USERS_REPORT_RESPONSE_TIMEOUT_MS = 10_000;
|
||||
|
||||
/**
|
||||
* Resolves when GET /v1/team/reports/users completes. Start this promise before the
|
||||
* Resolves when GET /v1/workspace/reports/users completes. Start this promise before the
|
||||
* action that triggers the fetch (click, search, etc.) so Playwright does not sit
|
||||
* on the timeout when the response already finished.
|
||||
*/
|
||||
export function waitForUsersReportResponse(page: Page) {
|
||||
return page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/v1/team/reports/users') &&
|
||||
response.url().includes('/v1/workspace/reports/users') &&
|
||||
!response.url().includes('/count') &&
|
||||
response.request().method() === 'GET' &&
|
||||
response.ok(),
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ test('Profile popover should show correct fields after at-mention autocomplete @
|
||||
// Reload the page so the browser fetches the new config synchronously.
|
||||
// waitUntil above only confirms the server-side state; the browser updates its
|
||||
// Redux store via WebSocket (CONFIG_CHANGED event) which can lag significantly.
|
||||
// A full page reload forces a fresh /v1/team/config/client fetch, so the privacy
|
||||
// A full page reload forces a fresh /v1/workspace/config/client fetch, so the privacy
|
||||
// settings are guaranteed to be in effect before we render any popover.
|
||||
await channelsPage.page.reload();
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
+1
-1
@@ -615,7 +615,7 @@ test.describe('Channel Settings Modal - Access Control Tab', () => {
|
||||
// # Confirm and wait for the access-control sync job that applies the removal
|
||||
const [syncJobResponse] = await Promise.all([
|
||||
page.waitForResponse(
|
||||
(response) => response.url().includes('/v1/team/jobs') && response.request().method() === 'POST',
|
||||
(response) => response.url().includes('/v1/workspace/jobs') && response.request().method() === 'POST',
|
||||
{timeout: 10000},
|
||||
),
|
||||
confirmModal.getByRole('button', {name: 'Save'}).click(),
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function closeRootModal(page: Page): Promise<void> {
|
||||
|
||||
/**
|
||||
* Run `send` (typically fill slash command + click Send) while waiting for
|
||||
* POST /v1/team/commands/execute so the server finishes the slash handler before assertions.
|
||||
* POST /v1/workspace/commands/execute so the server finishes the slash handler before assertions.
|
||||
*/
|
||||
/**
|
||||
* Upload a file via the UI attachment menu when the demo plugin is active.
|
||||
@@ -61,7 +61,7 @@ export async function uploadFileViaYourComputer(
|
||||
const filePath = path.join(assetPath, filename);
|
||||
const uploadResponsePromise = page.waitForResponse(
|
||||
(r) =>
|
||||
r.url().includes('/v1/team/files') &&
|
||||
r.url().includes('/v1/workspace/files') &&
|
||||
r.request().method() === 'POST' &&
|
||||
r.status() >= 200 &&
|
||||
r.status() < 300,
|
||||
@@ -80,7 +80,7 @@ export async function sendDemoSlashCommand(page: Page, send: () => Promise<void>
|
||||
// plugin is transiently inactive and the server returns HTTP 500. The caller is responsible
|
||||
// for detecting a failed command (e.g. via a retry loop or explicit status check).
|
||||
const responsePromise = page.waitForResponse(
|
||||
(r) => r.url().includes('/v1/team/commands/execute') && r.request().method() === 'POST',
|
||||
(r) => r.url().includes('/v1/workspace/commands/execute') && r.request().method() === 'POST',
|
||||
{timeout: 45_000},
|
||||
);
|
||||
await Promise.all([send(), responsePromise]);
|
||||
|
||||
+2
-2
@@ -9,11 +9,11 @@ import {expect, getFileFromAsset, test} from '@mattermost/playwright-lib';
|
||||
import {setupDemoPlugin, DEMO_PLUGIN_ID} from '../../helpers';
|
||||
|
||||
async function sendSlashCommand(page: Page, send: () => Promise<void>, adminClient: Client4): Promise<void> {
|
||||
// Slash commands hit POST /v1/team/commands/execute — not POST /posts (see web client executeCommand).
|
||||
// Slash commands hit POST /v1/workspace/commands/execute — not POST /posts (see web client executeCommand).
|
||||
// Retry once if the server returns 500 (plugin transiently inactive between setup and first use).
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
const responsePromise = page.waitForResponse(
|
||||
(r) => r.url().includes('/v1/team/commands/execute') && r.request().method() === 'POST',
|
||||
(r) => r.url().includes('/v1/workspace/commands/execute') && r.request().method() === 'POST',
|
||||
{timeout: 30_000},
|
||||
);
|
||||
const [, response] = await Promise.all([send(), responsePromise]);
|
||||
|
||||
+4
-4
@@ -113,14 +113,14 @@ test.describe('Attribute-Value Masking - Admin Roles', {tag: ['@abac', '@abac_ma
|
||||
|
||||
// Server: direct DELETE must return HTTP 403 regardless of UI state
|
||||
const status = await page.evaluate(async (id: string) => {
|
||||
const resp = await fetch(`/v1/team/access_control_policies/${id}`, {
|
||||
const resp = await fetch(`/v1/workspace/access_control_policies/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
return resp.status;
|
||||
}, policyId);
|
||||
|
||||
expect(status, `DELETE /v1/team/access_control_policies/${policyId} returned ${status}`).toBe(403);
|
||||
expect(status, `DELETE /v1/workspace/access_control_policies/${policyId} returned ${status}`).toBe(403);
|
||||
} finally {
|
||||
for (const id of policyIds) {
|
||||
try {
|
||||
@@ -247,7 +247,7 @@ test.describe('Attribute-Value Masking - Admin Roles', {tag: ['@abac', '@abac_ma
|
||||
expect(policyId).toMatch(/^[A-Za-z0-9]{26}$/);
|
||||
const status = await page.evaluate(
|
||||
async ({id, teamId}: {id: string; teamId: string}) => {
|
||||
const resp = await fetch(`/v1/team/access_control_policies/${id}?team_id=${teamId}`, {
|
||||
const resp = await fetch(`/v1/workspace/access_control_policies/${id}?team_id=${teamId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
@@ -350,7 +350,7 @@ test.describe('Attribute-Value Masking - Admin Roles', {tag: ['@abac', '@abac_ma
|
||||
// Server-side guard: direct DELETE by the channel admin must 403
|
||||
expect(policyId).toMatch(/^[A-Za-z0-9]{26}$/);
|
||||
const status = await page.evaluate(async (id: string) => {
|
||||
const resp = await fetch(`/v1/team/access_control_policies/${id}`, {
|
||||
const resp = await fetch(`/v1/workspace/access_control_policies/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
|
||||
+4
-4
@@ -350,14 +350,14 @@ test.describe('Attribute-Value Masking - API Redaction and Delete', {tag: ['@aba
|
||||
|
||||
// Server: direct DELETE must return HTTP 403
|
||||
const status = await page.evaluate(async (id: string) => {
|
||||
const resp = await fetch(`/v1/team/access_control_policies/${id}`, {
|
||||
const resp = await fetch(`/v1/workspace/access_control_policies/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
return resp.status;
|
||||
}, policyId);
|
||||
|
||||
expect(status, `DELETE /v1/team/access_control_policies/${policyId} returned ${status}`).toBe(403);
|
||||
expect(status, `DELETE /v1/workspace/access_control_policies/${policyId} returned ${status}`).toBe(403);
|
||||
|
||||
// Verify policy still exists via DB
|
||||
const expression = (await getStoredPolicyRuleExpressions(policyId))[0] ?? '';
|
||||
@@ -458,7 +458,7 @@ test.describe('Attribute-Value Masking - API Redaction and Delete', {tag: ['@aba
|
||||
// Server blocks a direct API attempt to remove a masked condition
|
||||
const status = await page.evaluate(
|
||||
async ({policyId: id, fn}: {policyId: string; fn: string}) => {
|
||||
const resp = await fetch('/v1/team/access_control_policies', {
|
||||
const resp = await fetch('/v1/workspace/access_control_policies', {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: JSON.stringify({
|
||||
@@ -473,7 +473,7 @@ test.describe('Attribute-Value Masking - API Redaction and Delete', {tag: ['@aba
|
||||
{policyId, fn: programFieldName},
|
||||
);
|
||||
|
||||
expect(status, `PUT /v1/team/access_control_policies (id=${policyId}) returned ${status}`).toBe(403);
|
||||
expect(status, `PUT /v1/workspace/access_control_policies (id=${policyId}) returned ${status}`).toBe(403);
|
||||
} finally {
|
||||
for (const id of policyIds) {
|
||||
try {
|
||||
|
||||
+4
-4
@@ -186,7 +186,7 @@ export async function openExistingPolicy(page: Page, policyName: string): Promis
|
||||
|
||||
export async function getRawPolicyExpression(page: Page, policyId: string): Promise<string> {
|
||||
const data = await page.evaluate(async (id: string) => {
|
||||
const resp = await fetch(`/v1/team/access_control_policies/${id}`, {
|
||||
const resp = await fetch(`/v1/workspace/access_control_policies/${id}`, {
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
return resp.json();
|
||||
@@ -209,14 +209,14 @@ export async function resubmitPolicyExpression(
|
||||
): Promise<{status: number; body: any}> {
|
||||
return page.evaluate(
|
||||
async ({id, expr}: {id: string; expr: string}) => {
|
||||
const getResp = await fetch(`/v1/team/access_control_policies/${id}`, {
|
||||
const getResp = await fetch(`/v1/workspace/access_control_policies/${id}`, {
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
const policy = await getResp.json();
|
||||
if (Array.isArray(policy?.rules) && policy.rules.length > 0) {
|
||||
policy.rules[0].expression = expr;
|
||||
}
|
||||
const putResp = await fetch('/v1/team/access_control_policies', {
|
||||
const putResp = await fetch('/v1/workspace/access_control_policies', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -233,7 +233,7 @@ export async function resubmitPolicyExpression(
|
||||
|
||||
export async function searchPoliciesExpression(page: Page, term: string): Promise<string> {
|
||||
const data = await page.evaluate(async (t: string) => {
|
||||
const resp = await fetch('/v1/team/access_control_policies/search', {
|
||||
const resp = await fetch('/v1/workspace/access_control_policies/search', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
+2
-2
@@ -342,7 +342,7 @@ test.describe('Attribute-Value Masking - Table Editor', {tag: ['@abac', '@abac_m
|
||||
// Try to create a policy containing a non-held value ("Delta") via direct API
|
||||
const statusWithDelta = await page.evaluate(
|
||||
async ({fieldName: fn}: {fieldName: string}) => {
|
||||
const resp = await fetch('/v1/team/access_control_policies', {
|
||||
const resp = await fetch('/v1/workspace/access_control_policies', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -365,7 +365,7 @@ test.describe('Attribute-Value Masking - Table Editor', {tag: ['@abac', '@abac_m
|
||||
// Also verify that the masked placeholder literal is rejected
|
||||
const statusWithMasked = await page.evaluate(
|
||||
async ({fieldName: fn}: {fieldName: string}) => {
|
||||
const resp = await fetch('/v1/team/access_control_policies', {
|
||||
const resp = await fetch('/v1/workspace/access_control_policies', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
+2
-2
@@ -254,7 +254,7 @@ test('MM-T5791 Editing policy to add attribute with auto-add enabled', async ({p
|
||||
// Intercept the sync-job POST triggered by "Apply policy" so we can poll the
|
||||
// exact job ID instead of using the racy UI-table path.
|
||||
const editSyncJobIdPromise = page
|
||||
.waitForResponse((r) => r.url().includes('/v1/team/jobs') && r.request().method() === 'POST', {timeout: 15_000})
|
||||
.waitForResponse((r) => r.url().includes('/v1/workspace/jobs') && r.request().method() === 'POST', {timeout: 15_000})
|
||||
.then(async (r) => (r.ok() ? (((await r.json()) as {id?: string}).id ?? null) : null))
|
||||
.catch(() => null);
|
||||
|
||||
@@ -492,7 +492,7 @@ test('MM-T5792 Editing policy to remove attribute rule with auto-add enabled', a
|
||||
// Intercept the sync-job POST triggered by "Apply policy" so we can poll the
|
||||
// exact job ID instead of using the racy UI-table path.
|
||||
const editSyncJobIdPromiseT5792 = page
|
||||
.waitForResponse((r) => r.url().includes('/v1/team/jobs') && r.request().method() === 'POST', {timeout: 15_000})
|
||||
.waitForResponse((r) => r.url().includes('/v1/workspace/jobs') && r.request().method() === 'POST', {timeout: 15_000})
|
||||
.then(async (r) => (r.ok() ? (((await r.json()) as {id?: string}).id ?? null) : null))
|
||||
.catch(() => null);
|
||||
|
||||
|
||||
@@ -553,7 +553,7 @@ export async function createBasicPolicy(
|
||||
if (applyVisible) {
|
||||
// Arm the response interceptor BEFORE the click so we never miss the POST.
|
||||
const jobResponsePromise = page
|
||||
.waitForResponse((r) => r.url().includes('/v1/team/jobs') && r.request().method() === 'POST', {
|
||||
.waitForResponse((r) => r.url().includes('/v1/workspace/jobs') && r.request().method() === 'POST', {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.then(async (r) => (r.ok() ? (((await r.json()) as {id?: string}).id ?? null) : null))
|
||||
@@ -731,7 +731,7 @@ export async function createMultiAttributePolicy(
|
||||
await applyPolicyButton.waitFor({state: 'visible', timeout: 5000});
|
||||
|
||||
const jobResponsePromise = page
|
||||
.waitForResponse((r) => r.url().includes('/v1/team/jobs') && r.request().method() === 'POST', {timeout: 10_000})
|
||||
.waitForResponse((r) => r.url().includes('/v1/workspace/jobs') && r.request().method() === 'POST', {timeout: 10_000})
|
||||
.then(async (r) => (r.ok() ? (((await r.json()) as {id?: string}).id ?? null) : null))
|
||||
.catch(() => null);
|
||||
|
||||
@@ -901,7 +901,7 @@ export async function createAdvancedPolicy(
|
||||
|
||||
// Arm the response interceptor BEFORE the click so we never miss the POST.
|
||||
const jobResponsePromise = page
|
||||
.waitForResponse((r) => r.url().includes('/v1/team/jobs') && r.request().method() === 'POST', {timeout: 10_000})
|
||||
.waitForResponse((r) => r.url().includes('/v1/workspace/jobs') && r.request().method() === 'POST', {timeout: 10_000})
|
||||
.then(async (r) => (r.ok() ? (((await r.json()) as {id?: string}).id ?? null) : null))
|
||||
.catch(() => null);
|
||||
|
||||
@@ -924,7 +924,7 @@ export async function activatePolicy(client: Client4, policyId: string): Promise
|
||||
* Wait for a sync job to complete.
|
||||
*
|
||||
* When `expectedJobId` is supplied (obtained from `runSyncJob()` which
|
||||
* intercepts the POST /v1/team/jobs response), polls GET /v1/team/jobs/{id}
|
||||
* intercepts the POST /v1/workspace/jobs response), polls GET /v1/workspace/jobs/{id}
|
||||
* directly — race-free under PW_WORKERS >= 2 because it checks the exact
|
||||
* job, not the first row of a shared list.
|
||||
*
|
||||
@@ -948,7 +948,7 @@ export async function waitForLatestSyncJob(
|
||||
async () => {
|
||||
try {
|
||||
const job: any = await page.evaluate(async (id: string) => {
|
||||
const resp = await fetch(`/v1/team/jobs/${encodeURIComponent(id)}`, {
|
||||
const resp = await fetch(`/v1/workspace/jobs/${encodeURIComponent(id)}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!resp.ok) {
|
||||
|
||||
@@ -5,12 +5,12 @@ import type {AdminConfig} from '@mattermost/types/config';
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
// Wait for the GET /v1/team/config re-fetch that our config_changed websocket
|
||||
// Wait for the GET /v1/workspace/config re-fetch that our config_changed websocket
|
||||
// handler triggers. This is the signal that entities.admin.config in the
|
||||
// browser's Redux store is now up-to-date with the server's current state.
|
||||
async function waitForAdminConfigRefresh(page: import('@playwright/test').Page) {
|
||||
return page.waitForResponse(
|
||||
(resp) => resp.url().endsWith('/v1/team/config') && resp.request().method() === 'GET' && resp.status() === 200,
|
||||
(resp) => resp.url().endsWith('/v1/workspace/config') && resp.request().method() === 'GET' && resp.status() === 200,
|
||||
{timeout: 10000},
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ test.describe('System Console > Concurrent config saves', () => {
|
||||
|
||||
// # Simulate another admin saving an unrelated section via API.
|
||||
// Set up the response listener before triggering the change so we don't
|
||||
// miss the GET /v1/team/config that our config_changed handler dispatches.
|
||||
// miss the GET /v1/workspace/config that our config_changed handler dispatches.
|
||||
const configRefresh = waitForAdminConfigRefresh(page);
|
||||
await adminClient.patchConfig({TeamSettings: {MaxUsersPerTeam: 99}} as Partial<AdminConfig>);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
// Helper function to intercept API request and modify the response
|
||||
async function interceptConfigWithLandingPage(page: Page, enabled: boolean) {
|
||||
const apiUrl = '**/v1/team/config/client?format=old**';
|
||||
const apiUrl = '**/v1/workspace/config/client?format=old**';
|
||||
await page.route(apiUrl, (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
|
||||
@@ -361,7 +361,7 @@ test('should configure new IntuneSettings with SAML auth provider', async ({pw})
|
||||
new Blob([idpCert], {type: 'application/x-x509-ca-cert'}),
|
||||
'Intune SAML Test.cer',
|
||||
);
|
||||
await fetch(`${serverUrl}/v1/team/saml/certificate/idp`, {
|
||||
await fetch(`${serverUrl}/v1/workspace/saml/certificate/idp`, {
|
||||
method: 'POST',
|
||||
body: idpFormData,
|
||||
credentials: 'include',
|
||||
@@ -372,7 +372,7 @@ test('should configure new IntuneSettings with SAML auth provider', async ({pw})
|
||||
'-----BEGIN CERTIFICATE-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArv1Qw4v7OMC2r7Qw4v7OMC2r7Qw4v7OMC2r7QwIDAQAB\n-----END CERTIFICATE-----\n';
|
||||
const spFormData = new FormData();
|
||||
spFormData.append('certificate', new Blob([spCert], {type: 'application/x-x509-ca-cert'}), 'saml-public-cert.pem');
|
||||
await fetch(`${serverUrl}/v1/team/saml/certificate/public`, {
|
||||
await fetch(`${serverUrl}/v1/workspace/saml/certificate/public`, {
|
||||
method: 'POST',
|
||||
body: spFormData,
|
||||
credentials: 'include',
|
||||
|
||||
@@ -335,10 +335,10 @@ test.describe('Single-channel guests', () => {
|
||||
// # Log in as admin (new browser context + page).
|
||||
const {context, systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
|
||||
// # Mock GET /v1/team/limits/server on the *context* before any navigation so the first
|
||||
// # Mock GET /v1/workspace/limits/server on the *context* before any navigation so the first
|
||||
// getServerLimits() populates Redux with a low limit. Also fulfill with an explicit
|
||||
// JSON body — route.fulfill({response, json}) does not reliably merge bodies in Playwright.
|
||||
await context.route('**/v1/team/limits/server', async (route) => {
|
||||
await context.route('**/v1/workspace/limits/server', async (route) => {
|
||||
const response = await route.fetch();
|
||||
let json: Record<string, unknown> = {};
|
||||
try {
|
||||
|
||||
+9
-9
@@ -233,7 +233,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
// Freeze the fields API so concurrent shard activity (field creates/deletes) cannot
|
||||
// trigger a WebSocket-driven re-fetch that wipes the CPA section from Redux mid-test.
|
||||
const frozenFields = Object.values(attributeFieldsMap);
|
||||
await systemConsolePage.page.route('**/v1/team/custom_profile_attributes/fields', async (route) => {
|
||||
await systemConsolePage.page.route('**/v1/workspace/custom_profile_attributes/fields', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
@@ -264,7 +264,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
// Keeping it active through the test body would intercept the save API call
|
||||
// (which also hits the /fields endpoint during submit), causing "Failed to update user".
|
||||
// Validation tests restore their own intercept via try/finally.
|
||||
await systemConsolePage.page.unroute('**/v1/team/custom_profile_attributes/fields').catch(() => {});
|
||||
await systemConsolePage.page.unroute('**/v1/workspace/custom_profile_attributes/fields').catch(() => {});
|
||||
});
|
||||
|
||||
test.afterEach(async ({pw}) => {
|
||||
@@ -275,7 +275,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
}
|
||||
// Safety-net unroute in case a validation test's try/finally was skipped by an
|
||||
// earlier error, or the beforeEach unroute was never reached.
|
||||
await systemConsolePage?.page.unroute('**/v1/team/custom_profile_attributes/fields').catch(() => {});
|
||||
await systemConsolePage?.page.unroute('**/v1/workspace/custom_profile_attributes/fields').catch(() => {});
|
||||
// Clean up custom user attribute fields
|
||||
const {adminClient: cleanupClient} = await pw.getAdminClient();
|
||||
await deleteCustomProfileAttributes(cleanupClient, attributeFieldsMap);
|
||||
@@ -412,7 +412,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
// handleCpaValueChange finds field===undefined and skips setting the error state,
|
||||
// so the field-error element never renders and the assertion times out.
|
||||
const frozenFields = Object.values(attributeFieldsMap);
|
||||
await systemConsolePage!.page.route('**/v1/team/custom_profile_attributes/fields', async (route) => {
|
||||
await systemConsolePage!.page.route('**/v1/workspace/custom_profile_attributes/fields', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
@@ -456,7 +456,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
// * Verify Save button remains disabled (no unsaved changes)
|
||||
await expect(userDetail.saveButton).toBeDisabled();
|
||||
} finally {
|
||||
await systemConsolePage!.page.unroute('**/v1/team/custom_profile_attributes/fields').catch(() => {});
|
||||
await systemConsolePage!.page.unroute('**/v1/workspace/custom_profile_attributes/fields').catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -466,7 +466,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
|
||||
// Re-apply the fields intercept — same race-condition guard as the email validation test.
|
||||
const frozenFields = Object.values(attributeFieldsMap);
|
||||
await systemConsolePage!.page.route('**/v1/team/custom_profile_attributes/fields', async (route) => {
|
||||
await systemConsolePage!.page.route('**/v1/workspace/custom_profile_attributes/fields', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
@@ -506,7 +506,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
// * Verify Cancel button disappears
|
||||
await expect(userDetail.cancelButton).not.toBeVisible();
|
||||
} finally {
|
||||
await systemConsolePage!.page.unroute('**/v1/team/custom_profile_attributes/fields').catch(() => {});
|
||||
await systemConsolePage!.page.unroute('**/v1/workspace/custom_profile_attributes/fields').catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -516,7 +516,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
|
||||
// Re-apply the fields intercept — same race-condition guard as the other validation tests.
|
||||
const frozenFields = Object.values(attributeFieldsMap);
|
||||
await systemConsolePage!.page.route('**/v1/team/custom_profile_attributes/fields', async (route) => {
|
||||
await systemConsolePage!.page.route('**/v1/workspace/custom_profile_attributes/fields', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
@@ -542,7 +542,7 @@ test.describe('System Console - Admin User Profile Editing', () => {
|
||||
// * Verify Cancel button is available
|
||||
await expect(userDetail.cancelButton).toBeVisible();
|
||||
} finally {
|
||||
await systemConsolePage!.page.unroute('**/v1/team/custom_profile_attributes/fields').catch(() => {});
|
||||
await systemConsolePage!.page.unroute('**/v1/workspace/custom_profile_attributes/fields').catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ test.describe('System Console - User Attributes display names', () => {
|
||||
const validIdentifier = `my_field_${Date.now()}`;
|
||||
|
||||
page.on('request', (request) => {
|
||||
if (request.method() === 'POST' && request.url().includes('/v1/team/custom_profile_attributes/fields')) {
|
||||
if (request.method() === 'POST' && request.url().includes('/v1/workspace/custom_profile_attributes/fields')) {
|
||||
apiPosts.push(request.url());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -834,7 +834,7 @@ func searchAccessControlPolicies(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
// updateActiveStatus updates the active status of a single access control policy.
|
||||
//
|
||||
// Deprecated: This endpoint is deprecated and will be removed in a future release.
|
||||
// Use PUT /v1/team/access_control/policies/activate instead, which supports batch updates.
|
||||
// Use PUT /v1/workspace/access_control/policies/activate instead, which supports batch updates.
|
||||
func updateActiveStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
|
||||
@@ -12,11 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) InitAgents() {
|
||||
// GET /v1/team/agents
|
||||
// GET /v1/workspace/agents
|
||||
api.BaseRoutes.Agents.Handle("", api.APISessionRequired(getAgents)).Methods(http.MethodGet)
|
||||
// GET /v1/team/agents/status
|
||||
// GET /v1/workspace/agents/status
|
||||
api.BaseRoutes.Agents.Handle("/status", api.APISessionRequired(getAgentsStatus)).Methods(http.MethodGet)
|
||||
// GET /v1/team/llmservices
|
||||
// GET /v1/workspace/llmservices
|
||||
api.BaseRoutes.LLMServices.Handle("", api.APISessionRequired(getLLMServices)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
||||
@@ -8192,7 +8192,7 @@ func TestChannelEndpointsExcludeSpaces(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestLocalChannelEndpoints404Spaces verifies the local (admin socket) channel endpoints 404 a
|
||||
// space, the same as /v1/team: space channels are excluded from the generic GetChannel these
|
||||
// space, the same as /v1/workspace: space channels are excluded from the generic GetChannel these
|
||||
// handlers resolve through, so they never reach the mutation.
|
||||
func TestLocalChannelEndpoints404Spaces(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestCORSRequestHandling(t *testing.T) {
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
host := fmt.Sprintf("http://localhost:%v", port)
|
||||
url := fmt.Sprintf("%v/v1/team/system/ping", host)
|
||||
url := fmt.Sprintf("%v/v1/workspace/system/ping", host)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -11,7 +11,7 @@ channels, posts, and other Hanzo Team resources.
|
||||
|
||||
# Architecture
|
||||
|
||||
The API is structured around resource-based endpoints under the /v1/team/ path.
|
||||
The API is structured around resource-based endpoints under the /v1/workspace/ path.
|
||||
Each endpoint is handled by specific handler functions that provide different
|
||||
levels of authentication and authorization:
|
||||
|
||||
|
||||
@@ -20,58 +20,58 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) InitGroup() {
|
||||
// GET /v1/team/groups
|
||||
// GET /v1/workspace/groups
|
||||
api.BaseRoutes.Groups.Handle("", api.APISessionRequired(getGroups)).Methods(http.MethodGet)
|
||||
|
||||
// POST /v1/team/groups
|
||||
// POST /v1/workspace/groups
|
||||
api.BaseRoutes.Groups.Handle("", api.APISessionRequired(createGroup)).Methods(http.MethodPost)
|
||||
|
||||
// GET /v1/team/groups/:group_id
|
||||
// GET /v1/workspace/groups/:group_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}",
|
||||
api.APISessionRequired(getGroup)).Methods(http.MethodGet)
|
||||
|
||||
// PUT /v1/team/groups/:group_id/patch
|
||||
// PUT /v1/workspace/groups/:group_id/patch
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/patch",
|
||||
api.APISessionRequired(patchGroup)).Methods(http.MethodPut)
|
||||
|
||||
// POST /v1/team/groups/:group_id/teams/:team_id/link
|
||||
// POST /v1/team/groups/:group_id/channels/:channel_id/link
|
||||
// POST /v1/workspace/groups/:group_id/teams/:team_id/link
|
||||
// POST /v1/workspace/groups/:group_id/channels/:channel_id/link
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link",
|
||||
api.APISessionRequired(linkGroupSyncable)).Methods(http.MethodPost)
|
||||
|
||||
// DELETE /v1/team/groups/:group_id/teams/:team_id/link
|
||||
// DELETE /v1/team/groups/:group_id/channels/:channel_id/link
|
||||
// DELETE /v1/workspace/groups/:group_id/teams/:team_id/link
|
||||
// DELETE /v1/workspace/groups/:group_id/channels/:channel_id/link
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link",
|
||||
api.APISessionRequired(unlinkGroupSyncable)).Methods(http.MethodDelete)
|
||||
|
||||
// GET /v1/team/groups/:group_id/teams/:team_id
|
||||
// GET /v1/team/groups/:group_id/channels/:channel_id
|
||||
// GET /v1/workspace/groups/:group_id/teams/:team_id
|
||||
// GET /v1/workspace/groups/:group_id/channels/:channel_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}",
|
||||
api.APISessionRequired(getGroupSyncable)).Methods(http.MethodGet)
|
||||
|
||||
// GET /v1/team/groups/:group_id/teams
|
||||
// GET /v1/team/groups/:group_id/channels
|
||||
// GET /v1/workspace/groups/:group_id/teams
|
||||
// GET /v1/workspace/groups/:group_id/channels
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}",
|
||||
api.APISessionRequired(getGroupSyncables)).Methods(http.MethodGet)
|
||||
|
||||
// PUT /v1/team/groups/:group_id/teams/:team_id/patch
|
||||
// PUT /v1/team/groups/:group_id/channels/:channel_id/patch
|
||||
// PUT /v1/workspace/groups/:group_id/teams/:team_id/patch
|
||||
// PUT /v1/workspace/groups/:group_id/channels/:channel_id/patch
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/patch",
|
||||
api.APISessionRequired(patchGroupSyncable)).Methods(http.MethodPut)
|
||||
|
||||
// GET /v1/team/groups/:group_id/stats
|
||||
// GET /v1/workspace/groups/:group_id/stats
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/stats",
|
||||
api.APISessionRequired(getGroupStats)).Methods(http.MethodGet)
|
||||
|
||||
// GET /v1/team/groups/:group_id/members
|
||||
// GET /v1/workspace/groups/:group_id/members
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.APISessionRequired(getGroupMembers)).Methods(http.MethodGet)
|
||||
|
||||
// GET /v1/team/users/:user_id/groups
|
||||
// GET /v1/workspace/users/:user_id/groups
|
||||
api.BaseRoutes.Users.Handle("/{user_id:[A-Za-z0-9]+}/groups",
|
||||
api.APISessionRequired(getGroupsByUserId)).Methods(http.MethodGet)
|
||||
|
||||
// GET /v1/team/channels/:channel_id/groups
|
||||
// GET /v1/workspace/channels/:channel_id/groups
|
||||
api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/groups",
|
||||
api.APISessionRequired(getGroupsByChannel)).Methods(http.MethodGet)
|
||||
|
||||
@@ -79,27 +79,27 @@ func (api *API) InitGroup() {
|
||||
api.BaseRoutes.Groups.Handle("/names",
|
||||
api.APISessionRequired(getGroupsByNames)).Methods(http.MethodPost)
|
||||
|
||||
// GET /v1/team/teams/:team_id/groups
|
||||
// GET /v1/workspace/teams/:team_id/groups
|
||||
api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups",
|
||||
api.APISessionRequired(getGroupsByTeam)).Methods(http.MethodGet)
|
||||
|
||||
// GET /v1/team/teams/:team_id/groups_by_channels
|
||||
// GET /v1/workspace/teams/:team_id/groups_by_channels
|
||||
api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups_by_channels",
|
||||
api.APISessionRequired(getGroupsAssociatedToChannelsByTeam)).Methods(http.MethodGet)
|
||||
|
||||
// DELETE /v1/team/groups/:group_id
|
||||
// DELETE /v1/workspace/groups/:group_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}",
|
||||
api.APISessionRequired(deleteGroup)).Methods(http.MethodDelete)
|
||||
|
||||
// POST /v1/team/groups/:group_id
|
||||
// POST /v1/workspace/groups/:group_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/restore",
|
||||
api.APISessionRequired(restoreGroup)).Methods(http.MethodPost)
|
||||
|
||||
// POST /v1/team/groups/:group_id/members
|
||||
// POST /v1/workspace/groups/:group_id/members
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.APISessionRequired(addGroupMembers)).Methods(http.MethodPost)
|
||||
|
||||
// DELETE /v1/team/groups/:group_id/members
|
||||
// DELETE /v1/workspace/groups/:group_id/members
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.APISessionRequired(deleteGroupMembers)).Methods(http.MethodDelete)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func handlerForGzip(t *testing.T) func(*Context, http.ResponseWriter, *http.Requ
|
||||
func testAPIHandlerGzipMode(t *testing.T, name string, h http.Handler, token string) {
|
||||
t.Run("Handler: "+name+" No Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
h.ServeHTTP(resp, req)
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
@@ -35,7 +35,7 @@ func testAPIHandlerGzipMode(t *testing.T, name string, h http.Handler, token str
|
||||
|
||||
t.Run("Handler: "+name+" With Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
|
||||
@@ -48,7 +48,7 @@ func testAPIHandlerGzipMode(t *testing.T, name string, h http.Handler, token str
|
||||
func testAPIHandlerNoGzipMode(t *testing.T, name string, h http.Handler, token string) {
|
||||
t.Run("Handler: "+name+" No Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
|
||||
h.ServeHTTP(resp, req)
|
||||
@@ -58,7 +58,7 @@ func testAPIHandlerNoGzipMode(t *testing.T, name string, h http.Handler, token s
|
||||
|
||||
t.Run("Handler: "+name+" With Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// APIs for self-hosted workspaces to communicate with the backing customer & payments system.
|
||||
// Endpoints for cloud installations should not go in this file.
|
||||
func (api *API) InitHostedCustomer() {
|
||||
// POST /v1/team/hosted_customer/available
|
||||
// POST /v1/workspace/hosted_customer/available
|
||||
api.BaseRoutes.HostedCustomer.Handle("/signup_available", api.APISessionRequired(handleSignupAvailable)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ func (api *API) InitLdap() {
|
||||
|
||||
api.BaseRoutes.LDAP.Handle("/migrateid", api.APISessionRequired(migrateIDLdap)).Methods(http.MethodPost)
|
||||
|
||||
// GET /v1/team/ldap/groups?page=0&per_page=1000
|
||||
// GET /v1/workspace/ldap/groups?page=0&per_page=1000
|
||||
api.BaseRoutes.LDAP.Handle("/groups", api.APISessionRequired(getLdapGroups)).Methods(http.MethodGet)
|
||||
|
||||
// POST /v1/team/ldap/groups/:remote_id/link
|
||||
// POST /v1/workspace/ldap/groups/:remote_id/link
|
||||
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.APISessionRequired(linkLdapGroup)).Methods(http.MethodPost)
|
||||
|
||||
// DELETE /v1/team/ldap/groups/:remote_id/link
|
||||
// DELETE /v1/workspace/ldap/groups/:remote_id/link
|
||||
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.APISessionRequired(unlinkLdapGroup)).Methods(http.MethodDelete)
|
||||
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/public", api.APISessionRequired(addLdapPublicCertificate, handlerParamFileAPI)).Methods(http.MethodPost)
|
||||
|
||||
@@ -1560,7 +1560,7 @@ func TestCreatePostCheckOnlineStatus(t *testing.T) {
|
||||
|
||||
postJSON, jsonErr := json.Marshal(post)
|
||||
require.NoError(t, jsonErr)
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?set_online=false", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?set_online=false", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1573,7 +1573,7 @@ func TestCreatePostCheckOnlineStatus(t *testing.T) {
|
||||
|
||||
postJSON, jsonErr = json.Marshal(post)
|
||||
require.NoError(t, jsonErr)
|
||||
req = httptest.NewRequest("POST", "/v1/team/posts", bytes.NewReader(postJSON))
|
||||
req = httptest.NewRequest("POST", "/v1/workspace/posts", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1603,7 +1603,7 @@ func TestCreatePostSilentRejectedForHumanUser(t *testing.T) {
|
||||
|
||||
postJSON, jsonErr := json.Marshal(post)
|
||||
require.NoError(t, jsonErr)
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=true", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?silent=true", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1629,7 +1629,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("invalid silent value returns 400", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=invalid", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?silent=invalid", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1639,7 +1639,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("silent=false does not set prop", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=false", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?silent=false", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1653,7 +1653,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("silent=0 does not set prop", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=0", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?silent=0", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1667,7 +1667,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("empty silent param is ignored", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?silent=", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1705,7 +1705,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
}
|
||||
botPostJSON, err := json.Marshal(botPost)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=true", bytes.NewReader(botPostJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/posts?silent=true", bytes.NewReader(botPostJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+rtoken.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestRateLimitingMiddleware(t *testing.T) {
|
||||
th.App.Srv().Store().(*storemocks.Store).On("License").Return(&licenseStore)
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/team/system/ping", port)
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/workspace/system/ping", port)
|
||||
client := &http.Client{}
|
||||
|
||||
t.Run("requests within burst succeed", func(t *testing.T) {
|
||||
@@ -93,7 +93,7 @@ func TestRateLimitingVaryByHeader(t *testing.T) {
|
||||
th.App.Srv().Store().(*storemocks.Store).On("License").Return(&licenseStore)
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/team/system/ping", port)
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/workspace/system/ping", port)
|
||||
client := &http.Client{}
|
||||
|
||||
// 2 requests with client-A should succeed
|
||||
@@ -157,7 +157,7 @@ func TestRateLimitingVaryByUser(t *testing.T) {
|
||||
th.App.Srv().RateLimiter = rl
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/team/system/ping", port)
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/workspace/system/ping", port)
|
||||
client := &http.Client{}
|
||||
|
||||
userAToken := th.Client.AuthToken
|
||||
|
||||
@@ -160,7 +160,7 @@ func fillUserReportOptions(values url.Values) (*model.UserReportOptions, *model.
|
||||
|
||||
// getPostsForReporting retrieves posts for reporting purposes with cursor-based pagination.
|
||||
//
|
||||
// API Endpoint: POST /v1/team/reports/posts
|
||||
// API Endpoint: POST /v1/workspace/reports/posts
|
||||
//
|
||||
// Cursor Behavior:
|
||||
// - The cursor is opaque and self-contained (base64-encoded)
|
||||
|
||||
@@ -329,7 +329,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -351,7 +351,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -364,7 +364,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.Client, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.Client, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -377,7 +377,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -395,7 +395,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -411,7 +411,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404 for non-existent channel")
|
||||
})
|
||||
|
||||
@@ -426,7 +426,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "empty cursor should be accepted")
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -447,7 +447,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
// cursor omitted
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "omitted cursor should be accepted")
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -466,7 +466,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "invalid cursor format should be rejected")
|
||||
})
|
||||
|
||||
@@ -480,7 +480,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5,
|
||||
}
|
||||
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody1)
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody1)
|
||||
require.Equal(t, http.StatusOK, resp1.StatusCode)
|
||||
|
||||
var result1 model.ReportPostListResponse
|
||||
@@ -496,7 +496,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5,
|
||||
}
|
||||
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody2)
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody2)
|
||||
require.Equal(t, http.StatusOK, resp2.StatusCode)
|
||||
|
||||
var result2 model.ReportPostListResponse
|
||||
@@ -524,7 +524,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -567,7 +567,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "cursor parameters should take precedence (self-contained cursor)")
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -609,7 +609,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -634,7 +634,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody1)
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody1)
|
||||
require.Equal(t, http.StatusOK, resp1.StatusCode)
|
||||
|
||||
var result1 model.ReportPostListResponse
|
||||
@@ -659,7 +659,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody2)
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody2)
|
||||
require.Equal(t, http.StatusOK, resp2.StatusCode)
|
||||
|
||||
var result2 model.ReportPostListResponse
|
||||
@@ -710,7 +710,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -732,7 +732,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5000, // More than max
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "should cap per_page to MaxReportingPerPage instead of rejecting")
|
||||
|
||||
// Verify it was capped to max (we can't directly check the query but the request succeeds)
|
||||
@@ -752,7 +752,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject invalid ID format")
|
||||
})
|
||||
|
||||
@@ -768,7 +768,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid time_field")
|
||||
})
|
||||
|
||||
@@ -784,7 +784,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid sort_direction")
|
||||
})
|
||||
|
||||
@@ -800,7 +800,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid channel_id")
|
||||
})
|
||||
|
||||
@@ -816,7 +816,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/workspace/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid cursor_id")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func NewSelfReferentialSyncHandler(t *testing.T, service *sharedchannel.Service,
|
||||
// HandleRequest processes incoming HTTP requests for the test server.
|
||||
func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v1/team/remotecluster/msg":
|
||||
case "/v1/workspace/remotecluster/msg":
|
||||
atomic.AddInt32(h.syncMessageCount, 1)
|
||||
|
||||
// Read and process the sync message
|
||||
@@ -117,10 +117,10 @@ func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *htt
|
||||
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/v1/team/remotecluster/ping":
|
||||
case "/v1/workspace/remotecluster/ping":
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/v1/team/remotecluster/confirm_invite":
|
||||
case "/v1/workspace/remotecluster/confirm_invite":
|
||||
writeOKResponse(w)
|
||||
|
||||
default:
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestSiteURLTest(t *testing.T) {
|
||||
client := th.Client
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/valid/v1/team/system/ping") {
|
||||
if strings.HasSuffix(r.URL.Path, "/valid/v1/workspace/system/ping") {
|
||||
w.WriteHeader(200)
|
||||
} else {
|
||||
w.WriteHeader(400)
|
||||
@@ -1051,7 +1051,7 @@ func TestPushNotificationAck(t *testing.T) {
|
||||
t.Run("should return error when the ack body is not passed", func(t *testing.T) {
|
||||
handler := api.APIHandler(pushNotificationAck)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/notifications/ack", nil)
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/notifications/ack", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1065,7 +1065,7 @@ func TestPushNotificationAck(t *testing.T) {
|
||||
|
||||
handler := api.APIHandler(pushNotificationAck)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/notifications/ack", nil)
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/notifications/ack", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
req.Body = io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"id":"123", "is_id_loaded":true, "post_id":"%s", "type": "%s"}`, privatePost.Id, model.PushTypeMessage)))
|
||||
|
||||
@@ -1121,7 +1121,7 @@ func TestPushNotificationAck(t *testing.T) {
|
||||
|
||||
handler := api.APIHandler(pushNotificationAck)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/v1/team/notifications/ack", nil)
|
||||
req := httptest.NewRequest("POST", "/v1/workspace/notifications/ack", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
req.Body = io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"id":"123", "is_id_loaded":true, "platform": "%s", "post_id":"%s", "type": "%s"}`, tc.platform, th.BasicPost.Id, model.PushTypeMessage)))
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) InitUsage() {
|
||||
// GET /v1/team/usage/posts
|
||||
// GET /v1/workspace/usage/posts
|
||||
api.BaseRoutes.Usage.Handle("/posts", api.APISessionRequired(getPostsUsage)).Methods(http.MethodGet)
|
||||
// GET /v1/team/usage/storage
|
||||
// GET /v1/workspace/usage/storage
|
||||
api.BaseRoutes.Usage.Handle("/storage", api.APISessionRequired(getStorageUsage)).Methods(http.MethodGet)
|
||||
// GET /v1/team/usage/teams
|
||||
// GET /v1/workspace/usage/teams
|
||||
api.BaseRoutes.Usage.Handle("/teams", api.APISessionRequired(getTeamsUsage)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
// channelJoinRequestPaginationDefaultPerPage matches the public /v1/team default
|
||||
// channelJoinRequestPaginationDefaultPerPage matches the public /v1/workspace default
|
||||
// for paginated endpoints.
|
||||
const channelJoinRequestPaginationDefaultPerPage = 60
|
||||
|
||||
|
||||
@@ -1767,7 +1767,7 @@ func TestGetPostActionClient(t *testing.T) {
|
||||
{
|
||||
name: "same host with non-plugin path does not get auth",
|
||||
siteURL: "http://localhost:8065",
|
||||
requestURL: "http://localhost:8065/v1/team/posts",
|
||||
requestURL: "http://localhost:8065/v1/workspace/posts",
|
||||
expectAuth: false,
|
||||
},
|
||||
{
|
||||
@@ -1791,8 +1791,8 @@ func TestGetPostActionClient(t *testing.T) {
|
||||
{
|
||||
name: "path traversal escaping plugins does not get auth",
|
||||
siteURL: "http://localhost:8065",
|
||||
requestURL: "http://localhost:8065/plugins/../v1/team/posts",
|
||||
expectAuth: false, // path.Clean normalizes to /v1/team/posts
|
||||
requestURL: "http://localhost:8065/plugins/../v1/workspace/posts",
|
||||
expectAuth: false, // path.Clean normalizes to /v1/workspace/posts
|
||||
},
|
||||
{
|
||||
name: "subpath with plugin path gets auth",
|
||||
@@ -2395,7 +2395,7 @@ func TestDoPluginRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "non-plugins path",
|
||||
rawURL: "/v1/team/users",
|
||||
rawURL: "/v1/workspace/users",
|
||||
expectErr: true,
|
||||
errDetail: "plugins not in path",
|
||||
},
|
||||
|
||||
@@ -953,7 +953,7 @@ func TestGetAuthorizationServerMetadata_DCRConfig(t *testing.T) {
|
||||
require.NotNil(t, metadata)
|
||||
|
||||
// Should include registration endpoint when DCR is enabled
|
||||
assert.Equal(t, "https://example.com/v1/team/oauth/apps/register", metadata.RegistrationEndpoint)
|
||||
assert.Equal(t, "https://example.com/v1/workspace/oauth/apps/register", metadata.RegistrationEndpoint)
|
||||
|
||||
// Should include basic OAuth endpoints
|
||||
assert.Equal(t, "https://example.com", metadata.Issuer)
|
||||
|
||||
@@ -1061,7 +1061,7 @@ func TestPreparePostForClientWithImageProxy(t *testing.T) {
|
||||
func testProxyLinkedImage(t *testing.T, th *TestHelper, shouldProxy bool) {
|
||||
postTemplate := ""
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/workspace/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
UserId: th.BasicUser.Id,
|
||||
@@ -1136,7 +1136,7 @@ func testProxyOpenGraphImage(t *testing.T, th *TestHelper, shouldProxy bool) {
|
||||
image := og.Images[0]
|
||||
if shouldProxy {
|
||||
assert.Equal(t, "", image.URL, "image URL should not be set with proxy")
|
||||
assert.Equal(t, "http://mymattermost.com/v1/team/image?url="+url.QueryEscape(server.URL+"/test-image3.png"), image.SecureURL, "secure image URL should be sent through proxy")
|
||||
assert.Equal(t, "http://mymattermost.com/v1/workspace/image?url="+url.QueryEscape(server.URL+"/test-image3.png"), image.SecureURL, "secure image URL should be sent through proxy")
|
||||
} else {
|
||||
assert.Equal(t, server.URL+"/test-image3.png", image.URL, "image URL should be set")
|
||||
assert.Equal(t, "", image.SecureURL, "secure image URL should not be set")
|
||||
@@ -2776,7 +2776,7 @@ func TestGetLinkMetadata(t *testing.T) {
|
||||
assert.IsType(t, &url.Error{}, err)
|
||||
assert.ErrorContains(t, err, httpservice.ErrAddressForbidden.Error())
|
||||
|
||||
requestURL = th.App.GetSiteURL() + "/v1/team/image?url=" + url.QueryEscape(requestURL)
|
||||
requestURL = th.App.GetSiteURL() + "/v1/workspace/image?url=" + url.QueryEscape(requestURL)
|
||||
|
||||
// Note that this request still fails while testing because the request made by the image proxy is blocked
|
||||
og, img, _, err = th.App.getLinkMetadata(th.Context, requestURL, timestamp, false, "")
|
||||
|
||||
@@ -910,7 +910,7 @@ func TestImageProxy(t *testing.T) {
|
||||
ProxyOptions: testHMACKey,
|
||||
ImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/v1/workspace/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
},
|
||||
"atmos/camo_SameSite": {
|
||||
ProxyType: model.ImageProxyTypeAtmosCamo,
|
||||
@@ -940,7 +940,7 @@ func TestImageProxy(t *testing.T) {
|
||||
ProxyType: model.ImageProxyTypeLocal,
|
||||
ImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/v1/workspace/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
},
|
||||
"local_SameSite": {
|
||||
ProxyType: model.ImageProxyTypeLocal,
|
||||
@@ -1172,7 +1172,7 @@ func TestCreatePost(t *testing.T) {
|
||||
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
||||
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/workspace/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
@@ -2037,7 +2037,7 @@ func TestPatchPost(t *testing.T) {
|
||||
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
||||
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/workspace/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
@@ -2498,7 +2498,7 @@ func TestUpdatePost(t *testing.T) {
|
||||
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
||||
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/workspace/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
|
||||
@@ -120,7 +120,7 @@ func sessionAttributeRawValues(t *testing.T, th *TestHelper, sessionID string) m
|
||||
|
||||
func newSessionAttributesRequest(t *testing.T, userAgent, remoteAddr string) *http.Request {
|
||||
t.Helper()
|
||||
r := httptest.NewRequest(http.MethodGet, "/v1/team/test", nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/v1/workspace/test", nil)
|
||||
if userAgent != "" {
|
||||
r.Header.Set("User-Agent", userAgent)
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
idx := i // Capture index for closure
|
||||
testServers[i] = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
clusterName := fmt.Sprintf("cluster-%d", idx+1)
|
||||
atomic.AddInt32(syncMessagesPerCluster[clusterName], 1)
|
||||
|
||||
@@ -455,7 +455,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v1/team/remotecluster/msg":
|
||||
case "/v1/workspace/remotecluster/msg":
|
||||
syncAttempts.Add(1)
|
||||
|
||||
if failureMode.Load() {
|
||||
@@ -470,7 +470,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
} else {
|
||||
writeOKResponse(w)
|
||||
}
|
||||
case "/v1/team/remotecluster/ping":
|
||||
case "/v1/workspace/remotecluster/ping":
|
||||
writeOKResponse(w)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -577,7 +577,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
syncMessageCount.Add(1)
|
||||
}
|
||||
writeOKResponse(w)
|
||||
@@ -666,7 +666,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
syncMessageCount.Add(1)
|
||||
|
||||
// Parse message to check if it's a user sync
|
||||
@@ -855,7 +855,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
syncAttempts.Add(1)
|
||||
// On second attempt, go offline
|
||||
if syncAttempts.Load() >= 2 {
|
||||
@@ -1106,7 +1106,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP servers for both "servers"
|
||||
serverAHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
// Parse message to track what gets synced back to A
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
var frame model.RemoteClusterFrame
|
||||
@@ -1125,7 +1125,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
})
|
||||
|
||||
serverBHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
// Parse message to track what gets synced to B
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
var frame model.RemoteClusterFrame
|
||||
|
||||
@@ -534,7 +534,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
var syncHandler *SelfReferentialSyncHandler
|
||||
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
syncAttempts.Add(1)
|
||||
|
||||
if failureMode.Load() {
|
||||
@@ -668,7 +668,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
|
||||
// Count add and remove operations
|
||||
@@ -866,7 +866,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
// Create test server for this cluster
|
||||
idx := i // Capture index for closure
|
||||
testServers[i] = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
clusterName := fmt.Sprintf("cluster-%d", idx+1)
|
||||
atomic.AddInt32(syncMessagesPerCluster[clusterName], 1)
|
||||
|
||||
@@ -1202,7 +1202,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
currentAttempt := syncAttempts.Add(1)
|
||||
// On second sync cycle, go offline (allow first full sync to complete)
|
||||
if currentAttempt > 2 {
|
||||
@@ -1387,7 +1387,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
// Create a wrapper handler to intercept sync messages
|
||||
testServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Intercept and track channel syncs
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
bodyBytes, pErr := io.ReadAll(r.Body)
|
||||
if pErr == nil {
|
||||
// Restore body for actual handler
|
||||
@@ -1584,7 +1584,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// // Create test HTTP server that tracks sync messages
|
||||
// testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
// if r.URL.Path == "/v1/workspace/remotecluster/msg" {
|
||||
// syncMessageCount.Add(1)
|
||||
|
||||
// // Read body once
|
||||
|
||||
@@ -62,12 +62,12 @@ func NewSelfReferentialSyncHandler(t *testing.T, service *sharedchannel.Service,
|
||||
|
||||
// HandleRequest processes incoming HTTP requests for the test server.
|
||||
// This handler includes common remote cluster endpoints to simulate a real remote cluster:
|
||||
// - /v1/team/remotecluster/msg: Main sync message endpoint
|
||||
// - /v1/team/remotecluster/ping: Ping endpoint to maintain online status (prevents offline after 5 minutes)
|
||||
// - /v1/team/remotecluster/confirm_invite: Invitation confirmation endpoint
|
||||
// - /v1/workspace/remotecluster/msg: Main sync message endpoint
|
||||
// - /v1/workspace/remotecluster/ping: Ping endpoint to maintain online status (prevents offline after 5 minutes)
|
||||
// - /v1/workspace/remotecluster/confirm_invite: Invitation confirmation endpoint
|
||||
func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v1/team/remotecluster/msg":
|
||||
case "/v1/workspace/remotecluster/msg":
|
||||
currentCall := h.syncMessageCount.Add(1)
|
||||
|
||||
// Read and process the sync message
|
||||
@@ -150,10 +150,10 @@ func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *htt
|
||||
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/v1/team/remotecluster/ping":
|
||||
case "/v1/workspace/remotecluster/ping":
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/v1/team/remotecluster/confirm_invite":
|
||||
case "/v1/workspace/remotecluster/confirm_invite":
|
||||
writeOKResponse(w)
|
||||
|
||||
default:
|
||||
|
||||
@@ -680,7 +680,7 @@ func TestCreateWebhookPostWithOverriddenIcon(t *testing.T) {
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, &model.PreparePostForClientOpts{IsNewPost: true})
|
||||
|
||||
assert.Equal(t, fmt.Sprintf("/v1/team/emoji/%s/image", emoji.Id), clientPost.GetProp(model.PostPropsOverrideIconURL))
|
||||
assert.Equal(t, fmt.Sprintf("/v1/workspace/emoji/%s/image", emoji.Id), clientPost.GetProp(model.PostPropsOverrideIconURL))
|
||||
})
|
||||
|
||||
t.Run("should set props based on icon_emoji (with colons around emoji name)", func(t *testing.T) {
|
||||
|
||||
@@ -38,9 +38,9 @@ func TestHandlerServeHTTPErrors(t *testing.T) {
|
||||
redirect bool
|
||||
}{
|
||||
{"redirect on desktop non-api endpoint", "/login/sso/saml", false, true},
|
||||
{"not redirect on desktop api endpoint", "/v1/team/test", false, false},
|
||||
{"not redirect on desktop api endpoint", "/v1/workspace/test", false, false},
|
||||
{"not redirect on mobile non-api endpoint", "/login/sso/saml", true, false},
|
||||
{"not redirect on mobile api endpoint", "/v1/team/test", true, false},
|
||||
{"not redirect on mobile api endpoint", "/v1/workspace/test", true, false},
|
||||
}
|
||||
|
||||
for _, tt := range flagtests {
|
||||
@@ -71,7 +71,7 @@ func TestHandlerServeDefaultSecurityHeaders(t *testing.T) {
|
||||
handler := web.NewHandler(handlerForServeDefaultSecurityHeaders)
|
||||
|
||||
paths := []string{
|
||||
"/v1/team/test", // API
|
||||
"/v1/workspace/test", // API
|
||||
"/static/manifest.json", // this should always exist. Static files have their own handler
|
||||
// Note that the plugin handler isn't tested, also plugins may support arbitrary functionality
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(handlerForHTTPSecureTransport)
|
||||
|
||||
request := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -145,7 +145,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
||||
*config.ServiceSettings.TLSStrictTransport = false
|
||||
})
|
||||
|
||||
request = httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
request = httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
|
||||
response = httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -199,7 +199,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// CSRF Token Used - Success Expected
|
||||
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -213,7 +213,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// No CSRF Token Used - Failure Expected
|
||||
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -229,7 +229,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
th.App.UpdateConfig(func(config *model.Config) {
|
||||
*config.ServiceSettings.ExperimentalStrictCSRFEnforcement = false
|
||||
})
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -266,7 +266,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// CSRF Token Used - Success Expected
|
||||
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -280,7 +280,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// No CSRF Token Used - Failure Expected
|
||||
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -310,7 +310,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
IsStatic: false,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
@@ -609,7 +609,7 @@ func TestHandlerServeInvalidToken(t *testing.T) {
|
||||
Value: "invalid",
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -670,7 +670,7 @@ func TestHandlerServeCSRFFailureClearsAuthCookie(t *testing.T) {
|
||||
Value: session.Token,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.Header.Add(model.HeaderRequestedWith, model.HeaderRequestedWithXML)
|
||||
response := httptest.NewRecorder()
|
||||
@@ -1022,7 +1022,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
||||
handler := web.NewHandler(noOpHandler)
|
||||
|
||||
// using the default URL length
|
||||
request := httptest.NewRequest("GET", "/v1/team/test?with=not&many=query_params", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/workspace/test?with=not&many=query_params", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
@@ -1053,7 +1053,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(noOpHandler)
|
||||
|
||||
request := httptest.NewRequest("GET", "/v1/team/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/workspace/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, http.StatusRequestURITooLong, response.Code)
|
||||
@@ -1086,7 +1086,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
||||
|
||||
// this URL is within the 20 characters limit excluding query params.
|
||||
// but this should still fail as URL length includes query params
|
||||
request := httptest.NewRequest("GET", "/v1/team/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/workspace/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, http.StatusRequestURITooLong, response.Code)
|
||||
@@ -1114,7 +1114,7 @@ func TestHandlerServeHTTPRequestPayloadLimit(t *testing.T) {
|
||||
handler := web.NewHandler(jsonReaderHandler)
|
||||
|
||||
body := strings.NewReader("\"a very small request body\"")
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", body)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", body)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
@@ -1150,7 +1150,7 @@ func TestHandlerServeHTTPRequestPayloadLimit(t *testing.T) {
|
||||
// Even though we have set the max payload size to be 1, we still need at least 513 bytes (1 byte configured + bytes.MinRead = 1 + 512 = 513) bytes.
|
||||
// This is because the buffer will always be at least bytes.MinRead bytes large, so the effective payload limit is bytes.MinRead + the configured value.
|
||||
body := strings.NewReader("\"weunfrwghyajuaqqkrecexurpmrmgpimjieymiwfhfrrrgiqpqrznkjtubwcuybyixyxwtwddpytukritccyugyeuvdtzjkkyiwhquzqkrqkhgyyfqnquzchjqkrkzfrxthduzizqtdxzqirxhzihbivmkdwpbeddepdanzuuzqxbdqfvgkwumervhghywexitbjdnvxcniuamwmqdecbbqbgnjjqwkdcucvnpwynuruztpdtmmvbpkevurpjdwdhpayaindzmnkmyybudfkjdkqwuiviriudtqytybuwfkkwpepwhpekfewnxgpkfctdqjmemngvntnizvfznaiqpbumgtcxidvawtgcdyqijbxzrgezvjmcwikiabbpqabrwfgncrmvqththepffatnhchhnmrhkuqvgrzfugzhuwicaemhcacrazmgzmrgrkuhwucfydhwxfhzfukzjhvdxkuhzjrxwippxadvwzigndxwdxvganxggjjxwdqtgqgnpqqygndviadvttwfntcreitijaqrpfygdehbcftyfcjvrfwvjmbtdptutjgtbyhbyddfecyyujgrxyujzmryymj\"")
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", body)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", body)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
@@ -1172,7 +1172,7 @@ func TestHandlerConnectionIdHeader(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(handlerFunc)
|
||||
|
||||
request := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
request.Header.Set(model.ConnectionId, connectionId)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -1193,7 +1193,7 @@ func TestHandlerConnectionIdHeader(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(handlerFunc)
|
||||
|
||||
request := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
@@ -1221,7 +1221,7 @@ func TestHandleContextErrorZeroStatusCode(t *testing.T) {
|
||||
Err: appErr,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
h := Handler{
|
||||
@@ -1246,7 +1246,7 @@ func TestHandleContextErrorZeroStatusCode(t *testing.T) {
|
||||
Err: appErr,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/workspace/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
h := Handler{
|
||||
|
||||
@@ -37,7 +37,7 @@ func newResponseWithHijack(original *httptest.ResponseRecorder) *responseRecorde
|
||||
|
||||
func TestStatusCodeIsAccessible(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}}
|
||||
@@ -47,7 +47,7 @@ func TestStatusCodeIsAccessible(t *testing.T) {
|
||||
|
||||
func TestStatusCodeShouldBe200IfNotHeaderWritten(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
n, err := w.Write([]byte{})
|
||||
require.NoError(t, err, "Failed to write response")
|
||||
@@ -59,7 +59,7 @@ func TestStatusCodeShouldBe200IfNotHeaderWritten(t *testing.T) {
|
||||
|
||||
func TestForUnsupportedHijack(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, rw, err := w.(*responseWriterWrapper).Hijack()
|
||||
assert.Error(t, err)
|
||||
@@ -72,7 +72,7 @@ func TestForUnsupportedHijack(t *testing.T) {
|
||||
|
||||
func TestForSupportedHijack(t *testing.T) {
|
||||
resp := newWrappedWriter(newResponseWithHijack(httptest.NewRecorder()))
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, rw, err := w.(*responseWriterWrapper).Hijack()
|
||||
require.NoError(t, err, "Hijack should succeed with supporting ResponseWriter")
|
||||
@@ -84,7 +84,7 @@ func TestForSupportedHijack(t *testing.T) {
|
||||
|
||||
func TestForSupportedFlush(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/workspace/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
n, err := w.Write([]byte{})
|
||||
require.NoError(t, err, "Failed to write response")
|
||||
|
||||
@@ -183,7 +183,7 @@ func (s *MmctlE2ETestSuite) TestArchiveCommandCmdF() {
|
||||
|
||||
err := archiveCommandCmdF(c, &cobra.Command{}, []string{nonexistentCommandID})
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal(fmt.Sprintf("Unable to archive command '%s' error: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/v1/team/commands/nonexistent-command-id'. Typo? are you missing a team_id or user_id as part of the url?", nonexistentCommandID), err.Error())
|
||||
s.Require().Equal(fmt.Sprintf("Unable to archive command '%s' error: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/v1/workspace/commands/nonexistent-command-id'. Typo? are you missing a team_id or user_id as part of the url?", nonexistentCommandID), err.Error())
|
||||
s.Require().Len(printer.GetLines(), 0)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
|
||||
@@ -168,7 +168,7 @@ func TestVerifyCertificates(t *testing.T) {
|
||||
func TestNewAPIv4Client(t *testing.T) {
|
||||
t.Run("should take http proxy into account", func(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/v1/team/users/me", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
router.Handle("/v1/workspace/users/me", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := &model.User{
|
||||
Id: model.NewId(),
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
- './../../enterprise:/home/enterprise'
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://leader:8065/v1/team/system/ping"]
|
||||
test: ["CMD", "curl", "-f", "http://leader:8065/v1/workspace/system/ping"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
@@ -165,7 +165,7 @@ services:
|
||||
- './../:/home/mattermost-server:Z'
|
||||
- './../../enterprise:/home/enterprise:Z'
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://follower:8065/v1/team/system/ping"]
|
||||
test: ["CMD", "curl", "-f", "http://follower:8065/v1/workspace/system/ping"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- './../:/home/mattermost-server:Z'
|
||||
- './../../enterprise:/home/enterprise:Z'
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://follower2:8065/v1/team/system/ping"]
|
||||
test: ["CMD", "curl", "-f", "http://follower2:8065/v1/workspace/system/ping"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestGetProxiedImageURL(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
imageURL := "https://hanzo.ai/wp-content/uploads/2022/02/logoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.com%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/v1/workspace/image?url=https%3A%2F%2Fmattermost.com%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
|
||||
proxy := ImageProxy{siteURL: parsedURL}
|
||||
|
||||
@@ -56,17 +56,17 @@ func TestGetProxiedImageURL(t *testing.T) {
|
||||
{
|
||||
Name: "should not bypass protocol relative URLs",
|
||||
Input: "//mattermost.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.com%2Fstatic%2Flogo.png",
|
||||
Expected: "https://mattermost.example.com/v1/workspace/image?url=https%3A%2F%2Fmattermost.com%2Fstatic%2Flogo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not bypass if the host prefix is same",
|
||||
Input: "https://mattermost.example.com.anothersite.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.example.com.anothersite.com%2Fstatic%2Flogo.png",
|
||||
Expected: "https://mattermost.example.com/v1/workspace/image?url=https%3A%2F%2Fmattermost.example.com.anothersite.com%2Fstatic%2Flogo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not bypass for user auth URLs",
|
||||
Input: "https://[email protected]/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.example.com%40anothersite.com%2Fstatic%2Flogo.png",
|
||||
Expected: "https://mattermost.example.com/v1/workspace/image?url=https%3A%2F%2Fmattermost.example.com%40anothersite.com%2Fstatic%2Flogo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy embedded image",
|
||||
@@ -86,7 +86,7 @@ func TestGetUnproxiedImageURL(t *testing.T) {
|
||||
// The proxied form embeds the image URL, so the two have to name the same
|
||||
// host or the round-trip this exercises is not being exercised at all.
|
||||
imageURL := "https://hanzo.ai/wp-content/uploads/2022/02/logoHorizontal.png"
|
||||
proxiedURL := "https://team.example.com/v1/team/image?url=https%3A%2F%2Fhanzo.ai%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
proxiedURL := "https://team.example.com/v1/workspace/image?url=https%3A%2F%2Fhanzo.ai%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
|
||||
@@ -158,50 +158,50 @@ sequenceDiagram
|
||||
participant SB as Server B
|
||||
|
||||
Note over UA,SB: Phase 1: Generate Invitation
|
||||
UA->>SA: POST /v1/team/remotecluster<br/>{name, display_name, password}
|
||||
UA->>SA: POST /v1/workspace/remotecluster<br/>{name, display_name, password}
|
||||
SA->>SA: Generate RemoteId, Token
|
||||
SA->>SA: Encrypt invitation (PBKDF2 + AES-GCM)
|
||||
SA->>UA: Return encrypted invite code
|
||||
|
||||
Note over UA,SB: Phase 2: Accept Invitation
|
||||
UA->>UB: Share invite code + password<br/>(out of band)
|
||||
UB->>SB: POST /v1/team/remotecluster/accept_invite<br/>{invite, password}
|
||||
UB->>SB: POST /v1/workspace/remotecluster/accept_invite<br/>{invite, password}
|
||||
SB->>SB: Decrypt invitation
|
||||
SB->>SB: Create RemoteCluster record
|
||||
SB->>SA: POST /v1/team/remotecluster/confirm_invite<br/>X-MM-RemoteCluster-Token: [token]<br/>{remote_id, site_url, token}
|
||||
SB->>SA: POST /v1/workspace/remotecluster/confirm_invite<br/>X-MM-RemoteCluster-Token: [token]<br/>{remote_id, site_url, token}
|
||||
SA->>SA: Update RemoteCluster with SiteURL
|
||||
SA->>SB: 200 OK
|
||||
SB->>UB: Connection established
|
||||
|
||||
Note over UA,SB: Phase 3: Continuous Heartbeat
|
||||
loop Every 60 seconds
|
||||
SA->>SB: POST /v1/team/remotecluster/ping<br/>{sent_at}
|
||||
SA->>SB: POST /v1/workspace/remotecluster/ping<br/>{sent_at}
|
||||
SB->>SA: {sent_at, recv_at}
|
||||
SB->>SA: POST /v1/team/remotecluster/ping<br/>{sent_at}
|
||||
SB->>SA: POST /v1/workspace/remotecluster/ping<br/>{sent_at}
|
||||
SA->>SB: {sent_at, recv_at}
|
||||
end
|
||||
```
|
||||
|
||||
**Step 1: Create Invitation (Server A)**
|
||||
- API: `POST /v1/team/remotecluster`
|
||||
- API: `POST /v1/workspace/remotecluster`
|
||||
- Generates encrypted invitation with PBKDF2 encryption
|
||||
- Returns base64-encoded invite code
|
||||
- Creates pending RemoteCluster record
|
||||
|
||||
**Step 2: Accept Invitation (Server B)**
|
||||
- API: `POST /v1/team/remotecluster/accept_invite`
|
||||
- API: `POST /v1/workspace/remotecluster/accept_invite`
|
||||
- Decrypts invitation using password
|
||||
- Creates local RemoteCluster record
|
||||
- Sends confirmation to Server A
|
||||
|
||||
**Step 3: Confirm Connection (Server A)**
|
||||
- API: `POST /v1/team/remotecluster/confirm_invite`
|
||||
- API: `POST /v1/workspace/remotecluster/confirm_invite`
|
||||
- Receives confirmation from Server B
|
||||
- Updates RemoteCluster with actual SiteURL
|
||||
- Connection established
|
||||
|
||||
**Step 4: Continuous Heartbeat**
|
||||
- API: `POST /v1/team/remotecluster/ping`
|
||||
- API: `POST /v1/workspace/remotecluster/ping`
|
||||
- Every 60 seconds (default)
|
||||
- Updates `LastPingAt` timestamp
|
||||
- Remote considered online if pinged within 5 minutes
|
||||
@@ -219,7 +219,7 @@ sequenceDiagram
|
||||
UA->>SA: Invite Remote B to Channel
|
||||
SA->>SA: Create SharedChannel (Home=true)
|
||||
SA->>SA: Create SharedChannelRemote
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_invite<br/>{channel_id, name, type, ...}
|
||||
SA->>SB: POST /v1/workspace/remotecluster/msg<br/>Topic: sharedchannel_invite<br/>{channel_id, name, type, ...}
|
||||
SB->>SB: Validate invitation
|
||||
SB->>SB: Create local channel
|
||||
SB->>SB: Create SharedChannel (Home=false)
|
||||
@@ -231,7 +231,7 @@ sequenceDiagram
|
||||
Note over UA,SB: Initial Sync
|
||||
SA->>SA: Queue sync task for channel
|
||||
SA->>SA: Collect users, posts, reactions
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>{users, posts, reactions, ...}
|
||||
SA->>SB: POST /v1/workspace/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>{users, posts, reactions, ...}
|
||||
SB->>SB: Process sync message
|
||||
SB->>SB: Create synthetic users
|
||||
SB->>SB: Create posts
|
||||
@@ -247,7 +247,7 @@ sequenceDiagram
|
||||
- Contains channel metadata (name, type, permissions)
|
||||
|
||||
**Step 2: Receive Invitation (Server B)**
|
||||
- API: `POST /v1/team/remotecluster/msg` (topic: `sharedchannel_invite`)
|
||||
- API: `POST /v1/workspace/remotecluster/msg` (topic: `sharedchannel_invite`)
|
||||
- Creates local channel (regular, DM, or GM)
|
||||
- Creates SharedChannel record (`Home=false`)
|
||||
- Creates SharedChannelRemote record
|
||||
@@ -278,13 +278,13 @@ sequenceDiagram
|
||||
SA->>SA: Filter & batch (100 posts max)
|
||||
|
||||
alt Has file attachments
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_upload<br/>{upload_session}
|
||||
SA->>SB: POST /v1/workspace/remotecluster/msg<br/>Topic: sharedchannel_upload<br/>{upload_session}
|
||||
SB->>SA: 200 OK {session_id}
|
||||
SA->>SB: POST /v1/team/remotecluster/upload/{id}<br/>multipart file data
|
||||
SA->>SB: POST /v1/workspace/remotecluster/upload/{id}<br/>multipart file data
|
||||
SB->>SB: Save file to filestore
|
||||
end
|
||||
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>Headers: X-MM-RemoteCluster-Id, Token<br/>{SyncMsg}
|
||||
SA->>SB: POST /v1/workspace/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>Headers: X-MM-RemoteCluster-Id, Token<br/>{SyncMsg}
|
||||
|
||||
Note over SB: Process Sync Message
|
||||
SB->>SB: Validate auth token
|
||||
@@ -302,7 +302,7 @@ sequenceDiagram
|
||||
SB->>SB: Save post
|
||||
SB->>UB: WebSocket: posted event
|
||||
SB->>SB: Queue sync task
|
||||
SB->>SA: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_sync
|
||||
SB->>SA: POST /v1/workspace/remotecluster/msg<br/>Topic: sharedchannel_sync
|
||||
SA->>SA: Process sync message
|
||||
SA->>UA: WebSocket: posted event
|
||||
SA->>SB: 200 OK {timestamps}
|
||||
@@ -346,7 +346,7 @@ graph LR
|
||||
- Attachments: All for synced posts
|
||||
|
||||
**Send Sync Message (Server A → Server B)**
|
||||
- API: `POST /v1/team/remotecluster/msg` (topic: `sharedchannel_sync`)
|
||||
- API: `POST /v1/workspace/remotecluster/msg` (topic: `sharedchannel_sync`)
|
||||
- Headers:
|
||||
- `X-MM-RemoteCluster-Id`: Remote cluster ID
|
||||
- `X-MM-RemoteCluster-Token`: Authentication token
|
||||
@@ -380,14 +380,14 @@ graph LR
|
||||
1. Server A creates upload session
|
||||
2. Sends upload creation message (topic: `sharedchannel_upload`)
|
||||
3. Server B creates matching session
|
||||
4. Server A streams file data: `POST /v1/team/remotecluster/upload/{upload_id}`
|
||||
4. Server A streams file data: `POST /v1/workspace/remotecluster/upload/{upload_id}`
|
||||
5. Server B saves file and creates FileInfo record
|
||||
|
||||
#### 5. Profile Image Synchronization
|
||||
|
||||
**Upload Flow:**
|
||||
1. Detect user image update (`LastPictureUpdate` changed)
|
||||
2. Server A uploads image: `POST /v1/team/remotecluster/{user_id}/image`
|
||||
2. Server A uploads image: `POST /v1/workspace/remotecluster/{user_id}/image`
|
||||
3. Server B validates user belongs to remote
|
||||
4. Saves image and invalidates cache
|
||||
|
||||
@@ -434,7 +434,7 @@ sequenceDiagram
|
||||
SB->>SB: Store RemoteToken = Token_A
|
||||
|
||||
Note over SA,SB: Server A sends message to Server B
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_B<br/>X-MM-RemoteCluster-Token: Token_B
|
||||
SA->>SB: POST /v1/workspace/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_B<br/>X-MM-RemoteCluster-Token: Token_B
|
||||
SB->>SB: Validate RemoteId_B exists
|
||||
SB->>SB: Validate Token matches stored Token_B
|
||||
alt Valid Token
|
||||
@@ -444,7 +444,7 @@ sequenceDiagram
|
||||
end
|
||||
|
||||
Note over SA,SB: Server B sends message to Server A
|
||||
SB->>SA: POST /v1/team/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_A<br/>X-MM-RemoteCluster-Token: Token_A
|
||||
SB->>SA: POST /v1/workspace/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_A<br/>X-MM-RemoteCluster-Token: Token_A
|
||||
SA->>SA: Validate RemoteId_A exists
|
||||
SA->>SA: Validate Token matches stored Token_A
|
||||
alt Valid Token
|
||||
@@ -473,20 +473,20 @@ sequenceDiagram
|
||||
### Key API Endpoints
|
||||
|
||||
**Remote Cluster Management:**
|
||||
- `POST /v1/team/remotecluster` - Create remote (generate invite)
|
||||
- `POST /v1/team/remotecluster/accept_invite` - Accept invitation
|
||||
- `POST /v1/team/remotecluster/confirm_invite` - Confirm connection
|
||||
- `POST /v1/team/remotecluster/ping` - Heartbeat
|
||||
- `POST /v1/team/remotecluster/msg` - Message delivery (all topics)
|
||||
- `POST /v1/workspace/remotecluster` - Create remote (generate invite)
|
||||
- `POST /v1/workspace/remotecluster/accept_invite` - Accept invitation
|
||||
- `POST /v1/workspace/remotecluster/confirm_invite` - Confirm connection
|
||||
- `POST /v1/workspace/remotecluster/ping` - Heartbeat
|
||||
- `POST /v1/workspace/remotecluster/msg` - Message delivery (all topics)
|
||||
|
||||
**Shared Channel Management:**
|
||||
- `GET /v1/team/sharedchannels/{team_id}` - List shared channels
|
||||
- `POST /v1/team/channels/{channel_id}/remotes/{remote_id}/invite` - Share channel
|
||||
- `POST /v1/team/channels/{channel_id}/remotes/{remote_id}/uninvite` - Unshare
|
||||
- `GET /v1/workspace/sharedchannels/{team_id}` - List shared channels
|
||||
- `POST /v1/workspace/channels/{channel_id}/remotes/{remote_id}/invite` - Share channel
|
||||
- `POST /v1/workspace/channels/{channel_id}/remotes/{remote_id}/uninvite` - Unshare
|
||||
|
||||
**File Operations:**
|
||||
- `POST /v1/team/remotecluster/upload/{upload_id}` - Upload file
|
||||
- `POST /v1/team/remotecluster/{user_id}/image` - Upload profile image
|
||||
- `POST /v1/workspace/remotecluster/upload/{upload_id}` - Upload file
|
||||
- `POST /v1/workspace/remotecluster/{user_id}/image` - Upload profile image
|
||||
|
||||
### Error Handling & Monitoring
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ const (
|
||||
ClientDir = "client"
|
||||
|
||||
// APIURLSuffix is the base path of this server's HTTP API.
|
||||
APIURLSuffix = "/v1/team"
|
||||
APIURLSuffix = "/v1/workspace"
|
||||
|
||||
// PushURLSuffix is the base path of the push notification service
|
||||
// addressed by EmailSettings.PushNotificationServer.
|
||||
@@ -70,7 +70,7 @@ type Response struct {
|
||||
|
||||
type Client4 struct {
|
||||
URL string // The location of the server, for example "http://localhost:8065"
|
||||
APIURL string // The api location of the server, for example "http://localhost:8065/v1/team"
|
||||
APIURL string // The api location of the server, for example "http://localhost:8065/v1/workspace"
|
||||
HTTPClient *http.Client // The http client
|
||||
AuthToken string
|
||||
AuthType string
|
||||
@@ -5635,7 +5635,7 @@ func (c *Client4) AuthorizeOAuthApp(ctx context.Context, authRequest *AuthorizeR
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
// The request doesn't go to the /v1/team subpath, so we can't use the usual helper methods
|
||||
// The request doesn't go to the /v1/workspace subpath, so we can't use the usual helper methods
|
||||
r, err := c.doAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/authorize", buf, "")
|
||||
if err != nil {
|
||||
return "", BuildResponse(r), err
|
||||
@@ -5656,7 +5656,7 @@ func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Respo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The request doesn't go to the /v1/team subpath, so we can't use the usual helper methods
|
||||
// The request doesn't go to the /v1/workspace subpath, so we can't use the usual helper methods
|
||||
r, err := c.doAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/deauthorize", buf, "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
@@ -5667,7 +5667,7 @@ func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Respo
|
||||
|
||||
// GetOAuthAccessToken is a test helper function for the OAuth access token endpoint.
|
||||
func (c *Client4) GetOAuthAccessToken(ctx context.Context, data url.Values) (*AccessResponse, *Response, error) {
|
||||
// The request doesn't go to the /v1/team subpath, so we can't use the usual helper methods
|
||||
// The request doesn't go to the /v1/workspace subpath, so we can't use the usual helper methods
|
||||
r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.URL+"/oauth/access_token", "application/x-www-form-urlencoded", strings.NewReader(data.Encode()), nil)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestClient4LookupInteractiveDialog(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify request method and endpoint
|
||||
assert.Equal(t, "POST", r.Method)
|
||||
assert.Equal(t, "/v1/team/actions/dialogs/lookup", r.URL.Path)
|
||||
assert.Equal(t, "/v1/workspace/actions/dialogs/lookup", r.URL.Path)
|
||||
|
||||
// Decode and verify request body
|
||||
var submission model.SubmitDialogRequest
|
||||
|
||||
@@ -43,8 +43,8 @@ const avgReadMsgSizeBytes = 1024
|
||||
// deadlocks from occurring in the program.
|
||||
type WebSocketClient struct {
|
||||
URL string // The location of the server like "ws://localhost:8065"
|
||||
APIURL string // The API location of the server like "ws://localhost:8065/v1/team"
|
||||
ConnectURL string // The WebSocket URL to connect to like "ws://localhost:8065/v1/team/websocket"
|
||||
APIURL string // The API location of the server like "ws://localhost:8065/v1/workspace"
|
||||
ConnectURL string // The WebSocket URL to connect to like "ws://localhost:8065/v1/workspace/websocket"
|
||||
Conn *websocket.Conn // The WebSocket connection
|
||||
AuthToken string // The token used to open the WebSocket connection
|
||||
Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
|
||||
|
||||
@@ -38,7 +38,7 @@ var (
|
||||
// audit level and should be applied whenever a REST API endpoint is accessed. It provides
|
||||
// a record of API usage patterns and access.
|
||||
//
|
||||
// Example usage: Logging when a user accesses the /v1/team/posts endpoint.
|
||||
// Example usage: Logging when a user accesses the /v1/workspace/posts endpoint.
|
||||
LvlAuditAPI = Level{ID: 100, Name: "audit-api"}
|
||||
|
||||
// LvlAuditContent is used for auditing content-generating operations such as creating posts,
|
||||
|
||||
@@ -26,7 +26,7 @@ exports[`component/PDFPreview should match snapshot, not successful 1`] = `
|
||||
>
|
||||
<a
|
||||
class="file-details__preview"
|
||||
href="https://pre-release.mattermost.com/v1/team/files/ips59w4w9jnfbrs3o94m1dbdie"
|
||||
href="https://pre-release.mattermost.com/v1/workspace/files/ips59w4w9jnfbrs3o94m1dbdie"
|
||||
>
|
||||
<span
|
||||
class="file-details__preview-helper"
|
||||
|
||||
@@ -26,7 +26,7 @@ exports[`components/SettingItemMin should match snapshot with active Save button
|
||||
<img
|
||||
alt="profile image"
|
||||
class="profile-img"
|
||||
src="http://localhost:8065/v1/team/users/src_id"
|
||||
src="http://localhost:8065/v1/workspace/users/src_id"
|
||||
/>
|
||||
</li>
|
||||
<div
|
||||
@@ -172,7 +172,7 @@ exports[`components/SettingItemMin should match snapshot, on loading picture 1`]
|
||||
<img
|
||||
alt="profile image"
|
||||
class="profile-img"
|
||||
src="http://localhost:8065/v1/team/users/src_id"
|
||||
src="http://localhost:8065/v1/workspace/users/src_id"
|
||||
/>
|
||||
</li>
|
||||
<div
|
||||
@@ -349,7 +349,7 @@ exports[`components/SettingItemMin should match snapshot, profile picture on sou
|
||||
<img
|
||||
alt="profile image"
|
||||
class="profile-img"
|
||||
src="http://localhost:8065/v1/team/users/src_id"
|
||||
src="http://localhost:8065/v1/workspace/users/src_id"
|
||||
/>
|
||||
</li>
|
||||
<div
|
||||
@@ -521,7 +521,7 @@ exports[`components/SettingItemMin should match snapshot, team icon on source 1`
|
||||
<img
|
||||
alt="team image"
|
||||
class="team-img"
|
||||
src="http://localhost:8065/v1/team/users/src_id"
|
||||
src="http://localhost:8065/v1/workspace/users/src_id"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@@ -624,7 +624,7 @@ exports[`components/SettingItemMin should match snapshot, user icon on source 1`
|
||||
<img
|
||||
alt="profile image"
|
||||
class="profile-img"
|
||||
src="http://localhost:8065/v1/team/users/src_id"
|
||||
src="http://localhost:8065/v1/workspace/users/src_id"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -35,7 +35,7 @@ exports[`components/UserList should match default snapshot when there are users
|
||||
alt="some-user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/id1/image"
|
||||
src="/v1/workspace/users/id1/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -89,7 +89,7 @@ exports[`components/UserList should match default snapshot when there are users
|
||||
alt="some-user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/id2/image"
|
||||
src="/v1/workspace/users/id2/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -4510,8 +4510,8 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
id: 'OAuthSettings',
|
||||
name: defineMessage({id: 'admin.authentication.oauth', defaultMessage: 'OAuth 2.0'}),
|
||||
onConfigLoad: (config) => {
|
||||
const newState: {oauthType?: string; 'GitLabSettings.Url'?: string} = {};
|
||||
if (config.GitLabSettings?.Enable) {
|
||||
const newState: {oauthType?: string} = {};
|
||||
if (config.HanzoSettings?.Enable) {
|
||||
newState.oauthType = Constants.HANZO_SERVICE;
|
||||
}
|
||||
if (config.Office365Settings?.Enable) {
|
||||
@@ -4521,25 +4521,22 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
newState.oauthType = Constants.GOOGLE_SERVICE;
|
||||
}
|
||||
|
||||
newState['GitLabSettings.Url'] = config.GitLabSettings?.UserAPIEndpoint?.replace('/api/v4/user', '');
|
||||
|
||||
return newState;
|
||||
},
|
||||
onConfigSave: (config) => {
|
||||
const newConfig = {...config};
|
||||
newConfig.GitLabSettings = config.GitLabSettings || {};
|
||||
newConfig.HanzoSettings = config.HanzoSettings || {};
|
||||
newConfig.Office365Settings = config.Office365Settings || {};
|
||||
newConfig.GoogleSettings = config.GoogleSettings || {};
|
||||
newConfig.OpenIdSettings = config.OpenIdSettings || {};
|
||||
|
||||
newConfig.GitLabSettings.Enable = false;
|
||||
newConfig.HanzoSettings.Enable = false;
|
||||
newConfig.Office365Settings.Enable = false;
|
||||
newConfig.GoogleSettings.Enable = false;
|
||||
newConfig.OpenIdSettings.Enable = false;
|
||||
newConfig.GitLabSettings.UserAPIEndpoint = config.GitLabSettings.Url.replace(/\/$/, '') + '/api/v4/user';
|
||||
|
||||
if (config.oauthType === Constants.HANZO_SERVICE) {
|
||||
newConfig.GitLabSettings.Enable = true;
|
||||
newConfig.HanzoSettings.Enable = true;
|
||||
}
|
||||
if (config.oauthType === Constants.OFFICE365_SERVICE) {
|
||||
newConfig.Office365Settings.Enable = true;
|
||||
@@ -4572,11 +4569,11 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
},
|
||||
{
|
||||
value: Constants.HANZO_SERVICE,
|
||||
display_name: defineMessage({id: 'admin.oauth.gitlab', defaultMessage: 'GitLab'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.EnableMarkdownDesc', defaultMessage: '1. Log in to your GitLab account and go to Profile Settings -> Applications.\n2. Enter Redirect URIs "<loginUrlChunk>your-hanzo-team-url</loginUrlChunk>" (example: http://localhost:8065/login/gitlab/complete) and "<signupUrlChunk>your-hanzo-team-url</signupUrlChunk>".\n3. Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.\n4. Complete the Endpoint URLs below.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
|
||||
display_name: defineMessage({id: 'admin.oauth.hanzo', defaultMessage: 'Hanzo IAM'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.EnableMarkdownDesc', defaultMessage: '1. Sign in to Hanzo IAM and open the application registered for this server.\n2. Add "<loginUrlChunk>your-hanzo-team-url</loginUrlChunk>" (example: http://localhost:8065/login/hanzo/complete) and "<signupUrlChunk>your-hanzo-team-url</signupUrlChunk>" to its redirect URIs.\n3. Copy the application Client ID and Client Secret into the fields below.\n4. The endpoints below already address hanzo.id. Change them only if you run your own IAM.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
|
||||
help_text_values: {
|
||||
loginUrlChunk: (chunk: string) => `<${chunk}>/login/gitlab/complete`,
|
||||
signupUrlChunk: (chunk: string) => `<${chunk}>/signup/gitlab/complete`,
|
||||
loginUrlChunk: (chunk: string) => `<${chunk}>/login/hanzo/complete`,
|
||||
signupUrlChunk: (chunk: string) => `<${chunk}>/signup/hanzo/complete`,
|
||||
},
|
||||
help_text_markdown: true,
|
||||
},
|
||||
@@ -4653,69 +4650,45 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.Id',
|
||||
label: defineMessage({id: 'admin.gitlab.clientIdTitle', defaultMessage: 'Application ID:'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.clientIdDescription', defaultMessage: 'Obtain this value via the instructions above for logging into GitLab.'}),
|
||||
placeholder: defineMessage({id: 'admin.gitlab.clientIdExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', 'hanzo')),
|
||||
key: 'HanzoSettings.Id',
|
||||
label: defineMessage({id: 'admin.hanzo.clientIdTitle', defaultMessage: 'Application ID:'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.clientIdDescription', defaultMessage: 'The Client ID of the application you registered in Hanzo IAM.'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.clientIdExample', defaultMessage: 'E.g.: "294b09fbc17f95daf2fe"'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.Secret',
|
||||
label: defineMessage({id: 'admin.gitlab.clientSecretTitle', defaultMessage: 'Application Secret Key:'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.clientSecretDescription', defaultMessage: 'Obtain this value via the instructions above for logging into GitLab.'}),
|
||||
placeholder: defineMessage({id: 'admin.gitlab.clientSecretExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', 'hanzo')),
|
||||
key: 'HanzoSettings.Secret',
|
||||
label: defineMessage({id: 'admin.hanzo.clientSecretTitle', defaultMessage: 'Application Secret Key:'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.clientSecretDescription', defaultMessage: 'The Client Secret of the application you registered in Hanzo IAM.'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.clientSecretExample', defaultMessage: 'E.g.: "6a41a0c3b1f1e6d05d1c94e1a8b1f5d0e4a2c3b7"'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.Url',
|
||||
label: defineMessage({id: 'admin.gitlab.siteUrl', defaultMessage: 'GitLab Site URL:'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.siteUrlDescription', defaultMessage: 'Enter the URL of your GitLab instance, e.g. https://example.com:3000. If your GitLab instance is not set up with SSL, start the URL with http:// instead of https://.'}),
|
||||
placeholder: defineMessage({id: 'admin.gitlab.siteUrlExample', defaultMessage: 'E.g.: https://'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', 'hanzo')),
|
||||
key: 'HanzoSettings.UserAPIEndpoint',
|
||||
label: defineMessage({id: 'admin.hanzo.userTitle', defaultMessage: 'User API Endpoint:'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.userExample', defaultMessage: 'E.g.: https://hanzo.id/v1/iam/oauth/userinfo'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.UserAPIEndpoint',
|
||||
label: defineMessage({id: 'admin.gitlab.userTitle', defaultMessage: 'User API Endpoint:'}),
|
||||
dynamic_value: (value, config, state) => {
|
||||
if (state['GitLabSettings.Url']) {
|
||||
return state['GitLabSettings.Url'].replace(/\/$/, '') + '/api/v4/user';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
isDisabled: true,
|
||||
isHidden: it.not(it.stateEquals('oauthType', 'hanzo')),
|
||||
key: 'HanzoSettings.AuthEndpoint',
|
||||
label: defineMessage({id: 'admin.hanzo.authTitle', defaultMessage: 'Auth Endpoint:'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.authExample', defaultMessage: 'E.g.: https://hanzo.id/v1/iam/oauth/authorize'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.AuthEndpoint',
|
||||
label: defineMessage({id: 'admin.gitlab.authTitle', defaultMessage: 'Auth Endpoint:'}),
|
||||
dynamic_value: (value, config, state) => {
|
||||
if (state['GitLabSettings.Url']) {
|
||||
return state['GitLabSettings.Url'].replace(/\/$/, '') + '/oauth/authorize';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
isDisabled: true,
|
||||
isHidden: it.not(it.stateEquals('oauthType', 'hanzo')),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.TokenEndpoint',
|
||||
label: defineMessage({id: 'admin.gitlab.tokenTitle', defaultMessage: 'Token Endpoint:'}),
|
||||
dynamic_value: (value, config, state) => {
|
||||
if (state['GitLabSettings.Url']) {
|
||||
return state['GitLabSettings.Url'].replace(/\/$/, '') + '/oauth/token';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
isDisabled: true,
|
||||
isHidden: it.not(it.stateEquals('oauthType', 'hanzo')),
|
||||
key: 'HanzoSettings.TokenEndpoint',
|
||||
label: defineMessage({id: 'admin.hanzo.tokenTitle', defaultMessage: 'Token Endpoint:'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.tokenExample', defaultMessage: 'E.g.: https://hanzo.id/v1/iam/oauth/token'}),
|
||||
isHidden: it.not(it.stateEquals('oauthType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@@ -4834,24 +4807,19 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
id: 'OpenIdSettings',
|
||||
name: defineMessage({id: 'admin.authentication.openid', defaultMessage: 'OpenID Connect'}),
|
||||
onConfigLoad: (config) => {
|
||||
const newState: {openidType?: string; 'GitLabSettings.Url'?: string} = {};
|
||||
const newState: {openidType?: string} = {};
|
||||
if (config.Office365Settings?.Enable) {
|
||||
newState.openidType = Constants.OFFICE365_SERVICE;
|
||||
}
|
||||
if (config.GoogleSettings?.Enable) {
|
||||
newState.openidType = Constants.GOOGLE_SERVICE;
|
||||
}
|
||||
if (config.GitLabSettings?.Enable) {
|
||||
if (config.HanzoSettings?.Enable) {
|
||||
newState.openidType = Constants.HANZO_SERVICE;
|
||||
}
|
||||
if (config.OpenIdSettings?.Enable) {
|
||||
newState.openidType = Constants.OPENID_SERVICE;
|
||||
}
|
||||
if (config.GitLabSettings?.UserAPIEndpoint) {
|
||||
newState['GitLabSettings.Url'] = config.GitLabSettings.UserAPIEndpoint.replace('/api/v4/user', '');
|
||||
} else if (config.GitLabSettings?.DiscoveryEndpoint) {
|
||||
newState['GitLabSettings.Url'] = config.GitLabSettings.DiscoveryEndpoint.replace('/.well-known/openid-configuration', '');
|
||||
}
|
||||
|
||||
return newState;
|
||||
},
|
||||
@@ -4859,12 +4827,12 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
const newConfig = {...config};
|
||||
newConfig.Office365Settings = config.Office365Settings || {};
|
||||
newConfig.GoogleSettings = config.GoogleSettings || {};
|
||||
newConfig.GitLabSettings = config.GitLabSettings || {};
|
||||
newConfig.HanzoSettings = config.HanzoSettings || {};
|
||||
newConfig.OpenIdSettings = config.OpenIdSettings || {};
|
||||
|
||||
newConfig.Office365Settings.Enable = false;
|
||||
newConfig.GoogleSettings.Enable = false;
|
||||
newConfig.GitLabSettings.Enable = false;
|
||||
newConfig.HanzoSettings.Enable = false;
|
||||
newConfig.OpenIdSettings.Enable = false;
|
||||
|
||||
let configSetting = '';
|
||||
@@ -4873,7 +4841,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
} else if (config.openidType === Constants.GOOGLE_SERVICE) {
|
||||
configSetting = 'GoogleSettings';
|
||||
} else if (config.openidType === Constants.HANZO_SERVICE) {
|
||||
configSetting = 'GitLabSettings';
|
||||
configSetting = 'HanzoSettings';
|
||||
} else if (config.openidType === Constants.OPENID_SERVICE) {
|
||||
configSetting = 'OpenIdSettings';
|
||||
}
|
||||
@@ -4911,11 +4879,11 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
},
|
||||
{
|
||||
value: Constants.HANZO_SERVICE,
|
||||
display_name: defineMessage({id: 'admin.openid.gitlab', defaultMessage: 'GitLab'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.EnableMarkdownDesc', defaultMessage: '1. Log in to your GitLab account and go to Profile Settings -> Applications.\n2. Enter Redirect URIs "<loginUrlChunk>your-hanzo-team-url</loginUrlChunk>" (example: http://localhost:8065/login/gitlab/complete) and "<signupUrlChunk>your-hanzo-team-url</signupUrlChunk>".\n3. Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.\n4. Complete the Endpoint URLs below.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
|
||||
display_name: defineMessage({id: 'admin.openid.hanzo', defaultMessage: 'Hanzo IAM'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.EnableMarkdownDesc', defaultMessage: '1. Sign in to Hanzo IAM and open the application registered for this server.\n2. Add "<loginUrlChunk>your-hanzo-team-url</loginUrlChunk>" (example: http://localhost:8065/login/hanzo/complete) and "<signupUrlChunk>your-hanzo-team-url</signupUrlChunk>" to its redirect URIs.\n3. Copy the application Client ID and Client Secret into the fields below.\n4. The endpoints below already address hanzo.id. Change them only if you run your own IAM.'}), // eslint-disable-line formatjs/enforce-placeholders -- placeholders provided
|
||||
help_text_values: {
|
||||
loginUrlChunk: (chunk: string) => `<${chunk}>/login/gitlab/complete`,
|
||||
signupUrlChunk: (chunk: string) => `<${chunk}>/signup/gitlab/complete`,
|
||||
loginUrlChunk: (chunk: string) => `<${chunk}>/login/hanzo/complete`,
|
||||
signupUrlChunk: (chunk: string) => `<${chunk}>/signup/hanzo/complete`,
|
||||
},
|
||||
help_text_markdown: false,
|
||||
},
|
||||
@@ -4996,43 +4964,29 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.Url',
|
||||
label: defineMessage({id: 'admin.gitlab.siteUrl', defaultMessage: 'GitLab Site URL:'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.siteUrlDescription', defaultMessage: 'Enter the URL of your GitLab instance, e.g. https://example.com:3000. If your GitLab instance is not set up with SSL, start the URL with http:// instead of https://.'}),
|
||||
placeholder: defineMessage({id: 'admin.gitlab.siteUrlExample', defaultMessage: 'E.g.: https://'}),
|
||||
isHidden: it.not(it.stateEquals('openidType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.DiscoveryEndpoint',
|
||||
key: 'HanzoSettings.DiscoveryEndpoint',
|
||||
label: defineMessage({id: 'admin.openid.discoveryEndpointTitle', defaultMessage: 'Discovery Endpoint:'}),
|
||||
help_text: defineMessage({id: 'admin.gitlab.discoveryEndpointDesc', defaultMessage: 'The URL of the discovery document for OpenID Connect with GitLab.'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.discoveryEndpointDesc', defaultMessage: 'The URL of the discovery document for OpenID Connect with Hanzo IAM.'}),
|
||||
help_text_markdown: false,
|
||||
dynamic_value: (value, config, state) => {
|
||||
if (state['GitLabSettings.Url']) {
|
||||
return state['GitLabSettings.Url'].replace(/\/$/, '') + '/.well-known/openid-configuration';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
isDisabled: true,
|
||||
isHidden: it.not(it.stateEquals('openidType', Constants.HANZO_SERVICE)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.Id',
|
||||
label: defineMessage({id: 'admin.openid.clientIdTitle', defaultMessage: 'Client ID:'}),
|
||||
help_text: defineMessage({id: 'admin.openid.clientIdDescription', defaultMessage: 'Obtaining the Client ID differs across providers. Please check you provider\'s documentation.'}),
|
||||
placeholder: defineMessage({id: 'admin.gitlab.clientIdExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.discoveryEndpointExample', defaultMessage: 'E.g.: https://hanzo.id/.well-known/openid-configuration'}),
|
||||
isHidden: it.not(it.stateEquals('openidType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'GitLabSettings.Secret',
|
||||
key: 'HanzoSettings.Id',
|
||||
label: defineMessage({id: 'admin.openid.clientIdTitle', defaultMessage: 'Client ID:'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.clientIdDescription', defaultMessage: 'The Client ID of the application you registered in Hanzo IAM.'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.clientIdExample', defaultMessage: 'E.g.: "294b09fbc17f95daf2fe"'}),
|
||||
isHidden: it.not(it.stateEquals('openidType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'HanzoSettings.Secret',
|
||||
label: defineMessage({id: 'admin.openid.clientSecretTitle', defaultMessage: 'Client Secret:'}),
|
||||
help_text: defineMessage({id: 'admin.openid.clientSecretDescription', defaultMessage: 'Obtaining the Client Secret differs across providers. Please check you provider\'s documentation.'}),
|
||||
placeholder: defineMessage({id: 'admin.gitlab.clientSecretExample', defaultMessage: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'}),
|
||||
help_text: defineMessage({id: 'admin.hanzo.clientSecretDescription', defaultMessage: 'The Client Secret of the application you registered in Hanzo IAM.'}),
|
||||
placeholder: defineMessage({id: 'admin.hanzo.clientSecretExample', defaultMessage: 'E.g.: "6a41a0c3b1f1e6d05d1c94e1a8b1f5d0e4a2c3b7"'}),
|
||||
isHidden: it.not(it.stateEquals('openidType', Constants.HANZO_SERVICE)),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.AUTHENTICATION.OPENID)),
|
||||
},
|
||||
@@ -5155,7 +5109,7 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
key: 'GitLabSettings.UsePreferredUsername',
|
||||
key: 'HanzoSettings.UsePreferredUsername',
|
||||
label: defineMessage({id: 'admin.openid.usePreferredUsernameTitle', defaultMessage: 'Use Preferred Username:'}),
|
||||
help_text: defineMessage({id: 'admin.openid.usePreferredUsernameDescription', defaultMessage: 'When true, use the `preferred_username` claim as the Hanzo Team username for the user. The scope must include `profile` and `openid` to use this feature.'}),
|
||||
help_text_markdown: true,
|
||||
|
||||
@@ -84,16 +84,16 @@ export const validators = {
|
||||
};
|
||||
|
||||
export const usesLegacyOauth = (config: Partial<AdminConfig>, state: any, license?: ClientLicense, enterpriseReady?: boolean, consoleAccess?: ConsoleAccess, cloud?: CloudState) => {
|
||||
if (!config.GitLabSettings || !config.GoogleSettings || !config.Office365Settings) {
|
||||
if (!config.HanzoSettings || !config.GoogleSettings || !config.Office365Settings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it.any(
|
||||
it.all(
|
||||
it.not(it.configContains('GitLabSettings', 'Scope', 'openid')),
|
||||
it.not(it.configContains('HanzoSettings', 'Scope', 'openid')),
|
||||
it.any(
|
||||
it.configIsTrue('GitLabSettings', 'Id'),
|
||||
it.configIsTrue('GitLabSettings', 'Secret'),
|
||||
it.configIsTrue('HanzoSettings', 'Id'),
|
||||
it.configIsTrue('HanzoSettings', 'Secret'),
|
||||
),
|
||||
),
|
||||
it.all(
|
||||
|
||||
@@ -322,9 +322,9 @@ describe('components/AdminSidebar', () => {
|
||||
Secret: 'googleSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
GitLabSettings: {
|
||||
Id: 'gitlabID',
|
||||
Secret: 'gitlabSecret',
|
||||
HanzoSettings: {
|
||||
Id: 'hanzoID',
|
||||
Secret: 'hanzoSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
Office365Settings: {
|
||||
@@ -387,9 +387,9 @@ describe('components/AdminSidebar', () => {
|
||||
Secret: 'googleSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
GitLabSettings: {
|
||||
Id: 'gitlabID',
|
||||
Secret: 'gitlabSecret',
|
||||
HanzoSettings: {
|
||||
Id: 'hanzoID',
|
||||
Secret: 'hanzoSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
Office365Settings: {
|
||||
@@ -452,9 +452,9 @@ describe('components/AdminSidebar', () => {
|
||||
Secret: 'googleSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
GitLabSettings: {
|
||||
Id: 'gitlabID',
|
||||
Secret: 'gitlabSecret',
|
||||
HanzoSettings: {
|
||||
Id: 'hanzoID',
|
||||
Secret: 'hanzoSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
Office365Settings: {
|
||||
@@ -517,9 +517,9 @@ describe('components/AdminSidebar', () => {
|
||||
Secret: 'googleSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
GitLabSettings: {
|
||||
Id: 'gitlabID',
|
||||
Secret: 'gitlabSecret',
|
||||
HanzoSettings: {
|
||||
Id: 'hanzoID',
|
||||
Secret: 'hanzoSecret',
|
||||
Scope: 'scope',
|
||||
} as SSOSettings,
|
||||
Office365Settings: {
|
||||
|
||||
+4
-4
@@ -24,7 +24,7 @@ exports[`components/admin_console/admin_user_card/admin_user_card should match d
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/1234/image"
|
||||
src="/v1/workspace/users/1234/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -90,7 +90,7 @@ exports[`components/admin_console/admin_user_card/admin_user_card should match s
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/1234/image"
|
||||
src="/v1/workspace/users/1234/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -151,7 +151,7 @@ exports[`components/admin_console/admin_user_card/admin_user_card should match s
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/1234/image"
|
||||
src="/v1/workspace/users/1234/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -210,7 +210,7 @@ exports[`components/admin_console/admin_user_card/admin_user_card should match s
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/1234/image"
|
||||
src="/v1/workspace/users/1234/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ exports[`components/admin_console/content_flagging/user_multiselector/UserProfil
|
||||
alt="testuser profile image"
|
||||
class="Avatar Avatar-xxs"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user-id-1/image?_=0"
|
||||
src="/v1/workspace/users/user-id-1/image?_=0"
|
||||
/>
|
||||
Test User
|
||||
<div
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ function makeClientError(serverErrorId: string, message = 'error'): ClientError
|
||||
message,
|
||||
server_error_id: serverErrorId,
|
||||
status_code: 422,
|
||||
url: 'https://example.com/v1/team/properties/groups/access_control/template/fields',
|
||||
url: 'https://example.com/v1/workspace/properties/groups/access_control/template/fields',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -231,7 +231,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user1 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/77777777777777777777777777/image"
|
||||
src="/v1/workspace/users/77777777777777777777777777/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -272,7 +272,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user2 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/88888888888888888888888888/image"
|
||||
src="/v1/workspace/users/88888888888888888888888888/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -313,7 +313,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user3 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/99999999999999999999999999/image"
|
||||
src="/v1/workspace/users/99999999999999999999999999/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -642,7 +642,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user1 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/77777777777777777777777777/image"
|
||||
src="/v1/workspace/users/77777777777777777777777777/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -683,7 +683,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user2 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/88888888888888888888888888/image"
|
||||
src="/v1/workspace/users/88888888888888888888888888/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -724,7 +724,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user3 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/99999999999999999999999999/image"
|
||||
src="/v1/workspace/users/99999999999999999999999999/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -1051,7 +1051,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user1 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/77777777777777777777777777/image"
|
||||
src="/v1/workspace/users/77777777777777777777777777/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -1092,7 +1092,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user2 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/88888888888888888888888888/image"
|
||||
src="/v1/workspace/users/88888888888888888888888888/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -1133,7 +1133,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user3 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/99999999999999999999999999/image"
|
||||
src="/v1/workspace/users/99999999999999999999999999/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -1458,7 +1458,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user1 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/77777777777777777777777777/image"
|
||||
src="/v1/workspace/users/77777777777777777777777777/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -1499,7 +1499,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user2 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/88888888888888888888888888/image"
|
||||
src="/v1/workspace/users/88888888888888888888888888/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
@@ -1540,7 +1540,7 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
alt="user3 profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/99999999999999999999999999/image"
|
||||
src="/v1/workspace/users/99999999999999999999999999/image"
|
||||
/>
|
||||
<div
|
||||
class="user-data"
|
||||
|
||||
+135
-135
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -66,7 +66,7 @@ exports[`ManageTeamsModal should match snapshot init 1`] = `
|
||||
alt="currentUsername profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/currentUserId/image?_=1234"
|
||||
src="/v1/workspace/users/currentUserId/image?_=1234"
|
||||
/>
|
||||
<div
|
||||
class="manage-teams__info"
|
||||
|
||||
+3
-3
@@ -65,7 +65,7 @@ exports[`components/admin_console/manage_tokens_modal/manage_tokens_modal.tsx in
|
||||
alt="some-user profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/defaultuser/image"
|
||||
src="/v1/workspace/users/defaultuser/image"
|
||||
/>
|
||||
<div
|
||||
class="manage-teams__info"
|
||||
@@ -203,7 +203,7 @@ exports[`components/admin_console/manage_tokens_modal/manage_tokens_modal.tsx sh
|
||||
alt="some-user profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/defaultuser/image"
|
||||
src="/v1/workspace/users/defaultuser/image"
|
||||
/>
|
||||
<div
|
||||
class="manage-teams__info"
|
||||
@@ -403,7 +403,7 @@ exports[`components/admin_console/manage_tokens_modal/manage_tokens_modal.tsx sh
|
||||
alt="some-user profile image"
|
||||
class="Avatar Avatar-lg"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/defaultuser/image"
|
||||
src="/v1/workspace/users/defaultuser/image"
|
||||
/>
|
||||
<div
|
||||
class="manage-teams__info"
|
||||
|
||||
@@ -43,12 +43,14 @@ const OpenIdConvert = ({
|
||||
}
|
||||
newConfig.GoogleSettings.DiscoveryEndpoint = 'https://accounts.google.com/.well-known/openid-configuration';
|
||||
|
||||
if (newConfig.GitLabSettings.UserAPIEndpoint) {
|
||||
const url = newConfig.GitLabSettings.UserAPIEndpoint.replace('/api/v4/user', '');
|
||||
newConfig.GitLabSettings.DiscoveryEndpoint = url + '/.well-known/openid-configuration';
|
||||
// Hanzo IAM serves its discovery document at the root of whichever host
|
||||
// holds the OAuth endpoints, so the configured one names the issuer.
|
||||
const iam = newConfig.HanzoSettings.UserAPIEndpoint;
|
||||
if (URL.canParse(iam)) {
|
||||
newConfig.HanzoSettings.DiscoveryEndpoint = new URL(iam).origin + '/.well-known/openid-configuration';
|
||||
}
|
||||
|
||||
['Office365Settings', 'GoogleSettings', 'GitLabSettings'].forEach((setting) => {
|
||||
['Office365Settings', 'GoogleSettings', 'HanzoSettings'].forEach((setting) => {
|
||||
newConfig[setting].Scope = Constants.OPENID_SCOPES;
|
||||
newConfig[setting].UserAPIEndpoint = '';
|
||||
newConfig[setting].AuthEndpoint = '';
|
||||
|
||||
@@ -80,13 +80,13 @@ describe('SchemaText', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
isMarkdown: true,
|
||||
text: 'This is [a link](http://localhost:8065/v1/team/users/src_id)',
|
||||
text: 'This is [a link](http://localhost:8065/v1/workspace/users/src_id)',
|
||||
};
|
||||
|
||||
const {container} = renderWithContext(<SchemaText {...props}/>);
|
||||
|
||||
const span = container.querySelector('span');
|
||||
expect(span).toHaveProperty('innerHTML', 'This is <a href="http://localhost:8065/v1/team/users/src_id">a link</a>');
|
||||
expect(span).toHaveProperty('innerHTML', 'This is <a href="http://localhost:8065/v1/workspace/users/src_id">a link</a>');
|
||||
});
|
||||
|
||||
test('should support explicit external links like FormattedMarkdownMessage', () => {
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ exports[`components/admin_console/server_logs/Logs should display the logs corre
|
||||
</button>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
href="/v1/team/logs/download"
|
||||
href="/v1/workspace/logs/download"
|
||||
location="download_logs"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
|
||||
+2
-2
@@ -247,7 +247,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -313,7 +313,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+7
-7
@@ -47,7 +47,7 @@ exports[`SystemUserDetail should match default snapshot 1`] = `
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -350,7 +350,7 @@ exports[`SystemUserDetail should match snapshot if MFA is enabled 1`] = `
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -653,7 +653,7 @@ exports[`SystemUserDetail should not fetch CPA data if disabled 1`] = `
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -956,7 +956,7 @@ exports[`SystemUserDetail should not show manage user settings button when user
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -1259,7 +1259,7 @@ exports[`SystemUserDetail should show manage user settings button as activated 1
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -1568,7 +1568,7 @@ exports[`SystemUserDetail should show manage user settings button as disabled wh
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
@@ -1871,7 +1871,7 @@ exports[`SystemUserDetail should show the activate user button as disabled when
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xxl"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image"
|
||||
src="/v1/workspace/users/user_id/image"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
+4
-4
@@ -419,7 +419,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemove shou
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid1/image"
|
||||
src="/v1/workspace/users/userid1/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -485,7 +485,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemove shou
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid2/image"
|
||||
src="/v1/workspace/users/userid2/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -756,7 +756,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemove shou
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid1/image"
|
||||
src="/v1/workspace/users/userid1/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -822,7 +822,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemove shou
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid2/image"
|
||||
src="/v1/workspace/users/userid2/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+6
-6
@@ -120,7 +120,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid1/image"
|
||||
src="/v1/workspace/users/userid1/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -185,7 +185,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid2/image"
|
||||
src="/v1/workspace/users/userid2/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -388,7 +388,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid-not-saved/image"
|
||||
src="/v1/workspace/users/userid-not-saved/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -463,7 +463,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid1/image"
|
||||
src="/v1/workspace/users/userid1/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -528,7 +528,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid2/image"
|
||||
src="/v1/workspace/users/userid2/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -731,7 +731,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/userid2/image"
|
||||
src="/v1/workspace/users/userid2/image"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import {ItemStatus} from '../dashboard.type';
|
||||
|
||||
jest.mock('mattermost-redux/client', () => ({
|
||||
Client4: {
|
||||
getBaseRoute: jest.fn(() => 'http://localhost/v1/team'),
|
||||
getBaseRoute: jest.fn(() => 'http://localhost/v1/workspace'),
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ exports[`AudioVideoPreview should match snapshot without children 1`] = `
|
||||
width="640"
|
||||
>
|
||||
<source
|
||||
src="/v1/team/files/file_id"
|
||||
src="/v1/workspace/files/file_id"
|
||||
/>
|
||||
</video>
|
||||
</div>
|
||||
@@ -22,7 +22,7 @@ exports[`AudioVideoPreview should match snapshot, cannot play 1`] = `
|
||||
>
|
||||
<a
|
||||
class="file-details__preview"
|
||||
href="/v1/team/files/file_id"
|
||||
href="/v1/workspace/files/file_id"
|
||||
>
|
||||
<span
|
||||
class="file-details__preview-helper"
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('AudioVideoPreview', () => {
|
||||
extension: 'mov',
|
||||
id: 'file_id',
|
||||
}),
|
||||
fileUrl: '/v1/team/files/file_id',
|
||||
fileUrl: '/v1/workspace/files/file_id',
|
||||
isMobileView: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('components/audit_table/audit_row/AuditRow', () => {
|
||||
};
|
||||
test('should describe a row recorded under the current API base', () => {
|
||||
const audit: Audit = {
|
||||
action: '/v1/team/channels',
|
||||
action: '/v1/workspace/channels',
|
||||
create_at: 50778112674,
|
||||
extra_info: `name=${channelName}`,
|
||||
id: 'id_2',
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function FormatAudit({
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
// An audit row stores the request path as it stood when the row was written,
|
||||
// so rows predating the move to /v1/team still carry an /api/vN base.
|
||||
// so rows predating the move to /v1/workspace still carry an /api/vN base.
|
||||
const actionURL = audit.action.replace(Client4.getUrlVersion(), '').replace(/^\/api\/v[1-9]/, '');
|
||||
|
||||
if (actionURL.indexOf('/channels') === 0) {
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('useBookmarkLink', () => {
|
||||
{entities: {files: {files: {f1: fileInfoFixture as FileInfo}}}},
|
||||
);
|
||||
|
||||
expect(result.current.href).toContain('/v1/team/files/f1');
|
||||
expect(result.current.href).toContain('/v1/workspace/files/f1');
|
||||
expect(result.current.isFile).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ jest.mock('mattermost-redux/client', () => ({
|
||||
getProfilesMatchingChannelPolicy: jest.fn().mockResolvedValue([]),
|
||||
searchUsers: jest.fn().mockResolvedValue([]),
|
||||
getProfilePictureUrl: jest.fn(() => 'mock-url'),
|
||||
getUsersRoute: jest.fn(() => '/v1/team/users'),
|
||||
getTeamsRoute: jest.fn(() => '/v1/team/teams'),
|
||||
getChannelsRoute: jest.fn(() => '/v1/team/channels'),
|
||||
getUsersRoute: jest.fn(() => '/v1/workspace/users'),
|
||||
getTeamsRoute: jest.fn(() => '/v1/workspace/teams'),
|
||||
getChannelsRoute: jest.fn(() => '/v1/workspace/channels'),
|
||||
getUrl: jest.fn(() => 'http://localhost:8065'),
|
||||
getBaseRoute: jest.fn(() => '/v1/team'),
|
||||
getBaseRoute: jest.fn(() => '/v1/workspace'),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -162,11 +162,11 @@ describe('components/channel_invite_modal', () => {
|
||||
Client4.getProfilesMatchingChannelPolicy.mockResolvedValue([]);
|
||||
Client4.searchUsers.mockResolvedValue([]);
|
||||
Client4.getProfilePictureUrl.mockReturnValue('mock-url');
|
||||
Client4.getUsersRoute.mockReturnValue('/v1/team/users');
|
||||
Client4.getTeamsRoute.mockReturnValue('/v1/team/teams');
|
||||
Client4.getChannelsRoute.mockReturnValue('/v1/team/channels');
|
||||
Client4.getUsersRoute.mockReturnValue('/v1/workspace/users');
|
||||
Client4.getTeamsRoute.mockReturnValue('/v1/workspace/teams');
|
||||
Client4.getChannelsRoute.mockReturnValue('/v1/workspace/channels');
|
||||
Client4.getUrl.mockReturnValue('http://localhost:8065');
|
||||
Client4.getBaseRoute.mockReturnValue('/v1/team');
|
||||
Client4.getBaseRoute.mockReturnValue('/v1/workspace');
|
||||
});
|
||||
|
||||
test('should match snapshot for channel_invite_modal with profiles', () => {
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ jest.mock('components/widgets/loading/loading_spinner', () => () => <div>{'Loadi
|
||||
describe('components/CommercialSupportModal', () => {
|
||||
beforeAll(() => {
|
||||
// Mock getSystemRoute to return a valid URL
|
||||
jest.spyOn(Client4, 'getSystemRoute').mockImplementation(() => 'http://localhost:8065/v1/team/system');
|
||||
jest.spyOn(Client4, 'getSystemRoute').mockImplementation(() => 'http://localhost:8065/v1/workspace/system');
|
||||
|
||||
// Mock createObjectURL
|
||||
window.URL.createObjectURL = jest.fn().mockReturnValue('mock-url');
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ exports[`components/drafts/draft_actions should match snapshot for DM channel 1`
|
||||
alt="user profile image"
|
||||
class="Avatar Avatar-xs DraftTitle__avatar"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/undefined/image?_=0"
|
||||
src="/v1/workspace/users/undefined/image?_=0"
|
||||
/>
|
||||
|
||||
<span>
|
||||
@@ -51,7 +51,7 @@ exports[`components/drafts/draft_actions should match snapshot for DM channel wi
|
||||
alt="username profile image"
|
||||
class="Avatar Avatar-xs DraftTitle__avatar"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/id/image?_=1000"
|
||||
src="/v1/workspace/users/id/image?_=1000"
|
||||
/>
|
||||
|
||||
<span>
|
||||
|
||||
@@ -24,7 +24,7 @@ exports[`components/drafts/panel/panel_body should have called handleFormattedTe
|
||||
alt="username profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image?_=0"
|
||||
src="/v1/workspace/users/user_id/image?_=0"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
@@ -111,7 +111,7 @@ exports[`components/drafts/panel/panel_body should match snapshot 1`] = `
|
||||
alt="username profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image?_=0"
|
||||
src="/v1/workspace/users/user_id/image?_=0"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
@@ -198,7 +198,7 @@ exports[`components/drafts/panel/panel_body should match snapshot for priority 1
|
||||
alt="username profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image?_=0"
|
||||
src="/v1/workspace/users/user_id/image?_=0"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
@@ -311,7 +311,7 @@ exports[`components/drafts/panel/panel_body should match snapshot for requested_
|
||||
alt="username profile image"
|
||||
class="Avatar Avatar-md"
|
||||
loading="lazy"
|
||||
src="/v1/team/users/user_id/image?_=0"
|
||||
src="/v1/workspace/users/user_id/image?_=0"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
|
||||
@@ -31,13 +31,13 @@ describe('ExternalIImage isSVGImage', () => {
|
||||
},
|
||||
{
|
||||
name: 'no metadata, proxied image',
|
||||
src: 'https://team.example.com/v1/team/image?url=' + encodeURIComponent('https://example.com/image.png'),
|
||||
src: 'https://team.example.com/v1/workspace/image?url=' + encodeURIComponent('https://example.com/image.png'),
|
||||
imageMetadata: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, proxied svg image',
|
||||
src: 'https://team.example.com/v1/team/image?url=' + encodeURIComponent('https://example.com/image.svg'),
|
||||
src: 'https://team.example.com/v1/workspace/image?url=' + encodeURIComponent('https://example.com/image.svg'),
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
|
||||
+9
-9
@@ -47,7 +47,7 @@ exports[`FileAttachment should match snapshot when file is deleted 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.pdf"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -109,7 +109,7 @@ exports[`FileAttachment should match snapshot with thumbnail disabled 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.pdf"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -172,7 +172,7 @@ exports[`FileAttachment should match snapshot, after change from file to image 1
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.png"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -234,7 +234,7 @@ exports[`FileAttachment should match snapshot, regular file 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.pdf"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -296,7 +296,7 @@ exports[`FileAttachment should match snapshot, regular image 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.png"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -358,7 +358,7 @@ exports[`FileAttachment should match snapshot, small image 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.png"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -420,7 +420,7 @@ exports[`FileAttachment should match snapshot, svg image 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.svg"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -482,7 +482,7 @@ exports[`FileAttachment should match snapshot, when file is not loaded 1`] = `
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.pdf"
|
||||
href="/v1/team/files/noLoad?download=1"
|
||||
href="/v1/workspace/files/noLoad?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -647,7 +647,7 @@ exports[`FileAttachment should not render menu items when disable actions is set
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test.pdf"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ exports[`components/file_attachment/FilenameOverlay should match snapshot, stand
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test_filename"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -89,7 +89,7 @@ exports[`components/file_attachment/FilenameOverlay should match snapshot, with
|
||||
aria-label="download"
|
||||
class="btn btn-icon btn-sm"
|
||||
download="test_filename"
|
||||
href="/v1/team/files/thumbnail_id?download=1"
|
||||
href="/v1/workspace/files/thumbnail_id?download=1"
|
||||
location="filename_overlay"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ exports[`FileThumbnail should render a normal-sized image 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="post-image normal"
|
||||
style="background-image: url(\\"/v1/team/files/thumbnail_id/thumbnail\\"); background-size: cover;"
|
||||
style="background-image: url(\\"/v1/workspace/files/thumbnail_id/thumbnail\\"); background-size: cover;"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -13,7 +13,7 @@ exports[`FileThumbnail should render a small image 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="post-image small"
|
||||
style="background-image: url(\\"/v1/team/files/thumbnail_id/thumbnail\\"); background-size: cover;"
|
||||
style="background-image: url(\\"/v1/workspace/files/thumbnail_id/thumbnail\\"); background-size: cover;"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -47,7 +47,7 @@ exports[`FileThumbnail should render an svg when svg previews are enabled 1`] =
|
||||
<img
|
||||
alt="file thumbnail image"
|
||||
class="post-image normal"
|
||||
src="/v1/team/files/thumbnail_id"
|
||||
src="/v1/workspace/files/thumbnail_id"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ exports[`components/FileInfoPreview should match snapshot, can download files 1`
|
||||
>
|
||||
<a
|
||||
class="file-details__preview"
|
||||
href="https://pre-release.mattermost.com/v1/team/files/rqir81f7a7ft8m6j6ej7g1txuo"
|
||||
href="https://pre-release.mattermost.com/v1/workspace/files/rqir81f7a7ft8m6j6ej7g1txuo"
|
||||
>
|
||||
<span
|
||||
class="file-details__preview-helper"
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('components/FileInfoPreview', () => {
|
||||
test('should match snapshot, can download files', () => {
|
||||
const {container} = renderWithContext(
|
||||
<FileInfoPreview
|
||||
fileUrl='https://pre-release.mattermost.com/v1/team/files/rqir81f7a7ft8m6j6ej7g1txuo'
|
||||
fileUrl='https://pre-release.mattermost.com/v1/workspace/files/rqir81f7a7ft8m6j6ej7g1txuo'
|
||||
fileInfo={{name: 'Test Image', size: 100, extension: 'jpg'} as FileInfo}
|
||||
canDownloadFiles={true}
|
||||
/>,
|
||||
@@ -25,7 +25,7 @@ describe('components/FileInfoPreview', () => {
|
||||
test('should match snapshot, cannot download files', () => {
|
||||
const {container} = renderWithContext(
|
||||
<FileInfoPreview
|
||||
fileUrl='https://pre-release.mattermost.com/v1/team/files/aasf9afshaskj1asf91jasf0a0'
|
||||
fileUrl='https://pre-release.mattermost.com/v1/workspace/files/aasf9afshaskj1asf91jasf0a0'
|
||||
fileInfo={{name: 'Test Image 2', size: 200, extension: 'png'} as FileInfo}
|
||||
canDownloadFiles={false}
|
||||
/>,
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@ exports[`FilePreview should match snapshot 1`] = `
|
||||
>
|
||||
<div
|
||||
class="post-image normal"
|
||||
style="background-image: url(\\"/v1/team/files/file_id_1/thumbnail\\"); background-size: cover;"
|
||||
style="background-image: url(\\"/v1/workspace/files/file_id_1/thumbnail\\"); background-size: cover;"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
@@ -138,7 +138,7 @@ exports[`FilePreview should match snapshot when props are changed 1`] = `
|
||||
>
|
||||
<div
|
||||
class="post-image normal"
|
||||
style="background-image: url(\\"/v1/team/files/file_id_1/thumbnail\\"); background-size: cover;"
|
||||
style="background-image: url(\\"/v1/workspace/files/file_id_1/thumbnail\\"); background-size: cover;"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
@@ -259,7 +259,7 @@ exports[`FilePreview should match snapshot when props are changed 2`] = `
|
||||
>
|
||||
<div
|
||||
class="post-image normal"
|
||||
style="background-image: url(\\"/v1/team/files/file_id_1/thumbnail\\"); background-size: cover;"
|
||||
style="background-image: url(\\"/v1/workspace/files/file_id_1/thumbnail\\"); background-size: cover;"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
@@ -312,7 +312,7 @@ exports[`FilePreview should match snapshot when props are changed 2`] = `
|
||||
>
|
||||
<div
|
||||
class="post-image normal"
|
||||
style="background-image: url(\\"/v1/team/files/file_id_2/thumbnail\\"); background-size: cover;"
|
||||
style="background-image: url(\\"/v1/workspace/files/file_id_2/thumbnail\\"); background-size: cover;"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ exports[`components/view_image/ImagePreview should match snapshot, with preview
|
||||
data-testid="imagePreview"
|
||||
draggable="false"
|
||||
loading="lazy"
|
||||
src="/v1/team/files/file_id_1/preview"
|
||||
src="/v1/workspace/files/file_id_1/preview"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@ exports[`components/view_image/ImagePreview should match snapshot, with preview,
|
||||
<img
|
||||
class="image_preview__image"
|
||||
draggable="false"
|
||||
src="/v1/team/files/file_id_1/preview"
|
||||
src="/v1/workspace/files/file_id_1/preview"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@ exports[`components/view_image/ImagePreview should match snapshot, without previ
|
||||
data-testid="imagePreview"
|
||||
draggable="false"
|
||||
loading="lazy"
|
||||
src="/v1/team/files/file_id?download=1"
|
||||
src="/v1/workspace/files/file_id?download=1"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@ exports[`components/view_image/ImagePreview should match snapshot, without previ
|
||||
<img
|
||||
class="image_preview__image"
|
||||
draggable="false"
|
||||
src="/v1/team/files/file_id?download=1"
|
||||
src="/v1/workspace/files/file_id?download=1"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user