From 746fa5566ff863bc4f8e4a9d3bcdb315185d3d40 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Mon, 1 Dec 2025 23:16:56 +0530 Subject: [PATCH 1/6] feat(zpm): Default CORS and JWT configuration for WebApplications --- src/cls/IPM/Main.cls | 23 +++ .../IPM/ResourceProcessor/CSPApplication.cls | 20 +++ src/cls/IPM/Storage/ModuleTemplate.cls | 69 +++++++++ .../Test/PM/Integration/CorsAndJWTTest.cls | 69 +++++++++ .../_data/cors-rest-apps/module.xml | 28 ++++ .../src/cls/CorsTest/Rest/Cors.cls | 17 +++ .../Test/PM/Unit/WebApJWTConfigTest.cls | 142 ++++++++++++++++++ .../Test/PM/Unit/WebAppCorsTest.cls | 139 +++++++++++++++++ 8 files changed, 507 insertions(+) create mode 100644 tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls create mode 100644 tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml create mode 100644 tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/src/cls/CorsTest/Rest/Cors.cls create mode 100644 tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls create mode 100644 tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index a28b2ad2..067af81f 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -1272,6 +1272,7 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] do ##class(%Library.Prompt).GetString("Enter module keywords:", .tKeywords) + #dim tTemplate As %IPM.Storage.ModuleTemplate set tTemplate = ##class(%IPM.Storage.ModuleTemplate).NewTemplate(tPath, tName, tVersion, tDescription, tKeywords) return:'$isobject(tTemplate) @@ -1304,6 +1305,28 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] } do ##class(%Library.Prompt).GetString(" Enter a comma separated list of web applications or * for all:", .tWebAppList) +#If $$$CacheVersionMajor>=2025 + write !,"Cross-Origin Settings:" + do ##class(%Library.Prompt).GetYesNo("Configure Access-Control-Allow-Credentials?: ",.tEnableCors,1,3) + if tEnableCors { + do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Headers: ",.tAllowedHeaders,,32767,"comma seperated values e.g: Access-Control-Allow-Origin,Access-Control-Allow-Headers") + while (1){ + do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Origins: ",.tAllowedOrigins,,32767,"comma seperated values e.g: http://www.example.com") + if tAllowedOrigins="" quit + set sc = tTemplate.ValidateCorsOrigin(tAllowedOrigins) + if $$$ISERR(sc) { + do ..DisplayError(sc) + set tAllowedOrigins = "" + continue + } + quit + } + set tCors("enableCors") = tEnableCors + set tCors("allowedHeaders") = tAllowedHeaders + set tCors("allowedOrigins") = tAllowedOrigins + do tTemplate.SetCORSProps(tWebAppList,.tCors) + } +#EndIf do tTemplate.AddWebApps(tWebAppList,.tCSPapps) // tCSP - list of CSP (not REST apps) for i=1:1:$listlength(tCSPapps) { set tCSPPath = "" diff --git a/src/cls/IPM/ResourceProcessor/CSPApplication.cls b/src/cls/IPM/ResourceProcessor/CSPApplication.cls index b9f3d455..3b2561f7 100644 --- a/src/cls/IPM/ResourceProcessor/CSPApplication.cls +++ b/src/cls/IPM/ResourceProcessor/CSPApplication.cls @@ -17,6 +17,8 @@ Parameter ATTRIBUTES = {""_ // Not in all reasonably modern versions of %Installer.CSPApplication "DispatchClass,MatchRoles,"_ + //CORS properties have not yet been added to the %IPM.ResourceProcessor.CSPApplication applications, so they were included manually. + "CorsCredentialsAllowed,CorsHeadersList,CorsAllowlist,"_ // The rest of these are in all reasonably modern versions of %Installer.CSPApplication // From: Write ##class(%IPM.ResourceProcessor.CSPApplication).GetInheritedProperties() // "Grant" removed (since it's replaced by "MatchRoles") @@ -67,6 +69,15 @@ Property Directory As %String(ATTRIBUTEREQUIRED = 0, MAXLEN = 1024) [ Required ] /// PermittedClasses default Property PermittedClasses As %String(MAXLEN = 32767); +/// CorsCredentialsAllowed +Property CorsCredentialsAllowed As %Boolean [ InitialExpression = 0 ]; + +/// Required CORS headers (Access-Control-Allow-*) +Property CorsHeadersList As %String(MAXLEN = 32767); + +/// Allowed CORS origins (e.g., http://www.example.com) +Property CorsAllowlist As %String(MAXLEN = 32767); + Method %OnNew(pResourceReference As %IPM.Storage.ResourceReference) As %Status [ Private, ServerOnly = 1 ] { set packageName = pResourceReference.Module.Name @@ -275,6 +286,15 @@ Method CreateOrUpdateCSPApp(pVerbose As %Boolean = 0) As %Status [ Internal ] set tSpecial("KerberosAuthEnabled") = "" set tSpecial("Url") = "" + // Cors properties + set tMap("CorsAllowlist")="CorsAllowlist" + set tMap("CorsCredentialsAllowed")="CorsCredentialsAllowed" + set tMap("CorsHeadersList")="CorsHeadersList" + //jwt + set tMap("JWTAccessTokenTimeout")="JWTAccessTokenTimeout" + set tMap("JWTAuthEnabled")="JWTAuthEnabled" + set tMap("JWTRefreshTokenTimeout")="JWTRefreshTokenTimeout" + set tProperties("AutheEnabled") = (..PasswordAuthEnabled * $$$AutheCache) + (..UnauthenticatedEnabled * $$$AutheUnauthenticated) + (..DelegatedAuthEnabled * $$$AutheDelegated) + diff --git a/src/cls/IPM/Storage/ModuleTemplate.cls b/src/cls/IPM/Storage/ModuleTemplate.cls index db88ad1e..06da2827 100644 --- a/src/cls/IPM/Storage/ModuleTemplate.cls +++ b/src/cls/IPM/Storage/ModuleTemplate.cls @@ -254,6 +254,19 @@ Method AddWebApps( set tMap("ServeFilesTimeout") = "ServeFilesTimeout" set tMap("TwoFactorEnabled") = "TwoFactorEnabled" set tMap("TwoFactorEnabled","default") = 0 + // cors + // verify the Cors defined. this check used for "gen" command + // NOTE: These mappings are skipped if the properties do not exist,so they will not cause any conflicts. + set tMap("CorsAllowlist")="CorsAllowlist" + set tMap("CorsAllowlist","default") = $select($data(..TemplateResources(tAppName,"CorsCredentialsAllowed"),tcors):tcors,1:"CorsAllowlist") + set tMap("CorsCredentialsAllowed")="CorsCredentialsAllowed" + set tMap("CorsCredentialsAllowed","default") = $select($data(..TemplateResources(tAppName,"CorsCredentialsAllowed"),tCorsAllowed):tCorsAllowed,1:1) + set tMap("CorsHeadersList")="CorsHeadersList" + set tMap("CorsHeadersList","default") = $select($data(..TemplateResources(tAppName,"CorsHeadersList"),tCorsHeaders):tCorsHeaders,1:"CorsHeadersList") + //jwt + set tMap("JWTAccessTokenTimeout")="JWTAccessTokenTimeout" + set tMap("JWTAuthEnabled")="JWTAuthEnabled" + set tMap("JWTRefreshTokenTimeout")="JWTRefreshTokenTimeout" set tAttr = "" for { @@ -275,6 +288,24 @@ Method SetSourcePathForCSPApp( set ..TemplateResources(pCSPApp,"Path") = pPath } +Method DefineReminWebAppProps(ByRef props) +{ + //add cors origin details + set props("CorsAllowlist") = "" + set props("CorsCredentialsAllowed") = 0 + // add cors headers + set props("CorsHeadersList") = "" + set props("DeepSeeEnabled") = 0 + set props("JWTAuthEnabled") = 0 + set props("JWTRefreshTokenTimeout") = 900 + set props("JWTAccessTokenTimeout") = 60 + set props("WSGIAppLocation") = "" + set props("WSGIAppName") = "" + set props("WSGICallable") = "" + set props("WSGIDebug") = "" + set props("WSGIType") = "" +} + ClassMethod GetGlobalsList(Output globals As %List) As %Status { set globals="" @@ -343,6 +374,40 @@ Method SetAuthorProps( return $$$OK } +Method SetCORSProps( + pApps As %String = "", + ByRef pCors As %String = "") As %Status +{ + set tAppList = "" + set pApps = $zstrip(pApps,"<>W") + if ( pApps = "*" ) { + do ..GetCSPApplications(.tAppList) + } else { + set tAppList = $listfromstring(pApps,",") + } + set ptr=0 + while $listnext(tAppList,ptr,tWebpApp){ + set ..TemplateResources(tWebpApp,"CorsCredentialsAllowed") = pCors("enableCors") + set ..TemplateResources(tWebpApp,"CorsHeadersList") = pCors("allowedHeaders") + set ..TemplateResources(tWebpApp,"CorsAllowlist") = pCors("allowedOrigins") + } + return $$$OK +} + +Method ValidateCorsOrigin(pAllowedOrigins As %String = "") +{ + new $namespace + set $namespace = "%SYS" + set tOriginsList = $listfromstring(pAllowedOrigins,",") + set ptr = 0 + set sc = 1 + while $listnext(tOriginsList, ptr, origin){ + set sc = ##class(Security.Applications).ValidateCorsOrigin(origin) + if $$$ISERR(sc) return sc + } + return sc +} + ClassMethod NewTemplate( pPath, pName, @@ -515,6 +580,10 @@ Method SetTemplateProps() As %Status set ..TemplateResources("rest","PasswordAuthEnabled") = 1 set ..TemplateResources("rest","UnauthenticatedEnabled") = 0 set ..TemplateResources("rest","Recurse") = 1 + ;cors + set ..TemplateResources("rest","CorsAllowlist") = "http://www.example.com" + set ..TemplateResources("rest","CorsCredentialsAllowed") = 1 + set ..TemplateResources("rest","CorsHeadersList") = "Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers, Origin, Access-Control-Request-Method, Access-Control-Request-Headers" // WEB APP set ..TemplateResources("web") = "/web" diff --git a/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls b/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls new file mode 100644 index 00000000..d01e69e3 --- /dev/null +++ b/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls @@ -0,0 +1,69 @@ +Class Test.PM.Integration.CorsAndJWTTest Extends Test.PM.Integration.Base +{ + +Parameter CommonPathPrefix As STRING = "cors-rest-apps"; + +Parameter WebAppName As STRING = "/testcors"; + +/// Test.PM.Integration.Uninstall +Method TestCORSEnabledWebAppViaModule() +{ + #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) + #define UTRoot ^UnitTestRoot + + set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) + set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") + if ##class(%File).DirectoryExists(moduleDir_"/module.xml") { + do $$$AssertStatusOK(1,"Module.xml File exist on "_moduleDir) + } + set status = ##class(%IPM.Main).Shell("load "_moduleDir) + do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully from "_moduleDir) + do ..VerifyCORSConfiguration() + do ..VerifyJWTConfiguration() + + set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) + do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") +} + +Method VerifyCORSConfiguration() +{ + new $namespace + set $namespace = "%SYS" + set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") + if $data(tProps("CorsAllowlist"),tCorsAllowlist) { + do $$$AssertStatusOK(1,"CorsAllowlist values are defined") + do $$$LogMessage(tCorsAllowlist) + } + if $data(tProps("CorsCredentialsAllowed"),tCorsAllow) { + do $$$AssertStatusOK(1,"CorsCredentialsAllowed values are defined") + do $$$LogMessage(tCorsAllow) + } + if $data(tProps("CorsHeadersList"),tCorsHeadersList) { + do $$$AssertStatusOK(1,"CorsHeadersList values are defined") + do $$$LogMessage(tCorsHeadersList) + } +} + +Method VerifyJWTConfiguration() +{ + new $namespace + set $namespace = "%SYS" + set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") + do $$$LogMessage("Validating JWT configuration") + if $data(tProps("JWTAccessTokenTimeout"),tJWTAccessTokenTimeout) { + do $$$AssertStatusOK(1,"JWTAccessTokenTimeout value is defined") + do $$$LogMessage(tJWTAccessTokenTimeout) + } + if $data(tProps("JWTAuthEnabled"),tJWTAuthEnabled) { + do $$$AssertStatusOK(1,"JWTAuthEnabled value is defined") + do $$$LogMessage(tJWTAuthEnabled) + } + if $data(tProps("JWTRefreshTokenTimeout"),tJWTRefreshTokenTimeout) { + do $$$AssertStatusOK(1,"JWTRefreshTokenTimeout value is defined") + do $$$LogMessage(tJWTRefreshTokenTimeout) + } +} + +} diff --git a/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml b/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml new file mode 100644 index 00000000..51f4bd42 --- /dev/null +++ b/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml @@ -0,0 +1,28 @@ + + + + + cors-rest-apps + 1.0.0 + cors enabling testing on webapplication + cors + module + + + %IPM.Lifecycle.Module + src + + + \ No newline at end of file diff --git a/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/src/cls/CorsTest/Rest/Cors.cls b/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/src/cls/CorsTest/Rest/Cors.cls new file mode 100644 index 00000000..93d6dd75 --- /dev/null +++ b/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/src/cls/CorsTest/Rest/Cors.cls @@ -0,0 +1,17 @@ +Class CorsTest.Rest.Cors Extends %RegisteredObject +{ + +XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] +{ + + + +} + +ClassMethod GetInfo() As %Status +{ + write "Hello, World!" + quit $$$OK +} + +} diff --git a/tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls b/tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls new file mode 100644 index 00000000..735f2291 --- /dev/null +++ b/tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls @@ -0,0 +1,142 @@ +Class Test.PM.Unit.WebApJWTConfigTest Extends %UnitTest.TestCase +{ + +Parameter CommonPathPrefix As STRING = "cors-rest-apps"; + +Parameter WebAppName As STRING = "/testcors"; + +/// create web application using tag +Method TestJWTEnabledViaCspAppTag() +{ + #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) + #define UTRoot ^UnitTestRoot + + set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) + set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") + + do $$$LogMessage("loading via tag") + + if ##class(%File).DirectoryExists(moduleDir_"/module.xml"){ + set status= ##class(%File).Delete(moduleDir_"/module.xml") + do $$$AssertStatusOK(status,"file already exist.So, Removed the 'module.xml' file from "_moduleDir) + } + set status = ..GenerateTemplate(moduleDir) + do $$$AssertStatusOK(status,"Creating module.xml on "_moduleDir_" successfully. ") + + set status = ##class(%IPM.Main).Shell("load "_moduleDir) + do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) + do ..VerifyJWTConfiguration() + + set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) + do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") +} + +/// create web application using tag +Method TestJWTEnabledViaWebAppTag() +{ + #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) + #define UTRoot ^UnitTestRoot + + do $$$LogMessage("loading via tag") + set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) + set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") + if '##class(%File).DirectoryExists(moduleDir) { + do ##class(%File).CreateDirectoryChain(moduleDir) + } + if ##class(%File).Exists(moduleDir_"/module.xml"){ + set status= ##class(%File).Delete(moduleDir_"/module.xml") + do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) + } + do $$$LogMessage("Creating module.xml with tag") + set moduleStream = ##class(%Dictionary.XDataDefinition).%OpenId($classname()_"||ModuleWithWebApTag").Data + set fileStream = ##class(%Stream.FileBinary).%New() + set fileStream.Filename=moduleDir_"/module.xml" + set status= fileStream.CopyFromAndSave(moduleStream) + do $$$AssertStatusOK(status,"Created module.xml manually on "_moduleDir_" successfully.") + + set status = ##class(%IPM.Main).Shell("load "_moduleDir) + do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) + do ..VerifyJWTConfiguration() + + set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) + do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") +} + +Method VerifyJWTConfiguration() +{ + new $namespace + set $namespace = "%SYS" + set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") + do $$$LogMessage("Validating JWT configuration") + if $data(tProps("JWTAccessTokenTimeout"),tJWTAccessTokenTimeout) { + do $$$AssertStatusOK(1,"JWTAccessTokenTimeout value is defined") + do $$$LogMessage(tJWTAccessTokenTimeout) + } + if $data(tProps("JWTAuthEnabled"),tJWTAuthEnabled) { + do $$$AssertStatusOK(1,"JWTAuthEnabled value is defined") + do $$$LogMessage(tJWTAuthEnabled) + } + if $data(tProps("JWTRefreshTokenTimeout"),tJWTRefreshTokenTimeout) { + do $$$AssertStatusOK(1,"JWTRefreshTokenTimeout value is defined") + do $$$LogMessage(tJWTRefreshTokenTimeout) + } +} + +ClassMethod GenerateTemplate(pPath As %String = "") As %Status +{ + return:(pPath="") $$$OK + set tTemplate = ##class(%IPM.Storage.ModuleTemplate).%New() + //do tTemplate.SetTemplateProps() + set tTemplate.Name = ..#CommonPathPrefix + set tTemplate.VersionString = "1.0.0" + set tTemplate.Description = "cors enabling testing on webapplication" + set tTemplate.Keywords = "cors" + + set tTemplate.Packaging = "module" + set tTemplate.SourcesRoot = "src" + + //set tTemplate.TemplateResources("cls") = "MyPackage.Demo.CLS" + //set tTemplate.TemplateResources("cls","Directory") = "cls" + + // REST APP + set tTemplate.TemplateResources(..#WebAppName) = ..#WebAppName + set tTemplate.TemplateResources(..#WebAppName,"Url") = ..#WebAppName + set tTemplate.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName + set tTemplate.TemplateResources(..#WebAppName,"UseCookies") = 2 + set tTemplate.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 + set tTemplate.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 + set tTemplate.TemplateResources(..#WebAppName,"Recurse") = 1 + ;cors + set tTemplate.TemplateResources(..#WebAppName,"JWTAccessTokenTimeout") = 60 + set tTemplate.TemplateResources(..#WebAppName,"JWTAuthEnabled") = 1 + set tTemplate.TemplateResources(..#WebAppName,"JWTRefreshTokenTimeout") = 900 + do tTemplate.ProcessResources() + return tTemplate.SaveFile(pPath) +} + +XData ModuleWithWebApTag [ MimeType = application/xml ] +{ + + + + + cors-rest-apps + 1.0.0 + cors enabling testing on webapplication + cors + module + + %IPM.Lifecycle.Module + src + + + +} + +} diff --git a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls new file mode 100644 index 00000000..6a712235 --- /dev/null +++ b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls @@ -0,0 +1,139 @@ +Class Test.PM.Unit.WebAppCorsTest Extends %UnitTest.TestCase +{ + +Parameter CommonPathPrefix As STRING = "cors-rest-apps"; + +Parameter WebAppName As STRING = "/testcors"; + +/// create web application using tag +Method TestCORSEnabledViaCSPAppTag() +{ + #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) + #define UTRoot ^UnitTestRoot + + set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) + set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") + + do $$$LogMessage("loading via tag") + if ##class(%File).Exists(moduleDir_"/module.xml"){ + set status= ##class(%File).Delete(moduleDir_"/module.xml") + do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) + } + set status = ..GenerateTemplate(moduleDir) + do $$$AssertStatusOK(status,"Creating module.xml on "_moduleDir_" successfully. ") + + set status = ##class(%IPM.Main).Shell("load "_moduleDir) + do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) + do ..VerifyCORSSettings() + + set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) + do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") +} + +/// create web application using tag +Method TestCORSEnabledViaWebAppTag() +{ + #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) + #define UTRoot ^UnitTestRoot + + do $$$LogMessage("loading via tag") + set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) + set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") + if '##class(%File).DirectoryExists(moduleDir) { + do ##class(%File).CreateDirectoryChain(moduleDir) + } + if ##class(%File).Exists(moduleDir_"/module.xml"){ + set status= ##class(%File).Delete(moduleDir_"/module.xml") + do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) + } + do $$$LogMessage("Creating module.xml with tag") + set moduleStream = ##class(%Dictionary.XDataDefinition).%OpenId($classname()_"||ModuleWithWebApTag").Data + set fileStream = ##class(%Stream.FileBinary).%New() + set fileStream.Filename=moduleDir_"/module.xml" + set status= fileStream.CopyFromAndSave(moduleStream) + do $$$AssertStatusOK(status,"Created module.xml manually on "_moduleDir_" successfully.") + + set status = ##class(%IPM.Main).Shell("load "_moduleDir) + do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) + do ..VerifyCORSSettings() + + set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) + do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") +} + +Method VerifyCORSSettings() +{ + new $namespace + set $namespace = "%SYS" + set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") + if $data(tProps("CorsAllowlist"),tCorsAllowlist)&&(tCorsAllowlist'="") { + do $$$AssertStatusOK(1,"CorsAllowlist values are defined") + do $$$LogMessage(tCorsAllowlist) + } + if $data(tProps("CorsCredentialsAllowed"),tCorsAllow)&&(tCorsAllow'="") { + do $$$AssertStatusOK(1,"CorsCredentialsAllowed values are defined") + do $$$LogMessage(tCorsAllow) + } + if $data(tProps("CorsHeadersList"),tCorsHeadersList)&&(tCorsHeadersList'="") { + do $$$AssertStatusOK(1,"CorsHeadersList values are defined") + do $$$LogMessage(tCorsHeadersList) + } +} + +ClassMethod GenerateTemplate(pPath As %String = "") As %Status +{ + return:(pPath="") $$$OK + set tTemplate = ##class(%IPM.Storage.ModuleTemplate).%New() + //do tTemplate.SetTemplateProps() + set tTemplate.Name = ..#CommonPathPrefix + set tTemplate.VersionString = "1.0.0" + set tTemplate.Description = "cors enabling testing on webapplication" + set tTemplate.Keywords = "cors" + + set tTemplate.Packaging = "module" + set tTemplate.SourcesRoot = "src" + + //set tTemplate.TemplateResources("cls") = "MyPackage.Demo.CLS" + //set tTemplate.TemplateResources("cls","Directory") = "cls" + + // REST APP + set tTemplate.TemplateResources(..#WebAppName) = ..#WebAppName + set tTemplate.TemplateResources(..#WebAppName,"Url") = ..#WebAppName + set tTemplate.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName + set tTemplate.TemplateResources(..#WebAppName,"UseCookies") = 2 + set tTemplate.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 + set tTemplate.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 + set tTemplate.TemplateResources(..#WebAppName,"Recurse") = 1 + ;cors + set tTemplate.TemplateResources(..#WebAppName,"CorsAllowlist") = "https://www.example.com,https://pm.intersystems.com" + set tTemplate.TemplateResources(..#WebAppName,"CorsCredentialsAllowed") = 1 + set tTemplate.TemplateResources(..#WebAppName,"CorsHeadersList") = "Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers, Access-Control-Request-Method, Access-Control-Request-Headers" + do tTemplate.ProcessResources() + return tTemplate.SaveFile(pPath) +} + +XData ModuleWithWebApTag [ MimeType = application/xml ] +{ + + + + + cors-rest-apps + 1.0.0 + cors enabling testing on webapplication + cors + module + + %IPM.Lifecycle.Module + src + + + +} + +} From e88d6f13ab4cd4c31c7d1d3c020f3e6228408e09 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Tue, 2 Dec 2025 18:16:05 +0530 Subject: [PATCH 2/6] refactor(variableName): Removed the t prefix from variable names and corrected unittest classnames. --- src/cls/IPM/Main.cls | 20 ++--- src/cls/IPM/Storage/ModuleTemplate.cls | 24 +++--- .../Test/PM/Integration/CorsAndJWTTest.cls | 33 ++++---- .../Test/PM/Unit/WebAppCorsTest.cls | 82 +++++++++---------- ...ConfigTest.cls => WebAppJWTConfigTest.cls} | 79 ++++++++---------- 5 files changed, 111 insertions(+), 127 deletions(-) rename tests/unit_tests/Test/PM/Unit/{WebApJWTConfigTest.cls => WebAppJWTConfigTest.cls} (58%) diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index 067af81f..2e2e21ae 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -1307,23 +1307,23 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] #If $$$CacheVersionMajor>=2025 write !,"Cross-Origin Settings:" - do ##class(%Library.Prompt).GetYesNo("Configure Access-Control-Allow-Credentials?: ",.tEnableCors,1,3) - if tEnableCors { - do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Headers: ",.tAllowedHeaders,,32767,"comma seperated values e.g: Access-Control-Allow-Origin,Access-Control-Allow-Headers") + do ##class(%Library.Prompt).GetYesNo("Configure Access-Control-Allow-Credentials?: ",.enableCors,1,3) + if enableCors { + do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Headers: ",.allowedHeaders,,32767,"comma seperated values e.g: Access-Control-Allow-Origin,Access-Control-Allow-Headers") while (1){ - do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Origins: ",.tAllowedOrigins,,32767,"comma seperated values e.g: http://www.example.com") - if tAllowedOrigins="" quit - set sc = tTemplate.ValidateCorsOrigin(tAllowedOrigins) + do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Origins: ",.allowedOrigins,,32767,"comma seperated values e.g: http://www.example.com") + if allowedOrigins="" quit + set sc = tTemplate.ValidateCorsOrigin(allowedOrigins) if $$$ISERR(sc) { do ..DisplayError(sc) - set tAllowedOrigins = "" + set allowedOrigins = "" continue } quit } - set tCors("enableCors") = tEnableCors - set tCors("allowedHeaders") = tAllowedHeaders - set tCors("allowedOrigins") = tAllowedOrigins + set tCors("enableCors") = enableCors + set tCors("allowedHeaders") = allowedHeaders + set tCors("allowedOrigins") = allowedOrigins do tTemplate.SetCORSProps(tWebAppList,.tCors) } #EndIf diff --git a/src/cls/IPM/Storage/ModuleTemplate.cls b/src/cls/IPM/Storage/ModuleTemplate.cls index 06da2827..b8deaf8c 100644 --- a/src/cls/IPM/Storage/ModuleTemplate.cls +++ b/src/cls/IPM/Storage/ModuleTemplate.cls @@ -378,32 +378,32 @@ Method SetCORSProps( pApps As %String = "", ByRef pCors As %String = "") As %Status { - set tAppList = "" + set appList = "" set pApps = $zstrip(pApps,"<>W") if ( pApps = "*" ) { - do ..GetCSPApplications(.tAppList) + do ..GetCSPApplications(.appList) } else { - set tAppList = $listfromstring(pApps,",") + set appList = $listfromstring(pApps,",") } set ptr=0 - while $listnext(tAppList,ptr,tWebpApp){ - set ..TemplateResources(tWebpApp,"CorsCredentialsAllowed") = pCors("enableCors") - set ..TemplateResources(tWebpApp,"CorsHeadersList") = pCors("allowedHeaders") - set ..TemplateResources(tWebpApp,"CorsAllowlist") = pCors("allowedOrigins") + while $listnext(appList, ptr, webpApp){ + set ..TemplateResources(webpApp,"CorsCredentialsAllowed") = pCors("enableCors") + set ..TemplateResources(webpApp,"CorsHeadersList") = pCors("allowedHeaders") + set ..TemplateResources(webpApp,"CorsAllowlist") = pCors("allowedOrigins") } return $$$OK } -Method ValidateCorsOrigin(pAllowedOrigins As %String = "") +Method ValidateCorsOrigin(pAllowedOrigins As %String = "") As %Status { new $namespace set $namespace = "%SYS" - set tOriginsList = $listfromstring(pAllowedOrigins,",") + set originsList = $listfromstring(pAllowedOrigins,",") set ptr = 0 - set sc = 1 - while $listnext(tOriginsList, ptr, origin){ + set sc = $$$OK + while $listnext(originsList, ptr, origin){ set sc = ##class(Security.Applications).ValidateCorsOrigin(origin) - if $$$ISERR(sc) return sc + if $$$ISERR(sc) quit } return sc } diff --git a/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls b/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls index d01e69e3..2537ce72 100644 --- a/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls +++ b/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls @@ -13,8 +13,9 @@ Method TestCORSEnabledWebAppViaModule() set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") - if ##class(%File).DirectoryExists(moduleDir_"/module.xml") { - do $$$AssertStatusOK(1,"Module.xml File exist on "_moduleDir) + set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) + if ##class(%File).DirectoryExists(moduleFile) { + do $$$AssertStatusOK(1,"module.xml File exist on "_moduleDir) } set status = ##class(%IPM.Main).Shell("load "_moduleDir) do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully from "_moduleDir) @@ -29,19 +30,19 @@ Method VerifyCORSConfiguration() { new $namespace set $namespace = "%SYS" - set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + set status = ##class(Security.Applications).Get(..#WebAppName, .props) do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") - if $data(tProps("CorsAllowlist"),tCorsAllowlist) { + if $data(props("CorsAllowlist"),corsAllowlist) { do $$$AssertStatusOK(1,"CorsAllowlist values are defined") - do $$$LogMessage(tCorsAllowlist) + do $$$LogMessage(corsAllowlist) } - if $data(tProps("CorsCredentialsAllowed"),tCorsAllow) { + if $data(props("CorsCredentialsAllowed"),corsAllow) { do $$$AssertStatusOK(1,"CorsCredentialsAllowed values are defined") - do $$$LogMessage(tCorsAllow) + do $$$LogMessage(corsAllow) } - if $data(tProps("CorsHeadersList"),tCorsHeadersList) { + if $data(props("CorsHeadersList"),corsHeadersList) { do $$$AssertStatusOK(1,"CorsHeadersList values are defined") - do $$$LogMessage(tCorsHeadersList) + do $$$LogMessage(corsHeadersList) } } @@ -49,20 +50,20 @@ Method VerifyJWTConfiguration() { new $namespace set $namespace = "%SYS" - set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + set status = ##class(Security.Applications).Get(..#WebAppName, .props) do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") do $$$LogMessage("Validating JWT configuration") - if $data(tProps("JWTAccessTokenTimeout"),tJWTAccessTokenTimeout) { + if $data(props("JWTAccessTokenTimeout"),JWTAccessTokenTimeout) { do $$$AssertStatusOK(1,"JWTAccessTokenTimeout value is defined") - do $$$LogMessage(tJWTAccessTokenTimeout) + do $$$LogMessage(JWTAccessTokenTimeout) } - if $data(tProps("JWTAuthEnabled"),tJWTAuthEnabled) { + if $data(props("JWTAuthEnabled"),JWTAuthEnabled) { do $$$AssertStatusOK(1,"JWTAuthEnabled value is defined") - do $$$LogMessage(tJWTAuthEnabled) + do $$$LogMessage(JWTAuthEnabled) } - if $data(tProps("JWTRefreshTokenTimeout"),tJWTRefreshTokenTimeout) { + if $data(props("JWTRefreshTokenTimeout"),JWTRefreshTokenTimeout) { do $$$AssertStatusOK(1,"JWTRefreshTokenTimeout value is defined") - do $$$LogMessage(tJWTRefreshTokenTimeout) + do $$$LogMessage(JWTRefreshTokenTimeout) } } diff --git a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls index 6a712235..694c4254 100644 --- a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls +++ b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls @@ -8,15 +8,13 @@ Parameter WebAppName As STRING = "/testcors"; /// create web application using tag Method TestCORSEnabledViaCSPAppTag() { - #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) - #define UTRoot ^UnitTestRoot - - set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) - set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") + set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) + set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") + set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) do $$$LogMessage("loading via tag") - if ##class(%File).Exists(moduleDir_"/module.xml"){ - set status= ##class(%File).Delete(moduleDir_"/module.xml") + if ##class(%File).Exists(moduleFile){ + set status= ##class(%File).Delete(moduleFile) do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) } set status = ..GenerateTemplate(moduleDir) @@ -33,23 +31,21 @@ Method TestCORSEnabledViaCSPAppTag() /// create web application using tag Method TestCORSEnabledViaWebAppTag() { - #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) - #define UTRoot ^UnitTestRoot - do $$$LogMessage("loading via tag") - set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) - set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") + set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) + set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") if '##class(%File).DirectoryExists(moduleDir) { do ##class(%File).CreateDirectoryChain(moduleDir) } - if ##class(%File).Exists(moduleDir_"/module.xml"){ - set status= ##class(%File).Delete(moduleDir_"/module.xml") + set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) + if ##class(%File).Exists(moduleFile){ + set status= ##class(%File).Delete(moduleFile) do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) } do $$$LogMessage("Creating module.xml with tag") set moduleStream = ##class(%Dictionary.XDataDefinition).%OpenId($classname()_"||ModuleWithWebApTag").Data set fileStream = ##class(%Stream.FileBinary).%New() - set fileStream.Filename=moduleDir_"/module.xml" + set fileStream.Filename=moduleFile set status= fileStream.CopyFromAndSave(moduleStream) do $$$AssertStatusOK(status,"Created module.xml manually on "_moduleDir_" successfully.") @@ -65,52 +61,48 @@ Method VerifyCORSSettings() { new $namespace set $namespace = "%SYS" - set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + set status = ##class(Security.Applications).Get(..#WebAppName,.props) do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") - if $data(tProps("CorsAllowlist"),tCorsAllowlist)&&(tCorsAllowlist'="") { + if $data(props("CorsAllowlist"),corsAllowlist)&&(corsAllowlist'="") { do $$$AssertStatusOK(1,"CorsAllowlist values are defined") - do $$$LogMessage(tCorsAllowlist) + do $$$LogMessage(corsAllowlist) } - if $data(tProps("CorsCredentialsAllowed"),tCorsAllow)&&(tCorsAllow'="") { + if $data(props("CorsCredentialsAllowed"),corsAllow)&&(corsAllow'="") { do $$$AssertStatusOK(1,"CorsCredentialsAllowed values are defined") - do $$$LogMessage(tCorsAllow) + do $$$LogMessage(corsAllow) } - if $data(tProps("CorsHeadersList"),tCorsHeadersList)&&(tCorsHeadersList'="") { + if $data(props("CorsHeadersList"),corsHeadersList)&&(corsHeadersList'="") { do $$$AssertStatusOK(1,"CorsHeadersList values are defined") - do $$$LogMessage(tCorsHeadersList) + do $$$LogMessage(corsHeadersList) } } ClassMethod GenerateTemplate(pPath As %String = "") As %Status { return:(pPath="") $$$OK - set tTemplate = ##class(%IPM.Storage.ModuleTemplate).%New() - //do tTemplate.SetTemplateProps() - set tTemplate.Name = ..#CommonPathPrefix - set tTemplate.VersionString = "1.0.0" - set tTemplate.Description = "cors enabling testing on webapplication" - set tTemplate.Keywords = "cors" - - set tTemplate.Packaging = "module" - set tTemplate.SourcesRoot = "src" + set template = ##class(%IPM.Storage.ModuleTemplate).%New() + set template.Name = ..#CommonPathPrefix + set template.VersionString = "1.0.0" + set template.Description = "cors enabling testing on webapplication" + set template.Keywords = "cors" - //set tTemplate.TemplateResources("cls") = "MyPackage.Demo.CLS" - //set tTemplate.TemplateResources("cls","Directory") = "cls" + set template.Packaging = "module" + set template.SourcesRoot = "src" // REST APP - set tTemplate.TemplateResources(..#WebAppName) = ..#WebAppName - set tTemplate.TemplateResources(..#WebAppName,"Url") = ..#WebAppName - set tTemplate.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName - set tTemplate.TemplateResources(..#WebAppName,"UseCookies") = 2 - set tTemplate.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 - set tTemplate.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 - set tTemplate.TemplateResources(..#WebAppName,"Recurse") = 1 + set template.TemplateResources(..#WebAppName) = ..#WebAppName + set template.TemplateResources(..#WebAppName,"Url") = ..#WebAppName + set template.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName + set template.TemplateResources(..#WebAppName,"UseCookies") = 2 + set template.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 + set template.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 + set template.TemplateResources(..#WebAppName,"Recurse") = 1 ;cors - set tTemplate.TemplateResources(..#WebAppName,"CorsAllowlist") = "https://www.example.com,https://pm.intersystems.com" - set tTemplate.TemplateResources(..#WebAppName,"CorsCredentialsAllowed") = 1 - set tTemplate.TemplateResources(..#WebAppName,"CorsHeadersList") = "Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers, Access-Control-Request-Method, Access-Control-Request-Headers" - do tTemplate.ProcessResources() - return tTemplate.SaveFile(pPath) + set template.TemplateResources(..#WebAppName,"CorsAllowlist") = "https://www.example.com,https://pm.intersystems.com" + set template.TemplateResources(..#WebAppName,"CorsCredentialsAllowed") = 1 + set template.TemplateResources(..#WebAppName,"CorsHeadersList") = "Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers, Access-Control-Request-Method, Access-Control-Request-Headers" + do template.ProcessResources() + return template.SaveFile(pPath) } XData ModuleWithWebApTag [ MimeType = application/xml ] diff --git a/tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls similarity index 58% rename from tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls rename to tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls index 735f2291..d88c0328 100644 --- a/tests/unit_tests/Test/PM/Unit/WebApJWTConfigTest.cls +++ b/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls @@ -1,4 +1,4 @@ -Class Test.PM.Unit.WebApJWTConfigTest Extends %UnitTest.TestCase +Class Test.PM.Unit.WebAppJWTConfigTest Extends %UnitTest.TestCase { Parameter CommonPathPrefix As STRING = "cors-rest-apps"; @@ -8,16 +8,13 @@ Parameter WebAppName As STRING = "/testcors"; /// create web application using tag Method TestJWTEnabledViaCspAppTag() { - #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) - #define UTRoot ^UnitTestRoot - - set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) - set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") - + set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) + set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") + set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) do $$$LogMessage("loading via tag") - if ##class(%File).DirectoryExists(moduleDir_"/module.xml"){ - set status= ##class(%File).Delete(moduleDir_"/module.xml") + if ##class(%File).DirectoryExists(moduleFile){ + set status= ##class(%File).Delete(moduleFile) do $$$AssertStatusOK(status,"file already exist.So, Removed the 'module.xml' file from "_moduleDir) } set status = ..GenerateTemplate(moduleDir) @@ -34,23 +31,21 @@ Method TestJWTEnabledViaCspAppTag() /// create web application using tag Method TestJWTEnabledViaWebAppTag() { - #define NormalizeDirectory(%path) ##class(%File).NormalizeDirectory(%path) - #define UTRoot ^UnitTestRoot - do $$$LogMessage("loading via tag") - set testRoot = $$$NormalizeDirectory($get($$$UTRoot)) - set moduleDir = $$$NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") + set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) + set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") if '##class(%File).DirectoryExists(moduleDir) { do ##class(%File).CreateDirectoryChain(moduleDir) } - if ##class(%File).Exists(moduleDir_"/module.xml"){ - set status= ##class(%File).Delete(moduleDir_"/module.xml") + set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) + if ##class(%File).Exists(moduleFile){ + set status= ##class(%File).Delete(moduleFile) do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) } do $$$LogMessage("Creating module.xml with tag") set moduleStream = ##class(%Dictionary.XDataDefinition).%OpenId($classname()_"||ModuleWithWebApTag").Data set fileStream = ##class(%Stream.FileBinary).%New() - set fileStream.Filename=moduleDir_"/module.xml" + set fileStream.Filename=moduleFile set status= fileStream.CopyFromAndSave(moduleStream) do $$$AssertStatusOK(status,"Created module.xml manually on "_moduleDir_" successfully.") @@ -66,18 +61,18 @@ Method VerifyJWTConfiguration() { new $namespace set $namespace = "%SYS" - set status = ##class(Security.Applications).Get(..#WebAppName,.tProps) + set status = ##class(Security.Applications).Get(..#WebAppName,.props) do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") do $$$LogMessage("Validating JWT configuration") - if $data(tProps("JWTAccessTokenTimeout"),tJWTAccessTokenTimeout) { + if $data(props("JWTAccessTokenTimeout"),tJWTAccessTokenTimeout) { do $$$AssertStatusOK(1,"JWTAccessTokenTimeout value is defined") do $$$LogMessage(tJWTAccessTokenTimeout) } - if $data(tProps("JWTAuthEnabled"),tJWTAuthEnabled) { + if $data(props("JWTAuthEnabled"),tJWTAuthEnabled) { do $$$AssertStatusOK(1,"JWTAuthEnabled value is defined") do $$$LogMessage(tJWTAuthEnabled) } - if $data(tProps("JWTRefreshTokenTimeout"),tJWTRefreshTokenTimeout) { + if $data(props("JWTRefreshTokenTimeout"),tJWTRefreshTokenTimeout) { do $$$AssertStatusOK(1,"JWTRefreshTokenTimeout value is defined") do $$$LogMessage(tJWTRefreshTokenTimeout) } @@ -86,33 +81,29 @@ Method VerifyJWTConfiguration() ClassMethod GenerateTemplate(pPath As %String = "") As %Status { return:(pPath="") $$$OK - set tTemplate = ##class(%IPM.Storage.ModuleTemplate).%New() - //do tTemplate.SetTemplateProps() - set tTemplate.Name = ..#CommonPathPrefix - set tTemplate.VersionString = "1.0.0" - set tTemplate.Description = "cors enabling testing on webapplication" - set tTemplate.Keywords = "cors" - - set tTemplate.Packaging = "module" - set tTemplate.SourcesRoot = "src" + set template = ##class(%IPM.Storage.ModuleTemplate).%New() + set template.Name = ..#CommonPathPrefix + set template.VersionString = "1.0.0" + set template.Description = "cors enabling testing on webapplication" + set template.Keywords = "cors" - //set tTemplate.TemplateResources("cls") = "MyPackage.Demo.CLS" - //set tTemplate.TemplateResources("cls","Directory") = "cls" + set template.Packaging = "module" + set template.SourcesRoot = "src" // REST APP - set tTemplate.TemplateResources(..#WebAppName) = ..#WebAppName - set tTemplate.TemplateResources(..#WebAppName,"Url") = ..#WebAppName - set tTemplate.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName - set tTemplate.TemplateResources(..#WebAppName,"UseCookies") = 2 - set tTemplate.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 - set tTemplate.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 - set tTemplate.TemplateResources(..#WebAppName,"Recurse") = 1 + set template.TemplateResources(..#WebAppName) = ..#WebAppName + set template.TemplateResources(..#WebAppName,"Url") = ..#WebAppName + set template.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName + set template.TemplateResources(..#WebAppName,"UseCookies") = 2 + set template.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 + set template.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 + set template.TemplateResources(..#WebAppName,"Recurse") = 1 ;cors - set tTemplate.TemplateResources(..#WebAppName,"JWTAccessTokenTimeout") = 60 - set tTemplate.TemplateResources(..#WebAppName,"JWTAuthEnabled") = 1 - set tTemplate.TemplateResources(..#WebAppName,"JWTRefreshTokenTimeout") = 900 - do tTemplate.ProcessResources() - return tTemplate.SaveFile(pPath) + set template.TemplateResources(..#WebAppName,"JWTAccessTokenTimeout") = 60 + set template.TemplateResources(..#WebAppName,"JWTAuthEnabled") = 1 + set template.TemplateResources(..#WebAppName,"JWTRefreshTokenTimeout") = 900 + do template.ProcessResources() + return template.SaveFile(pPath) } XData ModuleWithWebApTag [ MimeType = application/xml ] From 2897899917364df20935d2e4be5030612152d189 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Wed, 3 Dec 2025 00:13:20 +0530 Subject: [PATCH 3/6] fix: replace deprecated CSPApplication element with WebApplication and update CORS/JWT configuration --- src/cls/IPM/Main.cls | 30 ++++++++--- .../IPM/ResourceProcessor/CSPApplication.cls | 20 -------- src/cls/IPM/Storage/ModuleTemplate.cls | 41 ++++++++------- ...st.cls => CorsAndJwtConfigurationTest.cls} | 4 +- .../Test/PM/Unit/WebAppCorsTest.cls | 51 ------------------- .../Test/PM/Unit/WebAppJWTConfigTest.cls | 51 ------------------- 6 files changed, 49 insertions(+), 148 deletions(-) rename tests/integration_tests/Test/PM/Integration/{CorsAndJWTTest.cls => CorsAndJwtConfigurationTest.cls} (90%) diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index 2e2e21ae..64540c57 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -1306,12 +1306,13 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] do ##class(%Library.Prompt).GetString(" Enter a comma separated list of web applications or * for all:", .tWebAppList) #If $$$CacheVersionMajor>=2025 + //cors write !,"Cross-Origin Settings:" - do ##class(%Library.Prompt).GetYesNo("Configure Access-Control-Allow-Credentials?: ",.enableCors,1,3) + do ##class(%Library.Prompt).GetYesNo("Configure Access-Control-Allow-Credentials:",.enableCors) if enableCors { - do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Headers: ",.allowedHeaders,,32767,"comma seperated values e.g: Access-Control-Allow-Origin,Access-Control-Allow-Headers") + do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Headers:",.allowedHeaders,,32767,"comma seperated values e.g: Access-Control-Allow-Origin,Access-Control-Allow-Headers") while (1){ - do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Origins: ",.allowedOrigins,,32767,"comma seperated values e.g: http://www.example.com") + do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Origins:",.allowedOrigins,,32767,"comma seperated values e.g: http://www.example.com") if allowedOrigins="" quit set sc = tTemplate.ValidateCorsOrigin(allowedOrigins) if $$$ISERR(sc) { @@ -1321,12 +1322,27 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] } quit } - set tCors("enableCors") = enableCors - set tCors("allowedHeaders") = allowedHeaders - set tCors("allowedOrigins") = allowedOrigins - do tTemplate.SetCORSProps(tWebAppList,.tCors) + set corsConfig("enableCors") = enableCors + set corsConfig("allowedHeaders") = allowedHeaders + set corsConfig("allowedOrigins") = allowedOrigins + do tTemplate.SetCORSProps(tWebAppList,.corsConfig) } #EndIf + +#If $$$CacheVersionMajor>=2024 + //jwt + write !,"JWT Authentication" + do ##class(%Library.Prompt).GetYesNo("Configure JWT Authentication: ",.enableJWT) + if enableJWT { + do ##class(%Library.Prompt).GetNumber(" Enter JWT Access Token Timeout:",.JWTAccessTokenTO,1) + do ##class(%Library.Prompt).GetNumber(" Enter JWT Refresh Token Timeout:",.JWTRefreshTokenTO,1) + set jwtconfig("enableJWT") = enableJWT + set jwtconfig("JWTAccessTokenTO") = JWTAccessTokenTO + set jwtconfig("JWTRefreshTokenTO") = JWTRefreshTokenTO + do tTemplate.SetJWTProps(tWebAppList,.jwtconfig) + } +#EndIf + do tTemplate.AddWebApps(tWebAppList,.tCSPapps) // tCSP - list of CSP (not REST apps) for i=1:1:$listlength(tCSPapps) { set tCSPPath = "" diff --git a/src/cls/IPM/ResourceProcessor/CSPApplication.cls b/src/cls/IPM/ResourceProcessor/CSPApplication.cls index 3b2561f7..b9f3d455 100644 --- a/src/cls/IPM/ResourceProcessor/CSPApplication.cls +++ b/src/cls/IPM/ResourceProcessor/CSPApplication.cls @@ -17,8 +17,6 @@ Parameter ATTRIBUTES = {""_ // Not in all reasonably modern versions of %Installer.CSPApplication "DispatchClass,MatchRoles,"_ - //CORS properties have not yet been added to the %IPM.ResourceProcessor.CSPApplication applications, so they were included manually. - "CorsCredentialsAllowed,CorsHeadersList,CorsAllowlist,"_ // The rest of these are in all reasonably modern versions of %Installer.CSPApplication // From: Write ##class(%IPM.ResourceProcessor.CSPApplication).GetInheritedProperties() // "Grant" removed (since it's replaced by "MatchRoles") @@ -69,15 +67,6 @@ Property Directory As %String(ATTRIBUTEREQUIRED = 0, MAXLEN = 1024) [ Required ] /// PermittedClasses default Property PermittedClasses As %String(MAXLEN = 32767); -/// CorsCredentialsAllowed -Property CorsCredentialsAllowed As %Boolean [ InitialExpression = 0 ]; - -/// Required CORS headers (Access-Control-Allow-*) -Property CorsHeadersList As %String(MAXLEN = 32767); - -/// Allowed CORS origins (e.g., http://www.example.com) -Property CorsAllowlist As %String(MAXLEN = 32767); - Method %OnNew(pResourceReference As %IPM.Storage.ResourceReference) As %Status [ Private, ServerOnly = 1 ] { set packageName = pResourceReference.Module.Name @@ -286,15 +275,6 @@ Method CreateOrUpdateCSPApp(pVerbose As %Boolean = 0) As %Status [ Internal ] set tSpecial("KerberosAuthEnabled") = "" set tSpecial("Url") = "" - // Cors properties - set tMap("CorsAllowlist")="CorsAllowlist" - set tMap("CorsCredentialsAllowed")="CorsCredentialsAllowed" - set tMap("CorsHeadersList")="CorsHeadersList" - //jwt - set tMap("JWTAccessTokenTimeout")="JWTAccessTokenTimeout" - set tMap("JWTAuthEnabled")="JWTAuthEnabled" - set tMap("JWTRefreshTokenTimeout")="JWTRefreshTokenTimeout" - set tProperties("AutheEnabled") = (..PasswordAuthEnabled * $$$AutheCache) + (..UnauthenticatedEnabled * $$$AutheUnauthenticated) + (..DelegatedAuthEnabled * $$$AutheDelegated) + diff --git a/src/cls/IPM/Storage/ModuleTemplate.cls b/src/cls/IPM/Storage/ModuleTemplate.cls index b8deaf8c..e99bfa79 100644 --- a/src/cls/IPM/Storage/ModuleTemplate.cls +++ b/src/cls/IPM/Storage/ModuleTemplate.cls @@ -254,19 +254,6 @@ Method AddWebApps( set tMap("ServeFilesTimeout") = "ServeFilesTimeout" set tMap("TwoFactorEnabled") = "TwoFactorEnabled" set tMap("TwoFactorEnabled","default") = 0 - // cors - // verify the Cors defined. this check used for "gen" command - // NOTE: These mappings are skipped if the properties do not exist,so they will not cause any conflicts. - set tMap("CorsAllowlist")="CorsAllowlist" - set tMap("CorsAllowlist","default") = $select($data(..TemplateResources(tAppName,"CorsCredentialsAllowed"),tcors):tcors,1:"CorsAllowlist") - set tMap("CorsCredentialsAllowed")="CorsCredentialsAllowed" - set tMap("CorsCredentialsAllowed","default") = $select($data(..TemplateResources(tAppName,"CorsCredentialsAllowed"),tCorsAllowed):tCorsAllowed,1:1) - set tMap("CorsHeadersList")="CorsHeadersList" - set tMap("CorsHeadersList","default") = $select($data(..TemplateResources(tAppName,"CorsHeadersList"),tCorsHeaders):tCorsHeaders,1:"CorsHeadersList") - //jwt - set tMap("JWTAccessTokenTimeout")="JWTAccessTokenTimeout" - set tMap("JWTAuthEnabled")="JWTAuthEnabled" - set tMap("JWTRefreshTokenTimeout")="JWTRefreshTokenTimeout" set tAttr = "" for { @@ -376,7 +363,7 @@ Method SetAuthorProps( Method SetCORSProps( pApps As %String = "", - ByRef pCors As %String = "") As %Status + ByRef pCors As %String = "") { set appList = "" set pApps = $zstrip(pApps,"<>W") @@ -385,13 +372,31 @@ Method SetCORSProps( } else { set appList = $listfromstring(pApps,",") } - set ptr=0 + set ptr = 0 while $listnext(appList, ptr, webpApp){ set ..TemplateResources(webpApp,"CorsCredentialsAllowed") = pCors("enableCors") set ..TemplateResources(webpApp,"CorsHeadersList") = pCors("allowedHeaders") set ..TemplateResources(webpApp,"CorsAllowlist") = pCors("allowedOrigins") } - return $$$OK +} + +Method SetJWTProps( + pApps As %String = "", + ByRef pJWT As %String = "") +{ + set appList = "" + set pApps = $zstrip(pApps,"<>W") + if ( pApps = "*" ) { + do ..GetCSPApplications(.appList) + } else { + set appList = $listfromstring(pApps,",") + } + set ptr = 0 + while $listnext(appList, ptr, webpApp){ + set ..TemplateResources(webpApp,"JWTAccessTokenTimeout") = pJWT("JWTAccessTokenTO") + set ..TemplateResources(webpApp,"JWTAuthEnabled") = pJWT("allowedHeaders") + set ..TemplateResources(webpApp,"JWTRefreshTokenTimeout") = pJWT("JWTRefreshTokenTO") + } } Method ValidateCorsOrigin(pAllowedOrigins As %String = "") As %Status @@ -660,9 +665,9 @@ XData XSLT - + - + diff --git a/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls b/tests/integration_tests/Test/PM/Integration/CorsAndJwtConfigurationTest.cls similarity index 90% rename from tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls rename to tests/integration_tests/Test/PM/Integration/CorsAndJwtConfigurationTest.cls index 2537ce72..4268376c 100644 --- a/tests/integration_tests/Test/PM/Integration/CorsAndJWTTest.cls +++ b/tests/integration_tests/Test/PM/Integration/CorsAndJwtConfigurationTest.cls @@ -1,4 +1,6 @@ -Class Test.PM.Integration.CorsAndJWTTest Extends Test.PM.Integration.Base +/// This class validates that CORS headers and allowed origins are configured correctly, +/// and that JWT authentication is properly set up in the <WebApplication> configuration section. +Class Test.PM.Integration.ConfigCorsAndJWTInWebAppTest Extends Test.PM.Integration.Base { Parameter CommonPathPrefix As STRING = "cors-rest-apps"; diff --git a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls index 694c4254..62cc1a99 100644 --- a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls +++ b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls @@ -5,29 +5,6 @@ Parameter CommonPathPrefix As STRING = "cors-rest-apps"; Parameter WebAppName As STRING = "/testcors"; -/// create web application using tag -Method TestCORSEnabledViaCSPAppTag() -{ - set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) - set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") - set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) - - do $$$LogMessage("loading via tag") - if ##class(%File).Exists(moduleFile){ - set status= ##class(%File).Delete(moduleFile) - do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) - } - set status = ..GenerateTemplate(moduleDir) - do $$$AssertStatusOK(status,"Creating module.xml on "_moduleDir_" successfully. ") - - set status = ##class(%IPM.Main).Shell("load "_moduleDir) - do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) - do ..VerifyCORSSettings() - - set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) - do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") -} - /// create web application using tag Method TestCORSEnabledViaWebAppTag() { @@ -77,34 +54,6 @@ Method VerifyCORSSettings() } } -ClassMethod GenerateTemplate(pPath As %String = "") As %Status -{ - return:(pPath="") $$$OK - set template = ##class(%IPM.Storage.ModuleTemplate).%New() - set template.Name = ..#CommonPathPrefix - set template.VersionString = "1.0.0" - set template.Description = "cors enabling testing on webapplication" - set template.Keywords = "cors" - - set template.Packaging = "module" - set template.SourcesRoot = "src" - - // REST APP - set template.TemplateResources(..#WebAppName) = ..#WebAppName - set template.TemplateResources(..#WebAppName,"Url") = ..#WebAppName - set template.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName - set template.TemplateResources(..#WebAppName,"UseCookies") = 2 - set template.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 - set template.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 - set template.TemplateResources(..#WebAppName,"Recurse") = 1 - ;cors - set template.TemplateResources(..#WebAppName,"CorsAllowlist") = "https://www.example.com,https://pm.intersystems.com" - set template.TemplateResources(..#WebAppName,"CorsCredentialsAllowed") = 1 - set template.TemplateResources(..#WebAppName,"CorsHeadersList") = "Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers, Access-Control-Request-Method, Access-Control-Request-Headers" - do template.ProcessResources() - return template.SaveFile(pPath) -} - XData ModuleWithWebApTag [ MimeType = application/xml ] { diff --git a/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls index d88c0328..55b06855 100644 --- a/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls +++ b/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls @@ -5,29 +5,6 @@ Parameter CommonPathPrefix As STRING = "cors-rest-apps"; Parameter WebAppName As STRING = "/testcors"; -/// create web application using tag -Method TestJWTEnabledViaCspAppTag() -{ - set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) - set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/") - set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) - do $$$LogMessage("loading via tag") - - if ##class(%File).DirectoryExists(moduleFile){ - set status= ##class(%File).Delete(moduleFile) - do $$$AssertStatusOK(status,"file already exist.So, Removed the 'module.xml' file from "_moduleDir) - } - set status = ..GenerateTemplate(moduleDir) - do $$$AssertStatusOK(status,"Creating module.xml on "_moduleDir_" successfully. ") - - set status = ##class(%IPM.Main).Shell("load "_moduleDir) - do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) - do ..VerifyJWTConfiguration() - - set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) - do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") -} - /// create web application using tag Method TestJWTEnabledViaWebAppTag() { @@ -78,34 +55,6 @@ Method VerifyJWTConfiguration() } } -ClassMethod GenerateTemplate(pPath As %String = "") As %Status -{ - return:(pPath="") $$$OK - set template = ##class(%IPM.Storage.ModuleTemplate).%New() - set template.Name = ..#CommonPathPrefix - set template.VersionString = "1.0.0" - set template.Description = "cors enabling testing on webapplication" - set template.Keywords = "cors" - - set template.Packaging = "module" - set template.SourcesRoot = "src" - - // REST APP - set template.TemplateResources(..#WebAppName) = ..#WebAppName - set template.TemplateResources(..#WebAppName,"Url") = ..#WebAppName - set template.TemplateResources(..#WebAppName,"CookiePath") = ..#WebAppName - set template.TemplateResources(..#WebAppName,"UseCookies") = 2 - set template.TemplateResources(..#WebAppName,"PasswordAuthEnabled") = 1 - set template.TemplateResources(..#WebAppName,"UnauthenticatedEnabled") = 0 - set template.TemplateResources(..#WebAppName,"Recurse") = 1 - ;cors - set template.TemplateResources(..#WebAppName,"JWTAccessTokenTimeout") = 60 - set template.TemplateResources(..#WebAppName,"JWTAuthEnabled") = 1 - set template.TemplateResources(..#WebAppName,"JWTRefreshTokenTimeout") = 900 - do template.ProcessResources() - return template.SaveFile(pPath) -} - XData ModuleWithWebApTag [ MimeType = application/xml ] { From eb36e6bd87ab3107d652618231dfaf05e58fa756 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Wed, 3 Dec 2025 00:22:13 +0530 Subject: [PATCH 4/6] fix: update Integration testdata module.xml --- .../Test/PM/Integration/_data/cors-rest-apps/module.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml b/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml index 51f4bd42..bc003eef 100644 --- a/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml +++ b/tests/integration_tests/Test/PM/Integration/_data/cors-rest-apps/module.xml @@ -8,7 +8,7 @@ cors module - Date: Wed, 3 Dec 2025 13:02:54 +0530 Subject: [PATCH 5/6] Refactor: Move integration tests to ResourceProcessor.WebApplication and remove unit test classes --- .../WebApplication.cls} | 2 +- .../Test/PM/Unit/WebAppCorsTest.cls | 80 ------------------ .../Test/PM/Unit/WebAppJWTConfigTest.cls | 82 ------------------- 3 files changed, 1 insertion(+), 163 deletions(-) rename tests/integration_tests/Test/PM/Integration/{CorsAndJwtConfigurationTest.cls => ResourceProcessor/WebApplication.cls} (96%) delete mode 100644 tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls delete mode 100644 tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls diff --git a/tests/integration_tests/Test/PM/Integration/CorsAndJwtConfigurationTest.cls b/tests/integration_tests/Test/PM/Integration/ResourceProcessor/WebApplication.cls similarity index 96% rename from tests/integration_tests/Test/PM/Integration/CorsAndJwtConfigurationTest.cls rename to tests/integration_tests/Test/PM/Integration/ResourceProcessor/WebApplication.cls index 4268376c..ae2a9afc 100644 --- a/tests/integration_tests/Test/PM/Integration/CorsAndJwtConfigurationTest.cls +++ b/tests/integration_tests/Test/PM/Integration/ResourceProcessor/WebApplication.cls @@ -1,6 +1,6 @@ /// This class validates that CORS headers and allowed origins are configured correctly, /// and that JWT authentication is properly set up in the <WebApplication> configuration section. -Class Test.PM.Integration.ConfigCorsAndJWTInWebAppTest Extends Test.PM.Integration.Base +Class Test.PM.Integration.ResourceProcessor.WebApplication Extends Test.PM.Integration.Base { Parameter CommonPathPrefix As STRING = "cors-rest-apps"; diff --git a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls deleted file mode 100644 index 62cc1a99..00000000 --- a/tests/unit_tests/Test/PM/Unit/WebAppCorsTest.cls +++ /dev/null @@ -1,80 +0,0 @@ -Class Test.PM.Unit.WebAppCorsTest Extends %UnitTest.TestCase -{ - -Parameter CommonPathPrefix As STRING = "cors-rest-apps"; - -Parameter WebAppName As STRING = "/testcors"; - -/// create web application using tag -Method TestCORSEnabledViaWebAppTag() -{ - do $$$LogMessage("loading via tag") - set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) - set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") - if '##class(%File).DirectoryExists(moduleDir) { - do ##class(%File).CreateDirectoryChain(moduleDir) - } - set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) - if ##class(%File).Exists(moduleFile){ - set status= ##class(%File).Delete(moduleFile) - do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) - } - do $$$LogMessage("Creating module.xml with tag") - set moduleStream = ##class(%Dictionary.XDataDefinition).%OpenId($classname()_"||ModuleWithWebApTag").Data - set fileStream = ##class(%Stream.FileBinary).%New() - set fileStream.Filename=moduleFile - set status= fileStream.CopyFromAndSave(moduleStream) - do $$$AssertStatusOK(status,"Created module.xml manually on "_moduleDir_" successfully.") - - set status = ##class(%IPM.Main).Shell("load "_moduleDir) - do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) - do ..VerifyCORSSettings() - - set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) - do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") -} - -Method VerifyCORSSettings() -{ - new $namespace - set $namespace = "%SYS" - set status = ##class(Security.Applications).Get(..#WebAppName,.props) - do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") - if $data(props("CorsAllowlist"),corsAllowlist)&&(corsAllowlist'="") { - do $$$AssertStatusOK(1,"CorsAllowlist values are defined") - do $$$LogMessage(corsAllowlist) - } - if $data(props("CorsCredentialsAllowed"),corsAllow)&&(corsAllow'="") { - do $$$AssertStatusOK(1,"CorsCredentialsAllowed values are defined") - do $$$LogMessage(corsAllow) - } - if $data(props("CorsHeadersList"),corsHeadersList)&&(corsHeadersList'="") { - do $$$AssertStatusOK(1,"CorsHeadersList values are defined") - do $$$LogMessage(corsHeadersList) - } -} - -XData ModuleWithWebApTag [ MimeType = application/xml ] -{ - - - - - cors-rest-apps - 1.0.0 - cors enabling testing on webapplication - cors - module - - %IPM.Lifecycle.Module - src - - - -} - -} diff --git a/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls b/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls deleted file mode 100644 index 55b06855..00000000 --- a/tests/unit_tests/Test/PM/Unit/WebAppJWTConfigTest.cls +++ /dev/null @@ -1,82 +0,0 @@ -Class Test.PM.Unit.WebAppJWTConfigTest Extends %UnitTest.TestCase -{ - -Parameter CommonPathPrefix As STRING = "cors-rest-apps"; - -Parameter WebAppName As STRING = "/testcors"; - -/// create web application using tag -Method TestJWTEnabledViaWebAppTag() -{ - do $$$LogMessage("loading via tag") - set testRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) - set moduleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(testRoot)_"/_data/"_..#CommonPathPrefix_"/webapp/") - if '##class(%File).DirectoryExists(moduleDir) { - do ##class(%File).CreateDirectoryChain(moduleDir) - } - set moduleFile = ##class(%File).NormalizeFilename("module.xml",moduleDir) - if ##class(%File).Exists(moduleFile){ - set status= ##class(%File).Delete(moduleFile) - do $$$AssertStatusOK(status,"Removed the 'module.xml' file from "_moduleDir) - } - do $$$LogMessage("Creating module.xml with tag") - set moduleStream = ##class(%Dictionary.XDataDefinition).%OpenId($classname()_"||ModuleWithWebApTag").Data - set fileStream = ##class(%Stream.FileBinary).%New() - set fileStream.Filename=moduleFile - set status= fileStream.CopyFromAndSave(moduleStream) - do $$$AssertStatusOK(status,"Created module.xml manually on "_moduleDir_" successfully.") - - set status = ##class(%IPM.Main).Shell("load "_moduleDir) - do $$$AssertStatusOK(status,"Loaded "_..#CommonPathPrefix_" module successfully. "_moduleDir) - do ..VerifyJWTConfiguration() - - set status = ##class(%IPM.Main).Shell("uninstall "_..#CommonPathPrefix) - do $$$AssertStatusOK(status,"uninstalled "_..#CommonPathPrefix_" module successfully.") -} - -Method VerifyJWTConfiguration() -{ - new $namespace - set $namespace = "%SYS" - set status = ##class(Security.Applications).Get(..#WebAppName,.props) - do $$$AssertStatusOK(status,"Web applciation "_..#WebAppName_" created scuccessfully") - do $$$LogMessage("Validating JWT configuration") - if $data(props("JWTAccessTokenTimeout"),tJWTAccessTokenTimeout) { - do $$$AssertStatusOK(1,"JWTAccessTokenTimeout value is defined") - do $$$LogMessage(tJWTAccessTokenTimeout) - } - if $data(props("JWTAuthEnabled"),tJWTAuthEnabled) { - do $$$AssertStatusOK(1,"JWTAuthEnabled value is defined") - do $$$LogMessage(tJWTAuthEnabled) - } - if $data(props("JWTRefreshTokenTimeout"),tJWTRefreshTokenTimeout) { - do $$$AssertStatusOK(1,"JWTRefreshTokenTimeout value is defined") - do $$$LogMessage(tJWTRefreshTokenTimeout) - } -} - -XData ModuleWithWebApTag [ MimeType = application/xml ] -{ - - - - - cors-rest-apps - 1.0.0 - cors enabling testing on webapplication - cors - module - - %IPM.Lifecycle.Module - src - - - -} - -} From eb909482da974d47ee47e07c449306ee7ffdc047 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Fri, 9 Jan 2026 16:11:33 +0530 Subject: [PATCH 6/6] fix: updated the code and add changelog entry --- CHANGELOG.md | 2 +- src/cls/IPM/Main.cls | 10 +++--- src/cls/IPM/Storage/ModuleTemplate.cls | 45 +++++++++++++------------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 483e5619..6c92088b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #938 Added flag -export-python-deps to package command - #462: The `repo` command for repository configuration now supports secret input terminal mode for passwords with the `-password-stdin` flag - #935: Adding a generic JFrog Artifactory tarball resource processor for bundling artifact with a package and deploying it to a final location on install. - +- #973: Enables CORS and JWT configuration for WebApplications in module xml ### Changed - #316: All parameters, except developer mode, included with a `load`, `install` or `update` command will be propagated to dependencies diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index 64540c57..0e579e73 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -1305,13 +1305,13 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] } do ##class(%Library.Prompt).GetString(" Enter a comma separated list of web applications or * for all:", .tWebAppList) -#If $$$CacheVersionMajor>=2025 +#if $$$CacheVersionMajor>=2025 //cors write !,"Cross-Origin Settings:" do ##class(%Library.Prompt).GetYesNo("Configure Access-Control-Allow-Credentials:",.enableCors) if enableCors { do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Headers:",.allowedHeaders,,32767,"comma seperated values e.g: Access-Control-Allow-Origin,Access-Control-Allow-Headers") - while (1){ + while (1) { do ##class(%Library.Prompt).GetString(" Enter a comma separated list of Allowed Origins:",.allowedOrigins,,32767,"comma seperated values e.g: http://www.example.com") if allowedOrigins="" quit set sc = tTemplate.ValidateCorsOrigin(allowedOrigins) @@ -1327,9 +1327,9 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] set corsConfig("allowedOrigins") = allowedOrigins do tTemplate.SetCORSProps(tWebAppList,.corsConfig) } -#EndIf +#endif -#If $$$CacheVersionMajor>=2024 +#if $$$CacheVersionMajor>=2024 //jwt write !,"JWT Authentication" do ##class(%Library.Prompt).GetYesNo("Configure JWT Authentication: ",.enableJWT) @@ -1341,7 +1341,7 @@ ClassMethod GenerateModuleXML(ByRef pCommandInfo) As %Status [ Internal ] set jwtconfig("JWTRefreshTokenTO") = JWTRefreshTokenTO do tTemplate.SetJWTProps(tWebAppList,.jwtconfig) } -#EndIf +#endif do tTemplate.AddWebApps(tWebAppList,.tCSPapps) // tCSP - list of CSP (not REST apps) for i=1:1:$listlength(tCSPapps) { diff --git a/src/cls/IPM/Storage/ModuleTemplate.cls b/src/cls/IPM/Storage/ModuleTemplate.cls index e99bfa79..14bc4f8a 100644 --- a/src/cls/IPM/Storage/ModuleTemplate.cls +++ b/src/cls/IPM/Storage/ModuleTemplate.cls @@ -185,7 +185,7 @@ Method AddWebApps( { set tAppList = "" set pApps = $zstrip(pApps,"<>W") - if ( pApps = "*" ) { + if (pApps = "*") { do ..GetCSPApplications(.tAppList) } else { set tAppList = $listfromstring(pApps,",") @@ -362,40 +362,40 @@ Method SetAuthorProps( } Method SetCORSProps( - pApps As %String = "", - ByRef pCors As %String = "") + apps As %String = "", + ByRef cors) { set appList = "" - set pApps = $zstrip(pApps,"<>W") - if ( pApps = "*" ) { - do ..GetCSPApplications(.appList) + set apps = $zstrip(apps,"<>W") + if (apps = "*") { + $$$ThrowOnError(..GetCSPApplications(.appList)) } else { - set appList = $listfromstring(pApps,",") + set appList = $listfromstring(apps,",") } set ptr = 0 - while $listnext(appList, ptr, webpApp){ - set ..TemplateResources(webpApp,"CorsCredentialsAllowed") = pCors("enableCors") - set ..TemplateResources(webpApp,"CorsHeadersList") = pCors("allowedHeaders") - set ..TemplateResources(webpApp,"CorsAllowlist") = pCors("allowedOrigins") + while $listnext(appList, ptr, webpApp) { + set ..TemplateResources(webpApp,"CorsCredentialsAllowed") = cors("enableCors") + set ..TemplateResources(webpApp,"CorsHeadersList") = cors("allowedHeaders") + set ..TemplateResources(webpApp,"CorsAllowlist") = cors("allowedOrigins") } } Method SetJWTProps( - pApps As %String = "", - ByRef pJWT As %String = "") + apps As %String = "", + ByRef jwt) { set appList = "" - set pApps = $zstrip(pApps,"<>W") - if ( pApps = "*" ) { - do ..GetCSPApplications(.appList) + set apps = $zstrip(apps,"<>W") + if (apps = "*") { + $$$ThrowOnError(..GetCSPApplications(.appList)) } else { - set appList = $listfromstring(pApps,",") + set appList = $listfromstring(apps,",") } set ptr = 0 - while $listnext(appList, ptr, webpApp){ - set ..TemplateResources(webpApp,"JWTAccessTokenTimeout") = pJWT("JWTAccessTokenTO") - set ..TemplateResources(webpApp,"JWTAuthEnabled") = pJWT("allowedHeaders") - set ..TemplateResources(webpApp,"JWTRefreshTokenTimeout") = pJWT("JWTRefreshTokenTO") + while $listnext(appList, ptr, webpApp) { + set ..TemplateResources(webpApp,"JWTAccessTokenTimeout") = jwt("JWTAccessTokenTO") + set ..TemplateResources(webpApp,"JWTAuthEnabled") = jwt("allowedHeaders") + set ..TemplateResources(webpApp,"JWTRefreshTokenTimeout") = jwt("JWTRefreshTokenTO") } } @@ -585,7 +585,8 @@ Method SetTemplateProps() As %Status set ..TemplateResources("rest","PasswordAuthEnabled") = 1 set ..TemplateResources("rest","UnauthenticatedEnabled") = 0 set ..TemplateResources("rest","Recurse") = 1 - ;cors + + //CORS set ..TemplateResources("rest","CorsAllowlist") = "http://www.example.com" set ..TemplateResources("rest","CorsCredentialsAllowed") = 1 set ..TemplateResources("rest","CorsHeadersList") = "Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers, Origin, Access-Control-Request-Method, Access-Control-Request-Headers"