From e164bf7dfa658aceb57fe5d8167888497cb92f50 Mon Sep 17 00:00:00 2001 From: gavin hemsada <69586429+GavinHemsada@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:49:44 +0530 Subject: [PATCH 1/6] fix-publisher routes are not blocked for read only user --- .../components/Scopes/Create/CreateScope.jsx | 33 +++++++++++ .../src/app/components/Scopes/EditScope.jsx | 30 ++++++++++ .../webapp/source/src/app/data/AuthManager.js | 58 +++++++++++-------- 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Scopes/Create/CreateScope.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Scopes/Create/CreateScope.jsx index 01d18176784..ad002e8d688 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Scopes/Create/CreateScope.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Scopes/Create/CreateScope.jsx @@ -39,6 +39,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import Alert from 'AppComponents/Shared/Alert'; import API from 'AppData/api'; import { isRestricted } from 'AppData/AuthManager'; +import AuthorizedError from 'AppComponents/Base/Errors/AuthorizedError'; const PREFIX = 'CreateScope'; @@ -496,6 +497,35 @@ class CreateScope extends React.Component { }); } + /** + * Get allowed scopes based on API type + * @returns {string[]} Array of allowed scopes + */ + getAllowedScopes() { + const { api } = this.props; + if (api.apiType && api.apiType.toUpperCase() === 'MCP') { + return [ + 'apim:mcp_server_create', + 'apim:mcp_server_manage', + 'apim:mcp_server_publish', + ]; + } else { + return ['apim:api_create']; + } + } + + /** + * Check if the action is restricted + * @returns {boolean} True if the action is restricted, false otherwise + */ + isAccessRestricted() { + const { api } = this.props; + if (isRestricted(['apim:api_create'], api)) { + return true; + } + return isRestricted(this.getAllowedScopes(), api); + } + /** * * @@ -503,6 +533,9 @@ class CreateScope extends React.Component { * @memberof CreateScope */ render() { + if (this.isAccessRestricted()) { + return ; + } const url = '/scopes'; const { roleValidity, validRoles, invalidRoles, scopeAddDisabled, valid, sharedScope, diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Scopes/EditScope.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Scopes/EditScope.jsx index 51b68170a97..9e25d38bad7 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Scopes/EditScope.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Scopes/EditScope.jsx @@ -39,6 +39,7 @@ import InputAdornment from '@mui/material/InputAdornment'; import { isRestricted } from 'AppData/AuthManager'; import Error from '@mui/material/SvgIcon'; import API from 'AppData/api'; +import AuthorizedError from 'AppComponents/Base/Errors/AuthorizedError'; const PREFIX = 'EditScope'; @@ -404,6 +405,32 @@ class EditScope extends React.Component { return valid[id].invalid; } + /** + * Get allowed scopes based on API type + * @returns {string[]} Array of allowed scopes + */ + getAllowedScopes() { + const { api } = this.props; + if (api.apiType && api.apiType.toUpperCase() === 'MCP') { + return [ + 'apim:mcp_server_create', + 'apim:mcp_server_manage', + 'apim:mcp_server_publish', + ]; + } else { + return ['apim:api_create']; + } + } + + /** + * Check if the action is restricted + * @returns {boolean} True if the action is restricted, false otherwise + */ + isAccessRestricted() { + const { api } = this.props; + return isRestricted(this.getAllowedScopes(), api); + } + /** * * @@ -411,6 +438,9 @@ class EditScope extends React.Component { * @memberof EditScope */ render() { + if (this.isAccessRestricted()) { + return ; + } const { sharedScope, roleValidity, validRoles, invalidRoles, valid, } = this.state; diff --git a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js index 271a978ae5b..a77c906a54c 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js @@ -204,33 +204,43 @@ class AuthManager { * @param {*} api */ static isRestricted(scopesAllowedToEdit, api = {}) { - // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. + const user = AuthManager.getUser(); + + // Block read-only users right away + if (AuthManager.isReadOnlyUser()) { + return true; + } + + // If user doesn't have any of the required scopes → restrict + const hasAllowedScope = + user && scopesAllowedToEdit.some(scope => user.scopes.includes(scope)); + if (!hasAllowedScope) { + return true; + } + + // If API Product and user has publish scope → allow if (api.apiType === 'APIPRODUCT') { - if (AuthManager.getUser().scopes.includes('apim:api_publish')) { - return false; - } else { - return true; - } + return !user.scopes.includes('apim:api_publish'); } - // determines whether the user is a publisher or creator (based on what is passed from the element) - // if (scopesAllowedToEdit.filter(element => AuthManager.getUser().scopes.includes(element)).length > 0) { - if (AuthManager.getUser() - && scopesAllowedToEdit.find((element) => AuthManager.getUser().scopes.includes(element))) { - // if the user has publisher role, no need to consider the api LifeCycleStatus - const isPublisherOverride = AuthManager.getUser().scopes.includes('apim:api_publish') - || (api.apiType === 'MCP' && AuthManager.getUser().scopes.includes('apim:mcp_server_publish')); - if ((Object.keys(api).length === 0 && api.constructor === Object) || isPublisherOverride) { - return false; - } else if ( - // if the user has creator role, but not the publisher role - api.lifeCycleStatus === 'CREATED' - || api.lifeCycleStatus === 'PROTOTYPED' - ) { - return false; - } else { - return true; - } + // Check for publisher override (publisher can always access) + const isPublisherOverride = + user.scopes.includes('apim:api_publish') || + (api.apiType === 'MCP' && user.scopes.includes('apim:mcp_server_publish')); + + if (isPublisherOverride) { + return false; // unrestricted + } + + // Handle create-page case (empty api object) + if (Object.keys(api).length === 0 && api.constructor === Object) { + // Only allow users with create permission on create pages + return !user.scopes.includes('apim:api_create'); + } + + // Allow creator access based on lifecycle status + if (api.lifeCycleStatus === 'CREATED' || api.lifeCycleStatus === 'PROTOTYPED') { + return false; } return true; } From edfd06d19dc4daf8dc638518fe8aa8c9b028406e Mon Sep 17 00:00:00 2001 From: gavin hemsada <69586429+GavinHemsada@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:53:41 +0530 Subject: [PATCH 2/6] fix-scopes create not found error --- .../webapp/source/src/app/data/AuthManager.js | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js index 271a978ae5b..6309a58eb63 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js @@ -204,33 +204,43 @@ class AuthManager { * @param {*} api */ static isRestricted(scopesAllowedToEdit, api = {}) { - // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. + const user = AuthManager.getUser(); + + // Block read-only users right away + if (AuthManager.isReadOnlyUser()) { + return true; + } + + // If user doesn't have any of the required scopes → restrict + const hasAllowedScope = + user && scopesAllowedToEdit.some(scope => user.scopes.includes(scope)); + if (!hasAllowedScope) { + return true; + } + + // If API Product and user has publish scope → allow if (api.apiType === 'APIPRODUCT') { - if (AuthManager.getUser().scopes.includes('apim:api_publish')) { - return false; - } else { - return true; - } + return !user.scopes.includes('apim:api_publish'); } - // determines whether the user is a publisher or creator (based on what is passed from the element) - // if (scopesAllowedToEdit.filter(element => AuthManager.getUser().scopes.includes(element)).length > 0) { - if (AuthManager.getUser() - && scopesAllowedToEdit.find((element) => AuthManager.getUser().scopes.includes(element))) { - // if the user has publisher role, no need to consider the api LifeCycleStatus - const isPublisherOverride = AuthManager.getUser().scopes.includes('apim:api_publish') - || (api.apiType === 'MCP' && AuthManager.getUser().scopes.includes('apim:mcp_server_publish')); - if ((Object.keys(api).length === 0 && api.constructor === Object) || isPublisherOverride) { - return false; - } else if ( - // if the user has creator role, but not the publisher role - api.lifeCycleStatus === 'CREATED' - || api.lifeCycleStatus === 'PROTOTYPED' - ) { - return false; - } else { - return true; - } + // Check for publisher override (publisher can always access) + const isPublisherOverride = + user.scopes.includes('apim:api_publish') || + (api.apiType === 'MCP' && user.scopes.includes('apim:mcp_server_publish')); + + if (isPublisherOverride) { + return false; // unrestricted + } + + // Handle create-page case (empty api object) + if (Object.keys(api).length === 0 && api.constructor === Object) { + // Only allow users with create permission on create pages + return !user.scopes.includes('apim:api_create'); + } + + // Allow creator access based on lifecycle status + if (api.lifeCycleStatus === 'CREATED' || api.lifeCycleStatus === 'PROTOTYPED') { + return false; } return true; } From 68b6de6869d263dea8c4292ccd5d56659edcfe59 Mon Sep 17 00:00:00 2001 From: gavin hemsada <69586429+GavinHemsada@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:27:13 +0530 Subject: [PATCH 3/6] fix-user object not initialised --- .../main/webapp/source/src/app/data/AuthManager.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js index eb6df23d2b8..8fb5b226e2c 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js @@ -204,6 +204,19 @@ class AuthManager { * @param {*} api */ static isRestricted(scopesAllowedToEdit, api = {}) { + const user = AuthManager.getUser(); + + // Block read-only users right away + if (AuthManager.isReadOnlyUser()) { + return true; + } + + // If user doesn't have any of the required scopes → restrict + const hasAllowedScope = + user && scopesAllowedToEdit.some(scope => user.scopes.includes(scope)); + if (!hasAllowedScope) { + return true; + } // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. if (api.apiType === 'APIPRODUCT') { return !user.scopes.includes('apim:api_publish'); From a5c6647cda70214fa3fac7938d9cf1333c568b42 Mon Sep 17 00:00:00 2001 From: gavin hemsada <69586429+GavinHemsada@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:18:26 +0530 Subject: [PATCH 4/6] fix: handle null user object and allow publishers to delete their API Products --- .../webapp/source/src/app/data/AuthManager.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js index 8fb5b226e2c..d17d347193d 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js @@ -206,22 +206,23 @@ class AuthManager { static isRestricted(scopesAllowedToEdit, api = {}) { const user = AuthManager.getUser(); + if (!user) { + return true; + } // Block read-only users right away if (AuthManager.isReadOnlyUser()) { return true; } - + // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. + if (api.apiType === 'APIPRODUCT') { + return !user.scopes.includes('apim:api_publish'); + } // If user doesn't have any of the required scopes → restrict const hasAllowedScope = - user && scopesAllowedToEdit.some(scope => user.scopes.includes(scope)); + scopesAllowedToEdit.some(scope => user.scopes.includes(scope)); if (!hasAllowedScope) { return true; } - // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. - if (api.apiType === 'APIPRODUCT') { - return !user.scopes.includes('apim:api_publish'); - } - // Check for publisher override (publisher can always access) const isPublisherOverride = user.scopes.includes('apim:api_publish') || From c8b51d986a62fbaaac2ac925991571285616a3cf Mon Sep 17 00:00:00 2001 From: gavin hemsada <69586429+GavinHemsada@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:43:36 +0530 Subject: [PATCH 5/6] fix-pair this if statement and adding JSDoc comments to the isRestricted method --- .../webapp/source/src/app/data/AuthManager.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js index d17d347193d..1e971d56ba6 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js @@ -199,18 +199,19 @@ class AuthManager { } /** - * - * @param {*} scopesAllowedToEdit - * @param {*} api + * Determines if a user is restricted from editing an API based on their scopes and the API's state. + * + * @param {string[]} scopesAllowedToEdit - Array of scope strings that are allowed to edit + * @param {Object} api - The API object containing apiType, lifeCycleStatus, etc. + * @param {string} [api.apiType] - Type of API (e.g., 'APIPRODUCT', 'MCP') + * @param {string} [api.lifeCycleStatus] - Lifecycle status (e.g., 'CREATED', 'PROTOTYPED') + * @returns {boolean} true if user is restricted, false if unrestricted */ static isRestricted(scopesAllowedToEdit, api = {}) { const user = AuthManager.getUser(); - if (!user) { - return true; - } - // Block read-only users right away - if (AuthManager.isReadOnlyUser()) { + // Block if no user or read-only user + if (!user || AuthManager.isReadOnlyUser()) { return true; } // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. From 62d9caf50dcccec174ca52a45dccc75b15344b32 Mon Sep 17 00:00:00 2001 From: gavin hemsada <69586429+GavinHemsada@users.noreply.github.com> Date: Sun, 9 Nov 2025 12:05:10 +0530 Subject: [PATCH 6/6] fix error. --- .../webapp/source/src/app/data/AuthManager.js | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js index 1e971d56ba6..6572330e073 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/AuthManager.js @@ -208,40 +208,37 @@ class AuthManager { * @returns {boolean} true if user is restricted, false if unrestricted */ static isRestricted(scopesAllowedToEdit, api = {}) { - const user = AuthManager.getUser(); - - // Block if no user or read-only user - if (!user || AuthManager.isReadOnlyUser()) { + // Block read-only users from any API modification operations + if(AuthManager.getUser() && AuthManager.isReadOnlyUser()){ return true; } // determines whether the apiType is API PRODUCT and user has publisher role, then allow access. if (api.apiType === 'APIPRODUCT') { - return !user.scopes.includes('apim:api_publish'); - } - // If user doesn't have any of the required scopes → restrict - const hasAllowedScope = - scopesAllowedToEdit.some(scope => user.scopes.includes(scope)); - if (!hasAllowedScope) { - return true; - } - // Check for publisher override (publisher can always access) - const isPublisherOverride = - user.scopes.includes('apim:api_publish') || - (api.apiType === 'MCP' && user.scopes.includes('apim:mcp_server_publish')); - - if (isPublisherOverride) { - return false; // unrestricted - } - - // Handle create-page case (empty api object) - if (Object.keys(api).length === 0 && api.constructor === Object) { - // Only allow users with create permission on create pages - return !user.scopes.includes('apim:api_create'); + if (AuthManager.getUser().scopes.includes('apim:api_publish')) { + return false; + } else { + return true; + } } - // Allow creator access based on lifecycle status - if (api.lifeCycleStatus === 'CREATED' || api.lifeCycleStatus === 'PROTOTYPED') { - return false; + // determines whether the user is a publisher or creator (based on what is passed from the element) + // if (scopesAllowedToEdit.filter(element => AuthManager.getUser().scopes.includes(element)).length > 0) { + if (AuthManager.getUser() + && scopesAllowedToEdit.find((element) => AuthManager.getUser().scopes.includes(element))) { + // if the user has publisher role, no need to consider the api LifeCycleStatus + const isPublisherOverride = AuthManager.getUser().scopes.includes('apim:api_publish') + || (api.apiType === 'MCP' && AuthManager.getUser().scopes.includes('apim:mcp_server_publish')); + if ((Object.keys(api).length === 0 && api.constructor === Object) || isPublisherOverride) { + return false; + } else if ( + // if the user has creator role, but not the publisher role + api.lifeCycleStatus === 'CREATED' + || api.lifeCycleStatus === 'PROTOTYPED' + ) { + return false; + } else { + return true; + } } return true; }