Allow the default space to be accessed via /s/default (#77109)

* Allow the default space to be accessed via /s/default

* apply suggestions from code review
This commit is contained in:
Larry Gregory 2020-10-16 07:21:57 -04:00 committed by GitHub
parent 2e37bd0703
commit 51ac14ba57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 120 deletions

View file

@ -10,41 +10,76 @@ describe('getSpaceIdFromPath', () => {
describe('without a serverBasePath defined', () => {
test('it identifies the space url context', () => {
const basePath = `/s/my-awesome-space-lives-here`;
expect(getSpaceIdFromPath(basePath)).toEqual('my-awesome-space-lives-here');
expect(getSpaceIdFromPath(basePath)).toEqual({
spaceId: 'my-awesome-space-lives-here',
pathHasExplicitSpaceIdentifier: true,
});
});
test('ignores space identifiers in the middle of the path', () => {
const basePath = `/this/is/a/crazy/path/s/my-awesome-space-lives-here`;
expect(getSpaceIdFromPath(basePath)).toEqual(DEFAULT_SPACE_ID);
expect(getSpaceIdFromPath(basePath)).toEqual({
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: false,
});
});
test('it handles base url without a space url context', () => {
const basePath = `/this/is/a/crazy/path/s`;
expect(getSpaceIdFromPath(basePath)).toEqual(DEFAULT_SPACE_ID);
expect(getSpaceIdFromPath(basePath)).toEqual({
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: false,
});
});
test('it identifies the space url context with the default space', () => {
const basePath = `/s/${DEFAULT_SPACE_ID}`;
expect(getSpaceIdFromPath(basePath)).toEqual({
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: true,
});
});
});
describe('with a serverBasePath defined', () => {
test('it identifies the space url context', () => {
const basePath = `/s/my-awesome-space-lives-here`;
expect(getSpaceIdFromPath(basePath, '/')).toEqual('my-awesome-space-lives-here');
expect(getSpaceIdFromPath(basePath, '/')).toEqual({
spaceId: 'my-awesome-space-lives-here',
pathHasExplicitSpaceIdentifier: true,
});
});
test('it identifies the space url context following the server base path', () => {
const basePath = `/server-base-path-here/s/my-awesome-space-lives-here`;
expect(getSpaceIdFromPath(basePath, '/server-base-path-here')).toEqual(
'my-awesome-space-lives-here'
);
expect(getSpaceIdFromPath(basePath, '/server-base-path-here')).toEqual({
spaceId: 'my-awesome-space-lives-here',
pathHasExplicitSpaceIdentifier: true,
});
});
test('ignores space identifiers in the middle of the path', () => {
const basePath = `/this/is/a/crazy/path/s/my-awesome-space-lives-here`;
expect(getSpaceIdFromPath(basePath, '/this/is/a')).toEqual(DEFAULT_SPACE_ID);
expect(getSpaceIdFromPath(basePath, '/this/is/a')).toEqual({
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: false,
});
});
test('it identifies the space url context with the default space following the server base path', () => {
const basePath = `/server-base-path-here/s/${DEFAULT_SPACE_ID}`;
expect(getSpaceIdFromPath(basePath, '/server-base-path-here')).toEqual({
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: true,
});
});
test('it handles base url without a space url context', () => {
const basePath = `/this/is/a/crazy/path/s`;
expect(getSpaceIdFromPath(basePath, basePath)).toEqual(DEFAULT_SPACE_ID);
expect(getSpaceIdFromPath(basePath, basePath)).toEqual({
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: false,
});
});
});
});

View file

@ -5,20 +5,22 @@
*/
import { DEFAULT_SPACE_ID } from '../constants';
const spaceContextRegex = /^\/s\/([a-z0-9_\-]+)/;
export function getSpaceIdFromPath(
requestBasePath: string = '/',
serverBasePath: string = '/'
): string {
let pathToCheck: string = requestBasePath;
): { spaceId: string; pathHasExplicitSpaceIdentifier: boolean } {
const pathToCheck: string = stripServerBasePath(requestBasePath, serverBasePath);
if (serverBasePath && serverBasePath !== '/' && requestBasePath.startsWith(serverBasePath)) {
pathToCheck = requestBasePath.substr(serverBasePath.length);
}
// Look for `/s/space-url-context` in the base path
const matchResult = pathToCheck.match(/^\/s\/([a-z0-9_\-]+)/);
const matchResult = pathToCheck.match(spaceContextRegex);
if (!matchResult || matchResult.length === 0) {
return DEFAULT_SPACE_ID;
return {
spaceId: DEFAULT_SPACE_ID,
pathHasExplicitSpaceIdentifier: false,
};
}
// Ignoring first result, we only want the capture group result at index 1
@ -28,7 +30,10 @@ export function getSpaceIdFromPath(
throw new Error(`Unable to determine Space ID from request path: ${requestBasePath}`);
}
return spaceId;
return {
spaceId,
pathHasExplicitSpaceIdentifier: true,
};
}
export function addSpaceIdToPath(
@ -45,3 +50,10 @@ export function addSpaceIdToPath(
}
return `${basePath}${requestedPath}`;
}
function stripServerBasePath(requestBasePath: string, serverBasePath: string) {
if (serverBasePath && serverBasePath !== '/' && requestBasePath.startsWith(serverBasePath)) {
return requestBasePath.substr(serverBasePath.length);
}
return requestBasePath;
}

View file

@ -10,7 +10,6 @@ import {
CoreSetup,
} from 'src/core/server';
import { format } from 'url';
import { DEFAULT_SPACE_ID } from '../../../common/constants';
import { modifyUrl } from '../utils/url';
import { getSpaceIdFromPath } from '../../../common';
@ -28,9 +27,9 @@ export function initSpacesOnRequestInterceptor({ http }: OnRequestInterceptorDep
// If navigating within the context of a space, then we store the Space's URL Context on the request,
// and rewrite the request to not include the space identifier in the URL.
const spaceId = getSpaceIdFromPath(path, serverBasePath);
const { spaceId, pathHasExplicitSpaceIdentifier } = getSpaceIdFromPath(path, serverBasePath);
if (spaceId !== DEFAULT_SPACE_ID) {
if (pathHasExplicitSpaceIdentifier) {
const reqBasePath = `/s/${spaceId}`;
http.basePath.set(request, reqBasePath);

View file

@ -58,7 +58,7 @@ const createService = async (serverBasePath: string = '') => {
serverBasePath,
} as HttpServiceSetup['basePath'];
httpSetup.basePath.get = jest.fn().mockImplementation((request: KibanaRequest) => {
const spaceId = getSpaceIdFromPath(request.url.path);
const { spaceId } = getSpaceIdFromPath(request.url.path);
if (spaceId !== DEFAULT_SPACE_ID) {
return `/s/${spaceId}`;

View file

@ -63,7 +63,7 @@ export class SpacesService {
? (request as Record<string, any>).getBasePath()
: http.basePath.get(request);
const spaceId = getSpaceIdFromPath(basePath, http.basePath.serverBasePath);
const { spaceId } = getSpaceIdFromPath(basePath, http.basePath.serverBasePath);
return spaceId;
};

View file

@ -35,6 +35,20 @@ export default function ({ getService }: FtrProviderContext) {
});
});
it('returns the default space when explicitly referenced', async () => {
await supertest
.get('/s/default/internal/spaces/_active_space')
.set('kbn-xsrf', 'xxx')
.expect(200, {
id: 'default',
name: 'Default',
description: 'This is your default space!',
color: '#00bfb3',
disabledFeatures: [],
_reserved: true,
});
});
it('returns the foo space', async () => {
await supertest
.get('/s/foo-space/internal/spaces/_active_space')

View file

@ -13,3 +13,24 @@ export function getUrlPrefix(spaceId?: string) {
export function getIdPrefix(spaceId?: string) {
return spaceId === DEFAULT_SPACE_ID ? '' : `${spaceId}-`;
}
export function getTestScenariosForSpace(spaceId: string) {
const explicitScenario = {
spaceId,
urlPrefix: `/s/${spaceId}`,
scenario: `when referencing the ${spaceId} space explicitly in the URL`,
};
if (spaceId === DEFAULT_SPACE_ID) {
return [
{
spaceId,
urlPrefix: ``,
scenario: 'when referencing the default space implicitly',
},
explicitScenario,
];
}
return [explicitScenario];
}

View file

@ -6,7 +6,7 @@
import expect from '@kbn/expect';
import { SuperTest } from 'supertest';
import { getUrlPrefix } from '../lib/space_test_utils';
import { getTestScenariosForSpace } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
interface CreateTest {
@ -67,56 +67,58 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
{ user = {}, spaceId, tests }: CreateTestDefinition
) => {
describeFn(description, () => {
before(() => esArchiver.load('saved_objects/spaces'));
after(() => esArchiver.unload('saved_objects/spaces'));
beforeEach(() => esArchiver.load('saved_objects/spaces'));
afterEach(() => esArchiver.unload('saved_objects/spaces'));
it(`should return ${tests.newSpace.statusCode}`, async () => {
return supertest
.post(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(user.username, user.password)
.send({
name: 'marketing',
id: 'marketing',
description: 'a description',
color: '#5c5959',
disabledFeatures: [],
})
.expect(tests.newSpace.statusCode)
.then(tests.newSpace.response);
});
describe('when it already exists', () => {
it(`should return ${tests.alreadyExists.statusCode}`, async () => {
getTestScenariosForSpace(spaceId).forEach(({ urlPrefix, scenario }) => {
it(`should return ${tests.newSpace.statusCode} ${scenario}`, async () => {
return supertest
.post(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.post(`${urlPrefix}/api/spaces/space`)
.auth(user.username, user.password)
.send({
name: 'space_1',
id: 'space_1',
color: '#ffffff',
description: 'a description',
disabledFeatures: [],
})
.expect(tests.alreadyExists.statusCode)
.then(tests.alreadyExists.response);
});
});
describe('when _reserved is specified', () => {
it(`should return ${tests.reservedSpecified.statusCode} and ignore _reserved`, async () => {
return supertest
.post(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(user.username, user.password)
.send({
name: 'reserved space',
id: 'reserved',
name: 'marketing',
id: 'marketing',
description: 'a description',
color: '#5c5959',
_reserved: true,
disabledFeatures: [],
})
.expect(tests.reservedSpecified.statusCode)
.then(tests.reservedSpecified.response);
.expect(tests.newSpace.statusCode)
.then(tests.newSpace.response);
});
describe('when it already exists', () => {
it(`should return ${tests.alreadyExists.statusCode} ${scenario}`, async () => {
return supertest
.post(`${urlPrefix}/api/spaces/space`)
.auth(user.username, user.password)
.send({
name: 'space_1',
id: 'space_1',
color: '#ffffff',
description: 'a description',
disabledFeatures: [],
})
.expect(tests.alreadyExists.statusCode)
.then(tests.alreadyExists.response);
});
});
describe('when _reserved is specified', () => {
it(`should return ${tests.reservedSpecified.statusCode} and ignore _reserved ${scenario}`, async () => {
return supertest
.post(`${urlPrefix}/api/spaces/space`)
.auth(user.username, user.password)
.send({
name: 'reserved space',
id: 'reserved',
description: 'a description',
color: '#5c5959',
_reserved: true,
disabledFeatures: [],
})
.expect(tests.reservedSpecified.statusCode)
.then(tests.reservedSpecified.response);
});
});
});
});

View file

@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
import { SuperTest } from 'supertest';
import { getUrlPrefix } from '../lib/space_test_utils';
import { getTestScenariosForSpace } from '../lib/space_test_utils';
import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
@ -176,7 +176,7 @@ export function deleteTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
{ user = {}, spaceId, tests }: DeleteTestDefinition
) => {
describeFn(description, () => {
before(async () => {
beforeEach(async () => {
await esArchiver.load('saved_objects/spaces');
// since we want to verify that we only delete the right things
@ -189,33 +189,35 @@ export function deleteTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
.auth(user.username, user.password)
.expect(200);
});
after(() => esArchiver.unload('saved_objects/spaces'));
afterEach(() => esArchiver.unload('saved_objects/spaces'));
it(`should return ${tests.exists.statusCode}`, async () => {
return supertest
.delete(`${getUrlPrefix(spaceId)}/api/spaces/space/space_2`)
.auth(user.username, user.password)
.expect(tests.exists.statusCode)
.then(tests.exists.response);
});
describe(`when the space is reserved`, () => {
it(`should return ${tests.reservedSpace.statusCode}`, async () => {
getTestScenariosForSpace(spaceId).forEach(({ urlPrefix, scenario }) => {
it(`should return ${tests.exists.statusCode} ${scenario}`, async () => {
return supertest
.delete(`${getUrlPrefix(spaceId)}/api/spaces/space/default`)
.delete(`${urlPrefix}/api/spaces/space/space_2`)
.auth(user.username, user.password)
.expect(tests.reservedSpace.statusCode)
.then(tests.reservedSpace.response);
.expect(tests.exists.statusCode)
.then(tests.exists.response);
});
});
describe(`when the space doesn't exist`, () => {
it(`should return ${tests.doesntExist.statusCode}`, async () => {
return supertest
.delete(`${getUrlPrefix(spaceId)}/api/spaces/space/space_3`)
.auth(user.username, user.password)
.expect(tests.doesntExist.statusCode)
.then(tests.doesntExist.response);
describe(`when the space is reserved`, () => {
it(`should return ${tests.reservedSpace.statusCode} ${scenario}`, async () => {
return supertest
.delete(`${urlPrefix}/api/spaces/space/default`)
.auth(user.username, user.password)
.expect(tests.reservedSpace.statusCode)
.then(tests.reservedSpace.response);
});
});
describe(`when the space doesn't exist`, () => {
it(`should return ${tests.doesntExist.statusCode} ${scenario}`, async () => {
return supertest
.delete(`${urlPrefix}/api/spaces/space/space_3`)
.auth(user.username, user.password)
.expect(tests.doesntExist.statusCode)
.then(tests.doesntExist.response);
});
});
});
});

View file

@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
import { SuperAgent } from 'superagent';
import { getUrlPrefix } from '../lib/space_test_utils';
import { getTestScenariosForSpace } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
interface GetTest {
@ -80,12 +80,14 @@ export function getTestSuiteFactory(esArchiver: any, supertest: SuperAgent<any>)
before(() => esArchiver.load('saved_objects/spaces'));
after(() => esArchiver.unload('saved_objects/spaces'));
it(`should return ${tests.default.statusCode}`, async () => {
return supertest
.get(`${getUrlPrefix(currentSpaceId)}/api/spaces/space/${spaceId}`)
.auth(user.username, user.password)
.expect(tests.default.statusCode)
.then(tests.default.response);
getTestScenariosForSpace(currentSpaceId).forEach(({ urlPrefix, scenario }) => {
it(`should return ${tests.default.statusCode} ${scenario}`, async () => {
return supertest
.get(`${urlPrefix}/api/spaces/space/${spaceId}`)
.auth(user.username, user.password)
.expect(tests.default.statusCode)
.then(tests.default.response);
});
});
});
};

View file

@ -5,7 +5,7 @@
*/
import expect from '@kbn/expect';
import { SuperTest } from 'supertest';
import { getUrlPrefix } from '../lib/space_test_utils';
import { getTestScenariosForSpace } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
interface GetAllTest {
@ -71,33 +71,35 @@ export function getAllTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
before(() => esArchiver.load('saved_objects/spaces'));
after(() => esArchiver.unload('saved_objects/spaces'));
it(`should return ${tests.exists.statusCode}`, async () => {
return supertest
.get(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.auth(user.username, user.password)
.expect(tests.exists.statusCode)
.then(tests.exists.response);
});
describe('copySavedObjects purpose', () => {
it(`should return ${tests.copySavedObjectsPurpose.statusCode}`, async () => {
getTestScenariosForSpace(spaceId).forEach(({ scenario, urlPrefix }) => {
it(`should return ${tests.exists.statusCode} ${scenario}`, async () => {
return supertest
.get(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.query({ purpose: 'copySavedObjectsIntoSpace' })
.get(`${urlPrefix}/api/spaces/space`)
.auth(user.username, user.password)
.expect(tests.copySavedObjectsPurpose.statusCode)
.then(tests.copySavedObjectsPurpose.response);
.expect(tests.exists.statusCode)
.then(tests.exists.response);
});
});
describe('copySavedObjects purpose', () => {
it(`should return ${tests.shareSavedObjectsPurpose.statusCode}`, async () => {
return supertest
.get(`${getUrlPrefix(spaceId)}/api/spaces/space`)
.query({ purpose: 'shareSavedObjectsIntoSpace' })
.auth(user.username, user.password)
.expect(tests.copySavedObjectsPurpose.statusCode)
.then(tests.copySavedObjectsPurpose.response);
describe('copySavedObjects purpose', () => {
it(`should return ${tests.copySavedObjectsPurpose.statusCode} ${scenario}`, async () => {
return supertest
.get(`${urlPrefix}/api/spaces/space`)
.query({ purpose: 'copySavedObjectsIntoSpace' })
.auth(user.username, user.password)
.expect(tests.copySavedObjectsPurpose.statusCode)
.then(tests.copySavedObjectsPurpose.response);
});
});
describe('copySavedObjects purpose', () => {
it(`should return ${tests.shareSavedObjectsPurpose.statusCode} ${scenario}`, async () => {
return supertest
.get(`${urlPrefix}/api/spaces/space`)
.query({ purpose: 'shareSavedObjectsIntoSpace' })
.auth(user.username, user.password)
.expect(tests.copySavedObjectsPurpose.statusCode)
.then(tests.copySavedObjectsPurpose.response);
});
});
});
});