Skip to content

Commit

Permalink
Merge branch 'minor' into minor
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderGeere authored Nov 12, 2024
2 parents 5113539 + 9b7ff94 commit 6cbe9ab
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 38 deletions.
2 changes: 1 addition & 1 deletion lib/mapp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ self.mapp = {

version: '4.13.0-alpha',

hash: '52558fa8bbd8752e4f27036c7ca95a7f3426a205',
hash: '2e1f3b08ffcd0706a030c06a61dd0d15c673dfbc',

host: document.head?.dataset?.dir || '',

Expand Down
4 changes: 3 additions & 1 deletion lib/ui/elements/helpDialog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export default (params) => {
top: '3em',
left: '6em',
contained: true,
close: true,
closeBtn: true,
minimizeBtn: true,
headerDrag: true,
...params
}

Expand Down
7 changes: 5 additions & 2 deletions mod/provider/cloudfront.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,17 @@ module.exports = async function cloudfront(ref) {
}

const response = await fetch(signedURL)

logger(`${response.status} - ${url}`, 'cloudfront')

if (response.status >= 300) return new Error(`${response.status} ${ref}`)

if (url.match(/\.json$/i)) return await response.json()

if (ref.params?.buffer) return await response.buffer()
if (ref.params?.buffer) {
const arrayBuffer = await response.arrayBuffer()
return Buffer.from(arrayBuffer)
}

return await response.text()

Expand Down
73 changes: 66 additions & 7 deletions mod/utils/roles.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/**
@module /utils/roles
*/
* Roles utility module for handling role-based access control and object merging
* @module /utils/roles
*/

/**
* @global
* @typedef {Object} roles
* @property {Object} roles - roles configuration object
* @property {boolean} [roles.*] - Wildcard role indicating unrestricted access
* @property {Object} [roles.key] - Role-specific properties to merge
* @property {Object} [roles.'!key'] - Negated role properties (applied when user doesn't have the role)
*/

const merge = require('./merge')

Expand All @@ -9,6 +19,25 @@ module.exports = {
objMerge
}

/**
* Checks if an object should be accessible based on user roles
* @param {Object} obj - The object to check access for
* @param {roles} obj.roles - Role configuration object
* @param {Array<string>} user_roles - Array of roles assigned to the user
* @returns {(Object|boolean)} Returns the original object if access is granted, false otherwise
*
* @example
* // Object with unrestricted access
* check({ roles: { '*': true }, data: 'content' }, ['user']) // returns object
*
* // Object with role restriction
* check({ roles: { admin: true }, data: 'content' }, ['user']) // returns false
* check({ roles: { admin: true }, data: 'content' }, ['admin']) // returns object
*
* // Object with negated roles
* check({ roles: { '!guest': true }, data: 'content' }, ['guest']) // returns false
* check({ roles: { '!guest': true }, data: 'content' }, ['user']) // returns object
*/
function check(obj, user_roles) {

// The object to check has no roles assigned.
Expand Down Expand Up @@ -44,6 +73,38 @@ function check(obj, user_roles) {
return false;
}

/**
* Recursively merges role-specific object properties based on user roles
* @param {Object} obj - The object to process
* @param {roles} obj.roles - Role configuration object
* @param {Array<string>} user_roles - Array of roles assigned to the user
* @returns {Object} Processed object with merged role-specific properties
*
* @example
* const obj = {
* name: 'layer',
* roles: {
* admin: { secretField: 'sensitive' },
* user: { publicField: 'visible' }
* }
* };
*
* // With admin role
* objMerge(obj, ['admin']);
* // Returns: { name: 'layer', secretField: 'sensitive', roles: {...} }
*
* // With user role
* objMerge(obj, ['user']);
* // Returns: { name: 'layer', publicField: 'visible', roles: {...} }
*
* @description
* The function handles several special cases:
* - Recursively processes nested objects
* - Handles arrays by mapping over their elements
* - Processes negated roles (prefixed with '!')
* - Preserves the original object if conditions aren't met
* - Skip null or undefined values
*/
function objMerge(obj, user_roles) {

if (typeof obj !== 'object') return obj;
Expand Down Expand Up @@ -77,8 +138,8 @@ function objMerge(obj, user_roles) {

function notIncludesNegatedRole(role, user_roles) {

return role.match(/(?<=^!)(.*)/g)?.[0]?
!user_roles.includes(role.match(/(?<=^!)(.*)/g)?.[0]):
return role.match(/(?<=^!)(.*)/g)?.[0] ?
!user_roles.includes(role.match(/(?<=^!)(.*)/g)?.[0]) :
false
}

Expand All @@ -92,7 +153,5 @@ function objMerge(obj, user_roles) {
merge(clone, clone.roles[role])
})

delete clone.roles

return clone
}
}
43 changes: 40 additions & 3 deletions mod/workspace/_workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ async function locale(req, res) {
.filter(layer => !(layer instanceof Error))
})

return res.json(locale)
return res.json(removeRoles(locale))
}

// Check layer access.
Expand All @@ -209,7 +209,7 @@ async function locale(req, res) {
.filter(layer => !!Roles.check(layer[1], req.params.user?.roles))
.map(layer => layer[0])

res.json(locale)
res.json(removeRoles(locale))
}

/**
Expand Down Expand Up @@ -341,7 +341,6 @@ async function test(req, res) {

// If the locale has no layers, just skip it.
if (!locale.layers) continue;


for (const layerKey of Object.keys(locale.layers)) {

Expand Down Expand Up @@ -451,3 +450,41 @@ function templateUse(obj, test) {
}
})
}

/**
* @function removeRoles
* @description
* Recursively removes all 'roles' objects from the provided locale.
* This function is designed to sanitize locale configuration objects before sending to the client,
* ensuring that role-based permissions data is not exposed.
* @param {object} obj
* @returns {object}
*/
function removeRoles(obj) {

// If param is not an object or is null, return as is
if (typeof obj !== 'object' || obj === null) {
return obj;
}

// If object is an array, process each element
if (Array.isArray(obj)) {
return obj.map(item => removeRoles(item));
}

// Create a new object to store cleaned properties
const cleanedObj = {};

// Process each property in the object
for (const [key, value] of Object.entries(obj)) {
// Skip 'roles' properties
if (key === 'roles') {
continue;
}

// Recursively clean nested objects
cleanedObj[key] = removeRoles(value);
}

return cleanedObj;
}
152 changes: 152 additions & 0 deletions tests/assets/layers/roles/layer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"group": "layer",
"name": "mvt_test",
"format": "mvt",
"table": "test.mvt_test",
"geom": "geom_3857",
"srid": "3857",
"qID": "id",
"infoj": [
{
"type": "pin",
"label": "ST_PointOnSurface",
"field": "pin",
"fieldfx": "ARRAY[ST_X(ST_PointOnSurface(geom_3857)),ST_Y(ST_PointOnSurface(geom_3857))]",
"roles": {
"infoj_role": "from the infoj"
}
}
],
"style": {
"roles": {
"style_role": "from the style"
},
"default": {
"strokeWidth": "0",
"fillColor": "#fff",
"fillOpacity": 0.4,
"strokeColor": null
},
"themes": {
"roles": {
"style_theme_role": "from a styles theme"
},
"theme_1": {
"title": "theme_1",
"type": "categorized",
"field": "numeric_field",
"roles": {
"style_theme_1_role": "from the styles theme_1 theme"
},
"cat": {
"1": {
"label": "Lowest",
"style": {
"fillColor": "#3193ED"
}
},
"2": {
"label": "-",
"style": {
"fillColor": "#5DC29A"
}
},
"3": {
"label": "-",
"style": {
"fillColor": "#8FE15A"
}
},
"4": {
"label": "-",
"style": {
"fillColor": "#D8D758"
}
},
"5": {
"label": "-",
"style": {
"fillColor": "#FFB956"
}
},
"6": {
"label": "-",
"style": {
"fillColor": "#FE8355"
}
},
"7": {
"label": "-",
"style": {
"fillColor": "#FA5652"
}
},
"8": {
"label": "Highest",
"style": {
"fillColor": "#F0304D"
}
}
}
},
"theme_2": {
"title": "theme_2",
"type": "categorized",
"field": "numeric_field",
"roles": {
"A": null
},
"cat": {
"1": {
"label": "Lowest",
"style": {
"fillColor": "#6B2E94"
}
},
"2": {
"label": "-",
"style": {
"fillColor": "#8B44B8"
}
},
"3": {
"label": "-",
"style": {
"fillColor": "#9B6FCD"
}
},
"4": {
"label": "-",
"style": {
"fillColor": "#89A7D6"
}
},
"5": {
"label": "-",
"style": {
"fillColor": "#70C1C9"
}
},
"6": {
"label": "-",
"style": {
"fillColor": "#52C4A3"
}
},
"7": {
"label": "-",
"style": {
"fillColor": "#38B77C"
}
},
"8": {
"label": "Highest",
"style": {
"fillColor": "#1FA855"
}
}
}
}
}
}
}
5 changes: 5 additions & 0 deletions tests/assets/layers/roles/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"roles": {
"template_role": "from a template"
}
}
Loading

0 comments on commit 6cbe9ab

Please sign in to comment.