@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
457 lines (456 loc) • 18.9 kB
JavaScript
/*
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
* This file is part of Sync-in | The open source file sync and share solution
* See the LICENSE file for licensing details
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
const _tsjest = require("@golevelup/ts-jest");
const _common = require("@nestjs/common");
const _testing = require("@nestjs/testing");
const _functions = require("../../../common/functions");
const _cacheservice = require("../../../infrastructure/cache/services/cache.service");
const _contextmanagerservice = require("../../../infrastructure/context/services/context-manager.service");
const _constants = require("../../../infrastructure/database/constants");
const _filesqueriesservice = require("../../files/services/files-queries.service");
const _linksqueriesservice = require("../../links/services/links-queries.service");
const _notificationsmanagerservice = require("../../notifications/services/notifications-manager.service");
const _shares = require("../../shares/constants/shares");
const _sharesmanagerservice = require("../../shares/services/shares-manager.service");
const _sharesqueriesservice = require("../../shares/services/shares-queries.service");
const _user = require("../../users/constants/user");
const _usermodel = require("../../users/models/user.model");
const _usersqueriesservice = require("../../users/services/users-queries.service");
const _test = require("../../users/utils/test");
const _webdavcontextdecorator = require("../../webdav/decorators/webdav-context.decorator");
const _spaces = require("../constants/spaces");
const _spaceskippermissionsdecorator = require("../decorators/space-skip-permissions.decorator");
const _spacesmanagerservice = require("../services/spaces-manager.service");
const _spacesqueriesservice = require("../services/spaces-queries.service");
const _spaceguard = require("./space.guard");
describe(_spaceguard.SpaceGuard.name, ()=>{
let spacesGuard;
let spacesManager;
let spacesQueries;
let userTest;
let context;
beforeAll(async ()=>{
const module = await _testing.Test.createTestingModule({
providers: [
{
provide: _constants.DB_TOKEN_PROVIDER,
useValue: {}
},
{
provide: _cacheservice.Cache,
useValue: {}
},
{
provide: _contextmanagerservice.ContextManager,
useValue: {}
},
{
provide: _notificationsmanagerservice.NotificationsManager,
useValue: {}
},
_spaceguard.SpaceGuard,
_spacesmanagerservice.SpacesManager,
_spacesqueriesservice.SpacesQueries,
_sharesmanagerservice.SharesManager,
_sharesqueriesservice.SharesQueries,
_filesqueriesservice.FilesQueries,
_usersqueriesservice.UsersQueries,
_linksqueriesservice.LinksQueries
]
}).compile();
spacesManager = module.get(_spacesmanagerservice.SpacesManager);
spacesQueries = module.get(_spacesqueriesservice.SpacesQueries);
spacesGuard = module.get(_spaceguard.SpaceGuard);
// mocks
spacesManager['setQuotaExceeded'] = jest.fn();
userTest = new _usermodel.UserModel((0, _test.generateUserTest)());
context = (0, _tsjest.createMock)();
});
it('should be defined', ()=>{
expect(spacesGuard).toBeDefined();
expect(spacesManager).toBeDefined();
expect(userTest).toBeDefined();
});
it('should pass for a personal space', async ()=>{
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'files/personal/root/foo/bar'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
const req = context.switchToHttp().getRequest();
expect(req.space.id).toBe(0);
expect(req.space.repository).toBe(_spaces.SPACE_REPOSITORY.FILES);
expect(req.space.alias).toBe(_spaces.SPACE_ALIAS.PERSONAL);
expect(req.space.name).toBe(_spaces.SPACE_ALIAS.PERSONAL);
expect(req.space.enabled).toBe(true);
expect(req.space.root).toBeUndefined();
expect(req.space.dbFile).toMatchObject({
inTrash: false,
ownerId: userTest.id,
path: 'root/foo/bar'
});
expect(req.space.permissions).toBe(_spaces.SPACE_ALL_OPERATIONS);
expect(req.space.envPermissions).toBe(_spaces.SPACE_ALL_OPERATIONS);
expect(req.space.inFilesRepository).toBe(true);
expect(req.space.inPersonalSpace).toBe(true);
expect(req.space.inTrashRepository).toBe(false);
expect(req.space.inSharesRepository).toBe(false);
expect(req.space.inSharesList).toBe(false);
expect(req.space.paths).toEqual(expect.arrayContaining([
'root',
'foo',
'bar'
]));
});
it('should pass for a common space', async ()=>{
const fakeSpace = {
id: -1,
alias: 'test',
name: 'Test',
enabled: true,
permissions: ':a:d:m:so',
role: 0
};
spacesQueries.permissions = jest.fn().mockReturnValueOnce(fakeSpace);
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'files/test'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
const req = context.switchToHttp().getRequest();
expect(req.space.id).toBe(fakeSpace.id);
expect(req.space.repository).toBe(_spaces.SPACE_REPOSITORY.FILES);
expect(req.space.alias).toBe(fakeSpace.alias);
expect(req.space.name).toBe(fakeSpace.name);
expect(req.space.enabled).toBe(true);
expect(req.space.root).toMatchObject({
id: 0,
alias: '',
name: '',
permissions: 'a:d:m:so'
});
expect(req.space.dbFile).toMatchObject({
inTrash: false,
spaceId: fakeSpace.id,
spaceExternalRootId: null,
path: '.'
});
expect(req.space.permissions).toBe(_shares.SHARE_ALL_OPERATIONS);
expect(req.space.envPermissions).not.toContain('d');
expect(req.space.inFilesRepository).toBe(true);
expect(req.space.inPersonalSpace).toBe(false);
expect(req.space.inTrashRepository).toBe(false);
expect(req.space.inSharesRepository).toBe(false);
expect(req.space.inSharesList).toBe(false);
expect(req.space.paths).toHaveLength(0);
});
it('should pass for a common space root', async ()=>{
const fakeSpace = {
id: -1,
alias: 'test',
name: 'Test',
enabled: true,
permissions: 'a:d:m:so',
role: 1,
root: {
id: -2,
alias: 'root',
name: 'Root',
permissions: 'a:d:so',
owner: {
id: -3,
login: 'johaven'
},
file: {
id: -4,
path: 'code',
inTrash: false
},
externalPath: null
}
};
spacesQueries.permissions = jest.fn().mockReturnValueOnce(fakeSpace);
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'files/test/root'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
const req = context.switchToHttp().getRequest();
expect(req.space.id).toBe(fakeSpace.id);
expect(req.space.repository).toBe(_spaces.SPACE_REPOSITORY.FILES);
expect(req.space.alias).toBe(fakeSpace.alias);
expect(req.space.name).toBe(fakeSpace.name);
expect(req.space.enabled).toBe(true);
expect(req.space.root).toMatchObject(fakeSpace.root);
expect(req.space.dbFile).toMatchObject({
inTrash: false,
ownerId: fakeSpace.root.owner.id,
path: fakeSpace.root.file.path
});
expect(req.space.permissions).toBe(_spaces.SPACE_ALL_OPERATIONS);
expect(req.space.envPermissions).toBe((0, _functions.intersectPermissions)(fakeSpace.permissions, fakeSpace.root.permissions).split(_spaces.SPACE_PERMS_SEP).filter((p)=>p !== _spaces.SPACE_OPERATION.DELETE).join(_spaces.SPACE_PERMS_SEP));
expect(req.space.inFilesRepository).toBe(true);
expect(req.space.inPersonalSpace).toBe(false);
expect(req.space.inTrashRepository).toBe(false);
expect(req.space.inSharesRepository).toBe(false);
expect(req.space.inSharesList).toBe(false);
expect(req.space.paths).toHaveLength(0);
});
it('should pass for a common space root with a path', async ()=>{
const fakeSpace = {
id: -1,
alias: 'test',
name: 'Test',
enabled: true,
permissions: 'a:d:m:so',
role: 1,
root: {
id: -2,
alias: 'root',
name: 'Root',
permissions: 'a:d:so',
owner: {
id: -3,
login: 'johaven'
},
file: {
id: -4,
path: 'code',
inTrash: false
},
externalPath: null
}
};
spacesQueries.permissions = jest.fn().mockReturnValueOnce(fakeSpace);
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'files/test/root/path'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
const req = context.switchToHttp().getRequest();
expect(req.space.id).toBe(fakeSpace.id);
expect(req.space.repository).toBe(_spaces.SPACE_REPOSITORY.FILES);
expect(req.space.alias).toBe(fakeSpace.alias);
expect(req.space.name).toBe(fakeSpace.name);
expect(req.space.enabled).toBe(true);
expect(req.space.root).toMatchObject(fakeSpace.root);
expect(req.space.dbFile).toMatchObject({
inTrash: false,
ownerId: fakeSpace.root.owner.id,
path: `${fakeSpace.root.file.path}/${req.space.paths[0]}`
});
expect(req.space.permissions).toBe(_spaces.SPACE_ALL_OPERATIONS);
expect(req.space.envPermissions).toBe(fakeSpace.root.permissions);
expect(req.space.inFilesRepository).toBe(true);
expect(req.space.inPersonalSpace).toBe(false);
expect(req.space.inTrashRepository).toBe(false);
expect(req.space.inSharesRepository).toBe(false);
expect(req.space.inSharesList).toBe(false);
expect(req.space.paths).toHaveLength(1);
});
it('should pass for a space in shares repository', async ()=>{
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'shares'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
const req = context.switchToHttp().getRequest();
expect(req.space.id).toBe(0);
expect(req.space.repository).toBe(_spaces.SPACE_REPOSITORY.SHARES);
expect(req.space.alias).toBe(_spaces.SPACE_REPOSITORY.SHARES);
expect(req.space.name).toBe(_spaces.SPACE_REPOSITORY.SHARES);
expect(req.space.enabled).toBe(true);
expect(req.space.root).toBeUndefined();
expect(req.space.dbFile).toBeUndefined();
expect(req.space.permissions).toBe('');
expect(req.space.envPermissions).toBe('');
expect(req.space.inFilesRepository).toBe(false);
expect(req.space.inPersonalSpace).toBe(false);
expect(req.space.inTrashRepository).toBe(false);
expect(req.space.inSharesRepository).toBe(true);
expect(req.space.inSharesList).toBe(true);
expect(req.space.paths).toHaveLength(0);
});
it('should not pass if the space is not found or not valid', async ()=>{
const fakeSpace = null;
spacesQueries.permissions = jest.fn().mockReturnValueOnce(fakeSpace);
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'files/foo'
}
});
await expect(spacesGuard.canActivate(context)).rejects.toThrow(_common.HttpException);
});
it('should validate (or not) the access to the space depending on the user permissions', async ()=>{
// we only check the `spacesManager.checkAccessToSpace` function, ignores the `spacesManager.spaceEnv`
userTest.applications = [
_user.USER_PERMISSION.PERSONAL_SPACE
];
for (const url of [
'',
'shares/personal',
'shares/toto'
]){
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': url
}
});
await expect(spacesGuard.canActivate(context)).rejects.toThrow(_common.HttpException);
}
// should pass because it is a standard user
// should not pass because user is a guest (and dot not have the permission to access to a personal space)
spacesManager.spaceEnv = jest.fn().mockReturnValueOnce({
enabled: true
}); // only for user role
context.switchToHttp().getRequest.mockReturnValue({
user: userTest,
params: {
'*': 'files/personal/root/foo/bar'
}
});
for (const userRole of [
_user.USER_ROLE.USER,
_user.USER_ROLE.GUEST
]){
userTest.role = userRole;
if (userRole === _user.USER_ROLE.USER) {
expect(await spacesGuard.canActivate(context)).toBe(true);
} else {
await expect(spacesGuard.canActivate(context)).rejects.toThrow(_common.HttpException);
}
}
});
it('should check user permissions on route', async ()=>{
userTest.role = _user.USER_ROLE.USER;
spacesManager.spaceEnv = jest.fn().mockReturnValue({
enabled: true,
envPermissions: `${_spaces.SPACE_OPERATION.MODIFY}`
});
// does not allow creation (only modify)
context.switchToHttp().getRequest.mockReturnValueOnce({
method: 'POST',
user: userTest,
params: {
'*': 'files/personal'
}
});
await expect(spacesGuard.canActivate(context)).rejects.toThrow(_common.HttpException);
// allows modification
context.switchToHttp().getRequest.mockReturnValue({
method: 'PATCH',
user: userTest,
params: {
'*': 'files/personal'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
// does not allow guest (on personal space)
userTest.role = _user.USER_ROLE.GUEST;
await expect(spacesGuard.canActivate(context)).rejects.toThrow(_common.HttpException);
// allow if SkipSpacePermissionsCheck is enabled
userTest.role = _user.USER_ROLE.USER;
(0, _spaceskippermissionsdecorator.SkipSpacePermissionsCheck)()(context.getHandler());
spacesManager.spaceEnv = jest.fn().mockReturnValue({
enabled: true,
envPermissions: `${_spaces.SPACE_OPERATION.MODIFY}`
});
// does not allow creation but pass
context.switchToHttp().getRequest.mockReturnValueOnce({
method: 'POST',
user: userTest,
params: {
'*': 'files/personal'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
// reset context
context = (0, _tsjest.createMock)();
});
it('should fail with quota exceeded', async ()=>{
userTest.role = _user.USER_ROLE.USER;
spacesManager.spaceEnv = jest.fn().mockReturnValueOnce({
enabled: true,
envPermissions: `${_spaces.SPACE_OPERATION.ADD}`,
quotaIsExceeded: true
});
context.switchToHttp().getRequest.mockReturnValueOnce({
method: 'POST',
user: userTest,
params: {
'*': 'files/personal'
}
});
let thrown = false;
try {
await spacesGuard.canActivate(context);
} catch (e) {
thrown = true;
expect(e).toBeInstanceOf(_common.HttpException);
expect(e.status).toBe(_common.HttpStatus.INSUFFICIENT_STORAGE);
}
expect(thrown).toBe(true);
});
it('should fail with space disabled', async ()=>{
userTest.role = _user.USER_ROLE.USER;
spacesManager.spaceEnv = jest.fn().mockReturnValueOnce({
enabled: false
});
context.switchToHttp().getRequest.mockReturnValueOnce({
method: 'POST',
user: userTest,
params: {
'*': 'files/personal'
}
});
let thrown = false;
try {
await spacesGuard.canActivate(context);
} catch (e) {
thrown = true;
expect(e).toBeInstanceOf(_common.HttpException);
expect(e.status).toBe(_common.HttpStatus.FORBIDDEN);
}
expect(thrown).toBe(true);
});
it('should validate (or not) the webdav routes', async ()=>{
userTest.role = _user.USER_ROLE.USER;
spacesManager.spaceEnv = jest.fn().mockReturnValue({
enabled: true
});
(0, _webdavcontextdecorator.WebDAVContext)()(context.getHandler());
context.switchToHttp().getRequest.mockReturnValueOnce({
user: userTest,
params: {
'*': 'webdav/personal'
}
});
expect(await spacesGuard.canActivate(context)).toBe(true);
context.switchToHttp().getRequest.mockReturnValueOnce({
user: userTest,
params: {
'*': 'webdav/files'
}
});
await expect(spacesGuard.canActivate(context)).rejects.toThrow(_common.HttpException);
});
});
//# sourceMappingURL=space.guard.spec.js.map