Java tutorial
/** * Copyright (c) 2015-2017 Inria * <p> * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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. * <p> * Contributors: * - Christophe Gourdin <christophe.gourdin@inria.fr> */ package org.occiware.mart.servlet.impl; import org.apache.commons.io.IOUtils; import org.occiware.mart.security.UserManagement; import org.occiware.mart.security.constants.SecurityConstants; import org.occiware.mart.security.exceptions.AuthenticationException; import org.occiware.mart.security.exceptions.ParseUserException; import org.occiware.mart.server.exception.ParseOCCIException; import org.occiware.mart.server.parser.DefaultParser; import org.occiware.mart.server.parser.HeaderPojo; import org.occiware.mart.server.parser.IRequestParser; import org.occiware.mart.server.parser.ParserFactory; import org.occiware.mart.server.utils.CollectionFilter; import org.occiware.mart.server.utils.Constants; import org.occiware.mart.servlet.utils.ServletUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * Created by cgourdin on 13/04/2017. * This class is used with main servlet, when a query is done on the servlet, this parse input entries datas (for all queries). */ public abstract class ServletEntry { private static final Logger LOGGER = LoggerFactory.getLogger(ServletEntry.class); protected OCCIServletInputRequest occiRequest; protected OCCIServletOutputResponse occiResponse; private HttpServletRequest httpRequest; private HeaderPojo headers; private HttpServletResponse httpResponse; private String path; private String contentType = Constants.MEDIA_TYPE_JSON; private String acceptType = Constants.MEDIA_TYPE_JSON; /** * User request is for creating / updating / deleting / listing users via /mart/users/. */ private boolean userRequest = false; /** * If parameters are in inputquery, it must declared here. Key: name of the * parameter Value: Value of the parameter (if url ==> decode before set the * value). */ private Map<String, String> parameters = new HashMap<>(); /** * Uri of the server. */ private URI serverURI; /** * Build a new Servlet input entry for workers. * * @param serverURI http server uri object like http://localhost:8080 * @param resp Http servlet response. * @param headers headers in a map KEY:String, Value: String. * @param req http servlet request object. * @param path the relative request path. ex: /mycompute/1/ */ public ServletEntry(URI serverURI, HttpServletResponse resp, HeaderPojo headers, HttpServletRequest req, String path) { this.serverURI = serverURI; this.httpResponse = resp; this.headers = headers; this.httpRequest = req; this.path = path; } /** * Get here the parameters of a request, this can be filters, action * parameters, pagination as describe here. Parse the request parameters * filtering : http://localhost:9090/myquery?attribute=myattributename or * http://localh...?category=mymixintag usage with category or attributes. * Pagination : * http://localhost:9090/myquery?attribute=myattributename&page=2&number=5 * where page = current page, number : max number of items to display. * Request on collections are possible with http://localhost:9090/compute/ * with text/uri-list accept type, give the full compute resources created * uri. Request on collections if no text/uri-list given, return in response * the entities defined in detail like a get on each entity (if text/occi * this may return a maximum of 3 entities not more due to limitations size * of header area in http). */ public void parseRequestParameters() { Map<String, String[]> params = httpRequest.getParameterMap(); String key; String[] vals; String val; if (params != null && !params.isEmpty()) { for (Map.Entry<String, String[]> entry : params.entrySet()) { key = entry.getKey(); vals = entry.getValue(); if (vals != null && vals.length > 0) { val = vals[0]; parameters.put(key, val); } } } } /** * @return */ public Map<String, String> getRequestParameters() { if (parameters == null) { parameters = new HashMap<>(); } return parameters; } /** * The server uri. (server part of the full url). like http://localhost:8080/ * * @return */ public URI getServerURI() { return serverURI; } public void setServerURI(final URI serverURI) { this.serverURI = serverURI; } public HttpServletResponse buildInputDatas() { String contextRoot = serverURI.toString(); LOGGER.debug("Context root : " + contextRoot); LOGGER.debug("URI relative path: " + path); // Load the parsers. // check content-type header. contentType = headers.getFirst(Constants.HEADER_CONTENT_TYPE); acceptType = headers.getFirst(Constants.HEADER_ACCEPT); // TODO : add support for uri-list combined with other type rendering. if (acceptType == null) { acceptType = contentType; } // Default values. if (contentType == null || contentType.isEmpty()) { contentType = Constants.MEDIA_TYPE_JSON; } if (acceptType == null || acceptType.isEmpty() || acceptType.equals("*/*")) { // Default to MEDIA_TYPE_JSON. acceptType = Constants.MEDIA_TYPE_JSON; } // Make it so that web browsers can get the json even when they don't have the proper header if (acceptType.equals(Constants.MEDIA_TYPE_HTML)) { acceptType = Constants.MEDIA_TYPE_JSON; } String username = "anonymous"; LOGGER.info("Input parser implement: " + contentType); LOGGER.info("Output parser implement : " + acceptType); IRequestParser outputParser = ParserFactory.build(acceptType, username); try { occiResponse = new OCCIServletOutputResponse(acceptType, username, httpResponse, outputParser); username = validateAuth(); } catch (AuthenticationException ex) { LOGGER.error(ex.getMessage()); if (!occiResponse.hasExceptions()) { parseResponseAuthenticationFailed(); occiResponse.setExceptionThrown(ex); } return occiResponse.getHttpResponse(); } // Build inputparser and output parser. IRequestParser inputParser = ParserFactory.build(contentType, username); outputParser.setUsername(username); inputParser.setServerURI(serverURI); outputParser.setServerURI(serverURI); // Create occiRequest objects. occiResponse.setUsername(username); occiRequest = new OCCIServletInputRequest(occiResponse, contentType, username, httpRequest, headers, this.getRequestParameters(), inputParser); if (inputParser instanceof DefaultParser) { LOGGER.warn("No parser for content type : " + contentType); return occiResponse.parseMessage("content type : " + contentType + " not implemented.", HttpServletResponse.SC_NOT_IMPLEMENTED); } if (outputParser instanceof DefaultParser) { LOGGER.warn("No parser for accept type : " + acceptType); return occiResponse.parseMessage("accept type : " + contentType + " not implemented.", HttpServletResponse.SC_NOT_IMPLEMENTED); } // Get Client user agent to complain with http_protocol spec, control the occi version if set by client. boolean result = ServletUtils.checkClientOCCIVersion(headers); if (!result) { LOGGER.warn("Version is not compliant, max: OCCI v1.2"); return occiResponse.parseMessage("The requested version is not implemented", HttpServletResponse.SC_NOT_IMPLEMENTED); } parseRequestParameters(); // Define if the request is a users CRUD management request. userRequest = occiRequest.getRequestPath().startsWith(Constants.RESERVED_URI_LIST_USERS); if (userRequest && contentType.equals(Constants.MEDIA_TYPE_JSON)) { // if there is content, parse user profile in json format. try { occiRequest.parseUserInput(); } catch (ParseUserException ex) { String msg = "Error while parsing input request: " + ex.getMessage(); LOGGER.error(msg); return occiResponse.parseMessage(msg, HttpServletResponse.SC_BAD_REQUEST); } } // Parse OCCI worker datas content. try { if (!userRequest) { // Parse input query to data objects. occiRequest.parseInput(); } } catch (ParseOCCIException ex) { String msg = "Error while parsing input request: " + ex.getMessage(); LOGGER.error(msg); return occiResponse.parseMessage(msg, HttpServletResponse.SC_BAD_REQUEST); } return httpResponse; } /** * Authenticate with http authentication method like basic, digest or oauth2. * * @return the current username to use with core model. * @throws AuthenticationException if there is an exception thrown during authentication process. */ private String validateAuth() throws AuthenticationException { String message; String authenticationMethod = null; // Read the authorization header values. String headerAuth = headers.getFirst(Constants.HEADER_AUTHORIZATION); if (headerAuth == null || headerAuth.trim().isEmpty()) { // No header found // if the application parameter user mode is set to none, the validation will pass through. if not the user validation will failed. if (UserManagement.checkBasicUserAuthorisation("anonymous", "")) { return "anonymous"; } else { parseResponseAuthenticationFailed(); throw (AuthenticationException) occiResponse.getExceptionThrown(); } } // parse the header to determine on which authentication method this is based (Basic, Bearer or Digest?) String valuesSpaceDelimit[] = headerAuth.split("\\s+"); if (valuesSpaceDelimit.length <= 0) { parseResponseAuthenticationFailed(); throw (AuthenticationException) occiResponse.getExceptionThrown(); } if (valuesSpaceDelimit.length > 0) { authenticationMethod = valuesSpaceDelimit[0].trim(); LOGGER.info("HTTP Authentication method : " + authenticationMethod); } if (authenticationMethod == null || authenticationMethod.trim().isEmpty()) { LOGGER.warn("please set a value for authentication like: Basic, Bearer, Digest "); parseResponseAuthenticationFailed(); throw (AuthenticationException) occiResponse.getExceptionThrown(); } String username = null; switch (authenticationMethod) { case SecurityConstants.AUTHENTICATION_BASIC: case "basic": username = parseAuthBasicUsernamePassword(valuesSpaceDelimit[1]); break; case SecurityConstants.AUTHENTICATION_DIGEST: case "digest": case SecurityConstants.AUTHENTICATION_OAUTH2: case "bearer": // for oauthv1 case "OAuth": parseResponseAuthenticationNotImplemented(authenticationMethod); break; default: parseResponseAuthenticationUnknown(authenticationMethod); break; } return username; } /** * @param values * @return * @throws AuthenticationException */ private String parseAuthBasicUsernamePassword(final String values) throws AuthenticationException { if (values == null || values.trim().isEmpty()) { parseResponseAuthenticationFailed(); throw (AuthenticationException) occiResponse.getExceptionThrown(); } // Base 64 decode the value string. byte[] bval = Base64.getDecoder().decode(values); String sval; try { sval = IOUtils.toString(bval, "UTF-8"); String[] vals = sval.split(":"); String username; String password; username = vals[0]; if (vals.length <= 1) { password = ""; } else { password = vals[1]; } // Check user authorization. if (UserManagement.checkBasicUserAuthorisation(username, password)) { return username; } else { parseResponseAuthenticationFailed(); throw (AuthenticationException) occiResponse.getExceptionThrown(); } } catch (IOException ex) { parseResponseAuthenticationFailed(); throw (AuthenticationException) occiResponse.getExceptionThrown(); } } private void parseResponseAuthenticationFailed() { occiResponse.getHttpResponse().setHeader(Constants.HEADER_WWW_AUTHENTICATE, Constants.HEADER_WWW_AUTHENTICATE_BASIC_PARTIAL + getServerURI() + "\""); String message = "Failed to authenticate"; occiResponse.parseMessage(message, HttpServletResponse.SC_UNAUTHORIZED); occiResponse.setExceptionThrown(new AuthenticationException(message)); } private void parseResponseAuthenticationNotImplemented(String authenticationMethod) { occiResponse.getHttpResponse().setHeader(Constants.HEADER_WWW_AUTHENTICATE, authenticationMethod + " realm=\"" + getServerURI() + "\""); String message = "HTTP " + authenticationMethod + " authentication method is not implemented at this time"; occiResponse.parseMessage(message, HttpServletResponse.SC_NOT_IMPLEMENTED); occiResponse.setExceptionThrown(new AuthenticationException(message)); } private void parseResponseAuthenticationUnknown(String authenticationMethod) { occiResponse.getHttpResponse().setHeader(Constants.HEADER_WWW_AUTHENTICATE, authenticationMethod + " realm=\"" + getServerURI() + "\""); String message = "HTTP " + authenticationMethod + " authentication method is unknown"; occiResponse.parseMessage(message, HttpServletResponse.SC_UNAUTHORIZED); occiResponse.setExceptionThrown(new AuthenticationException(message)); } /** * Build a collection filter. * * @return Must never return null. */ public CollectionFilter buildCollectionFilter() { String pageTmp = getRequestParameters().get(Constants.CURRENT_PAGE_KEY); String itemsNumber = getRequestParameters().get(Constants.NUMBER_ITEMS_PER_PAGE_KEY); int items = Constants.DEFAULT_NUMBER_ITEMS_PER_PAGE; int page = Constants.DEFAULT_CURRENT_PAGE; if (itemsNumber != null && !itemsNumber.trim().isEmpty()) { // Set the value from request only if this is a number. try { items = Integer.valueOf(itemsNumber); } catch (NumberFormatException ex) { // Cant parse the number LOGGER.error( "The parameter \"number\" is not set correctly, please check the parameter, this must be a number."); LOGGER.error("Default to " + items); } } if (pageTmp != null && !pageTmp.trim().isEmpty()) { try { page = Integer.valueOf(pageTmp); } catch (NumberFormatException ex) { LOGGER.error( "The parameter \"page\" is not set correctly, please check the parameter, this must be a number."); LOGGER.error("Default to " + page); } } String operatorTmp = getRequestParameters().get(Constants.OPERATOR_KEY); if (operatorTmp == null || operatorTmp.trim().isEmpty()) { operatorTmp = "0"; } int operator = 0; try { operator = Integer.valueOf(operatorTmp); } catch (NumberFormatException ex) { } String categoryFilter = getRequestParameters().get(Constants.CATEGORY_KEY); String attributeFilter = getRequestParameters().get(Constants.ATTRIBUTE_KEY); String attributeValue = getRequestParameters().get(Constants.VALUE_KEY); CollectionFilter filter = new CollectionFilter(); filter.setOperator(operator); filter.setNumberOfItemsPerPage(items); filter.setCurrentPage(page); filter.setCategoryFilter(categoryFilter); filter.setAttributeFilter(attributeFilter); filter.setValue(attributeValue); String requestPath = occiRequest.getRequestPath(); // Collection on partial or complete entity location. boolean onEntitiesLocation = occiRequest.isOnBoundedLocation(); boolean onEntityLocation = occiRequest.isOnEntityLocation(); if (onEntitiesLocation || onEntityLocation) { filter.setFilterOnEntitiesPath(path); return filter; } // Collection on mixin tag location. boolean onMixinTagLocation = occiRequest.isOnMixinTagLocation(); if (onMixinTagLocation) { // Case of the mixin tag entities request. Optional<String> optMixinTag = occiRequest.getMixinTagSchemeTermFromLocation(path); if (optMixinTag.isPresent()) { // Check if we need a subfilter. if (categoryFilter != null) { filter.setSubCategoryFilter(filter.getCategoryFilter()); filter.setCategoryFilter(optMixinTag.get()); filter.setFilterOnEntitiesPath(null); } else { filter.setCategoryFilter(optMixinTag.get()); filter.setSubCategoryFilter(null); filter.setFilterOnEntitiesPath(null); } return filter; } else { LOGGER.warn("Unknown mixin tag location : " + requestPath); } } // Determine if the collection is on a category path (kind or mixins term location). boolean onExtensionCategoryLocation = occiRequest.isOnCategoryLocation(); if (onExtensionCategoryLocation) { String categorySchemeTermFilter = null; Optional<String> optCat = occiRequest.getCategorySchemeTermFromLocation(requestPath); if (optCat.isPresent()) { // If a category filter is already there via request parameters we use a subfilter. if (categoryFilter != null) { filter.setSubCategoryFilter(filter.getCategoryFilter()); filter.setCategoryFilter(optCat.get()); filter.setFilterOnEntitiesPath(null); } else { filter.setCategoryFilter(optCat.get()); filter.setSubCategoryFilter(null); filter.setFilterOnEntitiesPath(null); } return filter; } else { LOGGER.warn("Unknown Category kind/mixin location : " + requestPath); } } // Default to entities location. filter.setFilterOnEntitiesPath(requestPath); return filter; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getContentType() { return contentType; } public void setContentType(String contentType) { this.contentType = contentType; } public String getAcceptType() { return acceptType; } public void setAcceptType(String acceptType) { this.acceptType = acceptType; } public boolean isUserRequest() { return userRequest; } public void setUserRequest(boolean userRequest) { this.userRequest = userRequest; } }