Michael Pasternak has uploaded a new change for review. Change subject: restapi: allow client to define the HTTP session TTL #921013 ......................................................................
restapi: allow client to define the HTTP session TTL #921013 Session TTL can be explicitly set by user now via "Session-TTL:xxx" HTTP header, Session-TTL is the time between client requests before the servlet container will invalidate this session. An interval value of zero or less indicates that the session should never timeout. https://bugzilla.redhat.com/show_bug.cgi?id=921013 Change-Id: I753a379c60b2d7f88867cd0123ee69321a331f0f Signed-off-by: Michael Pasternak <mpast...@redhat.com> --- M backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/Challenger.java M backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/SessionUtils.java M backend/manager/modules/restapi/interface/common/jaxrs/src/test/java/org/ovirt/engine/api/common/security/auth/ChallengerTest.java M backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendApiResource.java A backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/EntryPointBuilder.java M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/RsdlBuilder.java M backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml 8 files changed, 219 insertions(+), 13 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/44/13044/1 diff --git a/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/Challenger.java b/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/Challenger.java index 8fc9fe1..9cf204d 100644 --- a/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/Challenger.java +++ b/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/Challenger.java @@ -34,11 +34,18 @@ import org.jboss.resteasy.spi.interception.PreProcessInterceptor; import org.ovirt.engine.api.common.invocation.Current; +import org.ovirt.engine.core.utils.log.Log; +import org.ovirt.engine.core.utils.log.LogFactory; @Provider @ServerInterceptor @Precedence("SECURITY") public class Challenger implements PreProcessInterceptor { + + private static final int SECONDS_IN_MINUTE = 60; + protected static final Log LOG = LogFactory.getLog(Challenger.class); + private static final String SESSION_TTL_EXTRACT_ERROR = + "%1$s header content extraction has failed because of bad number format: %2$s"; private String realm; private Scheme scheme; @@ -83,11 +90,20 @@ HttpHeaders headers = request.getHttpHeaders(); boolean preferPersistentAuth = checkPersistentAuthentication(headers); boolean hasAuthorizationHeader = checkAuthorizationHeader(headers); + Integer customHttpSessionTtl = getCustomHttpSessionTtl(headers); // Will create a new one if it is the first session, and we want to persist sessions // (and then the "isNew" test below will return true) // Otherwise, it will return null httpSession = getCurrentSession(preferPersistentAuth); + + // Specifies the time, between client requests before the servlet + // container will invalidate this session. An interval value of zero + // or less indicates that the session should never timeout. + if (httpSession != null && customHttpSessionTtl != null) { + httpSession.setMaxInactiveInterval( + customHttpSessionTtl.intValue() * SECONDS_IN_MINUTE); + } // If the session isn't new and doesn't carry authorization header, we validate it if (validator != null && httpSession != null && !httpSession.isNew() && !hasAuthorizationHeader) { @@ -113,6 +129,35 @@ return response; } + /** + * Extracts the SESSION_TTL_HEADER + * + * @param headers + * HTTP headers + * + * @return SESSION_TTL_HEADER or null + */ + private Integer getCustomHttpSessionTtl(HttpHeaders headers) { + Integer ttl = null; + + List<String> sessionTtlFields = SessionUtils.getHeaderField( + headers, + SessionUtils.SESSION_TTL_HEADER_FIELD); + + if (sessionTtlFields != null && !sessionTtlFields.isEmpty()) { + try { + return Integer.valueOf(sessionTtlFields.get(0)); + } catch (NumberFormatException e) { + LOG.error(String.format( + SESSION_TTL_EXTRACT_ERROR, + SessionUtils.SESSION_TTL_HEADER_FIELD, + sessionTtlFields.get(0))); + } + } + + return ttl; + } + /* * This method executes the basic authentication, and returns true whether it was successful and false otherwise. * It also sets the logged-in principal and the challenger object in the Current object diff --git a/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/SessionUtils.java b/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/SessionUtils.java index ccddaae..8000c0c 100644 --- a/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/SessionUtils.java +++ b/backend/manager/modules/restapi/interface/common/jaxrs/src/main/java/org/ovirt/engine/api/common/security/auth/SessionUtils.java @@ -17,6 +17,7 @@ public class SessionUtils { public static String ENGINE_SESSION_ID_KEY = "engineSessionId"; public static String PREFER_HEADER_FIELD = "Prefer"; + public static String SESSION_TTL_HEADER_FIELD = "Session-TTL"; public static String PERSIST_FIELD_VALUE = "persistent-auth"; public static String JSESSIONID_HEADER = "JSESSIONID"; private static final Log log = LogFactory.getLog(SessionUtils.class); diff --git a/backend/manager/modules/restapi/interface/common/jaxrs/src/test/java/org/ovirt/engine/api/common/security/auth/ChallengerTest.java b/backend/manager/modules/restapi/interface/common/jaxrs/src/test/java/org/ovirt/engine/api/common/security/auth/ChallengerTest.java index e9e4997..c322a7b 100644 --- a/backend/manager/modules/restapi/interface/common/jaxrs/src/test/java/org/ovirt/engine/api/common/security/auth/ChallengerTest.java +++ b/backend/manager/modules/restapi/interface/common/jaxrs/src/test/java/org/ovirt/engine/api/common/security/auth/ChallengerTest.java @@ -118,6 +118,32 @@ } @Test + public void testValidSessionTtl() { + HttpSession httpSession = new TestHttpSession(sessionId, false); + doReturn(httpSession).when(challenger).getCurrentSession(anyBoolean()); + challenger.setValidator(new ConstValidator(true, sessionId)); + ResourceMethod resource = control.createMock(ResourceMethod.class); + ServerResponse response = + challenger.preProcess(setUpRequestExpectations( + null, true, true, String.valueOf(5)), resource); + assertNull(response); + control.verify(); + } + + @Test + public void testNotValidSessionTtl() { + HttpSession httpSession = new TestHttpSession(sessionId, false); + doReturn(httpSession).when(challenger).getCurrentSession(anyBoolean()); + challenger.setValidator(new ConstValidator(true, sessionId)); + ResourceMethod resource = control.createMock(ResourceMethod.class); + ServerResponse response = + challenger.preProcess(setUpRequestExpectations( + null, true, true, "bad-ttl"), resource); + assertNull(response); + control.verify(); + } + + @Test public void testValidateSessionFalseOnWrongEngineSessionId() { HttpSession httpSession = new TestHttpSession(sessionId, false); doReturn(httpSession).when(challenger).getCurrentSession(anyBoolean()); @@ -156,6 +182,10 @@ } private HttpRequest setUpRequestExpectations(String credentials, boolean valid, boolean preferHeader) { + return setUpRequestExpectations(credentials, valid, preferHeader, null); + } + + private HttpRequest setUpRequestExpectations(String credentials, boolean valid, boolean preferHeader, String ttl) { Scheme authorizer = control.createMock(Scheme.class); challenger.setScheme(authorizer); Current current = control.createMock(Current.class); @@ -181,6 +211,11 @@ EasyMock.expectLastCall(); } } + if (ttl != null) { + List<String> preferHeaders = new ArrayList<String>(); + preferHeaders.add(ttl); + expect(headers.getRequestHeader(SessionUtils.SESSION_TTL_HEADER_FIELD)).andReturn(preferHeaders); + } control.replay(); return request; } diff --git a/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd b/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd index 16578c9..0bcd249 100644 --- a/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd +++ b/backend/manager/modules/restapi/interface/definition/src/main/resources/api.xsd @@ -2831,6 +2831,20 @@ </xs:extension> </xs:complexContent> </xs:complexType> + + + <xs:element name="entry_point" type="EntryPoint"/> + + <xs:complexType name="EntryPoint"> + <xs:complexContent> + <xs:extension base="DetailedLink"> + <xs:sequence> + <xs:element name="name" type="xs:string" minOccurs="0" maxOccurs="1"/> + <xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> <xs:element name="rsdl" type="RSDL"/> @@ -2839,6 +2853,7 @@ <xs:element name="description" type="xs:string" minOccurs="0"/> <xs:element type="Version" name="version" minOccurs="0" maxOccurs="1" /> <xs:element ref="schema" minOccurs="0" maxOccurs="1" /> + <xs:element type="EntryPoint" name="api" minOccurs="0"/> <xs:element type="DetailedLinks" name="links" minOccurs="0"/> </xs:sequence> <xs:attribute name="href" type="xs:string"/> diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendApiResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendApiResource.java index 8fa6182..646fc10 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendApiResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendApiResource.java @@ -53,6 +53,7 @@ import org.ovirt.engine.api.model.Users; import org.ovirt.engine.api.model.VMs; import org.ovirt.engine.api.resource.ApiResource; +import org.ovirt.engine.api.restapi.rsdl.EntryPointBuilder; import org.ovirt.engine.api.restapi.rsdl.RsdlBuilder; import org.ovirt.engine.api.restapi.rsdl.SchemaBuilder; import org.ovirt.engine.api.restapi.types.DateMapper; @@ -81,9 +82,13 @@ private static final String RSDL_DESCRIPTION = "The oVirt RESTful API description language."; private static final String SCHEMA_DESCRIPTION = "oVirt API entities schema."; private static final String SCHEMA_NAME = "ovirt-engine-api-schema.xsd"; + private static final String QUERY_PARAMETER = "?"; + private static final String ENTRY_POINT_REL = "api"; + private static final String ENTRY_POINT_NAME = "root"; + private static final String ENTRY_POINT_DESCRIPTION = "The oVirt RESTful API."; private static RSDL rsdl = null; - private static final String QUERY_PARAMETER = "?"; + protected final ObjectFactory OBJECT_FACTORY = new ObjectFactory(); ApplicationMode appMode = ApplicationMode.AllModes; @@ -351,15 +356,24 @@ private synchronized RSDL getRSDL() { if (rsdl == null) { - rsdl = new RsdlBuilder(this).description(RSDL_DESCRIPTION). - rel(RSDL_REL). - href(getUriInfo().getBaseUri().getPath() + QUERY_PARAMETER + RSDL_CONSTRAINT_PARAMETER). - schema(new SchemaBuilder().rel(SCHEMA_REL) - .href(getUriInfo().getBaseUri().getPath() + QUERY_PARAMETER + SCHEMA_CONSTRAINT_PARAMETER) - .name(SCHEMA_NAME) - .description(SCHEMA_DESCRIPTION) - .build()). - build(); + rsdl = new RsdlBuilder(this).description(RSDL_DESCRIPTION) + .rel(RSDL_REL) + .href(getUriInfo().getBaseUri().getPath() + + QUERY_PARAMETER + RSDL_CONSTRAINT_PARAMETER) + .schema(new SchemaBuilder() + .rel(SCHEMA_REL) + .href(getUriInfo().getBaseUri().getPath() + + QUERY_PARAMETER + SCHEMA_CONSTRAINT_PARAMETER) + .name(SCHEMA_NAME) + .description(SCHEMA_DESCRIPTION) + .build()) + .entryPoint(new EntryPointBuilder() + .rel(ENTRY_POINT_REL) + .href(getUriInfo().getBaseUri().getPath()) + .name(ENTRY_POINT_NAME) + .description(ENTRY_POINT_DESCRIPTION) + .build()) + .build(); } return rsdl; } diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/EntryPointBuilder.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/EntryPointBuilder.java new file mode 100644 index 0000000..ac3819c --- /dev/null +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/EntryPointBuilder.java @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2010 Red Hat, Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package org.ovirt.engine.api.restapi.rsdl; + +import org.ovirt.engine.api.common.security.auth.SessionUtils; +import org.ovirt.engine.api.model.EntryPoint; +import org.ovirt.engine.api.model.Header; +import org.ovirt.engine.api.model.Headers; +import org.ovirt.engine.api.model.Request; + +public class EntryPointBuilder { + + private EntryPoint entryPoint; + + public EntryPointBuilder() { + this.entryPoint = new EntryPoint(); + } + + public EntryPoint build() { + produceRequestHeaders(); + + return this.entryPoint; + } + + private void produceRequestHeaders() { + this.entryPoint.setRequest(new Request()); + this.entryPoint.getRequest().setHeaders(new Headers()); + + injectSessionTtlHeader(this.entryPoint.getRequest().getHeaders()); + } + + private void injectSessionTtlHeader(Headers headers) { + String DESCRIPTION = + "Idle session TTL. An interval value of zero\n" + + "or less indicates that the session should never timeout"; + + if (headers != null) { + Header header = new Header(); + header.setRequired(false); + header.setName(SessionUtils.SESSION_TTL_HEADER_FIELD); + header.setValue("minutes"); + header.setDescription(DESCRIPTION); + + headers.getHeaders().add(header); + } + } + + public EntryPointBuilder description(String description) { + this.entryPoint.setDescription(description); + return this; + } + + public EntryPointBuilder href(String href) { + this.entryPoint.setHref(href); + return this; + } + + public EntryPointBuilder name(String name) { + this.entryPoint.setName(name); + return this; + } + + public EntryPointBuilder rel(String rel) { + this.entryPoint.setRel(rel); + return this; + } +} diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/RsdlBuilder.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/RsdlBuilder.java index 148e417..a3cbb79 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/RsdlBuilder.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/rsdl/RsdlBuilder.java @@ -43,6 +43,7 @@ import org.ovirt.engine.api.model.Body; import org.ovirt.engine.api.model.DetailedLink; import org.ovirt.engine.api.model.DetailedLinks; +import org.ovirt.engine.api.model.EntryPoint; import org.ovirt.engine.api.model.Header; import org.ovirt.engine.api.model.Headers; import org.ovirt.engine.api.model.HttpMethod; @@ -67,12 +68,13 @@ private static final String COLLECTION_PARAMETER_RSDL = "collection"; private static final String COLLECTION_PARAMETER_YAML = "--COLLECTION"; private RSDL rsdl; - private String entryPoint; + private String entryPointPath; private BackendApiResource apiResource; private Map<String, Action> parametersMetaData; private String rel; private String href; private Schema schema; + private EntryPoint entryPoint; private String description; private static final String ACTION = "Action"; @@ -88,7 +90,7 @@ public RsdlBuilder(BackendApiResource apiResource) { this.apiResource = apiResource; - this.entryPoint = apiResource.getUriInfo().getBaseUri().getPath(); + this.entryPointPath = apiResource.getUriInfo().getBaseUri().getPath(); this.parametersMetaData = loadParametersMetaData(); } @@ -138,6 +140,7 @@ rsdl.setHref(getHref()); rsdl.setDescription(getDescription()); rsdl.setSchema(getSchema()); + rsdl.setApi(getEntryPoint()); } catch (Exception e) { e.printStackTrace(); LOG.error("RSDL generation failure.", e); @@ -160,6 +163,11 @@ return this; } + public RsdlBuilder entryPoint(EntryPoint entryPoint) { + this.entryPoint = entryPoint; + return this; + } + public RsdlBuilder description(String description) { this.description = description; return this; @@ -175,6 +183,10 @@ public Schema getSchema() { return schema; + } + + public EntryPoint getEntryPoint() { + return entryPoint; } public String getDescription() { @@ -228,7 +240,7 @@ List<Class<?>> classes = ReflectionHelper.getClasses(RESOURCES_PACKAGE); for (String path : apiResource.getRels()) { Class<?> resource = findResource(path, classes); - String prefix = entryPoint.endsWith("/") ? entryPoint + path : entryPoint + "/" + path; + String prefix = entryPointPath.endsWith("/") ? entryPointPath + path : entryPointPath + "/" + path; results.addAll(describe(resource, prefix, new HashMap<String, Type>())); } return results; diff --git a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml index 9f60e58..eade16d 100644 --- a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml +++ b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml @@ -23,4 +23,7 @@ </user-data-constraint> </security-constraint> + <session-config> + <session-timeout>180</session-timeout> + </session-config> </web-app> -- To view, visit http://gerrit.ovirt.org/13044 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I753a379c60b2d7f88867cd0123ee69321a331f0f Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Michael Pasternak <mpast...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches