Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
318 changes: 224 additions & 94 deletions omod/src/main/java/org/openmrs/web/security/DwrAuthorizationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package org.openmrs.web.security;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
Expand All @@ -20,6 +21,7 @@
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
Expand All @@ -30,36 +32,56 @@
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Context;
import org.openmrs.api.context.UserContext;
import org.openmrs.util.OpenmrsClassLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
* Servlet filter that enforces {@link RequirePrivilege} on every DWR method invocation served by
* the legacyui module. DWR has its own request pipeline ({@code /dwr/*},
* {@code /ms/call/plaincall/*}) outside Spring's {@code DispatcherServlet}, so
* {@link AuthorizationHandlerInterceptor} does not see these calls. This filter is the
* request-boundary check for that pipeline.
* Servlet filter that enforces {@link RequirePrivilege}, where it's actually declared, on DWR
* method invocations. DWR has its own request pipeline ({@code /dwr/*}, {@code /ms/call/plaincall/*})
* outside Spring's {@code DispatcherServlet}, so {@link AuthorizationHandlerInterceptor} does not
* see these calls; this filter is the request-boundary check for that pipeline, for the subset of
* DWR methods that opt into it.
* <p>
* At {@link #init(FilterConfig)} the filter parses {@code config.xml} from the classpath, walks
* every {@code <create>}/{@code <include method="...">} declaration, and resolves each exposed
* method to a {@link RequirePrivilege} annotation on the corresponding Java method. Methods
* without an annotation are rejected at request time with {@code 403 Forbidden}; a warning is
* logged at startup so any new DWR endpoint that ships without an annotation is visible.
* {@code WebModuleUtil#startModule} already folds every installed module's {@code <dwr>} block -
* legacyui's own included - into one combined {@code WEB-INF/dwr-modules.xml}, which is the exact
* input DWR's own engine parses to decide what is callable. This filter reads that same file
* (see {@link #getDwrModulesXmlFile(FilterConfig)}) rather than re-deriving the merge itself, so
* its view of "what DWR methods exist" can never drift from DWR's own. It re-checks the file's
* last-modified time on every request ({@link #reloadIfChanged()}) and re-scans when it changes,
* so a module started, stopped, or updated after the web application boots is picked up without
* requiring a manual filter re-init. Each {@code <create>}/{@code <include method="...">}
* declaration is resolved to a {@link RequirePrivilege} annotation on the corresponding Java
* method, using {@link OpenmrsClassLoader} to load the class regardless of which module it
* belongs to.
* <p>
* Methods without the annotation are <strong>not</strong> blocked - this filter fails open on a
* missing annotation, deliberately, matching {@link AuthorizationHandlerInterceptor}'s own
* fail-open behavior for unannotated controllers. Many DWR methods just delegate to a service
* method that already enforces its own {@code @Authorized} privileges, and some (a login-style
* method meant to be called before authentication, for instance) must not be gated by this filter
* at all; forcing every DWR method across every module through one blanket privilege model here
* would be a requirement this filter has no business imposing. A warning is logged once at
* startup listing every method exposed without the annotation, purely for visibility - it is not
* enforcement.
* <p>
* When no real webapp deployment is available - this filter's own unit tests, for instance,
* which call {@link #init(FilterConfig)} with {@code null} - {@code dwr-modules.xml} can't be
* located. In that case the filter falls back to scanning legacyui's own {@code config.xml} off
* this class's classloader; other modules' DWR methods are not visible in that fallback mode.
* <p>
* For each request:
* <ol>
* <li>If the URL is not a DWR method call (e.g. {@code /dwr/engine.js},
* {@code /dwr/interface/Foo.js}), pass it through unchanged.</li>
* <li>If the URL is a method call, require authentication. Unauthenticated requests get
* {@code 401 Unauthorized}.</li>
* <li>Look up {@code {Script}.{method}}; if no annotation is registered (either because the
* pair is unknown or because the method is exposed in {@code config.xml} without
* {@link RequirePrivilege}), reject with {@code 403 Forbidden}.</li>
* <li>Look up {@code {Script}.{method}}; if it carries no {@link RequirePrivilege} (either
* because the pair is unknown or because the method is exposed in {@code config.xml} without
* the annotation), pass it through unchanged - see above.</li>
* <li>Otherwise require authentication. Unauthenticated requests get {@code 401 Unauthorized}.</li>
* <li>Enforce the annotation's privilege list. Missing privileges yield {@code 403 Forbidden}.</li>
* </ol>
* <p>
Expand All @@ -81,28 +103,144 @@

private Map<String, String> unannotatedMethods = Collections.emptyMap();

/**
* The aggregated DWR config file to watch, resolved once at {@link #init(FilterConfig)}.
* {@code null} when no real webapp deployment is available, in which case the filter falls
* back to a one-time scan of legacyui's own classpath {@code config.xml}.
*/
private File dwrModulesXmlFile;

private volatile long dwrModulesXmlLastModified = -1;

/** Guards {@link #fallbackLoaded} so the classpath fallback only ever runs once. */
private final Object reloadLock = new Object();

private boolean fallbackLoaded = false;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
try {
this.dwrModulesXmlFile = getDwrModulesXmlFile(filterConfig);
reloadIfChanged();
}
catch (Exception e) {
throw new ServletException("Failed to initialize DwrAuthorizationFilter", e);
}
}

/**
* Resolves the {@code WEB-INF/dwr-modules.xml} written by {@code WebModuleUtil#startModule},
* mirroring exactly how that class computes the same path from a {@code ServletContext}.
* Returns {@code null} if {@code filterConfig} (or its context) is unavailable, or if the
* context can't resolve a real filesystem path (e.g. an unexploded/embedded deployment).
*/
private File getDwrModulesXmlFile(FilterConfig filterConfig) {
if (filterConfig == null) {
return null;
}
ServletContext servletContext = filterConfig.getServletContext();
if (servletContext == null) {
return null;
}
String realPath = servletContext.getRealPath("/WEB-INF/dwr-modules.xml");
return realPath == null ? null : new File(realPath);
}

/**
* Re-scans {@link #dwrModulesXmlFile} if its last-modified time has changed since the last
* scan (including the very first one), so a module started, stopped, or updated after boot
* is reflected without requiring this filter to be re-initialized. A cheap no-op otherwise -
* just one {@code File#lastModified()} stat per request.
*/
private void reloadIfChanged() throws Exception {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything in DwrAuthorizationFilterTest still calls init(null), so only the classpath fallback ever runs under test. The path production actually takes (resolving dwr-modules.xml through the ServletContext, scanning it with the OpenmrsClassLoader, and this reload-on-mtime-change logic) has no coverage, and it's the part of this PR with real moving pieces. Since 2.1.0 already shipped a version of this filter whose production behavior diverged from what the tests exercised, I'd add a test for the primary path too. Not blocking on it, and it needs nothing new on the test classpath. I ran this shape against this branch and both tests pass:

public class DwrAuthorizationFilterPrimaryPathTest extends BaseModuleWebContextSensitiveTest {
	
	private File webappRoot;
	
	private File dwrXml;
	
	private DwrAuthorizationFilter filter;
	
	private RecordingFilterChain chain;
	
	@BeforeEach
	public void setUpFilter() throws Exception {
		webappRoot = Files.createTempDirectory("dwr-webapp").toFile();
		File webInf = new File(webappRoot, "WEB-INF");
		assertTrue(webInf.mkdirs() || webInf.isDirectory());
		dwrXml = new File(webInf, "dwr-modules.xml");
		writeDwrXml(false);
		// a "file:" resource base makes MockServletContext.getRealPath resolve into the temp dir
		MockServletContext servletContext = new MockServletContext("file:" + webappRoot.getAbsolutePath());
		filter = new DwrAuthorizationFilter();
		filter.init(new MockFilterConfig(servletContext, "dwrAuthorization"));
		chain = new RecordingFilterChain();
	}
	
	private void writeDwrXml(boolean includeStopMethod) throws Exception {
		String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<dwr>\n  <allow moduleId=\"legacyui\">\n"
		        + "    <create creator=\"new\" javascript=\"DWRHL7Service\">\n"
		        + "      <param name=\"class\" value=\"org.openmrs.web.dwr.DWRHL7Service\"/>\n"
		        + "      <include method=\"startHl7ArchiveMigration\"/>\n"
		        + (includeStopMethod ? "      <include method=\"stopHl7ArchiveMigration\"/>\n" : "")
		        + "    </create>\n  </allow>\n</dwr>\n";
		Files.write(dwrXml.toPath(), xml.getBytes(StandardCharsets.UTF_8));
	}
	
	@Test
	public void init_shouldScanAggregatedDwrModulesXmlNotClasspathFallback() {
		// the fallback (legacyui's own config.xml) would register far more than one method
		assertEquals(1, filter.getPrivilegesByScriptMethod().size());
		assertNotNull(filter.getPrivilegesByScriptMethod().get("DWRHL7Service.startHl7ArchiveMigration"));
	}
	
	@Test
	public void doFilter_shouldPickUpChangesToDwrModulesXml() throws Exception {
		assertNull(filter.getPrivilegesByScriptMethod().get("DWRHL7Service.stopHl7ArchiveMigration"));
		
		long before = dwrXml.lastModified();
		writeDwrXml(true);
		assertTrue(dwrXml.setLastModified(before + 5000), "must bump mtime past fs timestamp granularity");
		
		Context.logout();
		MockHttpServletRequest request = new MockHttpServletRequest("POST",
		    "/openmrs/dwr/call/plaincall/DWRHL7Service.stopHl7ArchiveMigration.dwr");
		MockHttpServletResponse response = new MockHttpServletResponse();
		
		filter.doFilter(request, response, chain);
		
		assertEquals(401, response.getStatus(),
		    "after reload the method is annotated, so an unauthenticated call must be rejected");
		assertFalse(chain.invoked);
	}
}

(RecordingFilterChain as in the existing test class.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added DwrAuthorizationFilterPrimaryPathTest in 4760e31, using this shape as-is (adjusted only for imports/package). Both pass: init_shouldScanAggregatedDwrModulesXmlNotClasspathFallback and doFilter_shouldPickUpChangesToDwrModulesXml. Confirms the primary path (ServletContext-resolved dwr-modules.xml, OpenmrsClassLoader scan, mtime-triggered reload) actually works, not just the classpath fallback.

if (dwrModulesXmlFile == null || !dwrModulesXmlFile.exists()) {
loadFallbackOnce();
return;
}

long modified = dwrModulesXmlFile.lastModified();
if (modified == dwrModulesXmlLastModified) {
return;
}

synchronized (reloadLock) {
if (modified == dwrModulesXmlLastModified) {
return; // another thread already reloaded while we were waiting
}

Map<String, RequirePrivilege> annotated = new HashMap<>();
Map<String, String> unannotated = new LinkedHashMap<>();

DocumentBuilderFactory dbf = newSecureDocumentBuilderFactory();
Document doc = dbf.newDocumentBuilder().parse(dwrModulesXmlFile);
scanDwrCreates(doc, OpenmrsClassLoader.getInstance(), annotated, unannotated);

applyLoadedAnnotations(annotated, unannotated);
dwrModulesXmlLastModified = modified;
}
}

/**
* Fallback for contexts with no real servlet deployment, e.g. this filter's own unit tests
* calling {@link #init(FilterConfig)} with {@code null}. Scans legacyui's own
* {@code config.xml} directly off this class's classloader; other modules' DWR methods are
* not visible in this mode, since there is no aggregated file to read them from.
*/
private void loadFallbackOnce() throws Exception {
synchronized (reloadLock) {
if (fallbackLoaded) {
return;
}

Map<String, RequirePrivilege> annotated = new HashMap<>();
Map<String, String> unannotated = new LinkedHashMap<>();
loadDwrAnnotations(annotated, unannotated);
this.privilegesByScriptMethod = Collections.unmodifiableMap(annotated);
this.unannotatedMethods = Collections.unmodifiableMap(unannotated);

log.info("DwrAuthorizationFilter loaded; annotated DWR methods: " + annotated.size()
+ ", unannotated (will be rejected with 403): " + unannotated.size());
if (!unannotated.isEmpty()) {
log.warn("The following DWR methods are exposed in config.xml without "
+ "@RequirePrivilege and will be rejected with 403: "
+ unannotated.keySet());

ClassLoader ownClassLoader = getClass().getClassLoader();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest we just use OpenmrsClassLoader here. Otherwise this might run in the context of the ModuleClassLoader for the LegacyUI.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked OpenmrsClassLoader before making this change, and I think switching here would introduce a real bug rather than fix one. OpenmrsClassLoader#getResourceAsStream iterates ModuleFactory.getModuleClassLoaders() (a Guava cache with no guaranteed iteration order) and returns the first non-null hit. Every module's jar has a top-level config.xml, so on a real instance with multiple modules loaded, OpenmrsClassLoader.getInstance().getResourceAsStream("config.xml") can return a different module's config.xml instead of legacyui's own, non-deterministically. getClass().getClassLoader() here is specifically legacyui's own ModuleClassLoader (this class is only ever loaded by it), so it's guaranteed to resolve legacyui's own config.xml unambiguously - which is exactly what this fallback path needs, since it only ever wants to scan legacyui's own DWR declarations. Left this one as-is; happy to reconsider if I'm missing something about how OpenmrsClassLoader resolves ambiguous resource names in practice.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This is Claude's response @ibacher - lmk if you disagree)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at how this actually resolves in core before responding, and I agree with Mike on this one. getModuleClassLoaders() is backed by a Guava cache with weak keys (ModuleFactory.moduleClassLoaders, exposed as asMap().values()), so iteration order is arbitrary and can differ between restarts. Since every installed module carries config.xml at the root of its omod (ModuleFileParser requires it there), OpenmrsClassLoader#getResourceAsStream("config.xml") can hand back any module's file. And because this filter now fails open, that wouldn't just be untidy: if some other module's config.xml wins the race in fallback mode, legacyui's own DWR methods drop out of the privileges map and become callable without authentication. Our tests wouldn't notice either, since in the test environment both classloaders resolve the same file.

The current line avoids that ambiguity for a reason that took me a minute to convince myself of: a ModuleClassLoader never has OpenmrsClassLoader as its parent (the constructor throws if you try; ModuleFactory passes the webapp classloader), and ModuleClassLoader#findResource checks the module's own URLs before its required and aware-of modules. So getClass().getClassLoader() resolves legacyui's own config.xml deterministically, in tests and in production. Where cross-module resolution really is needed, the dwr-modules.xml scan, this PR already uses OpenmrsClassLoader.getInstance(), which I think covers the intent here. I would keep this line as it is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... actually, yes, this is fair and I think my comment was based on misunderstanding what this method is doing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other feedback on this then? Are we good to merge?

try (InputStream in = ownClassLoader.getResourceAsStream("config.xml")) {
if (in == null) {
log.warn("config.xml not found on classpath; DwrAuthorizationFilter will allow no DWR calls");
} else {
DocumentBuilderFactory dbf = newSecureDocumentBuilderFactory();
Document doc = dbf.newDocumentBuilder().parse(in);
scanDwrCreates(doc, ownClassLoader, annotated, unannotated);
}
}

applyLoadedAnnotations(annotated, unannotated);
fallbackLoaded = true;
}
catch (Exception e) {
throw new ServletException("Failed to initialize DwrAuthorizationFilter", e);
}

private void applyLoadedAnnotations(Map<String, RequirePrivilege> annotated, Map<String, String> unannotated) {
this.privilegesByScriptMethod = Collections.unmodifiableMap(annotated);
this.unannotatedMethods = Collections.unmodifiableMap(unannotated);

log.info("DwrAuthorizationFilter (re)loaded; annotated DWR methods: " + annotated.size()
+ ", unannotated (will be rejected with 403): " + unannotated.size());
if (!unannotated.isEmpty()) {
log.warn("The following DWR methods are exposed without @RequirePrivilege and will be "
+ "rejected with 403: " + unannotated.keySet());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These messages still describe the old fail-closed behavior. With this PR, unannotated methods are passed through, not rejected with 403 (the class javadoc itself now says the warning "is not enforcement"). If this merges as-is, the first thing an operator sees after upgrading to fix LUI-212 is a WARN listing every reportingcompatibility DWR method as "will be rejected with 403", claiming the very breakage this PR removes is still in place, and anyone auditing DWR exposure from the logs will conclude those endpoints are blocked when they are in fact open. Two nearby spots have the same staleness: the fallback message in loadFallbackOnce says "will allow no DWR calls" when an empty map now means the filter gates nothing (everything passes through), and the field javadoc on privilegesByScriptMethod still says unannotated lookups "fall back to authentication-only", which is also not what happens (they pass through with no auth at all). Worth fixing all three before merge:

Suggested change
log.info("DwrAuthorizationFilter (re)loaded; annotated DWR methods: " + annotated.size()
+ ", unannotated (will be rejected with 403): " + unannotated.size());
if (!unannotated.isEmpty()) {
log.warn("The following DWR methods are exposed without @RequirePrivilege and will be "
+ "rejected with 403: " + unannotated.keySet());
log.info("DwrAuthorizationFilter (re)loaded; annotated DWR methods: " + annotated.size()
+ ", unannotated (not gated by this filter): " + unannotated.size());
if (!unannotated.isEmpty()) {
log.warn("The following DWR methods are exposed without @RequirePrivilege and are not gated "
+ "by this filter; only their own service-layer checks apply: " + unannotated.keySet());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4760e31 (all three spots): the log.info/log.warn in applyLoadedAnnotations, the loadFallbackOnce warning (now "will gate no DWR calls"), and the privilegesByScriptMethod field javadoc (now says unannotated lookups pass straight through with no auth check at all, not "fall back to authentication-only").

}
}

/**
* {@code dwr-modules.xml} legitimately declares a DOCTYPE referencing DWR's own public DTD
* (e.g. {@code <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
* "http://directwebremoting.org/schema/dwr20.dtd">}), so unlike a typical anti-XXE setup we
* can't reject every DOCTYPE outright - that would fail every parse. Instead this allows the
* declaration but never fetches the (external, network-reachable) DTD it names and never
* resolves external entities - the OWASP-recommended shape for "parse XML with a DOCTYPE,
* safely", and one that also keeps this working with no network access at all.
*/
private DocumentBuilderFactory newSecureDocumentBuilderFactory() throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
return dbf;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
Expand All @@ -116,25 +254,34 @@
return;
}

UserContext userContext = Context.getUserContext();
if (userContext == null || !userContext.isAuthenticated()) {
deny(httpResponse, HttpServletResponse.SC_UNAUTHORIZED,
"Authentication required to invoke " + scriptMethod);
return;
try {
reloadIfChanged();
}
catch (Exception e) {
// Keep serving whatever was last successfully loaded rather than failing every
// request; a transient read/parse error here shouldn't take down all of DWR.
log.error("Failed to (re)load DWR authorization config; continuing with the previous snapshot", e);
}

RequirePrivilege annotation = privilegesByScriptMethod.get(scriptMethod);
if (annotation == null) {
// Two cases collapse to the same fail-closed response:
// - The {Script}.{method} is declared in config.xml but the Java method lacks
// @RequirePrivilege (a developer added a DWR method without annotating it).
// - The {Script}.{method} pair is not declared in config.xml at all (the DWR
// servlet would 404 anyway, but reject defensively in case of routing surprises).
if (unannotatedMethods.containsKey(scriptMethod)) {
log.warn("DWR method " + scriptMethod
+ " is declared in config.xml but has no @RequirePrivilege; rejecting");
}
deny(httpResponse, HttpServletResponse.SC_FORBIDDEN, "Forbidden");
// No @RequirePrivilege on this method - either it's declared in config.xml without
// the annotation, or the {Script}.{method} pair isn't declared anywhere we scanned.
// This filter does not fail closed on that: many DWR methods just delegate to a
// service method that already enforces its own @Authorized privileges, and some
// (e.g. a login-style method meant to be called before authentication) must not be
// gated at all. Forcing every DWR method through this filter's own privilege model
// would be a blanket requirement this filter has no business imposing. Visibility
// into which methods lack the annotation comes from the startup log
// (see #applyLoadedAnnotations), not from blocking each request here.
chain.doFilter(request, response);
return;
}

UserContext userContext = Context.getUserContext();
if (userContext == null || !userContext.isAuthenticated()) {
deny(httpResponse, HttpServletResponse.SC_UNAUTHORIZED,
"Authentication required to invoke " + scriptMethod);
return;
}

Expand Down Expand Up @@ -222,61 +369,44 @@
}

/**
* Parses {@code config.xml} from the legacyui classpath and resolves each declared DWR
* method to its {@link RequirePrivilege} annotation. Methods without the annotation are
* collected separately so the filter can warn at startup rather than fail outright while
* the rollout is in progress.
* Walks every {@code <create>}/{@code <include method="...">} declaration in {@code doc},
* resolving each to a {@link RequirePrivilege} annotation via {@code classLoader}.
*/
private void loadDwrAnnotations(Map<String, RequirePrivilege> annotated, Map<String, String> unannotated)
throws Exception {
ClassLoader classLoader = getClass().getClassLoader();
try (InputStream in = classLoader.getResourceAsStream("config.xml")) {
if (in == null) {
log.warn("config.xml not found on classpath; DwrAuthorizationFilter will allow all DWR calls");
return;
private void scanDwrCreates(Document doc, ClassLoader classLoader, Map<String, RequirePrivilege> annotated,

Check failure on line 375 in omod/src/main/java/org/openmrs/web/security/DwrAuthorizationFilter.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=openmrs_openmrs-module-legacyui&issues=AZ9_3bNV1Ov7pQ8yajA-&open=AZ9_3bNV1Ov7pQ8yajA-&pullRequest=268
Map<String, String> unannotated) {
NodeList creates = doc.getElementsByTagName("create");
for (int i = 0; i < creates.getLength(); i++) {
Element create = (Element) creates.item(i);
String script = create.getAttribute("javascript");
if (script == null || script.isEmpty()) {
continue;
}
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(in);

NodeList creates = doc.getElementsByTagName("create");
for (int i = 0; i < creates.getLength(); i++) {
Element create = (Element) creates.item(i);
String script = create.getAttribute("javascript");
if (script == null || script.isEmpty()) {
continue;
}
String className = findClassParam(create);
if (className == null) {
log.warn("DWR script '" + script + "' has no <param name=\"class\"/>; skipping");
continue;
}
Class<?> dwrClass;
try {
dwrClass = Class.forName(className, false, classLoader);
}
catch (ClassNotFoundException e) {
log.warn("DWR script '" + script + "' references missing class " + className + "; skipping");
String className = findClassParam(create);
if (className == null) {
log.warn("DWR script '" + script + "' has no <param name=\"class\"/>; skipping");
continue;
}
Class<?> dwrClass;
try {
dwrClass = Class.forName(className, false, classLoader);
}
catch (ClassNotFoundException e) {
log.warn("DWR script '" + script + "' references missing class " + className + "; skipping");
continue;
}
NodeList includes = create.getElementsByTagName("include");
for (int j = 0; j < includes.getLength(); j++) {
Element include = (Element) includes.item(j);
String methodName = include.getAttribute("method");
if (methodName == null || methodName.isEmpty()) {
continue;
}
NodeList includes = create.getElementsByTagName("include");
for (int j = 0; j < includes.getLength(); j++) {
Element include = (Element) includes.item(j);
String methodName = include.getAttribute("method");
if (methodName == null || methodName.isEmpty()) {
continue;
}
String key = script + "." + methodName;
RequirePrivilege annotation = findMethodAnnotation(dwrClass, methodName);
if (annotation != null) {
annotated.put(key, annotation);
} else {
unannotated.put(key, className + "#" + methodName);
}
String key = script + "." + methodName;
RequirePrivilege annotation = findMethodAnnotation(dwrClass, methodName);
if (annotation != null) {
annotated.put(key, annotation);
} else {
unannotated.put(key, className + "#" + methodName);
}
}
}
Expand Down
Loading