From a0c05bf3554e5dd7eb69024f0945a0a29292124e Mon Sep 17 00:00:00 2001 From: DBGR18 Date: Mon, 29 Jun 2026 16:06:55 +0800 Subject: [PATCH 1/3] fix: require auth and tenant scoping on GetUEPDUSessionInfo --- backend/WebUI/api_webui.go | 55 +++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/backend/WebUI/api_webui.go b/backend/WebUI/api_webui.go index 527152e..1169e6b 100644 --- a/backend/WebUI/api_webui.go +++ b/backend/WebUI/api_webui.go @@ -1808,6 +1808,16 @@ func GetUEPDUSessionInfo(c *gin.Context) { return } + isAdmin := CheckAuth(c) + tenantId, err := GetTenantId(c) + if err != nil { + logger.ProcLog.Errorln(err.Error()) + c.JSON(http.StatusUnauthorized, gin.H{ + "cause": "Illegal or Nil token", + }) + return + } + // TODO: support fetching data from multiple SMF if smfUris := webuiSelf.GetOamUris(models.NrfNfManagementNfType_SMF); smfUris != nil { requestUri := fmt.Sprintf("%s/nsmf-oam/v1/ue-pdu-session-info/%s", smfUris[0], smContextRef) @@ -1843,7 +1853,12 @@ func GetUEPDUSessionInfo(c *gin.Context) { logger.ProcLog.Error(closeErr) } }() - sendResponseToClient(c, resp) + + if isAdmin { + sendResponseToClient(c, resp) + } else { + sendResponseToClientFilterTenantSingle(c, resp, tenantId) + } } else { c.JSON(http.StatusInternalServerError, gin.H{ "cause": "No SMF Found", @@ -1851,6 +1866,44 @@ func GetUEPDUSessionInfo(c *gin.Context) { } } +// sendResponseToClientFilterTenantSingle forwards a single-object OAM response +// (e.g. SMF ue-pdu-session-info) only when its Supi belongs to the caller's +// tenant, so a non-admin user cannot read another tenant's PDU session. +func sendResponseToClientFilterTenantSingle(c *gin.Context, response *http.Response, tenantId string) { + filterTenantIdOnly := bson.M{"tenantId": tenantId} + amDataList, err := mongoapi.RestfulAPIGetMany(amDataColl, filterTenantIdOnly) + if err != nil { + logger.ProcLog.Errorf("sendResponseToClientFilterTenantSingle err: %+v", err) + c.JSON(http.StatusInternalServerError, gin.H{}) + return + } + + tenantCheck := func(supi string) bool { + for _, amData := range amDataList { + if supi == amData["ueId"] { + return true + } + } + return false + } + + var jsonData map[string]interface{} + if err = json.NewDecoder(response.Body).Decode(&jsonData); err != nil { + logger.ProcLog.Errorf("sendResponseToClientFilterTenantSingle err: %+v", err) + c.JSON(http.StatusInternalServerError, gin.H{}) + return + } + + if supi, ok := jsonData["Supi"].(string); ok && tenantCheck(supi) { + c.JSON(response.StatusCode, jsonData) + return + } + + c.JSON(http.StatusForbidden, gin.H{ + "cause": "Subscriber does not belong to this tenant", + }) +} + func ChangePasswordInfo(c *gin.Context) { setCorsHeader(c) From 59dd117f7f5ec2ccae8b3e708b3f8f24de00c55e Mon Sep 17 00:00:00 2001 From: DBGR18 Date: Wed, 1 Jul 2026 15:17:23 +0800 Subject: [PATCH 2/3] fix: resolve err shadow lint error in GetUEPDUSessionInfo --- backend/WebUI/api_webui.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/WebUI/api_webui.go b/backend/WebUI/api_webui.go index 1169e6b..bfba21f 100644 --- a/backend/WebUI/api_webui.go +++ b/backend/WebUI/api_webui.go @@ -1830,7 +1830,8 @@ func GetUEPDUSessionInfo(c *gin.Context) { return } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestUri, nil) + var req *http.Request + req, err = http.NewRequestWithContext(ctx, http.MethodGet, requestUri, nil) if err != nil { logger.ProcLog.Error(err) c.JSON(http.StatusInternalServerError, gin.H{}) @@ -1842,7 +1843,8 @@ func GetUEPDUSessionInfo(c *gin.Context) { return } - resp, err := httpsClient.Do(req) + var resp *http.Response + resp, err = httpsClient.Do(req) if err != nil { logger.ProcLog.Error(err) c.JSON(http.StatusInternalServerError, gin.H{}) From ab94409f98a8c296f408182f552c4488bb586836 Mon Sep 17 00:00:00 2001 From: DBGR18 Date: Wed, 1 Jul 2026 15:29:36 +0800 Subject: [PATCH 3/3] fix: address PR review on sendResponseToClientFilterTenantSingle --- backend/WebUI/api_webui.go | 40 +++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/backend/WebUI/api_webui.go b/backend/WebUI/api_webui.go index bfba21f..0bdb651 100644 --- a/backend/WebUI/api_webui.go +++ b/backend/WebUI/api_webui.go @@ -1872,38 +1872,42 @@ func GetUEPDUSessionInfo(c *gin.Context) { // (e.g. SMF ue-pdu-session-info) only when its Supi belongs to the caller's // tenant, so a non-admin user cannot read another tenant's PDU session. func sendResponseToClientFilterTenantSingle(c *gin.Context, response *http.Response, tenantId string) { - filterTenantIdOnly := bson.M{"tenantId": tenantId} - amDataList, err := mongoapi.RestfulAPIGetMany(amDataColl, filterTenantIdOnly) - if err != nil { + // Preserve upstream error responses as-is; only apply tenant check on success. + if response.StatusCode < 200 || response.StatusCode >= 300 { + sendResponseToClient(c, response) + return + } + + var jsonData map[string]interface{} + if err := json.NewDecoder(response.Body).Decode(&jsonData); err != nil { logger.ProcLog.Errorf("sendResponseToClientFilterTenantSingle err: %+v", err) c.JSON(http.StatusInternalServerError, gin.H{}) return } - tenantCheck := func(supi string) bool { - for _, amData := range amDataList { - if supi == amData["ueId"] { - return true - } - } - return false + supi, ok := jsonData["Supi"].(string) + if !ok || supi == "" { + c.JSON(http.StatusForbidden, gin.H{ + "cause": "Subscriber does not belong to this tenant", + }) + return } - var jsonData map[string]interface{} - if err = json.NewDecoder(response.Body).Decode(&jsonData); err != nil { + // Point query instead of loading all tenant rows (avoids O(N) scan). + amData, err := mongoapi.RestfulAPIGetOne(amDataColl, bson.M{"tenantId": tenantId, "ueId": supi}) + if err != nil { logger.ProcLog.Errorf("sendResponseToClientFilterTenantSingle err: %+v", err) c.JSON(http.StatusInternalServerError, gin.H{}) return } - - if supi, ok := jsonData["Supi"].(string); ok && tenantCheck(supi) { - c.JSON(response.StatusCode, jsonData) + if len(amData) == 0 { + c.JSON(http.StatusForbidden, gin.H{ + "cause": "Subscriber does not belong to this tenant", + }) return } - c.JSON(http.StatusForbidden, gin.H{ - "cause": "Subscriber does not belong to this tenant", - }) + c.JSON(response.StatusCode, jsonData) } func ChangePasswordInfo(c *gin.Context) {