Skip to content

Commit

Permalink
refactoring and permission integration tests for images
Browse files Browse the repository at this point in the history
  • Loading branch information
theorm committed Jan 15, 2025
1 parent 82347b1 commit d53c328
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 121 deletions.
2 changes: 1 addition & 1 deletion src/hooks/redaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ const webappAuthBitmapExtractor = (redactable: Redactable, kind: keyof Authoriza
* - the request is made through the app (not the public API)
* - user bitmap does not align with the content item bitmap
*/
export const webAppTranscriptRedactionCondition: RedactCondition = (context, redactable) => {
export const webAppExploreRedactionCondition: RedactCondition = (context, redactable) => {
return (
!inPublicApi(context, redactable) &&
!bitmapsAlign(context, redactable, x => webappAuthBitmapExtractor(x, 'explore'))
Expand Down
8 changes: 5 additions & 3 deletions src/models/articles.model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { OpenPermissions } from '../util/bigint'

const { DataTypes } = require('sequelize')
const lodash = require('lodash')
const config = require('@feathersjs/configuration')()()
Expand Down Expand Up @@ -725,9 +727,9 @@ class Article extends BaseArticle {
// permissions bitmaps
// if it's not defined, set max permissions for compatibility
// with old Solr version
bitmapExplore: BigInt(doc.rights_bm_explore_l ?? Number.MAX_SAFE_INTEGER),
bitmapGetTranscript: BigInt(doc.rights_bm_get_tr_l ?? Number.MAX_SAFE_INTEGER),
bitmapGetImages: BigInt(doc.rights_bm_get_img_l ?? Number.MAX_SAFE_INTEGER),
bitmapExplore: BigInt(doc.rights_bm_explore_l ?? OpenPermissions),
bitmapGetTranscript: BigInt(doc.rights_bm_get_tr_l ?? OpenPermissions),
bitmapGetImages: BigInt(doc.rights_bm_get_img_l ?? OpenPermissions),
})

if (!doc.pp_plain) {
Expand Down
92 changes: 58 additions & 34 deletions src/models/images.model.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const Page = require('./pages.model');
const Issue = require('./issues.model');
const Newspaper = require('./newspapers.model');
const Article = require('./articles.model');
const { getExternalFragment } = require('../hooks/iiif.js');
import { OpenPermissions } from '../util/bigint'

const Page = require('./pages.model')
const Issue = require('./issues.model')
const Newspaper = require('./newspapers.model')
const Article = require('./articles.model')
const { getExternalFragment } = require('../hooks/iiif.js')

class Image {
constructor ({
constructor({
uid = '',
type = 'image',
coords = [],
Expand All @@ -17,53 +19,61 @@ class Image {
isFront = false,
pages = [],
article = null,
// permissions bitmaps
bitmapExplore = undefined,
bitmapGetTranscript = undefined,
bitmapGetImages = undefined,
} = {}) {
this.uid = String(uid);
this.year = parseInt(year, 10);
this.type = String(type);
this.coords = coords;
this.pages = pages;
this.isFront = Boolean(isFront);
this.uid = String(uid)
this.year = parseInt(year, 10)
this.type = String(type)
this.coords = coords
this.pages = pages
this.isFront = Boolean(isFront)

this.title = String(title);
this.title = String(title)
if (date instanceof Date) {
this.date = date;
this.date = date
} else {
this.date = new Date(date);
this.date = new Date(date)
}
if (newspaper instanceof Newspaper) {
this.newspaper = newspaper;
this.newspaper = newspaper
} else {
this.newspaper = new Newspaper(newspaper);
this.newspaper = new Newspaper(newspaper)
}
if (issue instanceof Issue) {
this.issue = issue;
this.issue = issue
} else {
this.issue = new Issue(issue);
this.issue = new Issue(issue)
}

if (article instanceof Article) {
this.article = article;
this.article = article
} else if (typeof article === 'string') {
this.article = article;
this.article = article
}

this.bitmapExplore = bitmapExplore
this.bitmapGetTranscript = bitmapGetTranscript
this.bitmapGetImages = bitmapGetImages
}

assignIIIF () {
assignIIIF() {
this.regions = this.pages.map(page => ({
pageUid: page.uid,
coords: this.coords,
iiifFragment: getExternalFragment(page.iiif, { coords: this.coords, dim: '250,' }),
}));
}))
}

/**
* Return an Image mapper for Solr response document
*
* @return {function} {Image} image instance.
*/
static solrFactory () {
return (doc) => {
static solrFactory() {
return doc => {
const img = new Image({
uid: doc.id,
newspaper: new Newspaper({
Expand All @@ -73,10 +83,13 @@ class Image {
uid: doc.meta_issue_id_s,
}),
pages: Array.isArray(doc.page_nb_is)
? doc.page_nb_is.map(num => new Page({
uid: `${doc.meta_issue_id_s}-p${String(num).padStart(4, '0')}`,
num,
}))
? doc.page_nb_is.map(
num =>

Check failure on line 87 in src/models/images.model.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected indentation of 12 spaces but found 14
new Page({

Check failure on line 88 in src/models/images.model.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected indentation of 14 spaces but found 16
uid: `${doc.meta_issue_id_s}-p${String(num).padStart(4, '0')}`,

Check failure on line 89 in src/models/images.model.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected indentation of 16 spaces but found 18
num,

Check failure on line 90 in src/models/images.model.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected indentation of 16 spaces but found 18
})

Check failure on line 91 in src/models/images.model.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected indentation of 14 spaces but found 16
)

Check failure on line 92 in src/models/images.model.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected indentation of 10 spaces but found 12
: [],
title: Article.getUncertainField(doc, 'title'),
type: doc.item_type_s,
Expand All @@ -85,13 +98,19 @@ class Image {
coords: doc.coords_is,
isFront: doc.front_b,
article: doc.linked_art_s,
});
return img;
};
// permissions bitmaps
// if it's not defined, set max permissions for compatibility
// with old Solr version
bitmapExplore: BigInt(doc.rights_bm_explore_l ?? OpenPermissions),
bitmapGetTranscript: BigInt(doc.rights_bm_get_tr_l ?? OpenPermissions),
bitmapGetImages: BigInt(doc.rights_bm_get_img_l ?? OpenPermissions),
})
return img
}
}
}

module.exports = Image;
module.exports = Image
module.exports.SOLR_FL = [
'id',
'coords_is',
Expand All @@ -108,4 +127,9 @@ module.exports.SOLR_FL = [
'item_type_s',
'title_txt_fr',
'_version_',
];
// permissions bitmaps
// see https://github.com/search?q=org%3Aimpresso%20rights_bm_explore_l&type=code
'rights_bm_explore_l',
'rights_bm_get_tr_l',
'rights_bm_get_img_l',
]
6 changes: 4 additions & 2 deletions src/models/text-reuse-passages.model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { OpenPermissions } from '../util/bigint'

const { invert } = require('lodash')
const { solrDocsMapCallbackFn } = require('../util/solr/fields')

Expand Down Expand Up @@ -103,8 +105,8 @@ class TextReusePassage {
this.pageNumbers = pageNumbers
this.collections = collections

this.bitmapExplore = BigInt(bitmapExplore ?? Number.MAX_SAFE_INTEGER)
this.bitmapGetTranscript = BigInt(bitmapGetTranscript ?? Number.MAX_SAFE_INTEGER)
this.bitmapExplore = BigInt(bitmapExplore ?? OpenPermissions)
this.bitmapGetTranscript = BigInt(bitmapGetTranscript ?? OpenPermissions)
}

static CreateFromSolr(fieldsToPropsMapper = SolrFieldsToPropsMapper) {
Expand Down
11 changes: 8 additions & 3 deletions src/services/admin/admin.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
import { getUserAccountsWithAvailablePermissions, UserAccount } from '../../useCases/getUsersPermissionsDetails'

interface FindResponse {
permissionsDetails: ContentItemPermissionsDetails
contentItemsPermissionsDetails: ContentItemPermissionsDetails
imagesPermissionsDetails: ContentItemPermissionsDetails
userAccounts: UserAccount[]
}
interface FindParams {}
Expand All @@ -18,8 +19,12 @@ export class Service implements IService {
constructor(private readonly app: ImpressoApplication) {}

async find(params?: Params<FindParams>): Promise<FindResponse> {
const permissionsDetails = await getContentItemsPermissionsDetails(this.app.service('simpleSolrClient'))
const [contentItemsPermissionsDetails, imagesPermissionsDetails] = await Promise.all([
getContentItemsPermissionsDetails(this.app.service('simpleSolrClient'), 'Search'),
getContentItemsPermissionsDetails(this.app.service('simpleSolrClient'), 'Images'),
])

const userAccounts = await getUserAccountsWithAvailablePermissions(this.app.get('sequelizeClient')!)
return { permissionsDetails, userAccounts } satisfies FindResponse
return { contentItemsPermissionsDetails, imagesPermissionsDetails, userAccounts } satisfies FindResponse
}
}
6 changes: 3 additions & 3 deletions src/services/articles/articles.hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
redactResponse,
redactResponseDataItem,
publicApiTranscriptRedactionCondition,
webAppTranscriptRedactionCondition,
webAppExploreRedactionCondition,
inPublicApi,
} from '../../hooks/redaction'
import { loadYamlFile } from '../../util/yaml'
Expand Down Expand Up @@ -107,7 +107,7 @@ export default {
obfuscate(),
transformResponseDataItem(transformContentItem, inPublicApi),
redactResponseDataItem(contentItemRedactionPolicy, publicApiTranscriptRedactionCondition),
redactResponseDataItem(contentItemRedactionPolicyWebApp, webAppTranscriptRedactionCondition),
redactResponseDataItem(contentItemRedactionPolicyWebApp, webAppExploreRedactionCondition),
],
get: [
// save here cache, flush cache here
Expand All @@ -120,7 +120,7 @@ export default {
obfuscate(),
transformResponse(transformContentItem, inPublicApi),
redactResponse(contentItemRedactionPolicy, publicApiTranscriptRedactionCondition),
redactResponse(contentItemRedactionPolicyWebApp, webAppTranscriptRedactionCondition),
redactResponse(contentItemRedactionPolicyWebApp, webAppExploreRedactionCondition),
],
create: [],
update: [],
Expand Down
32 changes: 19 additions & 13 deletions src/services/images/images.hooks.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { loadYamlFile } from '../../util/yaml'
import { redactResponse, redactResponseDataItem, webAppExploreRedactionCondition } from '../../hooks/redaction'

// const { authenticate } = require('@feathersjs/authentication').hooks;
const {
utils, validate, validateEach, queryWithCommonParams, displayQueryParams, REGEX_UID,
} = require('../../hooks/params');
const {
qToSolrFilter, filtersToSolrQuery,
} = require('../../hooks/search');
utils,
validate,
validateEach,
queryWithCommonParams,
displayQueryParams,
REGEX_UID,
} = require('../../hooks/params')
const { qToSolrFilter, filtersToSolrQuery } = require('../../hooks/search')

const {
resolveFacets,
resolveQueryComponents,
} = require('../../hooks/search-info');
const { resolveFacets, resolveQueryComponents } = require('../../hooks/search-info')

const { eachFilterValidator } = require('../search/search.validators');
const { eachFilterValidator } = require('../search/search.validators')

module.exports = {
export const imageRedactionPolicyWebApp = loadYamlFile(`${__dirname}/resources/imageRedactionPolicyWebApp.yml`)

export default {
before: {
all: [
// authenticate('jwt')
Expand Down Expand Up @@ -92,8 +97,9 @@ module.exports = {
resolveFacets(),
displayQueryParams(['queryComponents', 'filters']),
resolveQueryComponents(),
redactResponseDataItem(imageRedactionPolicyWebApp, webAppExploreRedactionCondition),
],
get: [],
get: [redactResponse(imageRedactionPolicyWebApp, webAppExploreRedactionCondition)],
create: [],
update: [],
patch: [],
Expand All @@ -109,4 +115,4 @@ module.exports = {
patch: [],
remove: [],
},
};
}
21 changes: 12 additions & 9 deletions src/services/images/images.service.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// Initializes the `images` service on path `/images`
const createService = require('./images.class.js');
const hooks = require('./images.hooks');
import hooks from './images.hooks'
const createService = require('./images.class.js')

module.exports = function (app) {
// Initialize our service with any options it requires
app.use('/images', createService({
app,
name: 'images',
}));
app.use(
'/images',
createService({
app,
name: 'images',
})
)

// Get our initialized service so that we can register hooks
const service = app.service('images');
const service = app.service('images')

service.hooks(hooks);
};
service.hooks(hooks)
}
17 changes: 17 additions & 0 deletions src/services/images/resources/imageRedactionPolicyWebApp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# yaml-language-server: $schema=../../../schema/common/redactionPolicy.json
name: image-redaction-policy
items:
- jsonPath: $.title
valueConverterName: redact
- jsonPath: $.article.[*].title
valueConverterName: redact
- jsonPath: $.article.[*].excerpt
valueConverterName: redact
- jsonPath: $.article.[*].content
valueConverterName: redact
- jsonPath: $.regions.[*].iiifFragment
valueConverterName: contextNotAllowedImage
- jsonPath: $.pages.[*].iiifThumbnail
valueConverterName: contextNotAllowedImage
- jsonPath: $.pages.[*].iiif
valueConverterName: contextNotAllowedImage
4 changes: 2 additions & 2 deletions src/services/search/search.hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
redactResponseDataItem,
inPublicApi,
publicApiTranscriptRedactionCondition,
webAppTranscriptRedactionCondition,
webAppExploreRedactionCondition,
} from '../../hooks/redaction'
import { transformResponseDataItem, transformResponse, renameQueryParameters } from '../../hooks/transformation'
import { transformBaseFind } from '../../transformers/base'
Expand Down Expand Up @@ -120,7 +120,7 @@ module.exports = {
protect('content'),
transformResponseDataItem(transformContentItem, inPublicApi),
redactResponseDataItem(contentItemRedactionPolicy, publicApiTranscriptRedactionCondition),
redactResponseDataItem(contentItemRedactionPolicyWebApp, webAppTranscriptRedactionCondition),
redactResponseDataItem(contentItemRedactionPolicyWebApp, webAppExploreRedactionCondition),
],
get: [],
create: [],
Expand Down
5 changes: 3 additions & 2 deletions src/services/text-reuse-clusters/text-reuse-clusters.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { NewspapersService } from '../newspapers/newspapers.class'
import { SimpleSolrClient } from '../../internalServices/simpleSolr'
import { getToSelect } from '../../util/solr/adapters'
import { MediaSources } from '../media-sources/media-sources.class'
import { OpenPermissions } from '../../util/bigint'

const { mapValues, groupBy, values, uniq, clone, get } = require('lodash')
const { NotFound } = require('@feathersjs/errors')
Expand Down Expand Up @@ -50,8 +51,8 @@ function buildResponseClusters(
({ id, text: textSample, permissionBitmapExplore, permissionsBitmapGetTranscript }) => ({
cluster: clustersById[id],
textSample,
bitmapExplore: BigInt(permissionBitmapExplore ?? Number.MAX_SAFE_INTEGER),
bitmapGetTranscript: BigInt(permissionsBitmapGetTranscript ?? Number.MAX_SAFE_INTEGER),
bitmapExplore: BigInt(permissionBitmapExplore ?? OpenPermissions),
bitmapGetTranscript: BigInt(permissionsBitmapGetTranscript ?? OpenPermissions),
})
)
return results
Expand Down
Loading

0 comments on commit d53c328

Please sign in to comment.