Skip to content

Commit ea97f3a

Browse files
authored
Merge pull request #56 from Rakshitha-D/testBMGS
#SBCOSS-584: Removed hardcoded bmgs
2 parents 9d884d4 + 147e941 commit ea97f3a

File tree

11 files changed

+147
-142
lines changed

11 files changed

+147
-142
lines changed

controllers/accessPaths/board.js

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const _ = require('lodash');
2+
const { channelRead, frameworkRead } = require('../../helpers/learnerHelper');
3+
const debug = require('debug')('dynamicCategoryManager');
4+
5+
class DynamicCategoryManager {
6+
async getCategoriesForChannel(channelId) {
7+
try {
8+
const channelReadResponse = await channelRead({ channelId });
9+
const frameworkName = _.get(channelReadResponse, 'data.result.channel.defaultFramework');
10+
if (!frameworkName) {
11+
throw new Error('Default framework missing');
12+
}
13+
const frameworkReadResponse = await frameworkRead({ frameworkId: frameworkName });
14+
const frameworkData = _.get(frameworkReadResponse, 'data.result.framework');
15+
const categories = _.map(frameworkData.categories, 'code');
16+
17+
return categories;
18+
} catch (error) {
19+
debug('Failed to fetch framework categories', error);
20+
return [];
21+
}
22+
}
23+
24+
}
25+
26+
module.exports = new DynamicCategoryManager();

controllers/accessPaths/gradeLevel.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

controllers/accessPaths/index.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const fs = require('fs');
22
const path = require('path');
33
const _ = require('lodash');
4+
const dynamicCategoryManager = require('./dynamicCategoryManager');
45

56
const CONSTANTS = require('../../resources/constants.json');
67
const { isUserAdmin } = require('../../helpers/userHelper');
@@ -34,7 +35,7 @@ const isCreatorOfReport = ({ user, report }) => _.get(report, 'createdby') === (
3435
* @description Validates a user context against the accesspath rules set for the report
3536
* @param {*} user
3637
*/
37-
const validateAccessPath = user => report => {
38+
const validateAccessPath = (user, req) => async report => {
3839
let { accesspath, type } = report;
3940

4041
if (type === CONSTANTS.REPORT_TYPE.PUBLIC) return true;
@@ -49,11 +50,37 @@ const validateAccessPath = user => report => {
4950
accesspath = accessPathForPrivateReports({ user });
5051
}
5152

53+
const channelId = req.get('x-channel-id') ||
54+
req.get('X-CHANNEL-ID') ||
55+
_.get(user, 'rootOrg.hashTagId') ||
56+
_.get(user, 'channel');
57+
if (!channelId) {
58+
const error = new Error('Channel ID is required');
59+
error.statusCode = 400;
60+
error.errorObject = { code: 'MISSING_CHANNEL_ID' };
61+
throw error;
62+
}
63+
const dynamicCategories = await dynamicCategoryManager.getCategoriesForChannel(channelId);
64+
5265
for (let [key, value] of Object.entries(accesspath)) {
53-
if (!rules.has(key)) return false;
54-
const validator = rules.get(key);
55-
const success = validator(user, value);
56-
if (!success) return false;
66+
if (rules.has(key)) {
67+
const validator = rules.get(key);
68+
const success = validator(user, value);
69+
70+
if (!success && dynamicCategories.includes(key)) {
71+
const normalizedValue = Array.isArray(value) ? value : [value];
72+
const userValues = _.get(user, `framework.${key}`, []);
73+
const normalizedUserValues = Array.isArray(userValues) ? userValues : [userValues];
74+
75+
const hasMatch = normalizedValue.some(val =>
76+
normalizedUserValues.some(uv =>
77+
String(uv).toLowerCase() === String(val).toLowerCase()
78+
)
79+
);
80+
81+
if (!hasMatch) return false;
82+
}
83+
}
5784
}
5885

5986
return true;

controllers/accessPaths/medium.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

controllers/accessPaths/subject.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

controllers/parameters/$board.js

Lines changed: 0 additions & 26 deletions
This file was deleted.

controllers/parameters/index.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const { getSharedAccessSignature } = require('../../helpers/azure-storage');
88
const { envVariables } = require('../../helpers/envHelpers');
99
const { isUserSuperAdmin } = require('../../helpers/userHelper');
1010
const CONSTANTS = require('../../resources/constants.json');
11+
const { channelRead, frameworkRead } = require('../../helpers/learnerHelper');
1112

1213
/*
1314
@@ -204,4 +205,57 @@ const getDatasets = async ({ document, user, req }) => {
204205
return Promise.all(dataSources.map(dataSource => getDataset({ dataSource, user, req })));
205206
};
206207

207-
module.exports = { populateReportsWithParameters, getDatasets, reportParameters: parameters, isReportParameterized };
208+
const setFrameworkCategoryParameters = async (req, user) => {
209+
const channelId =
210+
req.get('x-channel-id') ||
211+
req.get('X-CHANNEL-ID') ||
212+
_.get(user, 'rootOrg.hashTagId') ||
213+
_.get(user, 'channel');
214+
215+
if (!channelId) {
216+
const error = new Error('Channel ID is required');
217+
error.statusCode = 400;
218+
error.errorObject = { code: 'MISSING_CHANNEL_ID' };
219+
throw error;
220+
}
221+
222+
try {
223+
const channelReadResponse = await channelRead({ channelId });
224+
const frameworkName = _.get(channelReadResponse, 'data.result.channel.defaultFramework');
225+
if (!frameworkName) {
226+
const error = new Error('Default framework not found for the channel');
227+
error.statusCode = 404;
228+
error.errorObject = { code: 'MISSING_DEFAULT_FRAMEWORK' };
229+
throw error;
230+
}
231+
232+
const frameworkReadResponse = await frameworkRead({ frameworkId: frameworkName });
233+
const frameworkData = _.get(frameworkReadResponse, 'data.result.framework');
234+
const frameworkCategories = _.map(frameworkData.categories, 'code');
235+
236+
frameworkCategories.forEach(category => {
237+
parameters[`$${category}`] = {
238+
name: `$${category}`,
239+
value: (user) => _.get(user, `framework.${category}`),
240+
cache: false,
241+
masterData: () => {
242+
const categoryData = _.find(frameworkData.categories, ['code', category]);
243+
return _.map(_.get(categoryData, 'terms', []), 'name');
244+
}
245+
};
246+
});
247+
248+
Object.values(parameters).forEach(param => {
249+
});
250+
} catch (error) {
251+
debug(`Failed to set framework category parameters for channel ${channelId}`, error);
252+
}
253+
};
254+
255+
module.exports = {
256+
populateReportsWithParameters,
257+
getDatasets,
258+
reportParameters: parameters,
259+
isReportParameterized,
260+
setFrameworkCategoryParameters
261+
};

controllers/report.js

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { report, report_status, report_summary } = require('../models');
88
const CONSTANTS = require('../resources/constants.json');
99
const { formatApiResponse } = require('../helpers/responseFormatter');
1010
const { validateAccessPath, matchAccessPath, accessPathForPrivateReports, isCreatorOfReport, roleBasedAccess } = require('./accessPaths');
11-
const { getDatasets, isReportParameterized, populateReportsWithParameters } = require('./parameters');
11+
const { getDatasets, isReportParameterized, populateReportsWithParameters, setFrameworkCategoryParameters } = require('./parameters');
1212
const { fetchAndFormatExhaustDataset } = require('../helpers/dataServiceHelper');
1313

1414
// checks by reportid if the report exists in our database or not
@@ -38,6 +38,7 @@ const search = async (req, res, next) => {
3838
});
3939

4040
const userDetails = req.userDetails;
41+
await setFrameworkCategoryParameters(req, userDetails);
4142
const documents = populateReportsWithParameters(rows, userDetails);
4243

4344
//is accesspath is provided as search filter create a closure to filter reports
@@ -61,24 +62,40 @@ const search = async (req, res, next) => {
6162
2- is user report admin or not.
6263
3 - check access path for private and protected reports.
6364
*/
64-
filteredReports = _.filter(documents, row => {
65+
// Process all documents in parallel to check access
66+
const reportAccessChecks = await Promise.all(documents.map(async row => {
6567
const isCreator = isCreatorOfReport({ user: userDetails, report: row });
66-
if (isCreator) return true;
68+
if (isCreator) return { row, hasAccess: true };
6769

68-
if (!roleBasedAccess({ report: row, user: userDetails })) return false;
70+
if (!roleBasedAccess({ report: row, user: userDetails })) {
71+
return { row, hasAccess: false };
72+
}
6973

7074
if (accessPathMatchClosure) {
7175
const isMatched = accessPathMatchClosure(row);
72-
if (!isMatched) return false;
76+
if (!isMatched) return { row, hasAccess: false };
7377
}
7478

7579
const { type } = row;
76-
if (!type) return false;
77-
if (type === CONSTANTS.REPORT_TYPE.PUBLIC) return true;
78-
if ((type === CONSTANTS.REPORT_TYPE.PRIVATE) || (type === CONSTANTS.REPORT_TYPE.PROTECTED)) {
79-
return validateAccessPath(userDetails)(row);
80+
if (!type) return { row, hasAccess: false };
81+
if (type === CONSTANTS.REPORT_TYPE.PUBLIC) return { row, hasAccess: true };
82+
83+
if (type === CONSTANTS.REPORT_TYPE.PRIVATE || type === CONSTANTS.REPORT_TYPE.PROTECTED) {
84+
try {
85+
const hasAccess = await validateAccessPath(userDetails, req)(row);
86+
return { row, hasAccess };
87+
} catch (error) {
88+
debug('Error validating access path:', error);
89+
return { row, hasAccess: false };
90+
}
8091
}
81-
});
92+
93+
return { row, hasAccess: false };
94+
}));
95+
96+
filteredReports = reportAccessChecks
97+
.filter(({ hasAccess }) => hasAccess)
98+
.map(({ row }) => row);
8299
}
83100
return res.status(200).json(formatApiResponse({ id: req.id, result: { reports: filteredReports, count: filteredReports.length } }));
84101
} catch (error) {
@@ -229,6 +246,7 @@ const read = async (req, res, next) => {
229246
const userDetails = req.userDetails;
230247
let document;
231248
if (!hash) {
249+
await setFrameworkCategoryParameters(req, userDetails);
232250
[document] = populateReportsWithParameters([rawDocument], userDetails);
233251
if (!document) return next(createError(401, CONSTANTS.MESSAGES.FORBIDDEN));
234252
} else {
@@ -246,7 +264,7 @@ const read = async (req, res, next) => {
246264
}
247265

248266
if ((type === CONSTANTS.REPORT_TYPE.PROTECTED) || (type === CONSTANTS.REPORT_TYPE.PRIVATE)) {
249-
const isAuthorized = validateAccessPath(userDetails)(document);
267+
const isAuthorized = await validateAccessPath(userDetails, req)(document);
250268
if (!isAuthorized) {
251269
return next(createError(401, CONSTANTS.MESSAGES.FORBIDDEN));
252270
}
@@ -528,6 +546,7 @@ const readWithDatasets = async (req, res, next) => {
528546
const user = req.userDetails;
529547
let document;
530548
if (!hash) {
549+
await setFrameworkCategoryParameters(req, userDetails);
531550
[document] = populateReportsWithParameters([rawDocument], user);
532551
if (!document) return next(createError(401, CONSTANTS.MESSAGES.FORBIDDEN));
533552
} else {
@@ -545,7 +564,7 @@ const readWithDatasets = async (req, res, next) => {
545564
}
546565

547566
if ((document.type === CONSTANTS.REPORT_TYPE.PRIVATE) || (document.type === CONSTANTS.REPORT_TYPE.PROTECTED)) {
548-
const isAuthorized = validateAccessPath(user)(document);
567+
const isAuthorized = await validateAccessPath(user, req)(document);
549568
if (!isAuthorized) {
550569
return next(createError(401, CONSTANTS.MESSAGES.FORBIDDEN));
551570
}

0 commit comments

Comments
 (0)