Skip to content

Commit eec5567

Browse files
vagishaclaude
andcommitted
Follow-up on the "Targeted MS Guest Access" login toggle.
- Gate ShowCalibrationCurveAction (the single-curve page bots hit), not the ShowCalibrationCurvesAction list. It gates in validate() to skip building the curve, and addNavTrail always adds a crumb so the login-gate path has a title. - RestrictableAction is now keyed by action class: actions gate via getClass() (forClass lookup), and the settings-page label comes from the registered action name, so a renamed action keeps working. Stored keys (enum name()) are unchanged. - TargetedMSGuestAccessTest sets the master switch off explicitly in step 1, and restores the site-wide settings in @after so it runs under clean=false. Co-Authored-By: Claude <[email protected]>
1 parent 4fe53ce commit eec5567

3 files changed

Lines changed: 117 additions & 78 deletions

File tree

src/org/labkey/targetedms/GuestAccessManager.java

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package org.labkey.targetedms;
1717

1818
import org.jetbrains.annotations.NotNull;
19+
import org.jetbrains.annotations.Nullable;
20+
import org.labkey.api.action.BaseViewAction;
21+
import org.labkey.api.action.SpringActionController;
1922
import org.labkey.api.audit.AuditLogService;
2023
import org.labkey.api.audit.provider.SiteSettingsAuditProvider;
21-
import org.labkey.api.data.Container;
2224
import org.labkey.api.data.ContainerManager;
2325
import org.labkey.api.data.PropertyManager;
2426
import org.labkey.api.data.PropertyManager.PropertyMap;
@@ -29,7 +31,6 @@
2931
import java.util.EnumSet;
3032
import java.util.List;
3133
import java.util.Set;
32-
import java.util.stream.Collectors;
3334

3435
/**
3536
* Site-admin setting that can require a login for slow targetedms pages that get hit by bots. A master
@@ -48,44 +49,68 @@ public class GuestAccessManager
4849
private static final String TRUE = Boolean.TRUE.toString();
4950

5051
/**
51-
* The actions a site admin can choose to require a login.
52-
* defaultChecked is used when nothing has been saved yet.
52+
* The actions a site admin can choose to require a login. Each entry is keyed by its action class.
5353
*
5454
* The pages that draw many charts are checked by default. The single-chart image actions and the
55-
* precursor table are offered too but are off by default, so guests keep seeing inline charts and lists
56-
* during normal operation. An admin can also check those to block direct requests to them during an
57-
* bot attack (they show up in high volume because the detail pages embed many of them).
55+
* precursor table are offered too but are off by default. An admin can also check those to block direct
56+
* requests to them during a bot attack (they show up in high volume during an attack because the detail
57+
* pages embed many of them).
5858
*/
5959
public enum RestrictableAction
6060
{
61-
showProtein("Protein details page (showProtein)", true),
62-
showPeptide("Peptide details page (showPeptide)", true),
63-
showMolecule("Small molecule details page (showMolecule)", true),
64-
showCalibrationCurves("Calibration curves page (showCalibrationCurves)", true),
65-
showPrecursorList("Document details page (showPrecursorList)", false),
66-
showPeakAreas("Peak areas chart (showPeakAreas)", false),
67-
showRetentionTimesChart("Retention times chart (showRetentionTimesChart)", false),
68-
precursorChromatogramChart("Precursor chromatogram (precursorChromatogramChart)", false),
69-
groupChromatogramChart("Protein chromatogram (groupChromatogramChart)", false);
70-
71-
private final String _label;
61+
showProtein(TargetedMSController.ShowProteinAction.class, "Protein details page", true),
62+
showPeptide(TargetedMSController.ShowPeptideAction.class, "Peptide details page", true),
63+
showMolecule(TargetedMSController.ShowMoleculeAction.class, "Small molecule details page", true),
64+
showCalibrationCurve(TargetedMSController.ShowCalibrationCurveAction.class, "Calibration curve details page", true),
65+
showPrecursorList(TargetedMSController.ShowPrecursorListAction.class, "Document details page", false),
66+
showPeakAreas(TargetedMSController.ShowPeakAreasAction.class, "Peak areas chart", false),
67+
showRetentionTimesChart(TargetedMSController.ShowRetentionTimesChartAction.class, "Retention times chart", false),
68+
precursorChromatogramChart(TargetedMSController.PrecursorChromatogramChartAction.class, "Precursor chromatogram", false),
69+
groupChromatogramChart(TargetedMSController.GroupChromatogramChartAction.class, "Protein chromatogram", false);
70+
71+
private final Class<? extends BaseViewAction<?>> _actionClass;
72+
private final String _description;
7273
private final boolean _defaultChecked;
7374

74-
RestrictableAction(String label, boolean defaultChecked)
75+
RestrictableAction(Class<? extends BaseViewAction<?>> actionClass, String description, boolean defaultChecked)
7576
{
76-
_label = label;
77+
_actionClass = actionClass;
78+
_description = description;
7779
_defaultChecked = defaultChecked;
7880
}
7981

82+
/**
83+
* The action's registered URL name (e.g. "showCalibrationCurve"), derived from the action class.
84+
*/
85+
private String getActionName()
86+
{
87+
return SpringActionController.getActionName(_actionClass);
88+
}
89+
90+
/** Settings-page label, e.g. "Calibration curve details page (showCalibrationCurve)". */
8091
public String getLabel()
8192
{
82-
return _label;
93+
return _description + " (" + getActionName() + ")";
8394
}
8495

85-
public boolean isDefaultChecked()
96+
private boolean isDefaultChecked()
8697
{
8798
return _defaultChecked;
8899
}
100+
101+
/**
102+
* The restrictable action for this action class, or null if the class is not gated.
103+
*/
104+
@Nullable
105+
public static RestrictableAction forClass(@NotNull Class<? extends BaseViewAction<?>> actionClass)
106+
{
107+
for (RestrictableAction action : values())
108+
{
109+
if (action._actionClass.equals(actionClass))
110+
return action;
111+
}
112+
return null;
113+
}
89114
}
90115

91116
private GuestAccessManager()
@@ -122,14 +147,16 @@ public static boolean isRestricted(@NotNull RestrictableAction action)
122147
PropertyMap props = getProperties();
123148
if (!TRUE.equals(props.get(MASTER_KEY)))
124149
return false;
150+
// saved true/false is the admin's explicit choice (an explicit uncheck is respected). Absent means
151+
// never decided - e.g. an action added in a later release - so fall back to the action's default:
152+
// a new default-checked action is gated once the master switch is on, without waiting for a re-save.
125153
String saved = props.get(action.name());
126154
return saved == null ? action.isDefaultChecked() : TRUE.equals(saved);
127155
}
128156

129157
/** The set of currently-checked actions (independent of the master toggle). */
130-
public static Set<RestrictableAction> getCheckedActions()
158+
private static Set<RestrictableAction> getCheckedActions()
131159
{
132-
// Read the property map once and reuse it rather than re-fetching per action.
133160
PropertyMap props = getProperties();
134161
Set<RestrictableAction> checked = EnumSet.noneOf(RestrictableAction.class);
135162
for (RestrictableAction action : RestrictableAction.values())
@@ -182,8 +209,8 @@ private static String describe(Set<RestrictableAction> actions)
182209
for (RestrictableAction action : RestrictableAction.values())
183210
{
184211
if (actions.contains(action))
185-
names.add(action.name());
212+
names.add(action.getActionName());
186213
}
187-
return names.stream().collect(Collectors.joining(", "));
214+
return String.join(", ", names);
188215
}
189216
}

src/org/labkey/targetedms/TargetedMSController.java

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.labkey.api.action.ApiResponse;
5454
import org.labkey.api.action.ApiSimpleResponse;
5555
import org.labkey.api.action.ApiUsageException;
56+
import org.labkey.api.action.BaseViewAction;
5657
import org.labkey.api.action.ExportAction;
5758
import org.labkey.api.action.FormHandlerAction;
5859
import org.labkey.api.action.FormViewAction;
@@ -2107,7 +2108,7 @@ public class GroupChromatogramChartAction extends ExportAction<GroupChromatogram
21072108
@Override
21082109
public void export(GroupChromatogramForm form, HttpServletResponse response, BindException errors) throws Exception
21092110
{
2110-
redirectGuestToLoginForChart(GuestAccessManager.RestrictableAction.groupChromatogramChart, getViewContext(), getContainer());
2111+
redirectGuestToLoginForChart(getClass(), getViewContext(), getContainer());
21112112

21122113
PeptideGroup group = PeptideGroupManager.getPeptideGroup(getContainer(), form.getGroupId());
21132114
if (group == null)
@@ -2145,7 +2146,7 @@ public class PrecursorChromatogramChartAction extends ExportAction<ChromatogramF
21452146
@Override
21462147
public void export(ChromatogramForm form, HttpServletResponse response, BindException errors) throws Exception
21472148
{
2148-
redirectGuestToLoginForChart(GuestAccessManager.RestrictableAction.precursorChromatogramChart, getViewContext(), getContainer());
2149+
redirectGuestToLoginForChart(getClass(), getViewContext(), getContainer());
21492150

21502151
PrecursorChromInfo pChromInfo = PrecursorManager.getPrecursorChromInfo(getContainer(), form.getId());
21512152
if (pChromInfo == null)
@@ -3169,7 +3170,7 @@ public class ShowPeptideAction extends SimpleViewAction<ChromatogramForm>
31693170
@Override
31703171
public ModelAndView getView(ChromatogramForm form, BindException errors)
31713172
{
3172-
HtmlView loginGate = getGuestLoginGate(GuestAccessManager.RestrictableAction.showPeptide, getViewContext(), getContainer());
3173+
HtmlView loginGate = getGuestLoginGate(getClass(), getViewContext(), getContainer());
31733174
if (loginGate != null)
31743175
return loginGate;
31753176

@@ -3270,7 +3271,7 @@ public static class ShowMoleculeAction extends SimpleViewAction<ChromatogramForm
32703271
@Override
32713272
public ModelAndView getView(ChromatogramForm form, BindException errors)
32723273
{
3273-
HtmlView loginGate = getGuestLoginGate(GuestAccessManager.RestrictableAction.showMolecule, getViewContext(), getContainer());
3274+
HtmlView loginGate = getGuestLoginGate(getClass(), getViewContext(), getContainer());
32743275
if (loginGate != null)
32753276
return loginGate;
32763277

@@ -3793,7 +3794,7 @@ public void validate(SummaryChartForm form, BindException errors)
37933794
@Override
37943795
public void export(SummaryChartForm form, HttpServletResponse response, BindException errors) throws Exception
37953796
{
3796-
redirectGuestToLoginForChart(GuestAccessManager.RestrictableAction.showPeakAreas, getViewContext(), getContainer());
3797+
redirectGuestToLoginForChart(getClass(), getViewContext(), getContainer());
37973798

37983799
JFreeChart chart;
37993800
if (form.isAsProteomics())
@@ -3851,7 +3852,7 @@ public void validate(SummaryChartForm form, BindException errors)
38513852
@Override
38523853
public void export(SummaryChartForm form, HttpServletResponse response, BindException errors) throws Exception
38533854
{
3854-
redirectGuestToLoginForChart(GuestAccessManager.RestrictableAction.showRetentionTimesChart, getViewContext(), getContainer());
3855+
redirectGuestToLoginForChart(getClass(), getViewContext(), getContainer());
38553856

38563857
if (form.getValue() == null)
38573858
form.setValue("All");
@@ -4673,26 +4674,33 @@ private static HtmlView getLoginView(ViewContext context, Container container)
46734674
AuthenticationManager.isRegistrationEnabled() ? registerLink : "")));
46744675
}
46754676

4677+
/**
4678+
* True when a guest should be sent to login for this action: master switch on AND this action checked.
4679+
*/
4680+
private static boolean isGuestGated(Class<? extends BaseViewAction<?>> actionClass, ViewContext context)
4681+
{
4682+
GuestAccessManager.RestrictableAction action = GuestAccessManager.RestrictableAction.forClass(actionClass);
4683+
return action != null && context.getUser().isGuest() && GuestAccessManager.isRestricted(action);
4684+
}
4685+
46764686
/**
46774687
* Returns a login view when a guest should be sent to the login page for this action (the site-admin
46784688
* master switch is on AND this action's checkbox is checked), otherwise null so the action runs as
46794689
* normal. See {@link GuestAccessSettingsAction}.
46804690
*/
46814691
@Nullable
4682-
private static HtmlView getGuestLoginGate(GuestAccessManager.RestrictableAction action, ViewContext context, Container container)
4692+
private static HtmlView getGuestLoginGate(Class<? extends BaseViewAction<?>> actionClass, ViewContext context, Container container)
46834693
{
4684-
if (context.getUser().isGuest() && GuestAccessManager.isRestricted(action))
4685-
return getLoginView(context, container);
4686-
return null;
4694+
return isGuestGated(actionClass, context) ? getLoginView(context, container) : null;
46874695
}
46884696

46894697
/**
46904698
* Same check as {@link #getGuestLoginGate}, but for the chart actions that write an image. Those cannot
46914699
* return the HTML login view, so a restricted guest is redirected to the login page instead.
46924700
*/
4693-
private static void redirectGuestToLoginForChart(GuestAccessManager.RestrictableAction action, ViewContext context, Container container)
4701+
private static void redirectGuestToLoginForChart(Class<? extends BaseViewAction<?>> actionClass, ViewContext context, Container container)
46944702
{
4695-
if (context.getUser().isGuest() && GuestAccessManager.isRestricted(action))
4703+
if (isGuestGated(actionClass, context))
46964704
throw new RedirectException(PageFlowUtil.urlProvider(LoginUrls.class).getLoginURL(container, context.getActionURL()));
46974705
}
46984706

@@ -4713,7 +4721,7 @@ public ShowPrecursorListAction(ViewContext ctx)
47134721
@Override
47144722
public ModelAndView getView(RunDetailsForm form, BindException errors) throws Exception
47154723
{
4716-
HtmlView loginGate = getGuestLoginGate(GuestAccessManager.RestrictableAction.showPrecursorList, getViewContext(), getContainer());
4724+
HtmlView loginGate = getGuestLoginGate(getClass(), getViewContext(), getContainer());
47174725
return loginGate != null ? loginGate : super.getView(form, errors);
47184726
}
47194727

@@ -4891,13 +4899,6 @@ protected GroupComparisonView createQueryView(
48914899
@RequiresPermission(ReadPermission.class)
48924900
public class ShowCalibrationCurvesAction extends ShowRunSplitDetailsAction<CalibrationCurvesView>
48934901
{
4894-
@Override
4895-
public ModelAndView getView(RunDetailsForm form, BindException errors) throws Exception
4896-
{
4897-
HtmlView loginGate = getGuestLoginGate(GuestAccessManager.RestrictableAction.showCalibrationCurves, getViewContext(), getContainer());
4898-
return loginGate != null ? loginGate : super.getView(form, errors);
4899-
}
4900-
49014902
@Override
49024903
public String getDataRegionNamePeptide()
49034904
{
@@ -5703,7 +5704,7 @@ public class ShowProteinAction extends SimpleViewAction<ProteinForm>
57035704
@Override
57045705
public ModelAndView getView(final ProteinForm form, BindException errors)
57055706
{
5706-
HtmlView loginGate = getGuestLoginGate(GuestAccessManager.RestrictableAction.showProtein, getViewContext(), getContainer());
5707+
HtmlView loginGate = getGuestLoginGate(getClass(), getViewContext(), getContainer());
57075708
if (loginGate != null)
57085709
return loginGate;
57095710

@@ -8441,9 +8442,11 @@ public ShowCalibrationCurveAction()
84418442
@Override
84428443
public void addNavTrail(NavTree root)
84438444
{
8445+
// Add the top-level crumb unconditionally so the page has a title even on the guest login-gate
8446+
// path, where validate() returns early and _run is never set.
8447+
root.addChild("Targeted MS Runs", getShowListURL(getContainer()));
84448448
if (null != _run)
84458449
{
8446-
root.addChild("Targeted MS Runs", getShowListURL(getContainer()));
84478450
root.addChild(_run.getDescription(), getShowCalibrationCurvesURL(getContainer(), _run.getId()));
84488451
if (_curvePlotView.getChart().getMolecule() != null)
84498452
{
@@ -8455,6 +8458,11 @@ public void addNavTrail(NavTree root)
84558458
@Override
84568459
public void validate(CalibrationCurveForm form, BindException errors)
84578460
{
8461+
// Skip the expensive CalibrationCurveView construction below. Gating only in getView would be too late,
8462+
// because the expensive work is done here in validate().
8463+
if (isGuestGated(getClass(), getViewContext()))
8464+
return;
8465+
84588466
_curvePlotView = new CalibrationCurveView(getUser(), getContainer(), form.getCalibrationCurveId());
84598467
CalibrationCurveEntity chart = _curvePlotView.getChart().getCalibrationCurveEntity();
84608468
//ensure that the experiment run is valid and exists within the current container
@@ -8476,6 +8484,11 @@ protected QueryView createQueryView(CalibrationCurveForm form, BindException err
84768484
@Override
84778485
public ModelAndView getView(CalibrationCurveForm calibrationCurveForm, BindException errors)
84788486
{
8487+
// A restricted guest is short-circuited here; validate() already skipped the expensive work.
8488+
HtmlView loginGate = getGuestLoginGate(getClass(), getViewContext(), getContainer());
8489+
if (loginGate != null)
8490+
return loginGate;
8491+
84798492
CalibrationCurveChart chart = _curvePlotView.getChart();
84808493
GeneralMolecule<?, ?> molecule = chart.getMolecule();
84818494
if (molecule == null)

0 commit comments

Comments
 (0)