From 8322aa5dfbc7ceb3cf98718e25fc87d6e05d0f06 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Sat, 4 Mar 2023 13:56:20 +0100 Subject: [PATCH 1/4] feat: added authorizedCreate to block post beyond the role scope --- .../src/controllers/basesnippet.controller.ts | 48 +++++------ sci-log-db/src/controllers/file.controller.ts | 62 +++++++------- .../src/controllers/location.controller.ts | 48 +++++------ .../src/controllers/logbook.controller.ts | 48 +++++------ .../src/controllers/paragraph.controller.ts | 48 +++++------ sci-log-db/src/controllers/task.controller.ts | 54 ++++++------ .../controllers/user-preference.controller.ts | 28 +++---- sci-log-db/src/controllers/view.controller.ts | 26 +++--- .../mixins/basesnippet.repository-mixin.ts | 84 +++++++++++++------ .../add-logbook/add-logbook.component.ts | 25 +++--- 10 files changed, 256 insertions(+), 215 deletions(-) diff --git a/sci-log-db/src/controllers/basesnippet.controller.ts b/sci-log-db/src/controllers/basesnippet.controller.ts index 83d087f3..08853c02 100644 --- a/sci-log-db/src/controllers/basesnippet.controller.ts +++ b/sci-log-db/src/controllers/basesnippet.controller.ts @@ -1,6 +1,6 @@ -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {inject} from '@loopback/core'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { inject } from '@loopback/core'; import { Count, CountSchema, @@ -19,12 +19,12 @@ import { Response, RestBindings, } from '@loopback/rest'; -import {SecurityBindings, UserProfile} from '@loopback/security'; -import {Basesnippet} from '../models'; -import {BasesnippetRepository} from '../repositories'; -import {basicAuthorization} from '../services/basic.authorizor'; -import {getModelSchemaRef} from '../utils/misc'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; +import { SecurityBindings, UserProfile } from '@loopback/security'; +import { Basesnippet } from '../models'; +import { BasesnippetRepository } from '../repositories'; +import { basicAuthorization } from '../services/basic.authorizor'; +import { getModelSchemaRef } from '../utils/misc'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; @authenticate('jwt') @authorize({ @@ -38,14 +38,14 @@ export class BasesnippetController { @inject(SecurityBindings.USER) private user: UserProfile, @repository(BasesnippetRepository) public basesnippetRepository: BasesnippetRepository, - ) {} + ) { } @post('/basesnippets', { security: OPERATION_SECURITY_SPEC, responses: { '200': { description: 'Basesnippet model instance', - content: {'application/json': {schema: getModelSchemaRef(Basesnippet)}}, + content: { 'application/json': { schema: getModelSchemaRef(Basesnippet) } }, }, }, }) @@ -62,7 +62,7 @@ export class BasesnippetController { }) basesnippet: Omit, ): Promise { - return this.basesnippetRepository.create(basesnippet, { + return this.basesnippetRepository.authorizedCreate(basesnippet, { currentUser: this.user, }); } @@ -72,14 +72,14 @@ export class BasesnippetController { responses: { '200': { description: 'Basesnippet model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) async count( @param.where(Basesnippet) where?: Where, ): Promise { - return this.basesnippetRepository.count(where, {currentUser: this.user}); + return this.basesnippetRepository.count(where, { currentUser: this.user }); } @get('/basesnippets', { @@ -91,7 +91,7 @@ export class BasesnippetController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Basesnippet, {includeRelations: true}), + items: getModelSchemaRef(Basesnippet, { includeRelations: true }), }, }, }, @@ -101,7 +101,7 @@ export class BasesnippetController { async find( @param.filter(Basesnippet) filter?: Filter, ): Promise { - return this.basesnippetRepository.find(filter, {currentUser: this.user}); + return this.basesnippetRepository.find(filter, { currentUser: this.user }); } @get('/basesnippets/export={exportType}', { @@ -113,7 +113,7 @@ export class BasesnippetController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Basesnippet, {includeRelations: true}), + items: getModelSchemaRef(Basesnippet, { includeRelations: true }), }, }, }, @@ -143,7 +143,7 @@ export class BasesnippetController { 'Find the index (i.e position) of a basesnippet within a query.', content: { 'application/json': { - schema: {type: 'number'}, + schema: { type: 'number' }, }, }, }, @@ -165,7 +165,7 @@ export class BasesnippetController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Basesnippet, {includeRelations: true}), + items: getModelSchemaRef(Basesnippet, { includeRelations: true }), }, }, }, @@ -189,7 +189,7 @@ export class BasesnippetController { responses: { '200': { description: 'Basesnippet PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -197,7 +197,7 @@ export class BasesnippetController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Basesnippet, {partial: true}), + schema: getModelSchemaRef(Basesnippet, { partial: true }), }, }, }) @@ -216,7 +216,7 @@ export class BasesnippetController { description: 'Basesnippet model instance', content: { 'application/json': { - schema: getModelSchemaRef(Basesnippet, {includeRelations: true}), + schema: getModelSchemaRef(Basesnippet, { includeRelations: true }), }, }, }, @@ -224,7 +224,7 @@ export class BasesnippetController { }) async findById( @param.path.string('id') id: string, - @param.filter(Basesnippet, {exclude: 'where'}) + @param.filter(Basesnippet, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.basesnippetRepository.findById(id, filter, { @@ -245,7 +245,7 @@ export class BasesnippetController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Basesnippet, {partial: true}), + schema: getModelSchemaRef(Basesnippet, { partial: true }), }, }, }) diff --git a/sci-log-db/src/controllers/file.controller.ts b/sci-log-db/src/controllers/file.controller.ts index 94c0dee4..e924d7b8 100644 --- a/sci-log-db/src/controllers/file.controller.ts +++ b/sci-log-db/src/controllers/file.controller.ts @@ -1,6 +1,6 @@ -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {inject} from '@loopback/core'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { inject } from '@loopback/core'; import { Count, CountSchema, @@ -22,20 +22,20 @@ import { Response, RestBindings, } from '@loopback/rest'; -import {SecurityBindings, UserProfile} from '@loopback/security'; +import { SecurityBindings, UserProfile } from '@loopback/security'; import formidable from 'formidable'; import fs from 'fs'; import _ from 'lodash'; -import {STORAGE_DIRECTORY} from '../keys'; -import {Filesnippet} from '../models/file.model'; -import {FileRepository} from '../repositories/file.repository'; -import {basicAuthorization} from '../services/basic.authorizor'; +import { STORAGE_DIRECTORY } from '../keys'; +import { Filesnippet } from '../models/file.model'; +import { FileRepository } from '../repositories/file.repository'; +import { basicAuthorization } from '../services/basic.authorizor'; import { addOwnerGroupAccessGroups, getModelSchemaRefWithStrict, validateFieldsVSModel, } from '../utils/misc'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; const Mongo = require('mongodb'); const crypto = require('crypto'); @@ -82,14 +82,14 @@ export class FileController { @repository(FileRepository) public fileRepository: FileRepository, @inject(STORAGE_DIRECTORY) private storageDirectory: string, - ) {} + ) { } @post('/filesnippet', { security: OPERATION_SECURITY_SPEC, responses: { '200': { description: 'Filesnippet model instance', - content: {'application/json': {schema: getModelSchemaRef(Filesnippet)}}, + content: { 'application/json': { schema: getModelSchemaRef(Filesnippet) } }, }, }, }) @@ -106,7 +106,7 @@ export class FileController { }) file: Omit, ): Promise { - return this.fileRepository.create(file, {currentUser: this.user}); + return this.fileRepository.authorizedCreate(file, { currentUser: this.user }); } @post('/filesnippet/files', { @@ -155,13 +155,13 @@ export class FileController { ownerGroupAccessGroupsFilesnippetModel, reject, ); - resolve({fields: parsedFields, files: files}); + resolve({ fields: parsedFields, files: files }); }); }); return this.uploadToGridfs(formData, async (fm, resolve, reject) => { fm.fields['accessHash'] = crypto.randomBytes(64).toString('hex'); return this.fileRepository - .create(_.omit(fm.fields, ['id']), {currentUser: this.user}) + .create(_.omit(fm.fields, ['id']), { currentUser: this.user }) .then((file: Filesnippet) => { resolve(file); }) @@ -176,14 +176,14 @@ export class FileController { responses: { '200': { description: 'Filesnippet model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) async count( @param.where(Filesnippet) where?: Where, ): Promise { - return this.fileRepository.count(where, {currentUser: this.user}); + return this.fileRepository.count(where, { currentUser: this.user }); } @get('/filesnippet/index={id}', { @@ -193,7 +193,7 @@ export class FileController { description: 'Filesnippet model instance', content: { 'application/json': { - schema: getModelSchemaRef(Filesnippet, {includeRelations: true}), + schema: getModelSchemaRef(Filesnippet, { includeRelations: true }), }, }, }, @@ -201,7 +201,7 @@ export class FileController { }) async findIndexInBuffer( @param.path.string('id') id: string, - @param.filter(Filesnippet, {exclude: 'where'}) + @param.filter(Filesnippet, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.fileRepository.findIndexInBuffer(id, this.user, filter); @@ -216,7 +216,7 @@ export class FileController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Filesnippet, {includeRelations: true}), + items: getModelSchemaRef(Filesnippet, { includeRelations: true }), }, }, }, @@ -244,7 +244,7 @@ export class FileController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Filesnippet, {includeRelations: true}), + items: getModelSchemaRef(Filesnippet, { includeRelations: true }), }, }, }, @@ -254,7 +254,7 @@ export class FileController { async find( @param.filter(Filesnippet) filter?: Filter, ): Promise { - return this.fileRepository.find(filter, {currentUser: this.user}); + return this.fileRepository.find(filter, { currentUser: this.user }); } @patch('/filesnippet', { @@ -262,7 +262,7 @@ export class FileController { responses: { '200': { description: 'File PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -270,14 +270,14 @@ export class FileController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Filesnippet, {partial: true}), + schema: getModelSchemaRef(Filesnippet, { partial: true }), }, }, }) file: Filesnippet, @param.where(Filesnippet) where?: Where, ): Promise { - return this.fileRepository.updateAll(file, where, {currentUser: this.user}); + return this.fileRepository.updateAll(file, where, { currentUser: this.user }); } @get('/filesnippet/{id}', { @@ -287,7 +287,7 @@ export class FileController { description: 'Filesnippet model instance', content: { 'application/json': { - schema: getModelSchemaRef(Filesnippet, {includeRelations: true}), + schema: getModelSchemaRef(Filesnippet, { includeRelations: true }), }, }, }, @@ -295,10 +295,10 @@ export class FileController { }) async findById( @param.path.string('id') id: string, - @param.filter(Filesnippet, {exclude: 'where'}) + @param.filter(Filesnippet, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { - return this.fileRepository.findById(id, filter, {currentUser: this.user}); + return this.fileRepository.findById(id, filter, { currentUser: this.user }); } @get('/filesnippet/{id}/files', { @@ -313,7 +313,7 @@ export class FileController { async downloadFile( @param.path.string('id') id: string, @inject(RestBindings.Http.RESPONSE) response: Response, - @param.filter(Filesnippet, {exclude: 'where'}) + @param.filter(Filesnippet, { exclude: 'where' }) filter?: FilterExcludingWhere, ) { const data = await this.fileRepository.findById(id, filter, { @@ -378,7 +378,7 @@ export class FileController { ownerGroupAccessGroupsFilesnippetModel, reject, ); - resolve({fields: parsedFields, files: files}); + resolve({ fields: parsedFields, files: files }); }); }); return this.uploadToGridfs(formData, async (fm, resolve, reject) => { @@ -408,7 +408,7 @@ export class FileController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Filesnippet, {partial: true}), + schema: getModelSchemaRef(Filesnippet, { partial: true }), }, }, }) @@ -471,7 +471,7 @@ export class FileController { // eslint-disable-next-line @typescript-eslint/no-explicit-any .on('error', (error: any) => { console.log(error); - reject({error: error}); + reject({ error: error }); }) .on('finish', async () => { formData.fields['_fileId'] = id; diff --git a/sci-log-db/src/controllers/location.controller.ts b/sci-log-db/src/controllers/location.controller.ts index 08d47475..afc03d8c 100644 --- a/sci-log-db/src/controllers/location.controller.ts +++ b/sci-log-db/src/controllers/location.controller.ts @@ -6,17 +6,17 @@ import { repository, Where, } from '@loopback/repository'; -import {post, param, get, patch, del, requestBody} from '@loopback/rest'; -import {Location} from '../models'; -import {LocationRepository} from '../repositories'; +import { post, param, get, patch, del, requestBody } from '@loopback/rest'; +import { Location } from '../models'; +import { LocationRepository } from '../repositories'; -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {basicAuthorization} from '../services/basic.authorizor'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; -import {SecurityBindings, UserProfile} from '@loopback/security'; -import {inject} from '@loopback/core'; -import {getModelSchemaRef} from '../utils/misc'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { basicAuthorization } from '../services/basic.authorizor'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; +import { SecurityBindings, UserProfile } from '@loopback/security'; +import { inject } from '@loopback/core'; +import { getModelSchemaRef } from '../utils/misc'; @authenticate('jwt') @authorize({ @@ -28,14 +28,14 @@ export class LocationController { @inject(SecurityBindings.USER) private user: UserProfile, @repository(LocationRepository) public locationRepository: LocationRepository, - ) {} + ) { } @post('/locations', { security: OPERATION_SECURITY_SPEC, responses: { '200': { description: 'Location model instance', - content: {'application/json': {schema: getModelSchemaRef(Location)}}, + content: { 'application/json': { schema: getModelSchemaRef(Location) } }, }, }, }) @@ -52,7 +52,7 @@ export class LocationController { }) location: Omit, ): Promise { - return this.locationRepository.create(location, {currentUser: this.user}); + return this.locationRepository.authorizedCreate(location, { currentUser: this.user }); } @get('/locations/count', { @@ -60,12 +60,12 @@ export class LocationController { responses: { '200': { description: 'Location model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) async count(@param.where(Location) where?: Where): Promise { - return this.locationRepository.count(where, {currentUser: this.user}); + return this.locationRepository.count(where, { currentUser: this.user }); } @get('/locations/search={search}', { @@ -77,7 +77,7 @@ export class LocationController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Location, {includeRelations: true}), + items: getModelSchemaRef(Location, { includeRelations: true }), }, }, }, @@ -104,7 +104,7 @@ export class LocationController { 'Find the index (i.e position) of a location within a query.', content: { 'application/json': { - schema: {type: 'number'}, + schema: { type: 'number' }, }, }, }, @@ -126,7 +126,7 @@ export class LocationController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Location, {includeRelations: true}), + items: getModelSchemaRef(Location, { includeRelations: true }), }, }, }, @@ -136,7 +136,7 @@ export class LocationController { async find( @param.filter(Location) filter?: Filter, ): Promise { - return this.locationRepository.find(filter, {currentUser: this.user}); + return this.locationRepository.find(filter, { currentUser: this.user }); } @patch('/locations', { @@ -144,7 +144,7 @@ export class LocationController { responses: { '200': { description: 'Location PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -152,7 +152,7 @@ export class LocationController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Location, {partial: true}), + schema: getModelSchemaRef(Location, { partial: true }), }, }, }) @@ -171,7 +171,7 @@ export class LocationController { description: 'Location model instance', content: { 'application/json': { - schema: getModelSchemaRef(Location, {includeRelations: true}), + schema: getModelSchemaRef(Location, { includeRelations: true }), }, }, }, @@ -179,7 +179,7 @@ export class LocationController { }) async findById( @param.path.string('id') id: string, - @param.filter(Location, {exclude: 'where'}) + @param.filter(Location, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.locationRepository.findById(id, filter, { @@ -200,7 +200,7 @@ export class LocationController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Location, {partial: true}), + schema: getModelSchemaRef(Location, { partial: true }), }, }, }) diff --git a/sci-log-db/src/controllers/logbook.controller.ts b/sci-log-db/src/controllers/logbook.controller.ts index 01c070e4..4a33023d 100644 --- a/sci-log-db/src/controllers/logbook.controller.ts +++ b/sci-log-db/src/controllers/logbook.controller.ts @@ -1,6 +1,6 @@ -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {inject} from '@loopback/core'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { inject } from '@loopback/core'; import { Count, CountSchema, @@ -9,13 +9,13 @@ import { repository, Where, } from '@loopback/repository'; -import {del, get, param, patch, post, requestBody} from '@loopback/rest'; -import {SecurityBindings, UserProfile} from '@loopback/security'; -import {Logbook} from '../models'; -import {LogbookRepository} from '../repositories'; -import {basicAuthorization} from '../services/basic.authorizor'; -import {getModelSchemaRef} from '../utils/misc'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; +import { del, get, param, patch, post, requestBody } from '@loopback/rest'; +import { SecurityBindings, UserProfile } from '@loopback/security'; +import { Logbook } from '../models'; +import { LogbookRepository } from '../repositories'; +import { basicAuthorization } from '../services/basic.authorizor'; +import { getModelSchemaRef } from '../utils/misc'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; @authenticate('jwt') @authorize({ @@ -27,14 +27,14 @@ export class LogbookController { @inject(SecurityBindings.USER) private user: UserProfile, @repository(LogbookRepository) public logbookRepository: LogbookRepository, - ) {} + ) { } @post('/logbooks', { security: OPERATION_SECURITY_SPEC, responses: { '200': { description: 'Logbook model instance', - content: {'application/json': {schema: getModelSchemaRef(Logbook)}}, + content: { 'application/json': { schema: getModelSchemaRef(Logbook) } }, }, }, }) @@ -51,7 +51,7 @@ export class LogbookController { }) logbook: Omit, ): Promise { - return this.logbookRepository.create(logbook, {currentUser: this.user}); + return this.logbookRepository.authorizedCreate(logbook, { currentUser: this.user }); } @get('/logbooks/count', { @@ -59,12 +59,12 @@ export class LogbookController { responses: { '200': { description: 'Logbook model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) async count(@param.where(Logbook) where?: Where): Promise { - return this.logbookRepository.count(where, {currentUser: this.user}); + return this.logbookRepository.count(where, { currentUser: this.user }); } @get('/logbooks/search={search}', { security: OPERATION_SECURITY_SPEC, @@ -75,7 +75,7 @@ export class LogbookController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Logbook, {includeRelations: true}), + items: getModelSchemaRef(Logbook, { includeRelations: true }), }, }, }, @@ -102,7 +102,7 @@ export class LogbookController { 'Find the index (i.e position) of a logbook within a query.', content: { 'application/json': { - schema: {type: 'number'}, + schema: { type: 'number' }, }, }, }, @@ -124,7 +124,7 @@ export class LogbookController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Logbook, {includeRelations: true}), + items: getModelSchemaRef(Logbook, { includeRelations: true }), }, }, }, @@ -134,7 +134,7 @@ export class LogbookController { async find( @param.filter(Logbook) filter?: Filter, ): Promise { - return this.logbookRepository.find(filter, {currentUser: this.user}); + return this.logbookRepository.find(filter, { currentUser: this.user }); } @patch('/logbooks', { @@ -142,7 +142,7 @@ export class LogbookController { responses: { '200': { description: 'Logbook PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -150,7 +150,7 @@ export class LogbookController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Logbook, {partial: true}), + schema: getModelSchemaRef(Logbook, { partial: true }), }, }, }) @@ -169,7 +169,7 @@ export class LogbookController { description: 'Logbook model instance', content: { 'application/json': { - schema: getModelSchemaRef(Logbook, {includeRelations: true}), + schema: getModelSchemaRef(Logbook, { includeRelations: true }), }, }, }, @@ -177,7 +177,7 @@ export class LogbookController { }) async findById( @param.path.string('id') id: string, - @param.filter(Logbook, {exclude: 'where'}) + @param.filter(Logbook, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.logbookRepository.findById(id, filter, { @@ -198,7 +198,7 @@ export class LogbookController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Logbook, {partial: true}), + schema: getModelSchemaRef(Logbook, { partial: true }), }, }, }) diff --git a/sci-log-db/src/controllers/paragraph.controller.ts b/sci-log-db/src/controllers/paragraph.controller.ts index ae7a0bda..9ef915b6 100644 --- a/sci-log-db/src/controllers/paragraph.controller.ts +++ b/sci-log-db/src/controllers/paragraph.controller.ts @@ -6,17 +6,17 @@ import { repository, Where, } from '@loopback/repository'; -import {post, param, get, patch, del, requestBody} from '@loopback/rest'; -import {Paragraph} from '../models'; -import {ParagraphRepository} from '../repositories'; +import { post, param, get, patch, del, requestBody } from '@loopback/rest'; +import { Paragraph } from '../models'; +import { ParagraphRepository } from '../repositories'; -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {basicAuthorization} from '../services/basic.authorizor'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; -import {SecurityBindings, UserProfile} from '@loopback/security'; -import {inject} from '@loopback/core'; -import {getModelSchemaRef} from '../utils/misc'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { basicAuthorization } from '../services/basic.authorizor'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; +import { SecurityBindings, UserProfile } from '@loopback/security'; +import { inject } from '@loopback/core'; +import { getModelSchemaRef } from '../utils/misc'; @authenticate('jwt') @authorize({ @@ -28,14 +28,14 @@ export class ParagraphController { @inject(SecurityBindings.USER) private user: UserProfile, @repository(ParagraphRepository) public paragraphRepository: ParagraphRepository, - ) {} + ) { } @post('/paragraphs', { security: OPERATION_SECURITY_SPEC, responses: { '200': { description: 'Paragraph model instance', - content: {'application/json': {schema: getModelSchemaRef(Paragraph)}}, + content: { 'application/json': { schema: getModelSchemaRef(Paragraph) } }, }, }, }) @@ -52,7 +52,7 @@ export class ParagraphController { }) paragraph: Omit, ): Promise { - return this.paragraphRepository.create(paragraph, {currentUser: this.user}); + return this.paragraphRepository.authorizedCreate(paragraph, { currentUser: this.user }); } @get('/paragraphs/count', { @@ -60,14 +60,14 @@ export class ParagraphController { responses: { '200': { description: 'Paragraph model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) async count( @param.where(Paragraph) where?: Where, ): Promise { - return this.paragraphRepository.count(where, {currentUser: this.user}); + return this.paragraphRepository.count(where, { currentUser: this.user }); } @get('/paragraphs/search={search}', { @@ -79,7 +79,7 @@ export class ParagraphController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Paragraph, {includeRelations: true}), + items: getModelSchemaRef(Paragraph, { includeRelations: true }), }, }, }, @@ -106,7 +106,7 @@ export class ParagraphController { 'Find the index (i.e position) of a paragraph within a query.', content: { 'application/json': { - schema: {type: 'number'}, + schema: { type: 'number' }, }, }, }, @@ -128,7 +128,7 @@ export class ParagraphController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Paragraph, {includeRelations: true}), + items: getModelSchemaRef(Paragraph, { includeRelations: true }), }, }, }, @@ -138,7 +138,7 @@ export class ParagraphController { async find( @param.filter(Paragraph) filter?: Filter, ): Promise { - return this.paragraphRepository.find(filter, {currentUser: this.user}); + return this.paragraphRepository.find(filter, { currentUser: this.user }); } @patch('/paragraphs', { @@ -146,7 +146,7 @@ export class ParagraphController { responses: { '200': { description: 'Paragraph PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -154,7 +154,7 @@ export class ParagraphController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Paragraph, {partial: true}), + schema: getModelSchemaRef(Paragraph, { partial: true }), }, }, }) @@ -173,7 +173,7 @@ export class ParagraphController { description: 'Paragraph model instance', content: { 'application/json': { - schema: getModelSchemaRef(Paragraph, {includeRelations: true}), + schema: getModelSchemaRef(Paragraph, { includeRelations: true }), }, }, }, @@ -181,7 +181,7 @@ export class ParagraphController { }) async findById( @param.path.string('id') id: string, - @param.filter(Paragraph, {exclude: 'where'}) + @param.filter(Paragraph, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.paragraphRepository.findById(id, filter, { @@ -202,7 +202,7 @@ export class ParagraphController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Paragraph, {partial: true}), + schema: getModelSchemaRef(Paragraph, { partial: true }), }, }, }) diff --git a/sci-log-db/src/controllers/task.controller.ts b/sci-log-db/src/controllers/task.controller.ts index d89729bb..89550c1c 100644 --- a/sci-log-db/src/controllers/task.controller.ts +++ b/sci-log-db/src/controllers/task.controller.ts @@ -1,6 +1,6 @@ -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {inject} from '@loopback/core'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { inject } from '@loopback/core'; import { Count, CountSchema, @@ -9,14 +9,14 @@ import { repository, Where, } from '@loopback/repository'; -import {del, get, param, patch, post, requestBody} from '@loopback/rest'; -import {SecurityBindings, UserProfile} from '@loopback/security'; -import {Task} from '../models'; -import {BasesnippetRepository, TaskRepository} from '../repositories'; -import {basicAuthorization} from '../services/basic.authorizor'; -import {getModelSchemaRef} from '../utils/misc'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; -import {BasesnippetController} from './basesnippet.controller'; +import { del, get, param, patch, post, requestBody } from '@loopback/rest'; +import { SecurityBindings, UserProfile } from '@loopback/security'; +import { Task } from '../models'; +import { BasesnippetRepository, TaskRepository } from '../repositories'; +import { basicAuthorization } from '../services/basic.authorizor'; +import { getModelSchemaRef } from '../utils/misc'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; +import { BasesnippetController } from './basesnippet.controller'; @authenticate('jwt') @authorize({ @@ -32,14 +32,14 @@ export class TaskController { public basesnippetRepository: BasesnippetRepository, @inject('controllers.BasesnippetController') public basesnippetController: BasesnippetController, - ) {} + ) { } @post('/tasks', { security: OPERATION_SECURITY_SPEC, responses: { '200': { description: 'Task model instance', - content: {'application/json': {schema: getModelSchemaRef(Task)}}, + content: { 'application/json': { schema: getModelSchemaRef(Task) } }, }, }, }) @@ -56,7 +56,7 @@ export class TaskController { }) task: Omit, ): Promise { - return this.taskRepository.create(task, {currentUser: this.user}); + return this.taskRepository.authorizedCreate(task, { currentUser: this.user }); } @get('/tasks/count', { @@ -64,12 +64,12 @@ export class TaskController { responses: { '200': { description: 'Task model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) async count(@param.where(Task) where?: Where): Promise { - return this.taskRepository.count(where, {currentUser: this.user}); + return this.taskRepository.count(where, { currentUser: this.user }); } @get('/tasks/search={search}', { @@ -81,7 +81,7 @@ export class TaskController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Task, {includeRelations: true}), + items: getModelSchemaRef(Task, { includeRelations: true }), }, }, }, @@ -107,7 +107,7 @@ export class TaskController { description: 'Find the index (i.e position) of a task within a query.', content: { 'application/json': { - schema: {type: 'number'}, + schema: { type: 'number' }, }, }, }, @@ -129,7 +129,7 @@ export class TaskController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(Task, {includeRelations: true}), + items: getModelSchemaRef(Task, { includeRelations: true }), }, }, }, @@ -137,7 +137,7 @@ export class TaskController { }, }) async find(@param.filter(Task) filter?: Filter): Promise { - return this.taskRepository.find(filter, {currentUser: this.user}); + return this.taskRepository.find(filter, { currentUser: this.user }); } @patch('/tasks', { @@ -145,7 +145,7 @@ export class TaskController { responses: { '200': { description: 'Task PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -153,14 +153,14 @@ export class TaskController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Task, {partial: true}), + schema: getModelSchemaRef(Task, { partial: true }), }, }, }) task: Task, @param.where(Task) where?: Where, ): Promise { - return this.taskRepository.updateAll(task, where, {currentUser: this.user}); + return this.taskRepository.updateAll(task, where, { currentUser: this.user }); } @get('/tasks/{id}', { @@ -170,7 +170,7 @@ export class TaskController { description: 'Task model instance', content: { 'application/json': { - schema: getModelSchemaRef(Task, {includeRelations: true}), + schema: getModelSchemaRef(Task, { includeRelations: true }), }, }, }, @@ -178,9 +178,9 @@ export class TaskController { }) async findById( @param.path.string('id') id: string, - @param.filter(Task, {exclude: 'where'}) filter?: FilterExcludingWhere, + @param.filter(Task, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { - return this.taskRepository.findById(id, filter, {currentUser: this.user}); + return this.taskRepository.findById(id, filter, { currentUser: this.user }); } @patch('/tasks/{id}', { @@ -196,7 +196,7 @@ export class TaskController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(Task, {partial: true}), + schema: getModelSchemaRef(Task, { partial: true }), }, }, }) diff --git a/sci-log-db/src/controllers/user-preference.controller.ts b/sci-log-db/src/controllers/user-preference.controller.ts index 878165dc..fb484854 100644 --- a/sci-log-db/src/controllers/user-preference.controller.ts +++ b/sci-log-db/src/controllers/user-preference.controller.ts @@ -15,12 +15,12 @@ import { del, requestBody, } from '@loopback/rest'; -import {UserPreference} from '../models'; -import {UserPreferenceRepository} from '../repositories'; -import {authenticate} from '@loopback/authentication'; -import {authorize} from '@loopback/authorization'; -import {basicAuthorization} from '../services/basic.authorizor'; -import {OPERATION_SECURITY_SPEC} from '../utils/security-spec'; +import { UserPreference } from '../models'; +import { UserPreferenceRepository } from '../repositories'; +import { authenticate } from '@loopback/authentication'; +import { authorize } from '@loopback/authorization'; +import { basicAuthorization } from '../services/basic.authorizor'; +import { OPERATION_SECURITY_SPEC } from '../utils/security-spec'; @authenticate('jwt') @authorize({ allowedRoles: ['any-authenticated-user'], @@ -30,7 +30,7 @@ export class UserPreferenceController { constructor( @repository(UserPreferenceRepository) public userPreferenceRepository: UserPreferenceRepository, - ) {} + ) { } @post('/user-preferences', { security: OPERATION_SECURITY_SPEC, @@ -38,7 +38,7 @@ export class UserPreferenceController { '200': { description: 'UserPreference model instance', content: { - 'application/json': {schema: getModelSchemaRef(UserPreference)}, + 'application/json': { schema: getModelSchemaRef(UserPreference) }, }, }, }, @@ -64,7 +64,7 @@ export class UserPreferenceController { responses: { '200': { description: 'UserPreference model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -103,7 +103,7 @@ export class UserPreferenceController { responses: { '200': { description: 'UserPreference PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -111,7 +111,7 @@ export class UserPreferenceController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(UserPreference, {partial: true}), + schema: getModelSchemaRef(UserPreference, { partial: true }), }, }, }) @@ -128,7 +128,7 @@ export class UserPreferenceController { description: 'UserPreference model instance', content: { 'application/json': { - schema: getModelSchemaRef(UserPreference, {includeRelations: true}), + schema: getModelSchemaRef(UserPreference, { includeRelations: true }), }, }, }, @@ -136,7 +136,7 @@ export class UserPreferenceController { }) async findById( @param.path.string('id') id: string, - @param.filter(UserPreference, {exclude: 'where'}) + @param.filter(UserPreference, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.userPreferenceRepository.findById(id, filter); @@ -155,7 +155,7 @@ export class UserPreferenceController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(UserPreference, {partial: true}), + schema: getModelSchemaRef(UserPreference, { partial: true }), }, }, }) diff --git a/sci-log-db/src/controllers/view.controller.ts b/sci-log-db/src/controllers/view.controller.ts index 28c0fb5d..91ffda5c 100644 --- a/sci-log-db/src/controllers/view.controller.ts +++ b/sci-log-db/src/controllers/view.controller.ts @@ -6,22 +6,22 @@ import { repository, Where, } from '@loopback/repository'; -import {post, param, get, patch, del, requestBody} from '@loopback/rest'; -import {View} from '../models'; -import {ViewRepository} from '../repositories'; -import {getModelSchemaRef} from '../utils/misc'; +import { post, param, get, patch, del, requestBody } from '@loopback/rest'; +import { View } from '../models'; +import { ViewRepository } from '../repositories'; +import { getModelSchemaRef } from '../utils/misc'; export class ViewController { constructor( @repository(ViewRepository) public viewRepository: ViewRepository, - ) {} + ) { } @post('/views', { responses: { '200': { description: 'View model instance', - content: {'application/json': {schema: getModelSchemaRef(View)}}, + content: { 'application/json': { schema: getModelSchemaRef(View) } }, }, }, }) @@ -45,7 +45,7 @@ export class ViewController { responses: { '200': { description: 'View model count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -61,7 +61,7 @@ export class ViewController { 'application/json': { schema: { type: 'array', - items: getModelSchemaRef(View, {includeRelations: true}), + items: getModelSchemaRef(View, { includeRelations: true }), }, }, }, @@ -76,7 +76,7 @@ export class ViewController { responses: { '200': { description: 'View PATCH success count', - content: {'application/json': {schema: CountSchema}}, + content: { 'application/json': { schema: CountSchema } }, }, }, }) @@ -84,7 +84,7 @@ export class ViewController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(View, {partial: true}), + schema: getModelSchemaRef(View, { partial: true }), }, }, }) @@ -100,7 +100,7 @@ export class ViewController { description: 'View model instance', content: { 'application/json': { - schema: getModelSchemaRef(View, {includeRelations: true}), + schema: getModelSchemaRef(View, { includeRelations: true }), }, }, }, @@ -108,7 +108,7 @@ export class ViewController { }) async findById( @param.path.string('id') id: string, - @param.filter(View, {exclude: 'where'}) filter?: FilterExcludingWhere, + @param.filter(View, { exclude: 'where' }) filter?: FilterExcludingWhere, ): Promise { return this.viewRepository.findById(id, filter); } @@ -125,7 +125,7 @@ export class ViewController { @requestBody({ content: { 'application/json': { - schema: getModelSchemaRef(View, {partial: true}), + schema: getModelSchemaRef(View, { partial: true }), }, }, }) diff --git a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts index b57081b4..63b177ef 100644 --- a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts +++ b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts @@ -1,4 +1,4 @@ -import {Getter, inject, MixinTarget} from '@loopback/core'; +import { Getter, inject, MixinTarget } from '@loopback/core'; import { DefaultCrudRepository, Entity, @@ -11,12 +11,12 @@ import { Condition, OrClause, } from '@loopback/repository'; -import {Basesnippet} from '../models'; -import {UserProfile} from '@loopback/security'; -import {HttpErrors, Response} from '@loopback/rest'; +import { Basesnippet } from '../models'; +import { UserProfile } from '@loopback/security'; +import { HttpErrors, Response } from '@loopback/rest'; import _ from 'lodash'; -import {EXPORT_SERVICE} from '../keys'; -import {ExportService} from '../services/export-snippets.service'; +import { EXPORT_SERVICE } from '../keys'; +import { ExportService } from '../services/export-snippets.service'; function UpdateAndDeleteRepositoryMixin< M extends Entity & { @@ -58,13 +58,49 @@ function UpdateAndDeleteRepositoryMixin< } else await this.updateById(id, basesnippet, options); } + async authorizedCreate(snippet: any, options?: Options) { + let user = options?.currentUser; + if (user == undefined) { + throw new HttpErrors.Forbidden('Cannot create new entries without user info.') + } + const baseSnippetRepository = await this.baseSnippetRepository(); + + // no need to check if we are dealing with an admin + let isAdmin = user.roles.find((role: string) => role == "admin"); + if (isAdmin != undefined) { + await baseSnippetRepository.create(snippet, { currentUser: user }); + return; + } + let roles = [...user.roles]; + let index = roles.indexOf('any-authenticated-user'); + if (index !== -1) { + roles.splice(index, 1); + } + let roleContainers = ['ownerGroup', 'accessGroups', 'readACL', 'createACL', 'updateACL', 'shareACL', 'adminACL', 'deleteACL']; + let requiredRoles: string[] = []; + roleContainers.forEach((roleContainer: any) => { + if (snippet.hasOwnProperty(roleContainer)) { + requiredRoles.push(snippet[roleContainer]); + } + }); + console.log("Required roles: ", requiredRoles) + if (requiredRoles.length == 0) { + throw new HttpErrors.Forbidden('Cannot create new entry without defining access roles.') + } + requiredRoles.forEach((requiredRole: any) => { + if (roles.find((role: any) => role == requiredRole) == undefined) { + throw new HttpErrors.Forbidden('Permission denied to create entry for group ' + requiredRole) + } + }) + } + private async addToHistory(snippet: M, user: UserProfile): Promise { const historySnippet = await this.getHistorySnippet(snippet, user); const snippetCopy = _.omit(snippet, 'id'); snippetCopy.parentId = historySnippet.id; snippetCopy.snippetType = 'updated'; const baseSnippetRepository = await this.baseSnippetRepository(); - await baseSnippetRepository.create(snippetCopy, {currentUser: user}); + await baseSnippetRepository.create(snippetCopy, { currentUser: user }); } private async getHistorySnippet( @@ -73,8 +109,8 @@ function UpdateAndDeleteRepositoryMixin< ): Promise { const baseSnippetRepository = await this.baseSnippetRepository(); let historySnippet = await baseSnippetRepository.findOne( - {where: {snippetType: 'history', parentId: snippet.id}}, - {currentUser: user}, + { where: { snippetType: 'history', parentId: snippet.id } }, + { currentUser: user }, ); if (historySnippet == null) { @@ -93,7 +129,7 @@ function UpdateAndDeleteRepositoryMixin< historySnippetPayload.snippetType = 'history'; historySnippet = await baseSnippetRepository.create( historySnippetPayload, - {currentUser: user}, + { currentUser: user }, ); } return historySnippet; @@ -103,7 +139,7 @@ function UpdateAndDeleteRepositoryMixin< // Two steps: // 1. set snippet to 'deleted=true' // 2. inside websocket and after informing the clients, replace the parentId or delete the snippet - await this.updateById(id as ID, {deleted: true}, options); + await this.updateById(id as ID, { deleted: true }, options); const snippet = await this.findById(id, {}, options); if (snippet?.versionable) { if (snippet?.parentId) { @@ -120,7 +156,7 @@ function UpdateAndDeleteRepositoryMixin< console.log('deleteById:parentHistory:', parentHistory); await this.updateById( id as ID, - {parentId: parentHistory.id}, + { parentId: parentHistory.id }, options, ); } @@ -130,13 +166,13 @@ function UpdateAndDeleteRepositoryMixin< } async restoreDeletedId(id: ID, user: UserProfile): Promise { - const snippet = await this.findById(id, {}, {currentUser: user}); + const snippet = await this.findById(id, {}, { currentUser: user }); if (snippet?.deleted && snippet.parentId) { const baseSnippetRepository = await this.baseSnippetRepository(); const historySnippet = await baseSnippetRepository.findById( snippet.parentId as unknown as ID, {}, - {currentUser: user}, + { currentUser: user }, ); const restoredSnippet = { deleted: false, @@ -152,9 +188,9 @@ function UpdateAndDeleteRepositoryMixin< } function FindWithSearchRepositoryMixin< - M extends Entity & {id: ID; tags?: string[]}, + M extends Entity & { id: ID; tags?: string[] }, ID, - Relations extends {subsnippets: M}, + Relations extends { subsnippets: M }, R extends MixinTarget>, >(superClass: R) { class Mixed extends superClass { @@ -164,10 +200,10 @@ function FindWithSearchRepositoryMixin< filter?: Filter, ): Promise { const includeTags = - (filter?.fields as {[P in keyof M]: boolean})?.tags ?? false; + (filter?.fields as { [P in keyof M]: boolean })?.tags ?? false; delete filter?.fields; - const searchRegex = {regexp: new RegExp(`.*?${search}.*?`, 'i')}; + const searchRegex = { regexp: new RegExp(`.*?${search}.*?`, 'i') }; const commonSearchableFields = { textcontent: { regexp: new RegExp( @@ -190,7 +226,7 @@ function FindWithSearchRepositoryMixin< ...commonSearchableFields, name: searchRegex, description: searchRegex, - id: {inq: withSubsnippets.map(s => s.id)}, + id: { inq: withSubsnippets.map(s => s.id) }, }; const snippets = await this._searchForSnippets( filter, @@ -252,7 +288,7 @@ function FindWithSearchRepositoryMixin< (filterCopy.include[subsnippetsIncludeIndex] as Inclusion).scope = includeFilter; withSubsnippets = await this.find( - {...filterCopy, fields: ['id']}, + { ...filterCopy, fields: ['id'] }, { currentUser: user, }, @@ -278,7 +314,7 @@ function FindWithSearchRepositoryMixin< return { or: Object.entries(commonSearchableFields).flatMap(([k, v]) => { if (!includeTags && k === 'tags') return [] as Where; - return {[k]: v} as Where; + return { [k]: v } as Where; }), }; } @@ -332,7 +368,7 @@ function ExportRepositoryMixin< const parent = await this.findById( snippets[0].parentId as unknown as ID, filter, - {currentUser: user}, + { currentUser: user }, ); job = { ownerGroup: parent.ownerGroup, @@ -360,7 +396,7 @@ function ExportRepositoryMixin< const fs = require('fs'); this.exportDir = basePath + jobEntity.id; if (!fs.existsSync(this.exportDir)) { - fs.mkdirSync(this.exportDir, {recursive: true}); + fs.mkdirSync(this.exportDir, { recursive: true }); } const exportService = await this.exportServiceGetter(); @@ -380,7 +416,7 @@ function ExportRepositoryMixin< response.download(downloadFile, (err, path = this.exportDir) => { console.log('file transferred successfully', err); if (path.includes(basePath)) { - fs.rmdirSync(path, {recursive: true}); + fs.rmdirSync(path, { recursive: true }); } }); return response; diff --git a/scilog/src/app/overview/add-logbook/add-logbook.component.ts b/scilog/src/app/overview/add-logbook/add-logbook.component.ts index b9bc9278..8ed6eb76 100644 --- a/scilog/src/app/overview/add-logbook/add-logbook.component.ts +++ b/scilog/src/app/overview/add-logbook/add-logbook.component.ts @@ -2,7 +2,7 @@ import { Component, ElementRef, Inject, Input, OnInit, ViewChild } from '@angula import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { Observable, Subscription } from 'rxjs'; -import { CompactType, DisplayGrid, GridsterConfig, GridsterItem,GridType } from 'angular-gridster2'; +import { CompactType, DisplayGrid, GridsterConfig, GridsterItem, GridType } from 'angular-gridster2'; import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; import { MatAutocompleteSelectedEvent, MatAutocomplete } from '@angular/material/autocomplete'; import { MatChipInputEvent } from '@angular/material/chips'; @@ -133,7 +133,7 @@ export class AddLogbookComponent implements OnInit { this.setDefaults(); } - setDefaults(){ + setDefaults() { if (this.logbook) { this.optionsFormGroup.get('title').setValue(this.logbook.name); this.optionsFormGroup.get('description').setValue(this.logbook.description); @@ -148,7 +148,12 @@ export class AddLogbookComponent implements OnInit { console.log("editing existing logbook"); } - this.accessGroupsAvail = this.userPreferences.userInfo?.roles; + let roles = [...this.userPreferences.userInfo?.roles]; + let index = roles.indexOf('any-authenticated-user'); + if (index !== -1) { + roles.splice(index, 1); + } + this.accessGroupsAvail = roles; this.filteredAccessGroups = this.accessGroupsCtrl.valueChanges.pipe(startWith(null), map((accessGroup: string | null) => accessGroup ? this._filter(accessGroup) : this.accessGroupsAvail.slice())); this.filteredOwnerGroups = this.optionsFormGroup.get('ownerGroup').valueChanges.pipe(startWith(null), map((accessGroup: string | null) => accessGroup ? this._filter(accessGroup) : this.accessGroupsAvail.slice())); this.optionsFormGroup.get('ownerGroup').setValidators([ownerGroupMemberValidator(this.accessGroupsAvail)]); @@ -160,9 +165,9 @@ export class AddLogbookComponent implements OnInit { } - async getLocations(){ + async getLocations() { let data = await this.logbookDataService.getLocations(); - if (data.length > 0){ + if (data.length > 0) { this.availLocations = data[0].subsnippets; } } @@ -196,9 +201,9 @@ export class AddLogbookComponent implements OnInit { if (this.optionsFormGroup.invalid) { console.log("form invalid") for (const key in this.optionsFormGroup.controls) { - if (this.optionsFormGroup.get(key).invalid){ - this.optionsFormGroup.get(key).setErrors({'required': true}); - } + if (this.optionsFormGroup.get(key).invalid) { + this.optionsFormGroup.get(key).setErrors({ 'required': true }); + } } return; } @@ -217,14 +222,14 @@ export class AddLogbookComponent implements OnInit { description: this.optionsFormGroup.get('description').value } - let fileData: {id: string}; + let fileData: { id: string }; // now that we have the id, let's upload the image if (this.uploadThumbnailFile != null) { // upload selected file let formData = new FormData(); let filenameStorage: string = this.logbook.id + "." + this.uploadThumbnailFile.name.split('.').pop(); if (this.logbook.ownerGroup) - formData.append('fields', JSON.stringify({accessGroups: [this.logbook.ownerGroup]})) + formData.append('fields', JSON.stringify({ accessGroups: [this.logbook.ownerGroup] })) formData.append('file', this.uploadThumbnailFile); fileData = await this.logbookDataService.uploadLogbookThumbnail(formData); } From 3a3b61fb497dfc58d74351c896b8b88fd18e611c Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Sat, 4 Mar 2023 16:13:38 +0100 Subject: [PATCH 2/4] test: added task test for 403 error --- .../acceptance/task.controller.acceptance.ts | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts b/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts index ddcb95d1..e4a20c8d 100644 --- a/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts +++ b/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts @@ -1,7 +1,7 @@ -import {Client, expect} from '@loopback/testlab'; -import {Suite} from 'mocha'; -import {SciLogDbApplication} from '../..'; -import {clearDatabase, createUserToken, setupApplication} from './test-helper'; +import { Client, expect } from '@loopback/testlab'; +import { Suite } from 'mocha'; +import { SciLogDbApplication } from '../..'; +import { clearDatabase, createUserToken, setupApplication } from './test-helper'; describe('TaskRepositorySnippet', function (this: Suite) { this.timeout(5000); @@ -15,7 +15,7 @@ describe('TaskRepositorySnippet', function (this: Suite) { readACL: ['taskAcceptance'], updateACL: ['taskAcceptance'], deleteACL: ['taskAcceptance'], - adminACL: ['admin'], + adminACL: ['taskAcceptance'], shareACL: ['taskAcceptance'], isPrivate: true, defaultOrder: 0, @@ -26,7 +26,7 @@ describe('TaskRepositorySnippet', function (this: Suite) { }; before('setupApplication', async () => { - ({app, client} = await setupApplication()); + ({ app, client } = await setupApplication()); await clearDatabase(app); token = await createUserToken(app, client, ['taskAcceptance']); }); @@ -59,6 +59,30 @@ describe('TaskRepositorySnippet', function (this: Suite) { }); }); + it('post a task with authentication should return 403 if taskSnippets permission are invalid', async () => { + const taskSnippetInvalid = { + ownerGroup: 'taskAcceptance', + createACL: ['taskAcceptance'], + readACL: ['taskAcceptance'], + updateACL: ['taskAcceptance'], + deleteACL: ['taskAcceptance'], + adminACL: ['admin'], + shareACL: ['taskAcceptance'], + isPrivate: true, + defaultOrder: 0, + expiresAt: '2055-10-10T14:04:19.522Z', + tags: ['aSearchableTag'], + dashboardName: 'string', + versionable: true, + }; + await client + .post('/tasks') + .set('Authorization', 'Bearer ' + token) + .set('Content-Type', 'application/json') + .send(taskSnippetInvalid) + .expect(403); + }); + it('count snippet without token should return 401', async () => { await client .get('/tasks/count') @@ -125,7 +149,7 @@ describe('TaskRepositorySnippet', function (this: Suite) { await client .patch('/tasks/') .set('Content-Type', 'application/json') - .send({dashboardName: 'aNewName'}) + .send({ dashboardName: 'aNewName' }) .expect(401); }); @@ -134,7 +158,7 @@ describe('TaskRepositorySnippet', function (this: Suite) { .patch('/tasks') .set('Authorization', 'Bearer ' + token) .set('Content-Type', 'application/json') - .send({dashboardName: 'aNewName'}) + .send({ dashboardName: 'aNewName' }) .expect(200) .then(result => expect(result.body.count).to.be.eql(1)) .catch(err => { @@ -162,7 +186,7 @@ describe('TaskRepositorySnippet', function (this: Suite) { }); it('Search index with token should return 200 and matching body.tags', async () => { - const includeTags = {fields: {tags: true}, include: ['subsnippets']}; + const includeTags = { fields: { tags: true }, include: ['subsnippets'] }; await client .get(`/tasks/search=aSearchabletag?filter=${JSON.stringify(includeTags)}`) .set('Authorization', 'Bearer ' + token) @@ -205,7 +229,7 @@ describe('TaskRepositorySnippet', function (this: Suite) { }); it('Search index with token should return 200 and empty result', async () => { - const includeTags = {fields: {tags: true}}; + const includeTags = { fields: { tags: true } }; await client .get(`/tasks/search=aSearchabletag?filter=${JSON.stringify(includeTags)}`) .set('Authorization', 'Bearer ' + token) From 36db93e9e3583bee4830a75328577e36ea17610f Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Sat, 4 Mar 2023 16:20:14 +0100 Subject: [PATCH 3/4] fix: fixed return value for authorizedCreate --- sci-log-db/src/mixins/basesnippet.repository-mixin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts index 63b177ef..c9f98761 100644 --- a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts +++ b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts @@ -68,8 +68,7 @@ function UpdateAndDeleteRepositoryMixin< // no need to check if we are dealing with an admin let isAdmin = user.roles.find((role: string) => role == "admin"); if (isAdmin != undefined) { - await baseSnippetRepository.create(snippet, { currentUser: user }); - return; + return baseSnippetRepository.create(snippet, { currentUser: user }); } let roles = [...user.roles]; let index = roles.indexOf('any-authenticated-user'); @@ -91,7 +90,8 @@ function UpdateAndDeleteRepositoryMixin< if (roles.find((role: any) => role == requiredRole) == undefined) { throw new HttpErrors.Forbidden('Permission denied to create entry for group ' + requiredRole) } - }) + }); + return baseSnippetRepository.create(snippet, { currentUser: user }); } private async addToHistory(snippet: M, user: UserProfile): Promise { From 7abe9a9fd4ce810a274bd1a596f1bca11afe53d7 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Sat, 4 Mar 2023 23:13:59 +0100 Subject: [PATCH 4/4] fix: fixed bug in authorizedCreate --- sci-log-db/src/mixins/basesnippet.repository-mixin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts index c9f98761..7e9ae41e 100644 --- a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts +++ b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts @@ -79,7 +79,8 @@ function UpdateAndDeleteRepositoryMixin< let requiredRoles: string[] = []; roleContainers.forEach((roleContainer: any) => { if (snippet.hasOwnProperty(roleContainer)) { - requiredRoles.push(snippet[roleContainer]); + requiredRoles = requiredRoles.concat(snippet[roleContainer]) + // requiredRoles.push(snippet[roleContainer]); } }); console.log("Required roles: ", requiredRoles)