Skip to content

Commit

Permalink
Fix permission for result list
Browse files Browse the repository at this point in the history
  • Loading branch information
ksuess committed Nov 15, 2021
1 parent a001b90 commit 16ae788
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 100 deletions.
10 changes: 10 additions & 0 deletions src/components/Blocks/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const SearchBlockSchema = {
fields: [
'elastic_search_api_url',
'elastic_search_api_index',
'backend_url',
'frontend_url',
'relocation',
'relocationcontext',
],
Expand All @@ -21,6 +23,14 @@ export const SearchBlockSchema = {
title: 'Elastic Search API Index',
default: 'esploneindex',
},
backend_url: {
title: 'Backend URL',
default: 'http://localhost:8080/Plone',
},
frontend_url: {
title: 'Frontend URL',
default: 'http://igib.example.com',
},
relocation: {
title: 'Relocation',
description:
Expand Down
76 changes: 24 additions & 52 deletions src/components/Searchkit/CustomESRequestSerializer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ import { extend, isEmpty } from 'lodash';
import { listFields, nestedFields } from './constants.js';

export class CustomESRequestSerializer {
constructor(config) {
this.reviewstatemapping = config.reviewstatemapping;
}
/**
* Convert Array of filters to Object of filters
* @param {Array} filters Array of filters
* @return {Object} Object of filters
* input: [
* [ 'type_agg', 'value1' ]
* [ 'type_agg', 'value2', [ 'subtype_agg', 'a value' ] ]
* ]
* output: {
* type_agg: ['value1', 'value2']
* subtype_agg: [ 'a value' ]
* }
*/
getFilters = (filters) => {
/**
* input: [
* [ 'type_agg', 'value1' ]
* [ 'type_agg', 'value2', [ 'subtype_agg', 'a value' ] ]
* ]
*/
const aggValueObj = {};

const getChildFilter = (filter) => {
Expand All @@ -29,13 +39,6 @@ export class CustomESRequestSerializer {
filters.forEach((filterObj) => {
getChildFilter(filterObj);
});

/**
* output: {
* type_agg: ['value1', 'value2']
* subtype_agg: [ 'a value' ]
* }
*/
return aggValueObj;
};

Expand All @@ -45,10 +48,11 @@ export class CustomESRequestSerializer {
*/
serialize = (stateQuery) => {
const { queryString, sortBy, sortOrder, page, size, filters } = stateQuery;
// console.debug('CustomESRequestSerializer queryString', queryString);
// console.debug('CustomESRequestSerializer sortBy', sortBy);
// console.debug('CustomESRequestSerializer filters', filters);
// console.debug('CustomESRequestSerializer ', stateQuery);

// TODO make allowed_content_types configurable
let allowed_content_types = ['Manual'];
// Check current users permissions
let allowed_review_states = this.reviewstatemapping['Manual'];

const bodyParams = {};

Expand Down Expand Up @@ -140,12 +144,13 @@ export class CustomESRequestSerializer {
let terms = [];
terms.push({
terms: {
portal_type: ['Manual'],
portal_type: allowed_content_types,
},
});
// TODO check current user for review_state he has access to
terms.push({
terms: {
review_state: ['internally_published', 'private', 'draft'],
review_state: allowed_review_states,
},
});

Expand All @@ -154,7 +159,6 @@ export class CustomESRequestSerializer {
// ES needs the field name as field, get the field name from the aggregation name
const aggValueObj = this.getFilters(filters);
// convert to object
// console.debug('serialize: aggValueObj', aggValueObj);
const additionalterms = Object.keys(aggValueObj).reduce(
(accumulator, aggName) => {
const obj = {};
Expand All @@ -167,10 +171,8 @@ export class CustomESRequestSerializer {
},
[],
);
// console.debug('serialize: additionalterms', additionalterms);
terms = terms.concat(additionalterms);

// console.debug('aggValueObj', aggValueObj);
filter = Object.keys(aggValueObj).reduce((accumulator, aggName) => {
const obj = {};
const fieldName = aggFieldsMapping[aggName];
Expand Down Expand Up @@ -201,11 +203,9 @@ export class CustomESRequestSerializer {
// listFields
const post_filter = { bool: { must: terms } };
// nestedFields
// console.debug('filter', filter);
if (!isEmpty(filter)) {
post_filter['bool']['filter'] = filter;
}
// console.debug('post_filter', post_filter);
bodyParams['post_filter'] = post_filter;

/**
Expand All @@ -218,7 +218,6 @@ export class CustomESRequestSerializer {
// 1. aggregations of listFields
Object.keys(aggFieldsMapping).map((aggName) => {
const fieldName = aggFieldsMapping[aggName];
// console.debug('aggs', fieldName, listFields.includes(fieldName));
if (listFields.includes(fieldName)) {
const aggBucketTermsComponent = {
[aggName]: { terms: { field: fieldName } },
Expand All @@ -229,12 +228,9 @@ export class CustomESRequestSerializer {

// 2. aggregations of nestedFields
Object.keys(aggFieldsMapping).map((aggName) => {
// console.debug('nestedFields', nestedFields);
const myaggs = aggName.split('.');
const fieldName = aggFieldsMapping[aggName];
// console.debug('aggs', fieldName, nestedFields.includes(fieldName));
if (nestedFields.includes(fieldName)) {
// console.debug('fieldName in nestedFields:', fieldName, aggName);

const filter_debug = {
nested: {
Expand Down Expand Up @@ -268,26 +264,6 @@ export class CustomESRequestSerializer {
};
}

// const aggBucketTermsComponent_old = {
// [myaggs[0]]: {
// nested: {
// path: fieldName,
// },
// aggs: {
// [myaggs[1]]: {
// terms: {
// field: fieldName + '.token',
// order: { _key: 'asc' },
// },
// aggs: {
// somemoredatafromelasticsearch: {
// top_hits: { size: 1, _source: { include: [fieldName] } },
// },
// },
// },
// },
// },
// };
const aggBucketTermsComponent = {
[myaggs[0]]: {
aggs: {
Expand Down Expand Up @@ -325,10 +301,6 @@ export class CustomESRequestSerializer {
extend(bodyParams['aggs'], aggBucketTermsComponent);
}
});
// console.debug(
// 'CustomESRequestSerializer serialize returns bodyParams',
// bodyParams,
// );
return bodyParams;
};
}
Expand Down
56 changes: 49 additions & 7 deletions src/components/Searchkit/CustomESResponseSerializer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
// aggregations.organisationunit_agg.organisationunit_token.buckets[1].somemoredatafromelasticsearch.hits.hits[0]._source.title

import { forEach, has } from 'lodash';
import {
addAppURL,
isInternalURL,
flattenToAppURL,
} from '@plone/volto/helpers';

import config from '@plone/volto/registry';

function _pimpedAggregations(aggregations) {
let result = Object.assign({}, aggregations);
Expand All @@ -28,20 +35,55 @@ function _pimpedAggregations(aggregations) {
}

export class CustomESResponseSerializer {
constructor() {
constructor(config) {
this.serialize = this.serialize.bind(this);
this.backend_url = config.backend_url;
this.frontend_url = config.frontend_url;
this._getAllowedHits = this._getAllowedHits.bind(this);
}

_getAllowedHits(hits) {
// return promise with array ot Promises. all of them resolve to Object(hit,status)
let listOfPromises = hits.map((hit) => {
return new Promise((resolve, reject) => {
// fetch one
fetch(
hit['_source']['@id'].replace(this.backend_url, this.frontend_url),
)
.then((res) => {
resolve({
hit: hit,
status: res.status,
});
})
.catch((err) => {
console.error('error while fetching', err);
});
});
});
return Promise.all(listOfPromises);
}

/**
* Return a serialized version of the API backend response for the app state `results`.
* @param {object} payload the backend response payload
*/
serialize(payload) {

async serialize(payload) {
const { aggregations, hits } = payload;
return {
aggregations: _pimpedAggregations(aggregations) || {},
hits: hits.hits.map((hit) => hit._source),
total: hits.total.value,
};

let filtered_hits = await this._getAllowedHits(hits.hits);
filtered_hits = filtered_hits
.filter((hit_info) => {
return hit_info.status === 200;
})
.map((hit_info) => hit_info.hit);
return new Promise((resolve, reject) => {
resolve({
aggregations: _pimpedAggregations(aggregations) || {},
hits: filtered_hits.map((hit) => hit._source),
total: hits.total.value < 11 ? filtered_hits.length : hits.total.value,
});
});
}
}
56 changes: 35 additions & 21 deletions src/components/Searchkit/ESSearchApi.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
/*
* This file is part of React-SearchKit.
* Copyright (C) 2019 CERN.
*
* React-SearchKit is free software; you can redistribute it and/or modify it
* under the terms of the MIT License; see LICENSE file for more details.
*/

import _get from 'lodash/get';
import _hasIn from 'lodash/hasIn';
import axios from 'axios';
import { RequestCancelledError } from 'react-searchkit';
import { ESRequestSerializer } from './ESRequestSerializer';
import { ESResponseSerializer } from './ESResponseSerializer';

export class ESSearchApi {
export class PloneSearchApi {
constructor(config) {
// console.debug("ESSearchApi config", config);
this.axiosConfig = _get(config, 'axios', {});
this.validateAxiosConfig();
this.initSerializers(config);
this.initInterceptors(config);
this.initAxios();
this.search = this.search.bind(this);
this.axiosCancelToken = axios.CancelToken;
}

validateAxiosConfig() {
if (!_hasIn(this.axiosConfig, 'url')) {
throw new Error('ESSearchApi config: `node` field is required.');
throw new Error('PloneSearchApi config: `node` field is required.');
}
}

Expand All @@ -35,7 +28,6 @@ export class ESSearchApi {
}

initSerializers(config) {
// console.debug("initSerializers config", config);
const requestSerializerCls = _get(
config,
'es.requestSerializer',
Expand All @@ -47,8 +39,13 @@ export class ESSearchApi {
ESResponseSerializer,
);

this.requestSerializer = new requestSerializerCls();
this.responseSerializer = new responseSerializerCls();
this.requestSerializer = new requestSerializerCls({
reviewstatemapping: config.reviewstatemapping,
});
this.responseSerializer = new responseSerializerCls({
backend_url: config.backend_url,
frontend_url: config.frontend_url,
});
}

initAxios() {
Expand All @@ -60,13 +57,13 @@ export class ESSearchApi {
if (this.requestInterceptor) {
this.http.interceptors.request.use(
this.requestInterceptor.resolve,
this.requestInterceptor.reject
this.requestInterceptor.reject,
);
}
if (this.responseInterceptor) {
this.http.interceptors.request.use(
this.responseInterceptor.resolve,
this.responseInterceptor.reject
this.responseInterceptor.reject,
);
}
}
Expand All @@ -76,11 +73,28 @@ export class ESSearchApi {
* @param {string} stateQuery the `query` state with the user input
*/
async search(stateQuery) {
// cancel any previous request in case it is still happening
this.axiosCancel && this.axiosCancel.cancel();
// generate a new cancel token for this request
this.axiosCancel = this.axiosCancelToken.source();

const payload = this.requestSerializer.serialize(stateQuery);
const response = await this.http.request({
method: 'POST',
data: payload,
});
return this.responseSerializer.serialize(response.data);

try {
const response = await this.http.request({
method: 'POST',
data: payload,
});

// await backend permission check
let results = await this.responseSerializer.serialize(response.data);
return results;
} catch (error) {
if (axios.isCancel(error)) {
throw new RequestCancelledError();
} else {
throw error;
}
}
}
}
Loading

0 comments on commit 16ae788

Please sign in to comment.