feat(backend): Bescherming van leerobject-manipulatie endpoints.
Ook delete route voor leerobjecten toegevoegd.
This commit is contained in:
		
							parent
							
								
									a7f90aace3
								
							
						
					
					
						commit
						20c04370b5
					
				
					 4 changed files with 50 additions and 4 deletions
				
			
		|  | @ -8,6 +8,7 @@ import { AuthenticatedRequest } from './authenticated-request.js'; | ||||||
| import { AuthenticationInfo } from './authentication-info.js'; | import { AuthenticationInfo } from './authentication-info.js'; | ||||||
| import { UnauthorizedException } from '../../exceptions/unauthorized-exception.js'; | import { UnauthorizedException } from '../../exceptions/unauthorized-exception.js'; | ||||||
| import { ForbiddenException } from '../../exceptions/forbidden-exception.js'; | import { ForbiddenException } from '../../exceptions/forbidden-exception.js'; | ||||||
|  | import { RequestHandler } from 'express'; | ||||||
| 
 | 
 | ||||||
| const JWKS_CACHE = true; | const JWKS_CACHE = true; | ||||||
| const JWKS_RATE_LIMIT = true; | const JWKS_RATE_LIMIT = true; | ||||||
|  | @ -115,11 +116,17 @@ export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; | ||||||
|  * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates |  * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates | ||||||
|  *                        to true. |  *                        to true. | ||||||
|  */ |  */ | ||||||
| export function authorize(accessCondition: (auth: AuthenticationInfo) => boolean) { | export function authorize( | ||||||
|     return (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { |     accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean> | ||||||
|  | ): RequestHandler { | ||||||
|  |     return async ( | ||||||
|  |         req: AuthenticatedRequest, | ||||||
|  |         _res: express.Response, | ||||||
|  |         next: express.NextFunction | ||||||
|  |     ): Promise<void> => { | ||||||
|         if (!req.auth) { |         if (!req.auth) { | ||||||
|             throw new UnauthorizedException(); |             throw new UnauthorizedException(); | ||||||
|         } else if (!accessCondition(req.auth)) { |         } else if (!(await accessCondition(req.auth, req))) { | ||||||
|             throw new ForbiddenException(); |             throw new ForbiddenException(); | ||||||
|         } else { |         } else { | ||||||
|             next(); |             next(); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | import { Language } from "@dwengo-1/common/util/language"; | ||||||
|  | import learningObjectService from "../../../services/learning-objects/learning-object-service"; | ||||||
|  | import { authorize } from "../auth"; | ||||||
|  | import { AuthenticatedRequest } from "../authenticated-request"; | ||||||
|  | import { AuthenticationInfo } from "../authentication-info"; | ||||||
|  | 
 | ||||||
|  | export const onlyAdminsForLearningObject = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { | ||||||
|  |     const { hruid } = req.params; | ||||||
|  |     const { version, language } = req.query; | ||||||
|  |     const admins = await learningObjectService.getAdmins({ | ||||||
|  |         hruid, | ||||||
|  |         language: language as Language, | ||||||
|  |         version: parseInt(version as string) | ||||||
|  |     }); | ||||||
|  |     return auth.username in admins; | ||||||
|  | }); | ||||||
|  | @ -4,12 +4,15 @@ import { | ||||||
|     getAttachment, |     getAttachment, | ||||||
|     getLearningObject, |     getLearningObject, | ||||||
|     getLearningObjectHTML, |     getLearningObjectHTML, | ||||||
|  |     handleDeleteLearningObject, | ||||||
|     handlePostLearningObject |     handlePostLearningObject | ||||||
| } from '../controllers/learning-objects.js'; | } from '../controllers/learning-objects.js'; | ||||||
| 
 | 
 | ||||||
| import submissionRoutes from './submissions.js'; | import submissionRoutes from './submissions.js'; | ||||||
| import questionRoutes from './questions.js'; | import questionRoutes from './questions.js'; | ||||||
| import fileUpload from "express-fileupload"; | import fileUpload from "express-fileupload"; | ||||||
|  | import { teachersOnly } from '../middleware/auth/auth.js'; | ||||||
|  | import { onlyAdminsForLearningObject } from '../middleware/auth/checks/learning-object-auth-checks.js'; | ||||||
| 
 | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -25,7 +28,7 @@ const router = express.Router(); | ||||||
| // Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie
 | // Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie
 | ||||||
| router.get('/', getAllLearningObjects); | router.get('/', getAllLearningObjects); | ||||||
| 
 | 
 | ||||||
| router.post('/', fileUpload({useTempFiles: true}), handlePostLearningObject) | router.post('/', teachersOnly, fileUpload({useTempFiles: true}), handlePostLearningObject) | ||||||
| 
 | 
 | ||||||
| // Parameter: hruid of learning object
 | // Parameter: hruid of learning object
 | ||||||
| // Query: language
 | // Query: language
 | ||||||
|  | @ -33,6 +36,12 @@ router.post('/', fileUpload({useTempFiles: true}), handlePostLearningObject) | ||||||
| // Example: http://localhost:3000/learningObject/un_ai7
 | // Example: http://localhost:3000/learningObject/un_ai7
 | ||||||
| router.get('/:hruid', getLearningObject); | router.get('/:hruid', getLearningObject); | ||||||
| 
 | 
 | ||||||
|  | // Parameter: hruid of learning object
 | ||||||
|  | // Query: language
 | ||||||
|  | // Route to delete a learning object based on its hruid.
 | ||||||
|  | // Example: http://localhost:3000/learningObject/un_ai7?language=nl&version=1
 | ||||||
|  | router.delete('/:hruid', onlyAdminsForLearningObject, handleDeleteLearningObject) | ||||||
|  | 
 | ||||||
| router.use('/:hruid/submissions', submissionRoutes); | router.use('/:hruid/submissions', submissionRoutes); | ||||||
| 
 | 
 | ||||||
| router.use('/:hruid/:version/questions', questionRoutes); | router.use('/:hruid/:version/questions', questionRoutes); | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import {getLearningObjectRepository, getTeacherRepository} from "../../data/repo | ||||||
| import {processLearningObjectZip} from "./learning-object-zip-processing-service"; | import {processLearningObjectZip} from "./learning-object-zip-processing-service"; | ||||||
| import {LearningObject} from "../../entities/content/learning-object.entity"; | import {LearningObject} from "../../entities/content/learning-object.entity"; | ||||||
| import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | ||||||
|  | import { NotFoundException } from '../../exceptions/not-found-exception.js'; | ||||||
| 
 | 
 | ||||||
| function getProvider(id: LearningObjectIdentifierDTO): LearningObjectProvider { | function getProvider(id: LearningObjectIdentifierDTO): LearningObjectProvider { | ||||||
|     if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { |     if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { | ||||||
|  | @ -92,6 +93,19 @@ const learningObjectService = { | ||||||
|     async deleteLearningObject(id: LearningObjectIdentifier): Promise<LearningObject | null> { |     async deleteLearningObject(id: LearningObjectIdentifier): Promise<LearningObject | null> { | ||||||
|         const learningObjectRepository = getLearningObjectRepository(); |         const learningObjectRepository = getLearningObjectRepository(); | ||||||
|         return await learningObjectRepository.removeByIdentifier(id); |         return await learningObjectRepository.removeByIdentifier(id); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns a list of the usernames of the administrators of the learning object with the given identifier. | ||||||
|  |      * @throws NotFoundException if the specified learning object was not found in the database. | ||||||
|  |      */ | ||||||
|  |     async getAdmins(id: LearningObjectIdentifier): Promise<string[]> { | ||||||
|  |         const learningObjectRepo = getLearningObjectRepository(); | ||||||
|  |         const learningObject = await learningObjectRepo.findByIdentifier(id); | ||||||
|  |         if (!learningObject) { | ||||||
|  |             throw new NotFoundException("The specified learning object does not exist."); | ||||||
|  |         } | ||||||
|  |         return learningObject.admins.map(admin => admin.username); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger