Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.eclipse.jetty.security.ConstraintSecurityHandler Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.security;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.HttpConstraintElement;
import javax.servlet.HttpMethodConstraintElement;
import javax.servlet.ServletSecurityElement;
import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.security.Constraint;
/**
* ConstraintSecurityHandler
*
* Handler to enforce SecurityConstraints. This implementation is servlet spec
* 3.1 compliant and pre-computes the constraint combinations for runtime
* efficiency.
*
* @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
*/
@Deprecated(since = "2021-05-27")
public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware {
// use same as SecurityHandler
private static final Logger LOG = Log.getLogger(SecurityHandler.class);
private static final String OMISSION_SUFFIX = ".omission";
private static final String ALL_METHODS = "*";
private final List _constraintMappings = new CopyOnWriteArrayList<>();
private final List _durableConstraintMappings = new CopyOnWriteArrayList<>();
private final Set _roles = new CopyOnWriteArraySet<>();
private final PathMap> _constraintMap = new PathMap<>();
private boolean _denyUncoveredMethods = false;
public static Constraint createConstraint() {
return new Constraint();
}
public static Constraint createConstraint(Constraint constraint) {
try {
return (Constraint) constraint.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
/**
* Create a security constraint
*
* @param name the name of the constraint
* @param authenticate true to authenticate
* @param roles list of roles
* @param dataConstraint the data constraint
* @return the constraint
*/
public static Constraint createConstraint(String name, boolean authenticate, String[] roles, int dataConstraint) {
Constraint constraint = createConstraint();
if (name != null)
constraint.setName(name);
constraint.setAuthenticate(authenticate);
constraint.setRoles(roles);
constraint.setDataConstraint(dataConstraint);
return constraint;
}
/**
* Create a Constraint
*
* @param name the name
* @param element the http constraint element
* @return the created constraint
*/
public static Constraint createConstraint(String name, HttpConstraintElement element) {
return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
}
/**
* Create Constraint
*
* @param name the name
* @param rolesAllowed the list of allowed roles
* @param permitOrDeny the permission semantic
* @param transport the transport guarantee
* @return the created constraint
*/
public static Constraint createConstraint(String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport) {
Constraint constraint = createConstraint();
if (rolesAllowed == null || rolesAllowed.length == 0) {
if (permitOrDeny.equals(EmptyRoleSemantic.DENY)) {
// Equivalent to with no roles
constraint.setName(name + "-Deny");
constraint.setAuthenticate(true);
} else {
// Equivalent to no
constraint.setName(name + "-Permit");
constraint.setAuthenticate(false);
}
} else {
// Equivalent to with list of s
constraint.setAuthenticate(true);
constraint.setRoles(rolesAllowed);
constraint.setName(name + "-RolesAllowed");
}
// Equivalent to //CONFIDENTIAL
constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL) ? Constraint.DC_CONFIDENTIAL : Constraint.DC_NONE));
return constraint;
}
public static List getConstraintMappingsForPath(String pathSpec, List constraintMappings) {
if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
return Collections.emptyList();
List mappings = new ArrayList<>();
for (ConstraintMapping mapping : constraintMappings) {
if (pathSpec.equals(mapping.getPathSpec())) {
mappings.add(mapping);
}
}
return mappings;
}
/**
* Take out of the constraint mappings those that match the
* given path.
*
* @param pathSpec the path spec
* @param constraintMappings a new list minus the matching constraints
* @return the list of constraint mappings
*/
public static List removeConstraintMappingsForPath(String pathSpec, List constraintMappings) {
if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
return Collections.emptyList();
List mappings = new ArrayList<>();
for (ConstraintMapping mapping : constraintMappings) {
// Remove the matching mappings by only copying in non-matching mappings
if (!pathSpec.equals(mapping.getPathSpec())) {
mappings.add(mapping);
}
}
return mappings;
}
/**
* Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
*
* @param name the name
* @param pathSpec the path spec
* @param securityElement the servlet security element
* @return the list of constraint mappings
*/
public static List createConstraintsWithMappingsForPath(String name, String pathSpec, ServletSecurityElement securityElement) {
List mappings = new ArrayList<>();
// Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
Constraint httpConstraint;
ConstraintMapping httpConstraintMapping = null;
if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT || securityElement.getRolesAllowed().length != 0 || securityElement.getTransportGuarantee() != TransportGuarantee.NONE) {
httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
// Create a mapping for the pathSpec for the default case
httpConstraintMapping = new ConstraintMapping();
httpConstraintMapping.setPathSpec(pathSpec);
httpConstraintMapping.setConstraint(httpConstraint);
mappings.add(httpConstraintMapping);
}
// See Spec 13.4.1.2 p127
List methodOmissions = new ArrayList<>();
// make constraint mappings for this url for each of the HttpMethodConstraintElements
Collection methodConstraintElements = securityElement.getHttpMethodConstraints();
if (methodConstraintElements != null) {
for (HttpMethodConstraintElement methodConstraintElement : methodConstraintElements) {
// Make a Constraint that captures the and elements supplied for the HttpMethodConstraintElement
Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setConstraint(methodConstraint);
mapping.setPathSpec(pathSpec);
if (methodConstraintElement.getMethodName() != null) {
mapping.setMethod(methodConstraintElement.getMethodName());
// See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
methodOmissions.add(methodConstraintElement.getMethodName());
}
mappings.add(mapping);
}
}
// See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
// UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
if (methodOmissions.size() > 0 && httpConstraintMapping != null)
httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[0]));
return mappings;
}
@Override
public List getConstraintMappings() {
return _constraintMappings;
}
@Override
public Set getRoles() {
return _roles;
}
/**
* Process the constraints following the combining rules in Servlet 3.0 EA
* spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
*
* @param constraintMappings The constraintMappings to set, from which the set of known roles
* is determined.
*/
public void setConstraintMappings(List constraintMappings) {
setConstraintMappings(constraintMappings, null);
}
/**
* Process the constraints following the combining rules in Servlet 3.0 EA
* spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
*
* @param constraintMappings The constraintMappings to set as array, from which the set of known roles
* is determined. Needed to retain API compatibility for 7.x
*/
public void setConstraintMappings(ConstraintMapping[] constraintMappings) {
setConstraintMappings(Arrays.asList(constraintMappings), null);
}
/**
* Process the constraints following the combining rules in Servlet 3.0 EA
* spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
*
* @param constraintMappings The constraintMappings to set.
* @param roles The known roles (or null to determine them from the mappings)
*/
@Override
public void setConstraintMappings(List constraintMappings, Set roles) {
_constraintMappings.clear();
_constraintMappings.addAll(constraintMappings);
_durableConstraintMappings.clear();
if (isInDurableState()) {
_durableConstraintMappings.addAll(constraintMappings);
}
if (roles == null) {
roles = new HashSet<>();
for (ConstraintMapping cm : constraintMappings) {
String[] cmr = cm.getConstraint().getRoles();
if (cmr != null) {
for (String r : cmr) {
if (!ALL_METHODS.equals(r))
roles.add(r);
}
}
}
}
setRoles(roles);
if (isStarted()) {
_constraintMappings.stream().forEach(m -> processConstraintMapping(m));
}
}
/**
* Set the known roles.
* This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
* {@link #setConstraintMappings(List, Set)}.
*
* @param roles The known roles (or null to determine them from the mappings)
*/
public void setRoles(Set roles) {
_roles.clear();
_roles.addAll(roles);
}
/**
* @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
*/
@Override
public void addConstraintMapping(ConstraintMapping mapping) {
_constraintMappings.add(mapping);
if (isInDurableState())
_durableConstraintMappings.add(mapping);
if (mapping.getConstraint() != null && mapping.getConstraint().getRoles() != null) {
// allow for lazy role naming: if a role is named in a security constraint, try and
// add it to the list of declared roles (ie as if it was declared with a security-role
for (String role : mapping.getConstraint().getRoles()) {
if ("*".equals(role) || "**".equals(role))
continue;
addRole(role);
}
}
if (isStarted())
processConstraintMapping(mapping);
}
/**
* @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
*/
@Override
public void addRole(String role) {
// add to list of declared roles
boolean modified = _roles.add(role);
if (isStarted() && modified) {
// Add the new role to currently defined any role role infos
for (Map map : _constraintMap.values()) {
for (RoleInfo info : map.values()) {
if (info.isAnyRole())
info.addRole(role);
}
}
}
}
/**
* @see org.eclipse.jetty.security.SecurityHandler#doStart()
*/
@Override
protected void doStart() throws Exception {
_constraintMappings.stream().forEach(m -> processConstraintMapping(m));
// Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
checkPathsWithUncoveredHttpMethods();
super.doStart();
}
@Override
protected void doStop() throws Exception {
super.doStop();
_constraintMap.clear();
_constraintMappings.clear();
_constraintMappings.addAll(_durableConstraintMappings);
}
/**
* Create and combine the constraint with the existing processed
* constraints.
*
* @param mapping the constraint mapping
*/
protected void processConstraintMapping(ConstraintMapping mapping) {
Map mappings = _constraintMap.get(mapping.getPathSpec());
if (mappings == null) {
mappings = new HashMap<>();
_constraintMap.put(mapping.getPathSpec(), mappings);
}
RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
return;
if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0) {
processConstraintMappingWithMethodOmissions(mapping, mappings);
return;
}
String httpMethod = mapping.getMethod();
if (httpMethod == null)
httpMethod = ALL_METHODS;
RoleInfo roleInfo = mappings.get(httpMethod);
if (roleInfo == null) {
roleInfo = new RoleInfo();
mappings.put(httpMethod, roleInfo);
if (allMethodsRoleInfo != null) {
roleInfo.combine(allMethodsRoleInfo);
}
}
if (roleInfo.isForbidden())
return;
// add in info from the constraint
configureRoleInfo(roleInfo, mapping);
if (roleInfo.isForbidden()) {
if (httpMethod.equals(ALL_METHODS)) {
mappings.clear();
mappings.put(ALL_METHODS, roleInfo);
}
}
}
/**
* Constraints that name method omissions are dealt with differently.
* We create an entry in the mappings with key "<method>.omission". This entry
* is only ever combined with other omissions for the same method to produce a
* consolidated RoleInfo. Then, when we wish to find the relevant constraints for
* a given Request (in prepareConstraintInfo()), we consult 3 types of entries in
* the mappings: an entry that names the method of the Request specifically, an
* entry that names constraints that apply to all methods, entries of the form
* <method>.omission, where the method of the Request is not named in the omission.
*
* @param mapping the constraint mapping
* @param mappings the mappings of roles
*/
protected void processConstraintMappingWithMethodOmissions(ConstraintMapping mapping, Map mappings) {
String[] omissions = mapping.getMethodOmissions();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < omissions.length; i++) {
if (i > 0)
sb.append(".");
sb.append(omissions[i]);
}
sb.append(OMISSION_SUFFIX);
RoleInfo ri = new RoleInfo();
mappings.put(sb.toString(), ri);
configureRoleInfo(ri, mapping);
}
/**
* Initialize or update the RoleInfo from the constraint
*
* @param ri the role info
* @param mapping the constraint mapping
*/
protected void configureRoleInfo(RoleInfo ri, ConstraintMapping mapping) {
Constraint constraint = mapping.getConstraint();
boolean forbidden = constraint.isForbidden();
ri.setForbidden(forbidden);
// set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
// which we need in order to do combining of omissions in prepareConstraintInfo
UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
ri.setUserDataConstraint(userDataConstraint);
// if forbidden, no point setting up roles
if (!ri.isForbidden()) {
// add in the roles
boolean checked = mapping.getConstraint().getAuthenticate();
ri.setChecked(checked);
if (ri.isChecked()) {
if (mapping.getConstraint().isAnyRole()) {
// * means matches any defined role
for (String role : _roles) {
ri.addRole(role);
}
ri.setAnyRole(true);
} else if (mapping.getConstraint().isAnyAuth()) {
// being authenticated is sufficient, not necessary to check roles
ri.setAnyAuth(true);
} else {
// user must be in one of the named roles
String[] newRoles = mapping.getConstraint().getRoles();
for (String role : newRoles) {
// check role has been defined
if (!_roles.contains(role))
throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
ri.addRole(role);
}
}
}
}
}
/**
* Find constraints that apply to the given path.
* In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
* represents a merged set of user data constraints, roles etc -:
*
* A mapping of an exact method name
* A mapping with key * that matches every method name
* Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given
*
*
* @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
*/
@Override
protected RoleInfo prepareConstraintInfo(String pathInContext, Request request) {
Map mappings = _constraintMap.match(pathInContext);
if (mappings != null) {
String httpMethod = request.getMethod();
RoleInfo roleInfo = mappings.get(httpMethod);
if (roleInfo == null) {
// No specific http-method names matched
List applicableConstraints = new ArrayList<>();
// Get info for constraint that matches all methods if it exists
RoleInfo all = mappings.get(ALL_METHODS);
if (all != null)
applicableConstraints.add(all);
// Get info for constraints that name method omissions where target method name is not omitted
// (ie matches because target method is not omitted, hence considered covered by the constraint)
for (Entry entry : mappings.entrySet()) {
if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && !entry.getKey().contains(httpMethod))
applicableConstraints.add(entry.getValue());
}
if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods()) {
roleInfo = new RoleInfo();
roleInfo.setForbidden(true);
} else if (applicableConstraints.size() == 1)
roleInfo = applicableConstraints.get(0);
else {
roleInfo = new RoleInfo();
roleInfo.setUserDataConstraint(UserDataConstraint.None);
for (RoleInfo r : applicableConstraints) {
roleInfo.combine(r);
}
}
}
return roleInfo;
}
return null;
}
@Override
protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException {
if (roleInfo == null)
return true;
if (roleInfo.isForbidden())
return false;
UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
return true;
Request baseRequest = Request.getBaseRequest(request);
HttpConfiguration httpConfig = baseRequest.getHttpChannel().getHttpConfiguration();
if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral) {
if (request.isSecure())
return true;
if (httpConfig.getSecurePort() > 0) {
String scheme = httpConfig.getSecureScheme();
int port = httpConfig.getSecurePort();
String url = URIUtil.newURI(scheme, request.getServerName(), port, request.getRequestURI(), request.getQueryString());
response.setContentLength(0);
response.sendRedirect(url, true);
} else
response.sendError(HttpStatus.FORBIDDEN_403, "!Secure");
request.setHandled(true);
return false;
} else {
throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
}
}
@Override
protected boolean isAuthMandatory(Request baseRequest, Response baseResponse, Object constraintInfo) {
return constraintInfo != null && ((RoleInfo) constraintInfo).isChecked();
}
/**
* @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
*/
@Override
protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity) throws IOException {
if (constraintInfo == null) {
return true;
}
RoleInfo roleInfo = (RoleInfo) constraintInfo;
if (!roleInfo.isChecked()) {
return true;
}
// handle ** role constraint
if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null) {
return true;
}
// check if user is any of the allowed roles
boolean isUserInRole = false;
for (String role : roleInfo.getRoles()) {
if (userIdentity.isUserInRole(role, null)) {
isUserInRole = true;
break;
}
}
// handle * role constraint
if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole) {
return true;
}
// normal role check
return isUserInRole;
}
@Override
public void dump(Appendable out, String indent) throws IOException {
dumpObjects(out, indent, DumpableCollection.from("roles", _roles), DumpableCollection.from("constraints", _constraintMap.entrySet()));
}
/**
* @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
*/
@Override
public void setDenyUncoveredHttpMethods(boolean deny) {
_denyUncoveredMethods = deny;
}
@Override
public boolean isDenyUncoveredHttpMethods() {
return _denyUncoveredMethods;
}
/**
* Servlet spec 3.1 pg. 147.
*/
@Override
public boolean checkPathsWithUncoveredHttpMethods() {
Set paths = getPathsWithUncoveredHttpMethods();
if (paths != null && !paths.isEmpty()) {
for (String p : paths) {
LOG.warn("{} has uncovered http methods for path: {}", ContextHandler.getCurrentContext(), p);
}
if (LOG.isDebugEnabled())
LOG.debug(new Throwable());
return true;
}
return false;
}
/**
* Servlet spec 3.1 pg. 147.
* The container must check all the combined security constraint
* information and log any methods that are not protected and the
* urls at which they are not protected
*
* @return list of paths for which there are uncovered methods
*/
public Set getPathsWithUncoveredHttpMethods() {
// if automatically denying uncovered methods, there are no uncovered methods
if (_denyUncoveredMethods)
return Collections.emptySet();
Set uncoveredPaths = new HashSet<>();
for (Entry> entry : _constraintMap.entrySet()) {
Map methodMappings = entry.getValue();
// Each key is either:
// : an exact method name
// : * which means that the constraint applies to every method
// : a name of the form ...omission, which means it applies to every method EXCEPT those named
if (methodMappings.get(ALL_METHODS) != null)
// can't be any uncovered methods for this url path
continue;
boolean hasOmissions = omissionsExist(entry.getKey(), methodMappings);
for (String method : methodMappings.keySet()) {
if (method.endsWith(OMISSION_SUFFIX)) {
Set omittedMethods = getOmittedMethods(method);
for (String m : omittedMethods) {
if (!methodMappings.containsKey(m))
uncoveredPaths.add(entry.getKey());
}
} else {
// an exact method name
if (!hasOmissions)
// an http-method does not have http-method-omission to cover the other method names
uncoveredPaths.add(entry.getKey());
}
}
}
return uncoveredPaths;
}
/**
* Check if any http method omissions exist in the list of method
* to auth info mappings.
*
* @param path the path
* @param methodMappings the method mappings
* @return true if omission exist
*/
protected boolean omissionsExist(String path, Map methodMappings) {
if (methodMappings == null)
return false;
boolean hasOmissions = false;
for (String m : methodMappings.keySet()) {
if (m.endsWith(OMISSION_SUFFIX))
hasOmissions = true;
}
return hasOmissions;
}
/**
* Given a string of the form <method>.<method>.omission
* split out the individual method names.
*
* @param omission the method
* @return the list of strings
*/
protected Set getOmittedMethods(String omission) {
if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
return Collections.emptySet();
String[] strings = omission.split("\\.");
Set methods = new HashSet<>();
for (int i = 0; i < strings.length - 1; i++) {
methods.add(strings[i]);
}
return methods;
}
/**
* Constraints can be added to the ConstraintSecurityHandler before the
* associated context is started. These constraints should persist across
* a stop/start. Others can be added after the associated context is starting
* (eg by a web.xml/web-fragment.xml, annotation or javax.servlet api call) -
* these should not be persisted across a stop/start as they will be re-added on
* the restart.
*
* @return true if the context with which this ConstraintSecurityHandler
* has not yet started, or if there is no context, the server has not yet started.
*/
private boolean isInDurableState() {
ContextHandler context = ContextHandler.getContextHandler(null);
Server server = getServer();
return (context == null && server == null) || (context != null && !context.isRunning()) || (context == null && server != null && !server.isRunning());
}
}