All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.captaingoldfish.scim.sdk.server.utils.RequestUtils Maven / Gradle / Ivy

// Generated by delombok at Thu Nov 02 20:38:53 CET 2023
package de.captaingoldfish.scim.sdk.server.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import de.captaingoldfish.scim.sdk.common.utils.EncodingUtils;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.apache.commons.lang3.StringUtils;
import de.captaingoldfish.scim.sdk.common.constants.ScimType;
import de.captaingoldfish.scim.sdk.common.exceptions.BadRequestException;
import de.captaingoldfish.scim.sdk.common.exceptions.InvalidFilterException;
import de.captaingoldfish.scim.sdk.common.request.BulkRequest;
import de.captaingoldfish.scim.sdk.common.resources.ServiceProvider;
import de.captaingoldfish.scim.sdk.common.schemas.Schema;
import de.captaingoldfish.scim.sdk.common.schemas.SchemaAttribute;
import de.captaingoldfish.scim.sdk.server.filter.AttributePathRoot;
import de.captaingoldfish.scim.sdk.server.filter.FilterNode;
import de.captaingoldfish.scim.sdk.server.filter.antlr.FilterAttributeName;
import de.captaingoldfish.scim.sdk.server.filter.antlr.FilterRuleErrorListener;
import de.captaingoldfish.scim.sdk.server.filter.antlr.FilterVisitor;
import de.captaingoldfish.scim.sdk.server.filter.antlr.ScimFilterLexer;
import de.captaingoldfish.scim.sdk.server.filter.antlr.ScimFilterParser;
import de.captaingoldfish.scim.sdk.server.schemas.ResourceType;


/**
 * author Pascal Knueppel 
* created at: 12.10.2019 - 20:08
*
* this class will add some helper methods that can be used to validate or modify request based attributes * based on the SCIM specification RFC7643 and RFC7644 */ public final class RequestUtils { @java.lang.SuppressWarnings("all") private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RequestUtils.class); /** * this method will parse either the attributes parameter or the excludedAttributes parameter into a list. The * expected form of the attributes list is: form (e.g., userName, name, emails) * * @param attributes the comma separated string of scim attribute names * @return the list of attributes */ public static List getAttributes(String attributes) { String[] attributeNameArray = getAttributeList(attributes).orElse(new String[0]); return Arrays.asList(attributeNameArray); } /** * this method will parse either the attributes parameter or the excludedAttributes parameter into a list of * {@link SchemaAttribute}s. The expected form of the attributes list is: form (e.g., userName, name, emails) * * @param attributes the comma separated string of scim attribute names * @return the list of attributes */ public static List getAttributes(ResourceType resourceType, String attributes) { String[] attributeNameArray = getAttributeList(attributes).orElse(new String[0]); return Arrays.stream(attributeNameArray) .map(s -> getSchemaAttributeByAttributeName(resourceType, s)) .collect(Collectors.toList()); } /** * parses the given attributes into an array of strings * * @param attributes the attributes request parameter that is expected to be a comma separated string * @return the array of the separated attribute names or an empty */ private static Optional getAttributeList(String attributes) { if (StringUtils.isBlank(attributes)) { return Optional.empty(); } if (!attributes.matches("(^[a-zA-Z0-9]([:a-zA-Z0-9.,]+)?[a-zA-Z0-9]$)*")) { String errorMessage = "the attributes or excludedAttributes parameter \'" + attributes + "\' is malformed please " + "check your syntax and please note that whitespaces are not allowed."; throw new BadRequestException(errorMessage, null, null); } return Optional.of(attributes.split(",")); } /** * From RFC7644 chapter 3.9:
* *
   *     Clients MAY request a partial resource representation on any
   *     operation that returns a resource within the response by specifying
   *     either of the mutually exclusive URL query parameters "attributes" or
   *     "excludedAttributes"
   * 
* * so only one these parameters are allowed to be specified in a request * * @param attributes the required attributes that should be present in the response * @param excludedAttributes the attributes that should not be returned in the response */ public static void validateAttributesAndExcludedAttributes(String attributes, String excludedAttributes) { if (StringUtils.isNotBlank(attributes) && StringUtils.isNotBlank(excludedAttributes)) { final String errorMessage = "the attributes and excludedAttributes parameter must not be set at the same time:" + "\n\tattributes: \'" + attributes + "\'\n\texcludedAttributes: \'" + excludedAttributes + "\'"; throw new BadRequestException(errorMessage, null, ScimType.Custom.INVALID_PARAMETERS); } } /** * parses the filter of a list request * * @param resourceType the resource type that describes the endpoint on which the filter is used so that the * filter expression can be correctly resolved * @param filter the filter expression that must apply to the given resource type * @return The parsed filter expression as resolvable tree structure that might be used to resolve them to jpa * predicates for example */ public static FilterNode parseFilter(ResourceType resourceType, String filter) { if (StringUtils.isBlank(filter)) { return null; } FilterRuleErrorListener filterRuleErrorListener = new FilterRuleErrorListener(); ScimFilterLexer lexer = new ScimFilterLexer(CharStreams.fromString(filter)); lexer.removeErrorListeners(); lexer.addErrorListener(filterRuleErrorListener); CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); ScimFilterParser scimFilterParser = new ScimFilterParser(commonTokenStream); scimFilterParser.removeErrorListeners(); scimFilterParser.addErrorListener(filterRuleErrorListener); ScimFilterParser.FilterContext filterContext = scimFilterParser.filter(); FilterVisitor filterVisitor = new FilterVisitor(resourceType); return filterVisitor.visit(filterContext); } /** * parses a value path context for patch path expressions * * @param resourceType the resource type that describes the endpoint on which the path expression is used * @param path the path expression that must apply to the given resource type * @return The parsed path expression as resolvable tree structure to find matching attributes within a single * resource */ public static AttributePathRoot parsePatchPath(ResourceType resourceType, String path) { if (StringUtils.isBlank(path)) { return null; } FilterRuleErrorListener filterRuleErrorListener = new FilterRuleErrorListener(); ScimFilterLexer lexer = new ScimFilterLexer(CharStreams.fromString(path)); lexer.removeErrorListeners(); lexer.addErrorListener(filterRuleErrorListener); CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); ScimFilterParser scimFilterParser = new ScimFilterParser(commonTokenStream); scimFilterParser.removeErrorListeners(); scimFilterParser.addErrorListener(filterRuleErrorListener); ScimFilterParser.ValuePathContext valuePathContext = scimFilterParser.valuePath(); FilterVisitor filterVisitor = new FilterVisitor(resourceType); FilterNode filterNode = filterVisitor.visit(valuePathContext); if (filterNode == null || !AttributePathRoot.class.isAssignableFrom(filterNode.getClass())) { throw new BadRequestException("the path expression is invalid and not supported for patch operations: \'" + path + "\'", null, ScimType.RFC7644.INVALID_PATH); } AttributePathRoot attributePathRoot = (AttributePathRoot)filterNode; attributePathRoot.setOriginalExpressionString(path); return attributePathRoot; } /** * tries to parse the incoming startIndex value as long number * * @param countQueryParameter the query parameter that should be a number * @return the parsed startIndex value or an empty if the parameter is missing */ public static Optional parseStartIndex(String startIndexQueryParameter) { if (StringUtils.isEmpty(startIndexQueryParameter)) { return Optional.empty(); } try { return Optional.of(Long.parseLong(startIndexQueryParameter)); } catch (NumberFormatException ex) { throw new BadRequestException(String.format("Got invalid startIndex value \'%s\'. StartIndex must be a number", startIndexQueryParameter)); } } /** * tries to parse the incoming count value as integer number * * @param countQueryParameter the query parameter that should be a number * @return the parsed count value or an empty if the parameter is missing */ public static Optional parseCount(String countQueryParameter) { if (StringUtils.isEmpty(countQueryParameter)) { return Optional.empty(); } try { return Optional.of(Integer.parseInt(countQueryParameter)); } catch (NumberFormatException ex) { throw new BadRequestException(String.format("Got invalid count value \'%s\'. Count must be a number", countQueryParameter)); } } /** * The 1-based index of the first query result. A value less than 1 SHALL be interpreted as 1. * * @param startIndex the index to start with to list the resources * @return number "1" or greater */ public static long getEffectiveStartIndex(Long startIndex) { if (startIndex == null || startIndex < 1) { return 1; } return startIndex; } /** * Will get the effective count value as described in RFC7644:
*
* Non-negative integer. Specifies the desired maximum number of query results per page, e.g., 10. A negative * value SHALL be interpreted as "0". A value of "0" indicates that no resource results are to be returned * except for "totalResults".
* DEFAULT: None
* When specified, the service provider MUST NOT return more results than specified, although it MAY return * fewer results. If unspecified, the maximum number of results is set by the service provider. */ public static int getEffectiveCount(ServiceProvider serviceProvider, Integer count) { if (count == null) { return serviceProvider.getFilterConfig().getMaxResults(); } if (count < 0) { return 0; } return Math.min(count, serviceProvider.getFilterConfig().getMaxResults()); } /** * gets the {@link SchemaAttribute} from the given {@link ResourceType} * * @param resourceType the resource type from which the attribute definition should be extracted * @param attributeName this instance holds the attribute name to extract the {@link SchemaAttribute} from the * {@link ResourceType} * @return the found {@link SchemaAttribute} definition * @throws BadRequestException if no {@link SchemaAttribute} was found for the given name attribute */ public static SchemaAttribute getSchemaAttributeByAttributeName(ResourceType resourceType, String attributeName) { try { return StringUtils.isBlank(attributeName) ? null : getSchemaAttribute(resourceType, new FilterAttributeName(attributeName)); } catch (BadRequestException ex) { ex.setScimType(ScimType.Custom.INVALID_PARAMETERS); throw ex; } } /** * gets the {@link SchemaAttribute} from the given {@link ResourceType} * * @param resourceType the resource type from which the attribute definition should be extracted * @param attributeName this instance holds the attribute name to extract the {@link SchemaAttribute} from the * {@link ResourceType} * @return the found {@link SchemaAttribute} definition * @throws InvalidFilterException if no {@link SchemaAttribute} was found for the given name attribute */ public static SchemaAttribute getSchemaAttributeForFilter(ResourceType resourceType, FilterAttributeName attributeName) { try { return getSchemaAttribute(resourceType, attributeName); } catch (BadRequestException ex) { throw new InvalidFilterException(ex.getMessage(), ex); } } /** * gets the {@link SchemaAttribute} from the given {@link ResourceType} * * @param resourceType the resource type from which the attribute definition should be extracted * @param attributeName this instance holds the attribute name to extract the {@link SchemaAttribute} from the * {@link ResourceType} * @return the found {@link SchemaAttribute} definition * @throws BadRequestException if no {@link SchemaAttribute} was found for the given name attribute */ private static SchemaAttribute getSchemaAttribute(ResourceType resourceType, FilterAttributeName attributeName) { if (attributeName == null) { return null; } final boolean resourceUriPresent = StringUtils.isNotBlank(attributeName.getResourceUri()); final String scimNodeName = attributeName.getShortName(); List resourceTypeSchemas = resourceType.getAllSchemas(); List schemaAttributeList; if (resourceUriPresent) { schemaAttributeList = resourceTypeSchemas.stream() .filter(schema -> attributeName.getResourceUri() .equals(schema.getId().orElse(null))) .map(schema -> schema.getSchemaAttribute(scimNodeName)) .filter(Objects::nonNull) .collect(Collectors.toList()); } else { schemaAttributeList = resourceTypeSchemas.stream() .map(schema -> schema.getSchemaAttribute(scimNodeName)) .filter(Objects::nonNull) .collect(Collectors.toList()); } if (schemaAttributeList.isEmpty()) { throw new BadRequestException("the attribute with the name \'" + attributeName.getShortName() + "\' is " + "unknown to resource type \'" + resourceType.getName() + "\'", null, ScimType.Custom.INVALID_PARAMETERS); } else if (schemaAttributeList.size() > 1) { String schemaIds = schemaAttributeList.stream() .map(schemaAttribute -> schemaAttribute.getSchema().getId().orElse(null)) .collect(Collectors.joining(",")); String exampleAttributeName = schemaAttributeList.get(0).getSchema().getId().orElse(null) + ":" + attributeName.getShortName(); throw new BadRequestException("the attribute with the name \'" + attributeName.getShortName() + "\' is " + "ambiguous it was found in the schemas with the ids [" + schemaIds + "]. " + "Please use the fully qualified Uri for this attribute e.g.: " + exampleAttributeName, null, null); } else { return schemaAttributeList.get(0); } } /** * gets the query parameter from the given URL * * @param query the query string * @return the query parameters as a map */ public static Map getQueryParameters(String query) { Map queryParameter = new HashMap<>(); if (StringUtils.isBlank(query)) { return Collections.emptyMap(); } String[] pairs = query.split("&"); for ( String pair : pairs ) { if (StringUtils.isBlank(pair) || pair.charAt(0) == '=') { continue; } int index = pair.indexOf("="); String param; String value; if (index == -1) { param = pair; value = ""; } else { param = pair.substring(0, index); value = pair.substring(index + 1); } queryParameter.put(EncodingUtils.urlDecode(param.toLowerCase()), EncodingUtils.urlDecode(value)); } return queryParameter; } /** * will check the failOnErrors attribute in a bulk request and return a sanitized value.
*
* RFC7644 chapter 3.7.3 defines the minimum value of failOnErrors as 1 * *
   *   The "failOnErrors" attribute is set to '1', indicating that the
   *   service provider will stop processing and return results after one
   *   error
   * 
* * @param bulkRequest the bulk request * @return a failOnErrors value that has been validated and sanitized */ public static int getEffectiveFailOnErrors(BulkRequest bulkRequest) { Integer failOnErrors = bulkRequest.getFailOnErrors().orElse(null); if (failOnErrors == null) { return Integer.MAX_VALUE; } if (failOnErrors < 1) { return 1; } return failOnErrors; } @java.lang.SuppressWarnings("all") private RequestUtils() {} }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy