'use strict'
const configRessource = require('../ressource/config')
const defaultSearchLimit = 25
const maxSearchLimit = 100
// sanitizeSearch isolé dans ce module, dépendant de $accessControl
module.exports = function ($accessControl) {
const { getCurrentUserGroupesMembre, getCurrentUserPid, hasGenericPermission } = $accessControl
/**
* retourne queryOptions d'après skip & limit passés en get
* @private
* @param {Context} context
* @param {string[]} warnings pour en ajouter le cas échéant
* @return {{limit: number, skip: number}}
*/
const getQueryOptions = (context, warnings) => {
const queryOptions = {
limit: defaultSearchLimit,
skip: 0
}
const wantedQuery = context.get
if (wantedQuery.skip) {
queryOptions.skip = Math.max(0, Math.round(wantedQuery.skip) || 0)
}
if (wantedQuery.limit) {
const limit = Math.round(wantedQuery.limit)
if (limit > 0 && limit <= maxSearchLimit) queryOptions.limit = limit
else warnings.push(`Limite ${wantedQuery.limit} invalide, ramenée à ${defaultSearchLimit}`)
}
if (wantedQuery.orderBy) {
// on controle rien, c'est vérifié dans le queryBuilder
// et sans risque si ça contient n'importe quoi
queryOptions.orderBy = wantedQuery.orderBy
}
if (wantedQuery.order === 'desc') queryOptions.order = 'desc'
return queryOptions
}
/**
* Set query.groupes en filtrant les groupes demandés
* @private
* @param {Context} context
* @param {object} query
* @param {string[]} warnings pour en ajouter le cas échéant
*/
const sanitizeSearchGroupes = (context, query, warnings) => {
const myGroups = getCurrentUserGroupesMembre(context)
const canReadAll = hasGenericPermission('read', context)
const trimAll = (nom) => nom.trim()
const addGroups = (prop) => {
const groupesAsked = context.get[prop]
if (!groupesAsked) return
const groupesWanted = (Array.isArray(groupesAsked)
? groupesAsked
: groupesAsked.split(','))
if (groupesWanted.length) {
if (canReadAll) {
query[prop] = groupesWanted.map(trimAll)
} else {
const groupesOk = groupesWanted.filter(groupe => myGroups.includes(groupe))
if (groupesOk.length < groupesWanted.length) {
const excluded = groupesWanted.filter(g => !myGroups.includes(g))
const message = (excluded.length > 1)
? `Vous n’êtes pas membre du groupe ${excluded[0]}, il a été exclu de la recherche`
: `Vous n’êtes pas membre des groupes ${excluded.join(', ')}, ils ont été exclus de la recherche`
warnings.push(message)
}
if (groupesOk.length) query[prop] = groupesOk.map(trimAll)
}
} else {
warnings.push('Groupe(s) invalide(s), ignoré(s)')
}
}
addGroups('groupes')
addGroups('groupesAuteurs')
}
/**
* Set query.publie
* @private
* @param {Context} context
* @param {boolean} canGrabPrivate
* @param {object} query
* @param {string[]} warnings pour en ajouter le cas échéant
*/
const sanitizeSearchPublie = (context, canGrabPrivate, query, warnings) => {
const wantedPublie = context.get.publie
if (canGrabPrivate) {
if (wantedPublie) query.publie = [(wantedPublie === 'true')]
// sinon on peut ne rien préciser
} else {
if (wantedPublie && wantedPublie !== 'true') {
warnings.push('Vous ne pouvez pas chercher de ressources non publiées sans filtrer sur vos ressources (ou celles de vos groupes)')
}
query.publie = [true]
}
}
/**
* Set query.restriction
* @private
* @param {Context} context
* @param {boolean} canGrabPrivate
* @param {object} query
* @param {string[]} warnings pour en ajouter le cas échéant
*/
const sanitizeSearchRestriction = (context, canGrabPrivate, query, warnings) => {
const canReadCorrection = hasGenericPermission('correction', context)
// un raccourci d'écriture, obj prop => value (integer)
const r = configRessource.constantes.restriction
const defaultRestriction = canReadCorrection ? [r.aucune, r.correction] : [r.aucune]
const setDefault = () => {
if (!canGrabPrivate) query.restriction = defaultRestriction
}
/** @type string[] */
let wantedRestrictions = context.get.restriction
if (!wantedRestrictions) return setDefault()
if (!Array.isArray(wantedRestrictions)) wantedRestrictions = [wantedRestrictions]
if (!wantedRestrictions.length) return setDefault()
// faut analyser, on passe par un set pour la déduplication
const restrictions = new Set()
wantedRestrictions.forEach(wantedRestriction => {
switch (wantedRestriction) {
case `${r.aucune}`:
restrictions.add(r.aucune)
break
case `${r.correction}`:
if (canReadCorrection) restrictions.add(r.correction)
else warnings.push('Vous ne pouvez pas consulter les corrections')
break
case `${r.groupe}`:
// si y'a déjà un filtre sur des groupes dont je fait partie canGrabPrivate est true
if (canGrabPrivate) restrictions.add(r.groupe)
else warnings.push('Vous ne pouvez pas consulter toutes les ressources de tous les groupes, vous devez restreindre aux votres')
break
case `${r.prive}`:
if (canGrabPrivate) restrictions.add(r.prive)
else warnings.push('Vous ne pouvez pas consulter de ressource privée autre que les votres (vous devez ajouter un critère pour restreindre à vos ressources)')
break
default:
warnings.push(`Restriction ${wantedRestriction} inconnue`)
}
})
if (restrictions.size) query.restriction = Array.from(restrictions)
else setDefault() // la liste passée était foireuse, y'a déjà les warnings
}
/**
* Retourne un objet avec query normalisée d'après les droits de l'utilisateur courant
* Ça garanti que ce que remontera cette recherche sera lisible par l'utilisateur courant
* @param {Context} context
* @return {{query: searchQuery, queryOptions: {limit: number, skip: number}, warnings: string[]}}
*/
const sanitizeSearch = (context) => {
function getStringValues (prop) {
const values = context.get[prop]
if (!values) return
if (Array.isArray(values)) return values
// forcément une string (c'est du get), donc falsy => string vide
return values.split(',').map(value => value.trim()).filter(v => v)
}
const warnings = []
const query = {}
const queryOptions = getQueryOptions(context, warnings)
const wantedQuery = context.get
// on regarde si c'est filtré sur ses propres ressources
const canReadAll = hasGenericPermission('read', context)
const myPid = getCurrentUserPid(context)
// si y'a un filtre sur mes ressources
const withinMine = myPid && (wantedQuery.auteurs === myPid || wantedQuery.contributeurs === myPid)
sanitizeSearchGroupes(context, query, warnings)
// si y'a un filtre sur les ressources publiées ou éditées par des groupes dont je suis membre
const withinMyGroups = Boolean(query.groupes || query.groupesAuteurs)
// le seul cas où on peut chercher du non publié, c'est sur ses propres ressources
// (sauf si on a le droit de lecture sur tout)
const canGrabPrivate = canReadAll || withinMine || withinMyGroups
sanitizeSearchPublie(context, canGrabPrivate, query, warnings)
sanitizeSearchRestriction(context, canGrabPrivate, query, warnings)
// input string (à priori single value pour les premiers, multiple autorisé)
;['titre', 'oid', 'origine', 'idOrigine', 'type', 'langue', 'auteurs', 'contributeurs', 'niveaux', 'fulltext'].forEach(prop => {
const values = getStringValues(prop)
if (!values) return
query[prop] = values
})
// cast en number
;['categories', 'typePedagogiques', 'typeDocumentaires'].forEach(prop => {
let values = getStringValues(prop)
if (!values) return
values = values.map(value => Number(value)).filter(value => Number.isInteger(value))
if (values.length) query[prop] = values
})
return { query, queryOptions, warnings }
}
return sanitizeSearch
}