Browse Source

Merge pull request #4700 from nocodb/fix/4699-attchement-upload

fix(gui): GUI - allow attachment upload if user have permission
pull/4706/head
Pranav C 2 years ago committed by GitHub
parent
commit
e9afe0235d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      packages/nocodb-sdk/src/lib/enums.ts
  2. 47
      packages/nocodb/src/lib/meta/api/attachmentApis.ts
  3. 2
      packages/nocodb/tests/unit/rest/index.test.ts
  4. 176
      packages/nocodb/tests/unit/rest/tests/attachment.test.ts

7
packages/nocodb-sdk/src/lib/enums.ts

@ -3,3 +3,10 @@ export enum OrgUserRoles {
CREATOR = 'org-level-creator', CREATOR = 'org-level-creator',
VIEWER = 'org-level-viewer', VIEWER = 'org-level-viewer',
} }
export enum ProjectRoles {
OWNER = 'owner',
CREATOR = 'creator',
EDITOR = 'editor',
COMMENTER = 'commenter',
VIEWER = 'viewer',
}

47
packages/nocodb/src/lib/meta/api/attachmentApis.ts

@ -2,15 +2,45 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import multer from 'multer'; import multer from 'multer';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk';
import path from 'path'; import path from 'path';
import slash from 'slash'; import slash from 'slash';
import Noco from '../../Noco';
import { MetaTable } from '../../utils/globals';
import mimetypes, { mimeIcons } from '../../utils/mimeTypes'; import mimetypes, { mimeIcons } from '../../utils/mimeTypes';
import { Tele } from 'nc-help'; import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import extractProjectIdAndAuthenticate from '../helpers/extractProjectIdAndAuthenticate';
import catchError from '../helpers/catchError'; import catchError, { NcError } from '../helpers/catchError';
import NcPluginMgrv2 from '../helpers/NcPluginMgrv2'; import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants'; import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants';
const isUploadAllowed = async (req: Request, _res: Response, next: any) => {
if (!req['user']?.id) {
NcError.unauthorized('Unauthorized');
}
try {
// check user is super admin or creator
if (
req['user'].roles?.includes(OrgUserRoles.SUPER_ADMIN) ||
req['user'].roles?.includes(OrgUserRoles.CREATOR) ||
// if viewer then check at-least one project have editor or higher role
// todo: cache
!!(await Noco.ncMeta
.knex(MetaTable.PROJECT_USERS)
.where(function () {
this.where('roles', ProjectRoles.OWNER);
this.orWhere('roles', ProjectRoles.CREATOR);
this.orWhere('roles', ProjectRoles.EDITOR);
})
.andWhere('fk_user_id', req['user'].id)
.first())
)
return next();
} catch {}
NcError.badRequest('Upload not allowed');
};
// const storageAdapter = new Local(); // const storageAdapter = new Local();
export async function upload(req: Request, res: Response) { export async function upload(req: Request, res: Response) {
const filePath = sanitizeUrlPath( const filePath = sanitizeUrlPath(
@ -156,11 +186,20 @@ router.post(
fieldSize: NC_ATTACHMENT_FIELD_SIZE, fieldSize: NC_ATTACHMENT_FIELD_SIZE,
}, },
}).any(), }).any(),
ncMetaAclMw(upload, 'upload') [
extractProjectIdAndAuthenticate,
catchError(isUploadAllowed),
catchError(upload),
]
); );
router.post( router.post(
'/api/v1/db/storage/upload-by-url', '/api/v1/db/storage/upload-by-url',
ncMetaAclMw(uploadViaURL, 'uploadViaURL')
[
extractProjectIdAndAuthenticate,
catchError(isUploadAllowed),
catchError(uploadViaURL),
]
); );
router.get(/^\/download\/(.+)$/, catchError(fileRead)); router.get(/^\/download\/(.+)$/, catchError(fileRead));

2
packages/nocodb/tests/unit/rest/index.test.ts

@ -6,6 +6,7 @@ import columnTypeSpecificTests from './tests/columnTypeSpecific.test';
import tableTests from './tests/table.test'; import tableTests from './tests/table.test';
import tableRowTests from './tests/tableRow.test'; import tableRowTests from './tests/tableRow.test';
import viewRowTests from './tests/viewRow.test'; import viewRowTests from './tests/viewRow.test';
import attachmentTests from './tests/attachment.test';
function restTests() { function restTests() {
authTests(); authTests();
@ -15,6 +16,7 @@ function restTests() {
tableRowTests(); tableRowTests();
viewRowTests(); viewRowTests();
columnTypeSpecificTests(); columnTypeSpecificTests();
attachmentTests();
} }
export default function () { export default function () {

176
packages/nocodb/tests/unit/rest/tests/attachment.test.ts

@ -0,0 +1,176 @@
import { expect } from 'chai'
import fs from 'fs'
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk'
import path from 'path'
import 'mocha'
import request from 'supertest'
import { createProject } from '../../factory/project'
import init from '../../init'
const FILE_PATH = path.join(__dirname, 'test.txt')
function attachmentTests() {
let context
beforeEach(async function() {
context = await init()
fs.writeFileSync(FILE_PATH, 'test', `utf-8`)
context = await init()
})
afterEach(function() {
fs.unlinkSync(FILE_PATH)
})
it('Upload file - Super admin', async () => {
const response = await request(context.app)
.post('/api/v1/db/storage/upload')
.attach('files', FILE_PATH)
.set('xc-auth', context.token)
.expect(200)
const attachments = response.body
expect(attachments).to.be.an('array')
expect(attachments[0].title).to.be.eq(path.basename(FILE_PATH))
})
it('Upload file - Without token', async () => {
const response = await request(context.app)
.post('/api/v1/db/storage/upload')
.attach('files', FILE_PATH)
.expect(401)
const msg = response.body.msg
expect(msg).to.be.eq('Unauthorized')
})
it('Upload file - Org level viewer', async () => {
// signup a user
const args = {
email: 'dummyuser@example.com',
password: 'A1234abh2@dsad',
}
const signupResponse = await request(context.app)
.post('/api/v1/auth/user/signup')
.send(args)
.expect(200)
const response = await request(context.app)
.post('/api/v1/db/storage/upload')
.attach('files', FILE_PATH)
.set('xc-auth', signupResponse.body.token)
.expect(400)
const msg = response.body.msg
expect(msg).to.be.eq('Upload not allowed')
})
it('Upload file - Org level creator', async () => {
// signup a user
const args = {
email: 'dummyuser@example.com',
password: 'A1234abh2@dsad',
}
await request(context.app)
.post('/api/v1/auth/user/signup')
.send(args)
.expect(200)
// update user role to creator
const usersListResponse = await request(context.app)
.get('/api/v1/users')
.set('xc-auth', context.token)
.expect(200)
const user = usersListResponse.body.list.find(u => u.email === args.email)
expect(user).to.have.property('roles').to.be.equal(OrgUserRoles.VIEWER)
await request(context.app)
.patch('/api/v1/users/' + user.id)
.set('xc-auth', context.token)
.send({ roles: OrgUserRoles.CREATOR })
.expect(200)
const signinResponse = await request(context.app)
.post('/api/v1/auth/user/signin')
// pass empty data in await request
.send(args)
.expect(200)
const response = await request(context.app)
.post('/api/v1/db/storage/upload')
.attach('files', FILE_PATH)
.set('xc-auth', signinResponse.body.token)
.expect(200)
const attachments = response.body
expect(attachments).to.be.an('array')
expect(attachments[0].title).to.be.eq(path.basename(FILE_PATH))
})
it('Upload file - Org level viewer with editor role in a project', async () => {
// signup a new user
const args = {
email: 'dummyuser@example.com',
password: 'A1234abh2@dsad',
}
await request(context.app)
.post('/api/v1/auth/user/signup')
.send(args)
.expect(200)
const newProject = await createProject(context, {
title: 'NewTitle1',
})
// invite user to project with editor role
await request(context.app)
.post(`/api/v1/db/meta/projects/${newProject.id}/users`)
.set('xc-auth', context.token)
.send({
roles: ProjectRoles.EDITOR,
email: args.email,
project_id: newProject.id,
projectName: newProject.title,
})
.expect(200)
// signin to get user token
const signinResponse = await request(context.app)
.post('/api/v1/auth/user/signin')
// pass empty data in await request
.send(args)
.expect(200)
const response = await request(context.app)
.post('/api/v1/db/storage/upload')
.attach('files', FILE_PATH)
.set('xc-auth', signinResponse.body.token)
.expect(200)
const attachments = response.body
expect(attachments).to.be.an('array')
expect(attachments[0].title).to.be.eq(path.basename(FILE_PATH))
})
}
export default function() {
describe('Attachment', attachmentTests)
}
Loading…
Cancel
Save