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;
}