diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java new file mode 100644 index 000000000..6748a909d --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java @@ -0,0 +1,176 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy; + +import static edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization.INCONCLUSIVE; + +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings; +import edu.cornell.mannlib.vitro.webapp.utils.developer.Key; + +/** + * If enabled in the developer settings (and log levels), log each + * PolicyDecision (subject to restrictions). + * + * Some restrictions apply to the logger as a whole. Others apply to the + * particular policy or the particular decision. + */ +public class PolicyDecisionLogger { + private static final Log log = LogFactory + .getLog(PolicyDecisionLogger.class); + + private static final Pattern NEVER_MATCHES = Pattern.compile("^__NEVER__$"); + + private static final BasicPolicyDecision NULL_DECISION = new BasicPolicyDecision( + INCONCLUSIVE, "The decision was null."); + + private final DeveloperSettings settings; + private final RequestedAction whatToAuth; + private final IdentifierBundle whoToAuth; + + private final boolean enabled; + + private final Pattern policyRestriction; + private final boolean skipInconclusive; + private final boolean includeIdentifiers; + + public PolicyDecisionLogger(IdentifierBundle whoToAuth, + RequestedAction whatToAuth) { + this.settings = DeveloperSettings.getInstance(); + this.whoToAuth = whoToAuth; + this.whatToAuth = whatToAuth; + + this.enabled = figureEnabled(); + + this.policyRestriction = figurePolicyRestriction(); + this.skipInconclusive = figureSkipInconclusive(); + this.includeIdentifiers = figureIncludeIdentifiers(); + } + + private boolean figureEnabled() { + return log.isInfoEnabled() + && settings.getBoolean(Key.AUTHORIZATION_LOG_DECISIONS_ENABLE) + && passesUserRestriction() && passesActionRestriction(); + } + + /** + * The identifier bundle passes if there is no restriction, or if the + * restriction pattern is found within concatenated string of the identifier + * bundle. + * + * If the restriction is invalid, the action fails. + */ + private boolean passesUserRestriction() { + Pattern userRestriction = compilePatternFromSetting(Key.AUTHORIZATION_LOG_DECISIONS_USER_RESTRICTION); + return userRestriction == null + || userRestriction.matcher(String.valueOf(whoToAuth)).find(); + } + + /** + * The requested action passes if there is no restriction, or if the + * restriction pattern is found within the class name of the action. + * + * If the restriction is invalid, the action fails. + */ + private boolean passesActionRestriction() { + Pattern actionRestriction = compilePatternFromSetting(Key.AUTHORIZATION_LOG_DECISIONS_ACTION_RESTRICTION); + return actionRestriction == null + || actionRestriction.matcher(String.valueOf(whatToAuth)).find(); + } + + /** + * Only compile the policy restriction pattern once. + */ + private Pattern figurePolicyRestriction() { + return compilePatternFromSetting(Key.AUTHORIZATION_LOG_DECISIONS_POLICY_RESTRICTION); + } + + /** + * Do we log inconclusive decisions? + */ + private boolean figureSkipInconclusive() { + return settings + .getBoolean(Key.AUTHORIZATION_LOG_DECISIONS_SKIP_INCONCLUSIVE); + } + + /** + * Do we include Identifiers in the log record? + */ + private boolean figureIncludeIdentifiers() { + return settings + .getBoolean(Key.AUTHORIZATION_LOG_DECISIONS_ADD_IDENTIFERS); + } + + /** + * If no pattern was provided, return null. If an invalid pattern was + * provided, return a pattern that never matches. + */ + private Pattern compilePatternFromSetting(Key key) { + String setting = settings.getString(key); + if (setting.isEmpty()) { + return null; + } else { + try { + return Pattern.compile(setting); + } catch (Exception e) { + return NEVER_MATCHES; + } + } + } + + /** + * If the logger and the policy and the decision all pass the restrictions, + * write to the log. A null decision is treated as inconclusive. + */ + public void log(PolicyIface policy, PolicyDecision pd) { + if (passesRestrictions(String.valueOf(policy), pd)) { + if (this.includeIdentifiers) { + log.info(String.format( + "Decision on %s by %s was %s; user is %s", + this.whatToAuth, policy, pd, this.whoToAuth)); + } else { + log.info(String.format("Decision on %s by %s was %s", + this.whatToAuth, policy, pd)); + } + } + } + + private boolean passesRestrictions(String policyString, PolicyDecision pd) { + if (pd == null) { + pd = NULL_DECISION; + } + return enabled && passesPolicyRestriction(policyString) + && passesConclusiveRestriction(pd); + } + + private boolean passesPolicyRestriction(String policyString) { + return this.policyRestriction == null + || this.policyRestriction.matcher(policyString).find(); + } + + private boolean passesConclusiveRestriction(PolicyDecision pd) { + return !(skipInconclusive && isInconclusive(pd)); + } + + private boolean isInconclusive(PolicyDecision pd) { + return pd == null || pd.getAuthorized() == INCONCLUSIVE; + } + + public void logNoDecision(PolicyDecision pd) { + if (enabled) { + if (this.includeIdentifiers) { + log.info(pd.getMessage() + "; user is " + this.whoToAuth); + } else { + log.info(pd.getMessage()); + } + } + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java index ca6e047ff..8c09034f0 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyList.java @@ -103,14 +103,16 @@ public class PolicyList extends ArrayList implements PolicyIface{ protected PolicyDecision checkAgainstPolicys( IdentifierBundle whoToAuth, RequestedAction whatToAuth){ PolicyDecision pd = null; + PolicyDecisionLogger logger = new PolicyDecisionLogger(whoToAuth, whatToAuth); for(PolicyIface policy : this){ try{ pd = policy.isAuthorized(whoToAuth, whatToAuth); + logger.log(policy, pd); if( pd != null ){ if( pd.getAuthorized() == Authorization.AUTHORIZED ) - break; + return pd; if( pd.getAuthorized() == Authorization.UNAUTHORIZED ) - break; + return pd; if( pd.getAuthorized() == Authorization.INCONCLUSIVE ) continue; } else{ @@ -120,8 +122,11 @@ public class PolicyList extends ArrayList implements PolicyIface{ log.error("ignoring exception in policy " + policy.toString(), th ); } } - log.debug("decision " + pd + " for " + whatToAuth); - return pd; + + pd = new BasicPolicyDecision(Authorization.INCONCLUSIVE, + "No policy returned a conclusive decision on " + whatToAuth); + logger.logNoDecision(pd); + return pd; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java index dfc6727a5..e5ce43bf9 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java @@ -205,6 +205,7 @@ public class DeveloperSettings { Properties dsProps = new Properties(); dsProps.load(reader); devSettings.updateFromProperties(dsProps); + log.info(devSettings); ss.info(this, "Loaded the 'developer.properties' file: " + devSettings); } catch (FileNotFoundException e) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java index d519f6b69..fed8dabd1 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/Key.java @@ -24,7 +24,7 @@ public enum Key { ENABLED("developer.enabled", true), /** - * If the developer panel is enabled, can a non-logged-in user change the + * If the developer panel is enabled, may an anonymous user change the * settings? */ PERMIT_ANONYMOUS_CONTROL("developer.permitAnonymousControl", true), @@ -84,7 +84,45 @@ public enum Key { * Tell the ShortViewLogger to note the use of non-default short views. */ PAGE_CONTENTS_LOG_CUSTOM_SHORT_VIEW( - "developer.pageContents.logCustomShortView", true); + "developer.pageContents.logCustomShortView", true), + + /** + * Enable the PolicyDecisionLogger. + */ + AUTHORIZATION_LOG_DECISIONS_ENABLE( + "developer.authorization.logDecisions.enable", true), + + /** + * Enable the PolicyDecisionLogger. + */ + AUTHORIZATION_LOG_DECISIONS_ADD_IDENTIFERS( + "developer.authorization.logDecisions.addIdentifiers", true), + + /** + * Enable the PolicyDecisionLogger. + */ + AUTHORIZATION_LOG_DECISIONS_SKIP_INCONCLUSIVE( + "developer.authorization.logDecisions.skipInconclusive", true), + + /** + * Don't log policy decisions unless the requested action meets this + * restriction. + */ + AUTHORIZATION_LOG_DECISIONS_ACTION_RESTRICTION( + "developer.authorization.logDecisions.actionRestriction", false), + + /** + * Don't log policy decisions unless the identifier bundle meets this + * restriction. + */ + AUTHORIZATION_LOG_DECISIONS_USER_RESTRICTION( + "developer.authorization.logDecisions.userRestriction", false), + + /** + * Don't log policy decisions unless the policy meets this restriction. + */ + AUTHORIZATION_LOG_DECISIONS_POLICY_RESTRICTION( + "developer.authorization.logDecisions.policyRestriction", false); private static final Log log = LogFactory.getLog(Key.class); private final String propertyName; diff --git a/webapp/web/css/developer/developerPanel.css b/webapp/web/css/developer/developerPanel.css index b93ca7288..8f28000cb 100644 --- a/webapp/web/css/developer/developerPanel.css +++ b/webapp/web/css/developer/developerPanel.css @@ -10,6 +10,9 @@ div.developer { div.developer #developerPanelBody { display: none; +} + +#developerPanelBody * { line-height: 1em; font-size: small; } diff --git a/webapp/web/js/developer/developerPanel.js b/webapp/web/js/developer/developerPanel.js index 684b96dc0..bf14183f9 100644 --- a/webapp/web/js/developer/developerPanel.js +++ b/webapp/web/js/developer/developerPanel.js @@ -48,11 +48,19 @@ function DeveloperPanel(developerAjaxUrl) { document.getElementById("developer_i18n_defeatCache").disabled = !developerEnabled; document.getElementById("developer_i18n_logStringRequests").disabled = !developerEnabled; document.getElementById("developer_loggingRDFService_enable").disabled = !developerEnabled; + document.getElementById("developer_authorization_logDecisions_enable").disabled = !developerEnabled; var rdfServiceEnabled = developerEnabled && document.getElementById("developer_loggingRDFService_enable").checked; document.getElementById("developer_loggingRDFService_stackTrace").disabled = !rdfServiceEnabled; document.getElementById("developer_loggingRDFService_queryRestriction").disabled = !rdfServiceEnabled; document.getElementById("developer_loggingRDFService_stackRestriction").disabled = !rdfServiceEnabled; + + var authLoggingEnabled = developerEnabled && document.getElementById("developer_authorization_logDecisions_enable").checked; + document.getElementById("developer_authorization_logDecisions_skipInconclusive").disabled = !authLoggingEnabled; + document.getElementById("developer_authorization_logDecisions_addIdentifiers").disabled = !authLoggingEnabled; + document.getElementById("developer_authorization_logDecisions_actionRestriction").disabled = !authLoggingEnabled; + document.getElementById("developer_authorization_logDecisions_policyRestriction").disabled = !authLoggingEnabled; + document.getElementById("developer_authorization_logDecisions_userRestriction").disabled = !authLoggingEnabled; } function collectFormData() { diff --git a/webapp/web/templates/freemarker/page/partials/developerPanel.ftl b/webapp/web/templates/freemarker/page/partials/developerPanel.ftl index 013d15973..ae2d05fe0 100644 --- a/webapp/web/templates/freemarker/page/partials/developerPanel.ftl +++ b/webapp/web/templates/freemarker/page/partials/developerPanel.ftl @@ -1,12 +1,18 @@ <#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> -<#macro showCheckbox key> - checked> +<#macro showCheckbox key, labelText> + -<#macro showTextbox key> - +<#macro showTextbox key, labelText> + <#if !settings.developer_enabled> @@ -21,14 +27,10 @@
- - + <@showCheckbox "developer_enabled", + "Enable developer mode" /> + <@showCheckbox "developer_permitAnonymousControl", + "Allow anonymous user to see and modify developer settings" />
@@ -41,26 +43,18 @@
Page configuration - - + <@showCheckbox "developer_pageContents_logCustomListView", + "Log the use of custom list view XML files." /> + <@showCheckbox "developer_pageContents_logCustomShortView" , + "Log the use of custom short views in search, index and browse pages."/>
Language support - - + <@showCheckbox "developer_i18n_defeatCache", + "Defeat the cache of language property files" /> + <@showCheckbox "developer_i18n_logStringRequests", + "Log the retrieval of language strings" />
@@ -77,43 +71,43 @@
Freemarker templates - - + <@showCheckbox "developer_defeatFreemarkerCache", + "Defeat the template cache" /> + <@showCheckbox "developer_insertFreemarkerDelimiters", + "Insert HTML comments at start and end of templates" />
SPARQL Queries - + <@showCheckbox "developer_loggingRDFService_enable", + "Log each query" />
- - - + <@showCheckbox "developer_loggingRDFService_stackTrace", + "Add stack trace" /> + <@showTextbox "developer_loggingRDFService_queryRestriction", + "Restrict by query string" /> + <@showTextbox "developer_loggingRDFService_stackRestriction", + "Restrict by calling stack" />
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. - Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + <@showCheckbox "developer_authorization_logDecisions_enable", + "Write policy decisions to the log" /> +
+ <@showCheckbox "developer_authorization_logDecisions_skipInconclusive", + "Skip inconclusive decisions" /> + <@showCheckbox "developer_authorization_logDecisions_addIdentifiers", + "Include the user identifiers in the log record" /> + <@showTextbox "developer_authorization_logDecisions_actionRestriction", + "Restrict by requested action" /> + <@showTextbox "developer_authorization_logDecisions_policyRestriction", + "Restrict by policy name" /> + <@showTextbox "developer_authorization_logDecisions_userRestriction", + "Restrict by user identifiers" /> +