From 96b82c288995fa8bcb45eb2c9cb290794125fe03 Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 8 Apr 2024 12:41:06 +0800 Subject: [PATCH 01/62] feat(): add cors key --- .../rpc/protocol/tri/rest/RestConstants.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 7a431bd02a3..4bf1622633d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -48,6 +48,22 @@ public final class RestConstants { public static final String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = "org.springframework.web.servlet.HandlerMapping.producibleMediaTypes"; + /* Cors Config */ + public static final String VARY = "Vary"; + public static final String ORIGIN = "Origin"; + public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + public static final String WS = "ws"; + public static final String WSS = "wss"; + /* Configuration Key */ public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size"; From 513789ae772dcf9f275137e08f4beff8a5602027 Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 8 Apr 2024 14:24:38 +0800 Subject: [PATCH 02/62] feat(): add base cors class --- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 491 ++++++++++++++++++ .../protocol/tri/rest/cors/CorsProcessor.java | 25 + .../tri/rest/cors/DefaultCorsProcessor.java | 187 +++++++ 3 files changed, 703 insertions(+) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java new file mode 100644 index 00000000000..2c7244960fb --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -0,0 +1,491 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpMethods; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ObjectUtils; + +public class CorsMeta { + + public static final String ALL = "*"; + + private static final List ALL_LIST = Collections.singletonList(ALL); + + private static final OriginPattern ALL_PATTERN = new OriginPattern("*"); + + private static final List ALL_PATTERN_LIST = Collections.singletonList(ALL_PATTERN); + + private static final List DEFAULT_PERMIT_ALL = Collections.singletonList(ALL); + + private static final List DEFAULT_METHODS = + Collections.unmodifiableList(Arrays.asList(HttpMethods.GET, HttpMethods.HEAD)); + + private static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( + Arrays.asList(HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name())); + + private List allowedOrigins; + + private List allowedOriginPatterns; + + private List allowedMethods; + + private List resolvedMethods = DEFAULT_METHODS; + + private List allowedHeaders; + + private List exposedHeaders; + + private Boolean allowCredentials; + + private Boolean allowPrivateNetwork; + + private Long maxAge; + + public CorsMeta(CorsMeta other) { + this.allowedOrigins = other.allowedOrigins; + this.allowedOriginPatterns = other.allowedOriginPatterns; + this.allowedMethods = other.allowedMethods; + this.resolvedMethods = other.resolvedMethods; + this.allowedHeaders = other.allowedHeaders; + this.exposedHeaders = other.exposedHeaders; + this.allowCredentials = other.allowCredentials; + this.allowPrivateNetwork = other.allowPrivateNetwork; + this.maxAge = other.maxAge; + } + + public void setAllowedOrigins(List origins) { + this.allowedOrigins = (origins == null + ? null + : origins.stream() + .filter(Objects::nonNull) + .map(this::trimTrailingSlash) + .collect(Collectors.toList())); + } + + private String trimTrailingSlash(String origin) { + return (origin.endsWith("/") ? origin.substring(0, origin.length() - 1) : origin); + } + + public List getAllowedOrigins() { + return this.allowedOrigins; + } + + public void addAllowedOrigin(String origin) { + if (origin == null) { + return; + } + if (this.allowedOrigins == null) { + this.allowedOrigins = new ArrayList<>(4); + } else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { + setAllowedOrigins(DEFAULT_PERMIT_ALL); + } + origin = trimTrailingSlash(origin); + this.allowedOrigins.add(origin); + } + + public CorsMeta setAllowedOriginPatterns(List allowedOriginPatterns) { + if (allowedOriginPatterns == null) { + this.allowedOriginPatterns = null; + } else { + this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size()); + for (String patternValue : allowedOriginPatterns) { + addAllowedOriginPattern(patternValue); + } + } + return this; + } + + public List getAllowedOriginPatterns() { + if (this.allowedOriginPatterns == null) { + return null; + } + return this.allowedOriginPatterns.stream() + .map(OriginPattern::getDeclaredPattern) + .collect(Collectors.toList()); + } + + public void addAllowedOriginPattern(String originPattern) { + if (originPattern == null) { + return; + } + if (this.allowedOriginPatterns == null) { + this.allowedOriginPatterns = new ArrayList<>(4); + } + originPattern = trimTrailingSlash(originPattern); + this.allowedOriginPatterns.add(new OriginPattern(originPattern)); + if (this.allowedOrigins == DEFAULT_PERMIT_ALL) { + this.allowedOrigins = null; + } + } + + public void setAllowedMethods(List allowedMethods) { + this.allowedMethods = (allowedMethods != null ? new ArrayList<>(allowedMethods) : null); + if (!CollectionUtils.isEmpty(allowedMethods)) { + this.resolvedMethods = new ArrayList<>(allowedMethods.size()); + for (String method : allowedMethods) { + if (ALL.equals(method)) { + this.resolvedMethods = null; + break; + } + this.resolvedMethods.add(HttpMethods.valueOf(method)); + } + } else { + this.resolvedMethods = DEFAULT_METHODS; + } + } + + public List getAllowedMethods() { + return this.allowedMethods; + } + + public void addAllowedMethod(HttpMethods method) { + addAllowedMethod(method.name()); + } + + public void addAllowedMethod(String method) { + if (StringUtils.hasText(method)) { + if (this.allowedMethods == null) { + this.allowedMethods = new ArrayList<>(4); + this.resolvedMethods = new ArrayList<>(4); + } else if (this.allowedMethods == DEFAULT_PERMIT_METHODS) { + setAllowedMethods(DEFAULT_PERMIT_METHODS); + } + this.allowedMethods.add(method); + if (ALL.equals(method)) { + this.resolvedMethods = null; + } else if (this.resolvedMethods != null) { + this.resolvedMethods.add(HttpMethods.valueOf(method)); + } + } + } + + public void setAllowedHeaders(List allowedHeaders) { + this.allowedHeaders = (allowedHeaders != null ? new ArrayList<>(allowedHeaders) : null); + } + + public List getAllowedHeaders() { + return this.allowedHeaders; + } + + public void addAllowedHeader(String allowedHeader) { + if (this.allowedHeaders == null) { + this.allowedHeaders = new ArrayList<>(4); + } else if (this.allowedHeaders == DEFAULT_PERMIT_ALL) { + setAllowedHeaders(DEFAULT_PERMIT_ALL); + } + this.allowedHeaders.add(allowedHeader); + } + + public void setExposedHeaders(List exposedHeaders) { + this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null); + } + + public List getExposedHeaders() { + return this.exposedHeaders; + } + + public void addExposedHeader(String exposedHeader) { + if (this.exposedHeaders == null) { + this.exposedHeaders = new ArrayList<>(4); + } + this.exposedHeaders.add(exposedHeader); + } + + public void setAllowCredentials(Boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } + + public Boolean getAllowCredentials() { + return this.allowCredentials; + } + + public void setAllowPrivateNetwork(Boolean allowPrivateNetwork) { + this.allowPrivateNetwork = allowPrivateNetwork; + } + + public Boolean getAllowPrivateNetwork() { + return this.allowPrivateNetwork; + } + + public void setMaxAge(Duration maxAge) { + this.maxAge = maxAge.getSeconds(); + } + + public void setMaxAge(Long maxAge) { + this.maxAge = maxAge; + } + + public Long getMaxAge() { + return this.maxAge; + } + + public CorsMeta applyPermitDefaultValues() { + if (this.allowedOrigins == null && this.allowedOriginPatterns == null) { + this.allowedOrigins = DEFAULT_PERMIT_ALL; + } + if (this.allowedMethods == null) { + this.allowedMethods = DEFAULT_PERMIT_METHODS; + this.resolvedMethods = + DEFAULT_PERMIT_METHODS.stream().map(HttpMethods::valueOf).collect(Collectors.toList()); + } + if (this.allowedHeaders == null) { + this.allowedHeaders = DEFAULT_PERMIT_ALL; + } + if (this.maxAge == null) { + this.maxAge = 1800L; + } + return this; + } + + public void validateAllowCredentials() { + if (this.allowCredentials.equals(Boolean.TRUE) + && this.allowedOrigins != null + && this.allowedOrigins.contains(ALL)) { + + throw new IllegalArgumentException( + "When allowCredentials is true, allowedOrigins cannot contain the special value \"*\" " + + "since that cannot be set on the \"Access-Control-Allow-Origin\" response header. " + + "To allow credentials to a set of origins, list them explicitly " + + "or consider using \"allowedOriginPatterns\" instead."); + } + } + + public void validateAllowPrivateNetwork() { + if (this.allowPrivateNetwork.equals(Boolean.TRUE) + && this.allowedOrigins != null + && this.allowedOrigins.contains(ALL)) { + + throw new IllegalArgumentException( + "When allowPrivateNetwork is true, allowedOrigins cannot contain the special value \"*\" " + + "as it is not recommended from a security perspective. " + + "To allow private network access to a set of origins, list them explicitly " + + "or consider using \"allowedOriginPatterns\" instead."); + } + } + + public CorsMeta combine(CorsMeta other) { + if (other == null) { + return this; + } + // Bypass setAllowedOrigins to avoid re-compiling patterns + CorsMeta config = new CorsMeta(this); + List origins = combine(getAllowedOrigins(), other.getAllowedOrigins()); + List patterns = combinePatterns(this.allowedOriginPatterns, other.allowedOriginPatterns); + config.allowedOrigins = (origins == DEFAULT_PERMIT_ALL && !CollectionUtils.isEmpty(patterns) ? null : origins); + config.allowedOriginPatterns = patterns; + config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods())); + config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders())); + config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders())); + Boolean allowCredentials = other.getAllowCredentials(); + if (allowCredentials != null) { + config.setAllowCredentials(allowCredentials); + } + Boolean allowPrivateNetwork = other.getAllowPrivateNetwork(); + if (allowPrivateNetwork != null) { + config.setAllowPrivateNetwork(allowPrivateNetwork); + } + Long maxAge = other.getMaxAge(); + if (maxAge != null) { + config.setMaxAge(maxAge); + } + return config; + } + + private List combine(List source, List other) { + if (other == null) { + return (source != null ? source : Collections.emptyList()); + } + if (source == null) { + return other; + } + if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) { + return other; + } + if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS) { + return source; + } + if (source.contains(ALL) || other.contains(ALL)) { + return ALL_LIST; + } + Set combined = new LinkedHashSet<>(source.size() + other.size()); + combined.addAll(source); + combined.addAll(other); + return new ArrayList<>(combined); + } + + private List combinePatterns(List source, List other) { + + if (other == null) { + return (source != null ? source : Collections.emptyList()); + } + if (source == null) { + return other; + } + if (source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) { + return ALL_PATTERN_LIST; + } + Set combined = new LinkedHashSet<>(source.size() + other.size()); + combined.addAll(source); + combined.addAll(other); + return new ArrayList<>(combined); + } + + public String checkOrigin(String origin) { + if (!StringUtils.hasText(origin)) { + return null; + } + String originToCheck = trimTrailingSlash(origin); + if (!ObjectUtils.isEmpty(this.allowedOrigins)) { + if (this.allowedOrigins.contains(ALL)) { + validateAllowCredentials(); + validateAllowPrivateNetwork(); + return ALL; + } + for (String allowedOrigin : this.allowedOrigins) { + if (originToCheck.equalsIgnoreCase(allowedOrigin)) { + return origin; + } + } + } + if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) { + for (OriginPattern p : this.allowedOriginPatterns) { + if (p.getDeclaredPattern().equals(ALL) + || p.getPattern().matcher(originToCheck).matches()) { + return origin; + } + } + } + return null; + } + + public List checkHttpMethods(HttpMethods requestMethod) { + if (requestMethod == null) { + return null; + } + if (this.resolvedMethods == null) { + return Collections.singletonList(requestMethod); + } + return (this.resolvedMethods.contains(requestMethod) ? this.resolvedMethods : null); + } + + public List checkHeaders(List requestHeaders) { + if (requestHeaders == null) { + return null; + } + if (requestHeaders.isEmpty()) { + return Collections.emptyList(); + } + if (ObjectUtils.isEmpty(this.allowedHeaders)) { + return null; + } + + boolean allowAnyHeader = this.allowedHeaders.contains(ALL); + List result = new ArrayList<>(requestHeaders.size()); + for (String requestHeader : requestHeaders) { + if (StringUtils.hasText(requestHeader)) { + requestHeader = requestHeader.trim(); + if (allowAnyHeader) { + result.add(requestHeader); + } else { + for (String allowedHeader : this.allowedHeaders) { + if (requestHeader.equalsIgnoreCase(allowedHeader)) { + result.add(requestHeader); + break; + } + } + } + } + } + return (result.isEmpty() ? null : result); + } + + private static class OriginPattern { + + private static final Pattern PORTS_PATTERN = Pattern.compile("(.*):\\[(\\*|\\d+(,\\d+)*)]"); + + private final String declaredPattern; + + private final Pattern pattern; + + OriginPattern(String declaredPattern) { + this.declaredPattern = declaredPattern; + this.pattern = initPattern(declaredPattern); + } + + private static Pattern initPattern(String patternValue) { + String portList = null; + Matcher matcher = PORTS_PATTERN.matcher(patternValue); + if (matcher.matches()) { + patternValue = matcher.group(1); + portList = matcher.group(2); + } + + patternValue = "\\Q" + patternValue + "\\E"; + patternValue = patternValue.replace("*", "\\E.*\\Q"); + + if (portList != null) { + patternValue += (portList.equals(ALL) ? "(:\\d+)?" : ":(" + portList.replace(',', '|') + ")"); + } + + return Pattern.compile(patternValue); + } + + public String getDeclaredPattern() { + return this.declaredPattern; + } + + public Pattern getPattern() { + return this.pattern; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || !getClass().equals(other.getClass())) { + return false; + } + return ObjectUtils.equals(this.declaredPattern, ((OriginPattern) other).declaredPattern); + } + + @Override + public int hashCode() { + return this.declaredPattern.hashCode(); + } + + @Override + public String toString() { + return this.declaredPattern; + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java new file mode 100644 index 00000000000..b566bdea0e2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; + +public interface CorsProcessor { + + boolean process(CorsMeta corsMeta, HttpRequest corsRequest, HttpResponse corsResponse); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java new file mode 100644 index 00000000000..4b5f480840f --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK; +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK; +import static org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils.getPort; + +public class DefaultCorsProcessor implements CorsProcessor { + private static final ErrorTypeAwareLogger LOGGER = + LoggerFactory.getErrorTypeAwareLogger(DefaultCorsProcessor.class); + + @Override + public boolean process(CorsMeta config, HttpRequest request, HttpResponse response) { + // set vary header + setVaryHeaders(response); + + // skip if is not a cors request + if (!isCorsRequest(request)) { + return true; + } + + // skip if origin already contains in Access-Control-Allow-Origin header + if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) != null) { + return true; + } + + if (config == null) { + // if no cors config and is a preflight request + if (isPreFlight(request)) { + reject(response); + return false; + } + return true; + } + + // handle cors request + return handleInternal(request, response, config, isPreFlight(request)); + } + + protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { + String allowOrigin = config.checkOrigin(request.header(RestConstants.ORIGIN)); + if (allowOrigin == null) { + reject(response); + return false; + } + + List allowHttpMethods = config.checkHttpMethods(getHttpMethods(request, isPreLight)); + if (allowHttpMethods == null) { + reject(response); + return false; + } + + List allowHeaders = config.checkHeaders(getHttpHeaders(request, isPreLight)); + if (isPreLight && allowHeaders == null) { + reject(response); + return false; + } + + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); + + if (isPreLight) { + response.setHeader( + RestConstants.ACCESS_CONTROL_ALLOW_METHODS, + allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); + } + + if (isPreLight && !allowHeaders.isEmpty()) { + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); + } + + if (!CollectionUtils.isEmpty(config.getExposedHeaders())) { + response.setHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS, config.getExposedHeaders()); + } + + if (Boolean.TRUE.equals(config.getAllowCredentials())) { + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); + } + + if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) + && Boolean.parseBoolean(request.header(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) { + response.setHeader(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.TRUE.toString()); + } + + if (isPreLight && config.getMaxAge() != null) { + response.setHeader( + RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); + } + + return true; + } + + private HttpMethods getHttpMethods(HttpRequest request, Boolean isPreLight) { + if (isPreLight) { + return HttpMethods.valueOf(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + } + return HttpMethods.valueOf(request.method()); + } + + private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { + if (isPreLight) { + return request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + } + return new ArrayList<>(request.headerNames()); + } + + private void reject(HttpResponse response) { + response.setStatus(HttpStatus.FORBIDDEN.getCode()); + response.setBody("Invalid CORS request"); + } + + private boolean isPreFlight(HttpRequest request) { + // preflight request is a OPTIONS request with Access-Control-Request-Method header + return request.method().equals(HttpMethods.OPTIONS.name()) + && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; + } + + private boolean isCorsRequest(HttpRequest request) { + // skip if request has no origin header + String origin = request.header(RestConstants.ORIGIN); + if (origin == null) { + return false; + } + + try { + URI uri = new URI(origin); + + // return true if origin is not the same as request's scheme, host and port + return !(Objects.equals(uri.getScheme(), request.scheme()) + && uri.getHost().equals(request.serverName()) + && getPort(uri.getScheme(), uri.getPort()) == getPort(request.scheme(), request.serverPort())); + } catch (URISyntaxException e) { + LOGGER.debug("Origin header is not a valid URI: " + origin); + // skip if origin is not a valid URI + return false; + } + } + + private void setVaryHeaders(HttpResponse response) { + List varyHeaders = response.headerValues(RestConstants.VARY); + if (varyHeaders == null) { + response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + } else { + if (!varyHeaders.contains(RestConstants.ORIGIN)) { + response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); + } + if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + } + if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + } + } + } +} From 6897c8fb8b6a45123709e5205a70eca2831240f7 Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 8 Apr 2024 16:03:10 +0800 Subject: [PATCH 03/62] feat(): add cors class in rpc-triple and rest-spring --- .../SpringMvcRequestMappingResolver.java | 22 ++++++++++ .../compatible/SpringDemoServiceImpl.java | 2 + .../rpc/protocol/tri/rest/RestConstants.java | 2 + .../rpc/protocol/tri/rest/cors/CorsMeta.java | 2 + .../DefaultRequestMappingRegistry.java | 1 - .../tri/rest/mapping/RequestMapping.java | 41 +++++++++++++++---- .../mapping/RestRequestHandlerMapping.java | 11 +++++ .../protocol/tri/rest/util/RequestUtils.java | 11 +++++ 8 files changed, 84 insertions(+), 8 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index ca4dd68a3bc..27fed032e73 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -19,6 +19,7 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; @@ -28,6 +29,8 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; +import java.util.Arrays; + import org.springframework.http.HttpStatus; @Activate(onClass = "org.springframework.web.bind.annotation.RequestMapping") @@ -62,9 +65,11 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { return null; } AnnotationMeta responseStatus = serviceMeta.findMergedAnnotation(Annotations.ResponseStatus); + AnnotationMeta crossOrigin = serviceMeta.findMergedAnnotation(Annotations.CrossOrigin); return builder(requestMapping, responseStatus) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) + .cors(createCorsMeta(crossOrigin)) .build(); } @@ -80,10 +85,13 @@ public RequestMapping resolve(MethodMeta methodMeta) { } ServiceMeta serviceMeta = methodMeta.getServiceMeta(); AnnotationMeta responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus); + + AnnotationMeta crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin); return builder(requestMapping, responseStatus) .name(methodMeta.getMethod().getName()) .contextPath(serviceMeta.getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .cors(createCorsMeta(crossOrigin)) .build(); } @@ -104,4 +112,18 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp .consume(requestMapping.getStringArray("consumes")) .produce(requestMapping.getStringArray("produces")); } + + private CorsMeta createCorsMeta(AnnotationMeta crossOrigin) { + CorsMeta meta = new CorsMeta(); + if (crossOrigin == null) { + return meta; + } + meta.setAllowCredentials(crossOrigin.getBoolean("allowCredentials")); + meta.setAllowedHeaders(Arrays.asList(crossOrigin.getStringArray("allowedHeaders"))); + meta.setAllowedMethods(Arrays.asList(crossOrigin.getStringArray("allowedMethods"))); + meta.setAllowedOrigins(Arrays.asList(crossOrigin.getStringArray("allowedOrigins"))); + meta.setExposedHeaders(Arrays.asList(crossOrigin.getStringArray("exposedHeaders"))); + meta.setMaxAge(crossOrigin.getNumber("maxAge").longValue()); + return meta; + } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java index bf235fed574..46498e5162e 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java @@ -23,8 +23,10 @@ import java.util.Map; import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.ExceptionHandler; +@CrossOrigin public class SpringDemoServiceImpl implements SpringRestDemoService { private static Map context; private boolean called; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 4bf1622633d..8cce4ef25b8 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -63,6 +63,8 @@ public final class RestConstants { public static final String HTTPS = "https"; public static final String WS = "ws"; public static final String WSS = "wss"; + public static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network"; + public static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network"; /* Configuration Key */ public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 2c7244960fb..311bf7f59da 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -70,6 +70,8 @@ public class CorsMeta { private Long maxAge; + public CorsMeta() {} + public CorsMeta(CorsMeta other) { this.allowedOrigins = other.allowedOrigins; this.allowedOriginPatterns = other.allowedOriginPatterns; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 81680ac4f24..34a0cdfee10 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -202,7 +202,6 @@ public HandlerMeta lookup(HttpRequest request) { if (producesCondition != null) { request.setAttribute(RestConstants.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producesCondition.getMediaTypes()); } - return handler; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 2c38367c71a..65c2c7da21a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -18,6 +18,7 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.Condition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConditionWrapper; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConsumesCondition; @@ -44,6 +45,7 @@ public final class RequestMapping implements Condition customCondition, - ResponseMeta response) { + ResponseMeta response, + CorsMeta corsMeta) { this.name = name; this.pathCondition = pathCondition; this.methodsCondition = methodsCondition; @@ -66,6 +69,7 @@ private RequestMapping( this.producesCondition = producesCondition; this.customCondition = customCondition == null ? null : new ConditionWrapper(customCondition); this.response = response; + this.corsMeta = corsMeta; } public static Builder builder() { @@ -83,7 +87,8 @@ public RequestMapping combine(RequestMapping other) { ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response); + CorsMeta meta = this.corsMeta.combine(other.corsMeta); + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response, meta); } private > T combine(T value, T other) { @@ -155,8 +160,8 @@ private RequestMapping doMatch(HttpRequest request, PathCondition pathCondition) return null; } } - - return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response); + return new RequestMapping( + name, paths, methods, params, headers, consumes, produces, custom, response, corsMeta); } public String getName() { @@ -175,6 +180,10 @@ public ResponseMeta getResponse() { return response; } + public CorsMeta getCorsMeta() { + return corsMeta; + } + @Override public int compareTo(RequestMapping other, HttpRequest request) { int result; @@ -224,6 +233,11 @@ public int compareTo(RequestMapping other, HttpRequest request) { result = customCondition.compareTo(other.customCondition, request); return result; } + if (corsMeta != null) { + // TODO + // result = corsMeta.compareTo(other.corsMeta, request); + // return result; + } return 0; } @@ -238,7 +252,8 @@ public int hashCode() { headersCondition, consumesCondition, producesCondition, - customCondition); + customCondition, + corsMeta); this.hashCode = hashCode; } return hashCode; @@ -259,7 +274,8 @@ public boolean equals(Object obj) { && Objects.equals(headersCondition, other.headersCondition) && Objects.equals(consumesCondition, other.consumesCondition) && Objects.equals(producesCondition, other.producesCondition) - && Objects.equals(customCondition, other.customCondition); + && Objects.equals(customCondition, other.customCondition) + && Objects.equals(corsMeta, other.corsMeta); } @Override @@ -290,6 +306,9 @@ public String toString() { if (response != null) { sb.append(", response=").append(response); } + if (corsMeta != null) { + sb.append(", corsMeta=").append(corsMeta); + } sb.append('}'); return sb.toString(); } @@ -306,6 +325,7 @@ public static final class Builder { private Condition customCondition; private Integer responseStatus; private String responseReason; + private CorsMeta corsMeta; public Builder name(String name) { this.name = name; @@ -362,6 +382,11 @@ public Builder responseReason(String reason) { return this; } + public Builder cors(CorsMeta corsMeta) { + this.corsMeta = corsMeta; + return this; + } + public RequestMapping build() { PathCondition pathCondition = isEmpty(paths) ? null : new PathCondition(contextPath, paths); MethodsCondition methodsCondition = isEmpty(methods) ? null : new MethodsCondition(methods); @@ -370,6 +395,7 @@ public RequestMapping build() { ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); + CorsMeta corsMeta = this.corsMeta == null ? new CorsMeta().applyPermitDefaultValues() : this.corsMeta; return new RequestMapping( name, pathCondition, @@ -379,7 +405,8 @@ public RequestMapping build() { consumesCondition, producesCondition, customCondition, - response); + response, + corsMeta); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index e04b9794de1..08ee9331050 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -23,6 +23,7 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -33,6 +34,8 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.DefaultCorsProcessor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; @@ -47,6 +50,7 @@ public final class RestRequestHandlerMapping implements RequestHandlerMapping { private final TypeConverter typeConverter; private final ContentNegotiator contentNegotiator; private final CodecUtils codecUtils; + private final CorsProcessor corsProcessor; public RestRequestHandlerMapping(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; @@ -56,15 +60,22 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { typeConverter = beanFactory.getOrRegisterBean(GeneralTypeConverter.class); contentNegotiator = beanFactory.getOrRegisterBean(ContentNegotiator.class); codecUtils = beanFactory.getOrRegisterBean(CodecUtils.class); + corsProcessor = beanFactory.getOrRegisterBean(DefaultCorsProcessor.class); } @Override public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { + HandlerMeta meta = requestMappingRegistry.lookup(request); if (meta == null) { return null; } + RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); + if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { + throw new HttpResultPayloadException(" CORS request rejected: " + request.uri()); + } + String requestMediaType = request.mediaType(); String responseMediaType = contentNegotiator.negotiate(request); if (responseMediaType != null) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java index a91fbb3289f..989b4ff4402 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java @@ -176,4 +176,15 @@ public static Object decodeBody(HttpRequest request, Class type) { } return value; } + + public static int getPort(String scheme, int port) { + if (port == -1) { + if (RestConstants.HTTP.equals(scheme) || RestConstants.WS.equals(scheme)) { + port = 80; + } else if (RestConstants.HTTPS.equals(scheme) || RestConstants.WSS.equals(scheme)) { + port = 443; + } + } + return port; + } } From a21d7a2fb33fc90463ec86b1174c6db592cda3a0 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 9 Apr 2024 11:40:40 +0800 Subject: [PATCH 04/62] feat(): add cors class in rpc-triple and rest-spring --- .../org/apache/dubbo/config/CorsConfig.java | 78 ++++++++ .../org/apache/dubbo/config/RestConfig.java | 7 + .../SpringMvcRequestMappingResolver.java | 6 +- .../compatible/SpringDemoServiceImpl.java | 5 + .../compatible/SpringMvcRestProtocolTest.java | 14 ++ .../compatible/SpringRestDemoService.java | 6 + .../rpc/protocol/tri/rest/RestConstants.java | 1 + .../protocol/tri/rest/cors/CorsProcessor.java | 166 +++++++++++++++- .../rpc/protocol/tri/rest/cors/CorsUtil.java | 76 +++++++ .../tri/rest/cors/DefaultCorsProcessor.java | 187 ------------------ .../DefaultRequestMappingRegistry.java | 18 +- .../tri/rest/mapping/RequestMapping.java | 12 +- .../mapping/RestRequestHandlerMapping.java | 3 +- 13 files changed, 377 insertions(+), 202 deletions(-) create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java new file mode 100644 index 00000000000..7b7ae3dec7d --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -0,0 +1,78 @@ +package org.apache.dubbo.config; + +import java.io.Serializable; + +public class CorsConfig implements Serializable { + private static final long serialVersionUID = 1L; + + private String allowedOrigins; + + private String allowedMethods; + + private String allowedHeaders; + + private String exposedHeaders; + + private Boolean allowCredentials; + + private Boolean allowPrivateNetWork; + + + private Long maxAge; + + public String getAllowedOrigins() { + return allowedOrigins; + } + + public void setAllowedOrigins(String allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } + + public String getAllowedMethods() { + return allowedMethods; + } + + public void setAllowedMethods(String allowedMethods) { + this.allowedMethods = allowedMethods; + } + + public String getAllowedHeaders() { + return allowedHeaders; + } + + public void setAllowedHeaders(String allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } + + public String getExposedHeaders() { + return exposedHeaders; + } + + public void setExposedHeaders(String exposedHeaders) { + this.exposedHeaders = exposedHeaders; + } + + public Boolean getAllowCredentials() { + return allowCredentials; + } + + public void setAllowCredentials(Boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } + + public Long getMaxAge() { + return maxAge; + } + + public void setMaxAge(Long maxAge) { + this.maxAge = maxAge; + } + + public Boolean getAllowPrivateNetWork() { + return allowPrivateNetWork; + } + + public void setAllowPrivateNetWork(Boolean allowPrivateNetWork) { + this.allowPrivateNetWork = allowPrivateNetWork; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java index 6a3674e119a..cf8adddd6ea 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java @@ -68,6 +68,11 @@ public class RestConfig implements Serializable { */ private String formatParameterName; + /** + * The config is used to set the Global CORS configuration properties. + */ + private CorsConfig corsConfig; + public Integer getMaxBodySize() { return maxBodySize; } @@ -115,4 +120,6 @@ public String getFormatParameterName() { public void setFormatParameterName(String formatParameterName) { this.formatParameterName = formatParameterName; } + + } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 27fed032e73..20a8c7f26a9 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -118,10 +118,10 @@ private CorsMeta createCorsMeta(AnnotationMeta crossOrigin) { if (crossOrigin == null) { return meta; } - meta.setAllowCredentials(crossOrigin.getBoolean("allowCredentials")); + meta.setAllowCredentials(Boolean.valueOf(crossOrigin.getString("allowCredentials"))); meta.setAllowedHeaders(Arrays.asList(crossOrigin.getStringArray("allowedHeaders"))); - meta.setAllowedMethods(Arrays.asList(crossOrigin.getStringArray("allowedMethods"))); - meta.setAllowedOrigins(Arrays.asList(crossOrigin.getStringArray("allowedOrigins"))); + meta.setAllowedMethods(Arrays.asList(crossOrigin.getStringArray("methods"))); + meta.setAllowedOrigins(Arrays.asList(crossOrigin.getStringArray("origins"))); meta.setExposedHeaders(Arrays.asList(crossOrigin.getStringArray("exposedHeaders"))); meta.setMaxAge(crossOrigin.getNumber("maxAge").longValue()); return meta; diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java index 46498e5162e..3685b9d32e6 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java @@ -106,4 +106,9 @@ public long primitiveByte(byte a, Long b) { public long primitiveShort(short a, Long b, int c) { return a + b; } + + @Override + public String cors() { + return "you should not pass"; + } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java index 6e124fe2ae8..2b9bc6a8537 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java @@ -85,6 +85,20 @@ public Exporter getExport(URL url, SpringRestDemoService return tProtocol.export(proxy.getInvoker(server, getServerClass(), url)); } + @Test + void testCors(){ + + SpringRestDemoService server = getServerImpl(); + + URL nettyUrl = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(nettyUrl, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + String cors = demoService.cors(); + Assertions.assertNotEquals("you should not pass", cors); + } + @Test void testRestProtocol() { int port = NetUtils.getAvailablePort(); diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java index 4696ac3971b..cb476267698 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java @@ -20,12 +20,14 @@ import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +@CrossOrigin @RequestMapping("/demoService") public interface SpringRestDemoService { @RequestMapping(value = "/hello", method = RequestMethod.GET) @@ -69,4 +71,8 @@ public interface SpringRestDemoService { @RequestMapping(method = RequestMethod.POST, value = "/primitiveShort") long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c); + + @RequestMapping(method = RequestMethod.GET, value = "/cors") + @CrossOrigin(origins = "http://localhowt:8080") + String cors(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 8cce4ef25b8..5678290050c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -68,6 +68,7 @@ public final class RestConstants { /* Configuration Key */ public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; + public static final String CORS_CONFIG_PREFIX = CONFIG_PREFIX + "cors."; public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size"; public static final String MAX_RESPONSE_BODY_SIZE_KEY = CONFIG_PREFIX + "max-response-body-size"; public static final String SUFFIX_PATTERN_MATCH_KEY = CONFIG_PREFIX + "suffix-pattern-match"; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index b566bdea0e2..c4b54811a64 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -16,10 +16,172 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.cors; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; -public interface CorsProcessor { +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; - boolean process(CorsMeta corsMeta, HttpRequest corsRequest, HttpResponse corsResponse); +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK; +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK; +import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil.getPort; + +public class CorsProcessor { + private static final ErrorTypeAwareLogger LOGGER = + LoggerFactory.getErrorTypeAwareLogger(CorsProcessor.class); + + + public boolean process(CorsMeta config, HttpRequest request, HttpResponse response) { + // set vary header + setVaryHeaders(response); + + // skip if is not a cors request + if (!isCorsRequest(request)) { + return true; + } + + // skip if origin already contains in Access-Control-Allow-Origin header + if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) != null) { + return true; + } + + if (config == null) { + // if no cors config and is a preflight request + if (isPreFlight(request)) { + reject(response); + return false; + } + return true; + } + + // handle cors request + return handleInternal(request, response, config, isPreFlight(request)); + } + + protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { + String allowOrigin = config.checkOrigin(request.header(RestConstants.ORIGIN)); + if (allowOrigin == null) { + reject(response); + return false; + } + + List allowHttpMethods = config.checkHttpMethods(getHttpMethods(request, isPreLight)); + if (allowHttpMethods == null) { + reject(response); + return false; + } + + List allowHeaders = config.checkHeaders(getHttpHeaders(request, isPreLight)); + if (isPreLight && allowHeaders == null) { + reject(response); + return false; + } + + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); + + if (isPreLight) { + response.setHeader( + RestConstants.ACCESS_CONTROL_ALLOW_METHODS, + allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); + } + + if (isPreLight && !allowHeaders.isEmpty()) { + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); + } + + if (!CollectionUtils.isEmpty(config.getExposedHeaders())) { + response.setHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS, config.getExposedHeaders()); + } + + if (Boolean.TRUE.equals(config.getAllowCredentials())) { + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); + } + + if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) + && Boolean.parseBoolean(request.header(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) { + response.setHeader(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.TRUE.toString()); + } + + if (isPreLight && config.getMaxAge() != null) { + response.setHeader( + RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); + } + + return true; + } + + private HttpMethods getHttpMethods(HttpRequest request, Boolean isPreLight) { + if (isPreLight) { + return HttpMethods.valueOf(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + } + return HttpMethods.valueOf(request.method()); + } + + private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { + if (isPreLight) { + return request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + } + return new ArrayList<>(request.headerNames()); + } + + private void reject(HttpResponse response) { + response.setStatus(HttpStatus.FORBIDDEN.getCode()); + response.setBody("Invalid CORS request"); + } + + private boolean isPreFlight(HttpRequest request) { + // preflight request is a OPTIONS request with Access-Control-Request-Method header + return request.method().equals(HttpMethods.OPTIONS.name()) + && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; + } + + private boolean isCorsRequest(HttpRequest request) { + // skip if request has no origin header + String origin = request.header(RestConstants.ORIGIN); + if (origin == null) { + return false; + } + + try { + URI uri = new URI(origin); + + // return true if origin is not the same as request's scheme, host and port + return !(Objects.equals(uri.getScheme(), request.scheme()) + && uri.getHost().equals(request.serverName()) + && getPort(uri.getScheme(), uri.getPort()) == getPort(request.scheme(), request.serverPort())); + } catch (URISyntaxException e) { + LOGGER.debug("Origin header is not a valid URI: " + origin); + // skip if origin is not a valid URI + return false; + } + } + + private void setVaryHeaders(HttpResponse response) { + List varyHeaders = response.headerValues(RestConstants.VARY); + if (varyHeaders == null) { + response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + } else { + if (!varyHeaders.contains(RestConstants.ORIGIN)) { + response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); + } + if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + } + if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { + response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + } + } + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java new file mode 100644 index 00000000000..ed5c2fb1cdf --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CONFIG_PREFIX; +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CORS_CONFIG_PREFIX; + +public class CorsUtil { + + public static int getPort(String scheme, int port) { + if (port == -1) { + if (RestConstants.HTTP.equals(scheme) || RestConstants.WS.equals(scheme)) { + port = 80; + } else if (RestConstants.HTTPS.equals(scheme) || RestConstants.WSS.equals(scheme)) { + port = 443; + } + } + return port; + } + + public static CorsMeta resolveGlobalMeta(Configuration config) { + // Get the CORS configuration properties from the configuration object. + String allowOrigins = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN); + String allowMethods = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_METHODS); + String allowHeaders = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_HEADERS); + String exposeHeaders = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS); + String maxAge = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_MAX_AGE); + String allowCredentials = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS); + String allowPrivateNetwork = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK); + // Create a new CorsMeta object and set the properties. + CorsMeta meta = new CorsMeta(); + meta.setAllowedOrigins(parseList(allowOrigins)); + meta.setAllowedMethods(parseList(allowMethods)); + meta.setAllowedHeaders(parseList(allowHeaders)); + meta.setExposedHeaders(parseList(exposeHeaders)); + meta.setMaxAge( maxAge == null ? null : Long.getLong(maxAge) ); + meta.setAllowCredentials(allowCredentials == null ?null : Boolean.getBoolean(allowCredentials)); + meta.setAllowPrivateNetwork( allowPrivateNetwork == null ?null : Boolean.getBoolean(allowPrivateNetwork)); + // Return the CorsMeta object. + return meta; + } + + private static List parseList(String value) { + if (value == null) { + return null; + } + + List list = new ArrayList<>(); + for (String item : value.split(",")) { + list.add(item.trim()); + } + + return list; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java deleted file mode 100644 index 4b5f480840f..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/DefaultCorsProcessor.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; - -import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.remoting.http12.HttpMethods; -import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.HttpStatus; -import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK; -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK; -import static org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils.getPort; - -public class DefaultCorsProcessor implements CorsProcessor { - private static final ErrorTypeAwareLogger LOGGER = - LoggerFactory.getErrorTypeAwareLogger(DefaultCorsProcessor.class); - - @Override - public boolean process(CorsMeta config, HttpRequest request, HttpResponse response) { - // set vary header - setVaryHeaders(response); - - // skip if is not a cors request - if (!isCorsRequest(request)) { - return true; - } - - // skip if origin already contains in Access-Control-Allow-Origin header - if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) != null) { - return true; - } - - if (config == null) { - // if no cors config and is a preflight request - if (isPreFlight(request)) { - reject(response); - return false; - } - return true; - } - - // handle cors request - return handleInternal(request, response, config, isPreFlight(request)); - } - - protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { - String allowOrigin = config.checkOrigin(request.header(RestConstants.ORIGIN)); - if (allowOrigin == null) { - reject(response); - return false; - } - - List allowHttpMethods = config.checkHttpMethods(getHttpMethods(request, isPreLight)); - if (allowHttpMethods == null) { - reject(response); - return false; - } - - List allowHeaders = config.checkHeaders(getHttpHeaders(request, isPreLight)); - if (isPreLight && allowHeaders == null) { - reject(response); - return false; - } - - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); - - if (isPreLight) { - response.setHeader( - RestConstants.ACCESS_CONTROL_ALLOW_METHODS, - allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); - } - - if (isPreLight && !allowHeaders.isEmpty()) { - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); - } - - if (!CollectionUtils.isEmpty(config.getExposedHeaders())) { - response.setHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS, config.getExposedHeaders()); - } - - if (Boolean.TRUE.equals(config.getAllowCredentials())) { - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); - } - - if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) - && Boolean.parseBoolean(request.header(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) { - response.setHeader(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.TRUE.toString()); - } - - if (isPreLight && config.getMaxAge() != null) { - response.setHeader( - RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); - } - - return true; - } - - private HttpMethods getHttpMethods(HttpRequest request, Boolean isPreLight) { - if (isPreLight) { - return HttpMethods.valueOf(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - } - return HttpMethods.valueOf(request.method()); - } - - private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { - if (isPreLight) { - return request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } - return new ArrayList<>(request.headerNames()); - } - - private void reject(HttpResponse response) { - response.setStatus(HttpStatus.FORBIDDEN.getCode()); - response.setBody("Invalid CORS request"); - } - - private boolean isPreFlight(HttpRequest request) { - // preflight request is a OPTIONS request with Access-Control-Request-Method header - return request.method().equals(HttpMethods.OPTIONS.name()) - && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; - } - - private boolean isCorsRequest(HttpRequest request) { - // skip if request has no origin header - String origin = request.header(RestConstants.ORIGIN); - if (origin == null) { - return false; - } - - try { - URI uri = new URI(origin); - - // return true if origin is not the same as request's scheme, host and port - return !(Objects.equals(uri.getScheme(), request.scheme()) - && uri.getHost().equals(request.serverName()) - && getPort(uri.getScheme(), uri.getPort()) == getPort(request.scheme(), request.serverPort())); - } catch (URISyntaxException e) { - LOGGER.debug("Origin header is not a valid URI: " + origin); - // skip if origin is not a valid URI - return false; - } - } - - private void setVaryHeaders(HttpResponse response) { - List varyHeaders = response.headerValues(RestConstants.VARY); - if (varyHeaders == null) { - response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } else { - if (!varyHeaders.contains(RestConstants.ORIGIN)) { - response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); - } - if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - } - if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 34a0cdfee10..ce421463e9f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -16,6 +16,8 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.utils.Assert; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.message.MethodMetadata; @@ -26,6 +28,8 @@ import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; @@ -53,22 +57,27 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist private final RadixTree tree = new RadixTree<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private CorsMeta globalCorsMeta; + public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); + loadGlobalCorsMeta(frameworkModel); } @Override public void register(Invoker invoker) { Object service = invoker.getUrl().getServiceModel().getProxyObject(); new MethodWalker().walk(service.getClass(), (classes, consumer) -> { - for (int i = 0, size = resolvers.size(); i < size; i++) { - RequestMappingResolver resolver = resolvers.get(i); + for (RequestMappingResolver resolver : resolvers) { RestToolKit toolKit = resolver.getRestToolKit(); ServiceMeta serviceMeta = new ServiceMeta(classes, service, invoker.getUrl(), toolKit); if (!resolver.accept(serviceMeta)) { continue; } RequestMapping classMapping = resolver.resolve(serviceMeta); + if (classMapping != null) { + classMapping.setCorsMeta(globalCorsMeta.combine(classMapping.getCorsMeta())); + } consumer.accept((methods) -> { MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); RequestMapping methodMapping = resolver.resolve(methodMeta); @@ -205,6 +214,11 @@ public HandlerMeta lookup(HttpRequest request) { return handler; } + private void loadGlobalCorsMeta(FrameworkModel frameworkModel) { + Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel); + globalCorsMeta = CorsUtil.resolveGlobalMeta(config); + } + private static final class Registration { RequestMapping mapping; HandlerMeta meta; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 65c2c7da21a..6d8c7bb1d97 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -184,6 +184,10 @@ public CorsMeta getCorsMeta() { return corsMeta; } + public void setCorsMeta(CorsMeta corsMeta) { + corsMeta = corsMeta; + } + @Override public int compareTo(RequestMapping other, HttpRequest request) { int result; @@ -233,11 +237,7 @@ public int compareTo(RequestMapping other, HttpRequest request) { result = customCondition.compareTo(other.customCondition, request); return result; } - if (corsMeta != null) { - // TODO - // result = corsMeta.compareTo(other.corsMeta, request); - // return result; - } + return 0; } @@ -395,7 +395,7 @@ public RequestMapping build() { ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); - CorsMeta corsMeta = this.corsMeta == null ? new CorsMeta().applyPermitDefaultValues() : this.corsMeta; + CorsMeta corsMeta = this.corsMeta == null ? null: this.corsMeta; return new RequestMapping( name, pathCondition, diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index 08ee9331050..d329e866e68 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -35,7 +35,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.DefaultCorsProcessor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; @@ -60,7 +59,7 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { typeConverter = beanFactory.getOrRegisterBean(GeneralTypeConverter.class); contentNegotiator = beanFactory.getOrRegisterBean(ContentNegotiator.class); codecUtils = beanFactory.getOrRegisterBean(CodecUtils.class); - corsProcessor = beanFactory.getOrRegisterBean(DefaultCorsProcessor.class); + corsProcessor = beanFactory.getOrRegisterBean(CorsProcessor.class); } @Override From f6594c544506f9c1716c2058e8a6e7de671f401a Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 9 Apr 2024 20:34:38 +0800 Subject: [PATCH 05/62] test(): add rpc-triple cors test --- .../org/apache/dubbo/config/CorsConfig.java | 17 +- .../org/apache/dubbo/config/RestConfig.java | 11 +- .../SpringMvcRequestMappingResolver.java | 21 +- .../rpc/protocol/tri/rest/RestConstants.java | 9 +- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 84 ++- .../protocol/tri/rest/cors/CorsProcessor.java | 25 +- .../rpc/protocol/tri/rest/cors/CorsUtil.java | 29 +- .../DefaultRequestMappingRegistry.java | 3 +- .../tri/rest/mapping/RequestMapping.java | 2 +- .../protocol/tri/rest/util/RequestUtils.java | 11 - .../protocol/tri/rest/cors/CorsMetaTest.java | 578 +++++++++++++++++ .../tri/rest/cors/CorsProcessorTest.java | 613 ++++++++++++++++++ .../protocol/tri/rest/cors/CorsUtilTest.java | 84 +++ 13 files changed, 1398 insertions(+), 89 deletions(-) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java index 7b7ae3dec7d..3c8eeec4d6e 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.config; import java.io.Serializable; @@ -17,7 +33,6 @@ public class CorsConfig implements Serializable { private Boolean allowPrivateNetWork; - private Long maxAge; public String getAllowedOrigins() { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java index cf8adddd6ea..dc7e9889286 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java @@ -16,6 +16,8 @@ */ package org.apache.dubbo.config; +import org.apache.dubbo.config.support.Nested; + import java.io.Serializable; /** @@ -71,7 +73,8 @@ public class RestConfig implements Serializable { /** * The config is used to set the Global CORS configuration properties. */ - private CorsConfig corsConfig; + @Nested + private CorsConfig cors; public Integer getMaxBodySize() { return maxBodySize; @@ -121,5 +124,11 @@ public void setFormatParameterName(String formatParameterName) { this.formatParameterName = formatParameterName; } + public CorsConfig getCors() { + return cors; + } + public void setCors(CorsConfig cors) { + this.cors = cors; + } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 20a8c7f26a9..688f87b6620 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -30,6 +30,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; import java.util.Arrays; +import java.util.Collections; import org.springframework.http.HttpStatus; @@ -118,12 +119,20 @@ private CorsMeta createCorsMeta(AnnotationMeta crossOrigin) { if (crossOrigin == null) { return meta; } - meta.setAllowCredentials(Boolean.valueOf(crossOrigin.getString("allowCredentials"))); - meta.setAllowedHeaders(Arrays.asList(crossOrigin.getStringArray("allowedHeaders"))); - meta.setAllowedMethods(Arrays.asList(crossOrigin.getStringArray("methods"))); - meta.setAllowedOrigins(Arrays.asList(crossOrigin.getStringArray("origins"))); - meta.setExposedHeaders(Arrays.asList(crossOrigin.getStringArray("exposedHeaders"))); - meta.setMaxAge(crossOrigin.getNumber("maxAge").longValue()); + String[] allowedHeaders = crossOrigin.getStringArray("allowedHeaders"); + meta.setAllowedHeaders(allowedHeaders != null ? Arrays.asList(allowedHeaders) : Collections.emptyList()); + String[] methods = crossOrigin.getStringArray("methods"); + meta.setAllowedMethods(methods != null ? Arrays.asList(methods) : Collections.emptyList()); + String[] origins = crossOrigin.getStringArray("origins"); + meta.setAllowedOrigins(origins != null ? Arrays.asList(origins) : Collections.emptyList()); + String[] exposedHeaders = crossOrigin.getStringArray("exposedHeaders"); + meta.setExposedHeaders(exposedHeaders != null ? Arrays.asList(exposedHeaders) : Collections.emptyList()); + String maxAge = crossOrigin.getString("maxAge"); + meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); + String allowCredentials = crossOrigin.getString("allowCredentials"); + meta.setAllowCredentials(allowCredentials != null ? Boolean.valueOf(allowCredentials) : null); + String allowPrivateNetwork = crossOrigin.getString("allowPrivateNetwork"); + meta.setAllowPrivateNetwork(allowPrivateNetwork != null ? Boolean.valueOf(allowPrivateNetwork) : null); return meta; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 5678290050c..101e13390ac 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -68,13 +68,20 @@ public final class RestConstants { /* Configuration Key */ public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; - public static final String CORS_CONFIG_PREFIX = CONFIG_PREFIX + "cors."; public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size"; public static final String MAX_RESPONSE_BODY_SIZE_KEY = CONFIG_PREFIX + "max-response-body-size"; public static final String SUFFIX_PATTERN_MATCH_KEY = CONFIG_PREFIX + "suffix-pattern-match"; public static final String TRAILING_SLASH_MATCH_KEY = CONFIG_PREFIX + "trailing-slash-match"; public static final String CASE_SENSITIVE_MATCH_KEY = CONFIG_PREFIX + "case-sensitive-match"; public static final String FORMAT_PARAMETER_NAME_KEY = CONFIG_PREFIX + "format-parameter-name"; + public static final String CORS_CONFIG_PREFIX = CONFIG_PREFIX + "cors."; + public static final String ALLOWED_ORIGINS = CORS_CONFIG_PREFIX + "allowed-origins"; + public static final String ALLOWED_METHODS = CORS_CONFIG_PREFIX + "allowed-methods"; + public static final String ALLOWED_HEADERS = CORS_CONFIG_PREFIX + "allowed-headers"; + public static final String EXPOSED_HEADERS = CORS_CONFIG_PREFIX + "exposed-headers"; + public static final String MAX_AGE = CORS_CONFIG_PREFIX + "max-age"; + public static final String ALLOW_CREDENTIALS = CORS_CONFIG_PREFIX + "allow-credentials"; + public static final String ALLOW_PRIVATE_NETWORK = CORS_CONFIG_PREFIX + "allow-private-network"; private RestConstants() {} } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 311bf7f59da..2aeeffb001d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -20,7 +20,6 @@ import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpMethods; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -38,20 +37,22 @@ public class CorsMeta { public static final String ALL = "*"; - private static final List ALL_LIST = Collections.singletonList(ALL); + public static final List ALL_LIST = Collections.singletonList(ALL); private static final OriginPattern ALL_PATTERN = new OriginPattern("*"); private static final List ALL_PATTERN_LIST = Collections.singletonList(ALL_PATTERN); - private static final List DEFAULT_PERMIT_ALL = Collections.singletonList(ALL); + public static final List DEFAULT_PERMIT_ALL = Collections.singletonList(ALL); - private static final List DEFAULT_METHODS = + public static final List DEFAULT_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethods.GET, HttpMethods.HEAD)); - private static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( + public static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( Arrays.asList(HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name())); + public static final Long DEFAULT_MAX_AGE = 1800L; + private List allowedOrigins; private List allowedOriginPatterns; @@ -114,7 +115,7 @@ public void addAllowedOrigin(String origin) { this.allowedOrigins.add(origin); } - public CorsMeta setAllowedOriginPatterns(List allowedOriginPatterns) { + public void setAllowedOriginPatterns(List allowedOriginPatterns) { if (allowedOriginPatterns == null) { this.allowedOriginPatterns = null; } else { @@ -123,7 +124,6 @@ public CorsMeta setAllowedOriginPatterns(List allowedOriginPatterns) { addAllowedOriginPattern(patternValue); } } - return this; } public List getAllowedOriginPatterns() { @@ -238,10 +238,6 @@ public Boolean getAllowPrivateNetwork() { return this.allowPrivateNetwork; } - public void setMaxAge(Duration maxAge) { - this.maxAge = maxAge.getSeconds(); - } - public void setMaxAge(Long maxAge) { this.maxAge = maxAge; } @@ -263,42 +259,49 @@ public CorsMeta applyPermitDefaultValues() { this.allowedHeaders = DEFAULT_PERMIT_ALL; } if (this.maxAge == null) { - this.maxAge = 1800L; + this.maxAge = DEFAULT_MAX_AGE; + } + if(this.allowCredentials == null){ + this.allowCredentials = false; + } + if(this.allowPrivateNetwork == null){ + this.allowPrivateNetwork = false; } return this; } - public void validateAllowCredentials() { - if (this.allowCredentials.equals(Boolean.TRUE) + public boolean validateAllowCredentials() { + // When allowCredentials is true, allowedOrigins cannot contain the special value \"*\" + // since that cannot be set on the \"Access-Control-Allow-Origin\" response header. + // To allow credentials to a set of origins, list them explicitly + // or consider using \"allowedOriginPatterns\" instead. + return this.allowCredentials != null + && this.allowCredentials.equals(Boolean.TRUE) && this.allowedOrigins != null - && this.allowedOrigins.contains(ALL)) { - - throw new IllegalArgumentException( - "When allowCredentials is true, allowedOrigins cannot contain the special value \"*\" " - + "since that cannot be set on the \"Access-Control-Allow-Origin\" response header. " - + "To allow credentials to a set of origins, list them explicitly " - + "or consider using \"allowedOriginPatterns\" instead."); - } + && this.allowedOrigins.contains(ALL); } - public void validateAllowPrivateNetwork() { - if (this.allowPrivateNetwork.equals(Boolean.TRUE) - && this.allowedOrigins != null - && this.allowedOrigins.contains(ALL)) { + public boolean validateAllowPrivateNetwork() { - throw new IllegalArgumentException( - "When allowPrivateNetwork is true, allowedOrigins cannot contain the special value \"*\" " - + "as it is not recommended from a security perspective. " - + "To allow private network access to a set of origins, list them explicitly " - + "or consider using \"allowedOriginPatterns\" instead."); - } + // When allowPrivateNetwork is true, allowedOrigins cannot contain the special value \"*\" + // as it is not recommended from a security perspective. + // To allow private network access to a set of origins, list them explicitly + // or consider using \"allowedOriginPatterns\" instead. + return this.allowPrivateNetwork != null + && this.allowPrivateNetwork.equals(Boolean.TRUE) + && this.allowedOrigins != null + && this.allowedOrigins.contains(ALL); } + /** + * the custom value always cover default value + * @param other other + * @return {@link CorsMeta} + */ public CorsMeta combine(CorsMeta other) { if (other == null) { return this; } - // Bypass setAllowedOrigins to avoid re-compiling patterns CorsMeta config = new CorsMeta(this); List origins = combine(getAllowedOrigins(), other.getAllowedOrigins()); List patterns = combinePatterns(this.allowedOriginPatterns, other.allowedOriginPatterns); @@ -322,6 +325,13 @@ public CorsMeta combine(CorsMeta other) { return config; } + /** + * combine + * + * @param source source + * @param other other + * @return {@link List}<{@link String}> + */ private List combine(List source, List other) { if (other == null) { return (source != null ? source : Collections.emptyList()); @@ -329,6 +339,7 @@ private List combine(List source, List other) { if (source == null) { return other; } + // save setting value at first if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) { return other; } @@ -368,8 +379,9 @@ public String checkOrigin(String origin) { String originToCheck = trimTrailingSlash(origin); if (!ObjectUtils.isEmpty(this.allowedOrigins)) { if (this.allowedOrigins.contains(ALL)) { - validateAllowCredentials(); - validateAllowPrivateNetwork(); + if (validateAllowCredentials() || validateAllowPrivateNetwork()) { + return null; + } return ALL; } for (String allowedOrigin : this.allowedOrigins) { @@ -477,7 +489,7 @@ public boolean equals(Object other) { if (other == null || !getClass().equals(other.getClass())) { return false; } - return ObjectUtils.equals(this.declaredPattern, ((OriginPattern) other).declaredPattern); + return Objects.equals(this.declaredPattern, ((OriginPattern) other).declaredPattern); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index c4b54811a64..db6a987ac44 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -37,9 +37,7 @@ import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil.getPort; public class CorsProcessor { - private static final ErrorTypeAwareLogger LOGGER = - LoggerFactory.getErrorTypeAwareLogger(CorsProcessor.class); - + private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(CorsProcessor.class); public boolean process(CorsMeta config, HttpRequest request, HttpResponse response) { // set vary header @@ -54,10 +52,10 @@ public boolean process(CorsMeta config, HttpRequest request, HttpResponse respon if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) != null) { return true; } - + boolean preFlight = isPreFlight(request); if (config == null) { // if no cors config and is a preflight request - if (isPreFlight(request)) { + if (preFlight) { reject(response); return false; } @@ -65,7 +63,7 @@ public boolean process(CorsMeta config, HttpRequest request, HttpResponse respon } // handle cors request - return handleInternal(request, response, config, isPreFlight(request)); + return handleInternal(request, response, config, preFlight); } protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { @@ -116,7 +114,6 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor response.setHeader( RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); } - return true; } @@ -162,25 +159,25 @@ private boolean isCorsRequest(HttpRequest request) { } catch (URISyntaxException e) { LOGGER.debug("Origin header is not a valid URI: " + origin); // skip if origin is not a valid URI - return false; + return true; } } private void setVaryHeaders(HttpResponse response) { List varyHeaders = response.headerValues(RestConstants.VARY); if (varyHeaders == null) { - response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + response.addHeader(RestConstants.VARY, RestConstants.ORIGIN); + response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); } else { if (!varyHeaders.contains(RestConstants.ORIGIN)) { - response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); + response.addHeader(RestConstants.VARY, RestConstants.ORIGIN); } if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); } if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { - response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java index ed5c2fb1cdf..cc4fdee230d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java @@ -20,12 +20,8 @@ import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CONFIG_PREFIX; -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CORS_CONFIG_PREFIX; - public class CorsUtil { public static int getPort(String scheme, int port) { @@ -40,37 +36,36 @@ public static int getPort(String scheme, int port) { } public static CorsMeta resolveGlobalMeta(Configuration config) { + // Get the CORS configuration properties from the configuration object. - String allowOrigins = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN); - String allowMethods = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_METHODS); - String allowHeaders = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_HEADERS); - String exposeHeaders = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS); - String maxAge = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_MAX_AGE); - String allowCredentials = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS); - String allowPrivateNetwork = config.getString(CORS_CONFIG_PREFIX+RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK); + String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); + String allowMethods = config.getString(RestConstants.ALLOWED_METHODS); + String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); + String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); + String maxAge = config.getString(RestConstants.MAX_AGE); + String allowCredentials = config.getString(RestConstants.ALLOW_CREDENTIALS); + String allowPrivateNetwork = config.getString(RestConstants.ALLOW_PRIVATE_NETWORK); // Create a new CorsMeta object and set the properties. CorsMeta meta = new CorsMeta(); meta.setAllowedOrigins(parseList(allowOrigins)); meta.setAllowedMethods(parseList(allowMethods)); meta.setAllowedHeaders(parseList(allowHeaders)); meta.setExposedHeaders(parseList(exposeHeaders)); - meta.setMaxAge( maxAge == null ? null : Long.getLong(maxAge) ); - meta.setAllowCredentials(allowCredentials == null ?null : Boolean.getBoolean(allowCredentials)); - meta.setAllowPrivateNetwork( allowPrivateNetwork == null ?null : Boolean.getBoolean(allowPrivateNetwork)); + meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); + meta.setAllowCredentials(allowCredentials == null ? null : Boolean.valueOf(allowCredentials)); + meta.setAllowPrivateNetwork(allowPrivateNetwork == null ? null : Boolean.valueOf(allowPrivateNetwork)); // Return the CorsMeta object. - return meta; + return meta.applyPermitDefaultValues(); } private static List parseList(String value) { if (value == null) { return null; } - List list = new ArrayList<>(); for (String item : value.split(",")) { list.add(item.trim()); } - return list; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index ce421463e9f..833abb1d891 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -76,7 +76,7 @@ public void register(Invoker invoker) { } RequestMapping classMapping = resolver.resolve(serviceMeta); if (classMapping != null) { - classMapping.setCorsMeta(globalCorsMeta.combine(classMapping.getCorsMeta())); + classMapping.setCorsMeta(classMapping.getCorsMeta().combine(globalCorsMeta)); } consumer.accept((methods) -> { MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); @@ -86,6 +86,7 @@ public void register(Invoker invoker) { } if (classMapping != null) { methodMapping = classMapping.combine(methodMapping); + methodMapping.setCorsMeta(methodMapping.getCorsMeta().combine(globalCorsMeta)); } register0(methodMapping, buildHandlerMeta(invoker, methodMeta)); }); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 6d8c7bb1d97..553fa61c778 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -395,7 +395,7 @@ public RequestMapping build() { ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); - CorsMeta corsMeta = this.corsMeta == null ? null: this.corsMeta; + CorsMeta corsMeta = this.corsMeta == null ? null : this.corsMeta; return new RequestMapping( name, pathCondition, diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java index 989b4ff4402..a91fbb3289f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java @@ -176,15 +176,4 @@ public static Object decodeBody(HttpRequest request, Class type) { } return value; } - - public static int getPort(String scheme, int port) { - if (port == -1) { - if (RestConstants.HTTP.equals(scheme) || RestConstants.WS.equals(scheme)) { - port = 80; - } else if (RestConstants.HTTPS.equals(scheme) || RestConstants.WSS.equals(scheme)) { - port = 443; - } - } - return port; - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java new file mode 100644 index 00000000000..6c112bc8144 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java @@ -0,0 +1,578 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.remoting.http12.HttpMethods; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class CorsMetaTest { + @Test + void setNullValues() { + CorsMeta config = new CorsMeta(); + config.setAllowedOrigins(null); + config.setAllowedOriginPatterns(null); + config.setAllowedHeaders(null); + config.setAllowedMethods(null); + config.setExposedHeaders(null); + config.setAllowCredentials(null); + config.setMaxAge(null); + config.setAllowPrivateNetwork(null); + Assertions.assertNull(config.getAllowedOrigins()); + Assertions.assertNull(config.getAllowedOriginPatterns()); + Assertions.assertNull(config.getAllowedHeaders()); + Assertions.assertNull(config.getAllowedMethods()); + Assertions.assertNull(config.getExposedHeaders()); + Assertions.assertNull(config.getAllowCredentials()); + Assertions.assertNull(config.getAllowPrivateNetwork()); + Assertions.assertNull(config.getMaxAge()); + } + + @Test + void setValues() { + CorsMeta config = new CorsMeta(); + + // Add allowed origin + config.addAllowedOrigin("*"); + config.addAllowedOriginPattern("http://*.example.com"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + config.addExposedHeader("*"); + config.setAllowCredentials(true); + config.setAllowPrivateNetwork(true); + config.setMaxAge(123L); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"http://*.example.com"}, + config.getAllowedOriginPatterns().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedMethods().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getExposedHeaders().toArray()); + Assertions.assertTrue(config.getAllowCredentials()); + Assertions.assertTrue(config.getAllowPrivateNetwork()); + Assertions.assertEquals(123L, config.getMaxAge().longValue()); + } + + @Test + void combineWithNull() { + + CorsMeta config = new CorsMeta(); + config.setAllowedOrigins(Collections.singletonList("*")); + config.combine(null); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedOrigins().toArray()); + Assertions.assertNull(config.getAllowedOriginPatterns()); + } + + @Test + void combineWithConfigWithNullProperties() { + CorsMeta config = new CorsMeta(); + config.addAllowedOrigin("*"); + config.setAllowedOriginPatterns(Collections.singletonList("http://*.example.com")); + config.addAllowedHeader("header1"); + config.addExposedHeader("header3"); + config.addAllowedMethod(HttpMethods.GET.name()); + config.setMaxAge(123L); + config.setAllowCredentials(true); + config.setAllowPrivateNetwork(true); + CorsMeta other = new CorsMeta(); + config = config.combine(other); + // Assert the combined config + Assertions.assertNotNull(config); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"http://*.example.com"}, + config.getAllowedOriginPatterns().toArray()); + Assertions.assertArrayEquals( + new String[] {"header1"}, config.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"header3"}, config.getExposedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name()}, + config.getAllowedMethods().toArray()); + Assertions.assertEquals(123L, config.getMaxAge().longValue()); + Assertions.assertTrue(config.getAllowCredentials()); + Assertions.assertTrue(config.getAllowPrivateNetwork()); + } + + @Test + void combineWithDefaultPermitValues() { + CorsMeta config = new CorsMeta().applyPermitDefaultValues(); + CorsMeta other = new CorsMeta(); + other.addAllowedOrigin("https://domain.com"); + other.addAllowedHeader("header1"); + other.addAllowedMethod(HttpMethods.PUT.name()); + CorsMeta combinedConfig = config.combine(other); + + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"https://domain.com"}, + combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"header1"}, combinedConfig.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.PUT.name()}, + combinedConfig.getAllowedMethods().toArray()); + Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); + combinedConfig = other.combine(config); + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"https://domain.com"}, + combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"header1"}, combinedConfig.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.PUT.name()}, + combinedConfig.getAllowedMethods().toArray()); + Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); + combinedConfig = config.combine(new CorsMeta()); + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}, + combinedConfig.getAllowedMethods().toArray()); + Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); + + // Combine an empty config with config + combinedConfig = new CorsMeta().combine(config); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, config.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}, + combinedConfig.getAllowedMethods().toArray()); + Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); + } + + @Test + void combinePatternWithDefaultPermitValues() { + // Create a config with default permit values + CorsMeta config = new CorsMeta().applyPermitDefaultValues(); + + // Create another config with an allowed origin pattern + CorsMeta other = new CorsMeta(); + other.addAllowedOriginPattern("http://*.com"); + + // Combine the configs, with 'other' first + CorsMeta combinedConfig = other.combine(config); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertNull(combinedConfig.getAllowedOrigins()); + Assertions.assertArrayEquals( + new String[] {"http://*.com"}, + combinedConfig.getAllowedOriginPatterns().toArray()); + + // Combine the configs, with 'config' first + combinedConfig = config.combine(other); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertNull(combinedConfig.getAllowedOrigins()); + Assertions.assertArrayEquals( + new String[] {"http://*.com"}, + combinedConfig.getAllowedOriginPatterns().toArray()); + } + + @Test + void combinePatternWithDefaultPermitValuesAndCustomOrigin() { + // Create a config with default permit values and a custom allowed origin + CorsMeta config = new CorsMeta().applyPermitDefaultValues(); + config.setAllowedOrigins(Collections.singletonList("https://domain.com")); + + // Create another config with an allowed origin pattern + CorsMeta other = new CorsMeta(); + other.addAllowedOriginPattern("http://*.com"); + + // Combine the configs, with 'other' first + CorsMeta combinedConfig = other.combine(config); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"https://domain.com"}, + combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"http://*.com"}, + combinedConfig.getAllowedOriginPatterns().toArray()); + + // Combine the configs, with 'config' first + combinedConfig = config.combine(other); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"https://domain.com"}, + combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"http://*.com"}, + combinedConfig.getAllowedOriginPatterns().toArray()); + } + + @Test + void combineWithAsteriskWildCard() { + // Create a config with wildcard values + CorsMeta config = new CorsMeta(); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addExposedHeader("*"); + config.addAllowedMethod("*"); + config.addAllowedOriginPattern("*"); + + // Create another config with some custom values + CorsMeta other = new CorsMeta(); + other.addAllowedOrigin("https://domain.com"); + other.addAllowedOriginPattern("http://*.company.com"); + other.addAllowedHeader("header1"); + other.addExposedHeader("header2"); + other.addAllowedHeader("anotherHeader1"); + other.addExposedHeader("anotherHeader2"); + other.addAllowedMethod(HttpMethods.PUT.name()); + + // Combine the configs, with 'config' first + CorsMeta combinedConfig = config.combine(other); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedOriginPatterns().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getExposedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedMethods().toArray()); + + // Combine the configs, with 'other' first + combinedConfig = other.combine(config); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedOriginPatterns().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getExposedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"*"}, combinedConfig.getAllowedMethods().toArray()); + } + + @Test + void combineWithDuplicatedElements() { + // Create a config with some duplicate values + CorsMeta config = new CorsMeta(); + config.addAllowedOrigin("https://domain1.com"); + config.addAllowedOrigin("https://domain2.com"); + config.addAllowedHeader("header1"); + config.addAllowedHeader("header2"); + config.addExposedHeader("header3"); + config.addExposedHeader("header4"); + config.addAllowedMethod(HttpMethods.GET.name()); + config.addAllowedMethod(HttpMethods.PUT.name()); + config.addAllowedOriginPattern("http://*.domain1.com"); + config.addAllowedOriginPattern("http://*.domain2.com"); + + // Create another config with some overlapping values + CorsMeta other = new CorsMeta(); + other.addAllowedOrigin("https://domain1.com"); + other.addAllowedOriginPattern("http://*.domain1.com"); + other.addAllowedHeader("header1"); + other.addExposedHeader("header3"); + other.addAllowedMethod(HttpMethods.GET.name()); + + // Combine the configs + CorsMeta combinedConfig = config.combine(other); + + // Assert the combined config + Assertions.assertNotNull(combinedConfig); + Assertions.assertArrayEquals( + new String[] {"https://domain1.com", "https://domain2.com"}, + combinedConfig.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"header1", "header2"}, + combinedConfig.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"header3", "header4"}, + combinedConfig.getExposedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.PUT.name()}, + combinedConfig.getAllowedMethods().toArray()); + Assertions.assertArrayEquals( + new String[] {"http://*.domain1.com", "http://*.domain2.com"}, + combinedConfig.getAllowedOriginPatterns().toArray()); + } + + @Test + void combine() { + // Create a config with some values + CorsMeta config = new CorsMeta(); + config.addAllowedOrigin("https://domain1.com"); + config.addAllowedOriginPattern("http://*.domain1.com"); + config.addAllowedHeader("header1"); + config.addExposedHeader("header3"); + config.addAllowedMethod(HttpMethods.GET.name()); + config.setMaxAge(123L); + config.setAllowCredentials(true); + config.setAllowPrivateNetwork(true); + + // Create another config with some different values + CorsMeta other = new CorsMeta(); + other.addAllowedOrigin("https://domain2.com"); + other.addAllowedOriginPattern("http://*.domain2.com"); + other.addAllowedHeader("header2"); + other.addExposedHeader("header4"); + other.addAllowedMethod(HttpMethods.PUT.name()); + other.setMaxAge(456L); + other.setAllowCredentials(false); + other.setAllowPrivateNetwork(false); + + // Combine the configs + config = config.combine(other); + + // Assert the combined config + Assertions.assertNotNull(config); + Assertions.assertArrayEquals( + new String[] {"https://domain1.com", "https://domain2.com"}, + config.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"header1", "header2"}, config.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"header3", "header4"}, config.getExposedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.PUT.name()}, + config.getAllowedMethods().toArray()); + Assertions.assertEquals(Long.valueOf(456), config.getMaxAge()); + Assertions.assertFalse(config.getAllowCredentials()); + Assertions.assertFalse(config.getAllowPrivateNetwork()); + Assertions.assertArrayEquals( + new String[] {"http://*.domain1.com", "http://*.domain2.com"}, + config.getAllowedOriginPatterns().toArray()); + } + + @Test + void checkOriginAllowed() { + // "*" matches + CorsMeta config = new CorsMeta(); + config.addAllowedOrigin("*"); + Assertions.assertEquals("*", config.checkOrigin("https://domain.com")); + + // "*" does not match together with allowCredentials + config.setAllowCredentials(true); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + + // specific origin matches Origin header with or without trailing "/" + config.setAllowedOrigins(Collections.singletonList("https://domain.com")); + Assertions.assertEquals("https://domain.com", config.checkOrigin("https://domain.com")); + Assertions.assertEquals("https://domain.com/", config.checkOrigin("https://domain.com/")); + + // specific origin with trailing "/" matches Origin header with or without trailing "/" + config.setAllowedOrigins(Collections.singletonList("https://domain.com/")); + Assertions.assertEquals("https://domain.com", config.checkOrigin("https://domain.com")); + Assertions.assertEquals("https://domain.com/", config.checkOrigin("https://domain.com/")); + + config.setAllowCredentials(false); + Assertions.assertEquals("https://domain.com", config.checkOrigin("https://domain.com")); + } + + @Test + void checkOriginNotAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertNull(config.checkOrigin(null)); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + + config.addAllowedOrigin("*"); + Assertions.assertNull(config.checkOrigin(null)); + + config.setAllowedOrigins(Collections.singletonList("https://domain1.com")); + Assertions.assertNull(config.checkOrigin("https://domain2.com")); + + config.setAllowedOrigins(new ArrayList<>()); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + } + + @Test + void checkOriginPatternAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + + config.applyPermitDefaultValues(); + Assertions.assertEquals("*", config.checkOrigin("https://domain.com")); + + config.setAllowCredentials(true); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + config.addAllowedOriginPattern("https://*.domain.com"); + Assertions.assertEquals("https://example.domain.com", config.checkOrigin("https://example.domain.com")); + + config.addAllowedOriginPattern("https://*.port.domain.com:[*]"); + Assertions.assertEquals( + "https://example.port.domain.com", config.checkOrigin("https://example.port.domain.com")); + Assertions.assertEquals( + "https://example.port.domain.com:1234", config.checkOrigin("https://example.port.domain.com:1234")); + + config.addAllowedOriginPattern("https://*.specific.port.com:[8080,8081]"); + Assertions.assertEquals( + "https://example.specific.port.com:8080", config.checkOrigin("https://example.specific.port.com:8080")); + Assertions.assertEquals( + "https://example.specific.port.com:8081", config.checkOrigin("https://example.specific.port.com:8081")); + Assertions.assertNull(config.checkOrigin("https://example.specific.port.com:1234")); + + config.setAllowCredentials(false); + Assertions.assertEquals("https://example.domain.com", config.checkOrigin("https://example.domain.com")); + } + + @Test + void checkOriginPatternNotAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertNull(config.checkOrigin(null)); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + + config.addAllowedOriginPattern("*"); + Assertions.assertNull(config.checkOrigin(null)); + + config.setAllowedOriginPatterns(Collections.singletonList("http://*.domain1.com")); + Assertions.assertNull(config.checkOrigin("https://domain2.com")); + + config.setAllowedOriginPatterns(new ArrayList<>()); + Assertions.assertNull(config.checkOrigin("https://domain.com")); + + config.setAllowedOriginPatterns(Collections.singletonList("https://*.specific.port.com:[8080,8081]")); + Assertions.assertNull(config.checkOrigin("https://example.specific.port.com:1234")); + } + + @Test + void checkMethodAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name()}, + config.checkHttpMethods(HttpMethods.GET).stream() + .map(HttpMethods::name) + .toArray(String[]::new)); + + config.addAllowedMethod("GET"); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name()}, + config.checkHttpMethods(HttpMethods.GET).stream() + .map(HttpMethods::name) + .toArray(String[]::new)); + + config.addAllowedMethod("POST"); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.POST.name()}, + config.checkHttpMethods(HttpMethods.GET).stream() + .map(HttpMethods::name) + .toArray(String[]::new)); + Assertions.assertArrayEquals( + new String[] {HttpMethods.GET.name(), HttpMethods.POST.name()}, + config.checkHttpMethods(HttpMethods.POST).stream() + .map(HttpMethods::name) + .toArray(String[]::new)); + } + + @Test + void checkMethodNotAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertNull(config.checkHttpMethods(null)); + Assertions.assertNull(config.checkHttpMethods(HttpMethods.DELETE)); + + config.setAllowedMethods(new ArrayList<>()); + Assertions.assertNull(config.checkHttpMethods(HttpMethods.POST)); + } + + @Test + void checkHeadersAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertEquals(Collections.emptyList(), config.checkHeaders(Collections.emptyList())); + + config.addAllowedHeader("header1"); + config.addAllowedHeader("header2"); + + Assertions.assertArrayEquals( + new String[] {"header1"}, + config.checkHeaders(Collections.singletonList("header1")).toArray()); + Assertions.assertArrayEquals( + new String[] {"header1", "header2"}, + config.checkHeaders(Arrays.asList("header1", "header2")).toArray()); + Assertions.assertArrayEquals( + new String[] {"header1", "header2"}, + config.checkHeaders(Arrays.asList("header1", "header2", "header3")) + .toArray()); + } + + @Test + void checkHeadersNotAllowed() { + CorsMeta config = new CorsMeta(); + Assertions.assertNull(config.checkHeaders(null)); + Assertions.assertNull(config.checkHeaders(Collections.singletonList("header1"))); + + config.setAllowedHeaders(Collections.emptyList()); + Assertions.assertNull(config.checkHeaders(Collections.singletonList("header1"))); + + config.addAllowedHeader("header2"); + config.addAllowedHeader("header3"); + Assertions.assertNull(config.checkHeaders(Collections.singletonList("header1"))); + } + + @Test + void changePermitDefaultValues() { + CorsMeta config = new CorsMeta().applyPermitDefaultValues(); + config.addAllowedOrigin("https://domain.com"); + config.addAllowedHeader("header1"); + config.addAllowedMethod("PATCH"); + + Assertions.assertArrayEquals( + new String[] {"*", "https://domain.com"}, + config.getAllowedOrigins().toArray()); + Assertions.assertArrayEquals( + new String[] {"*", "header1"}, config.getAllowedHeaders().toArray()); + Assertions.assertArrayEquals( + new String[] {"GET", "HEAD", "POST", "PATCH"}, + config.getAllowedMethods().toArray()); + } + + @Test + void permitDefaultDoesntSetOriginWhenPatternPresent() { + CorsMeta config = new CorsMeta(); + config.addAllowedOriginPattern("http://*.com"); + config = config.applyPermitDefaultValues(); + + Assertions.assertNull(config.getAllowedOrigins()); + Assertions.assertArrayEquals( + new String[] {"http://*.com"}, config.getAllowedOriginPatterns().toArray()); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java new file mode 100644 index 00000000000..796e988e105 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -0,0 +1,613 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.remoting.http12.message.DefaultHttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import java.util.Arrays; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class CorsProcessorTest { + + private HttpRequest request; + + private HttpResponse response; + + private CorsProcessor processor; + + private CorsMeta conf; + + @BeforeEach + public void setup() { + this.request = Mockito.mock(HttpRequest.class); + Mockito.when(this.request.uri()).thenReturn("/test.html"); + Mockito.when(this.request.serverName()).thenReturn("domain1.example"); + Mockito.when(this.request.scheme()).thenReturn("http"); + Mockito.when(this.request.serverPort()).thenReturn(80); + Mockito.when(this.request.remoteHost()).thenReturn("127.0.0.1"); + this.conf = new CorsMeta(); + this.response = new DefaultHttpResponse(); + this.response.setStatus(HttpStatus.OK.getCode()); + this.processor = new CorsProcessor(); + } + + @Test + void requestWithoutOriginHeader() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void sameOriginRequest() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("http://domain1.example"); + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void actualRequestWithOriginHeader() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } + + @Test + void actualRequestWithOriginHeaderAndNullConfig() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + + this.processor.process(null, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void actualRequestWithOriginHeaderAndAllowedOrigin() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + this.conf.addAllowedOrigin("*"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("*", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_MAX_AGE)); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void actualRequestCredentials() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + this.conf.addAllowedOrigin("https://domain1.com"); + this.conf.addAllowedOrigin("https://domain2.com"); + this.conf.addAllowedOrigin("http://domain3.example"); + this.conf.setAllowCredentials(true); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void actualRequestCredentialsWithWildcardOrigin() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + + this.conf.addAllowedOrigin("*"); + this.conf.setAllowCredentials(true); + Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); + + this.response = new DefaultHttpResponse(); + this.response.setStatus(HttpStatus.OK.getCode()); + this.conf.setAllowedOrigins(null); + this.conf.addAllowedOriginPattern("*"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void actualRequestCaseInsensitiveOriginMatch() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + this.conf.addAllowedOrigin("https://DOMAIN2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + void actualRequestTrailingSlashOriginMatch() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + this.conf.addAllowedOrigin("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + void actualRequestExposedHeaders() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + this.conf.addExposedHeader("header1"); + this.conf.addExposedHeader("header2"); + this.conf.addAllowedOrigin("https://domain2.com"); + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS)); + Assertions.assertTrue(this.response + .headerValues(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS) + .contains("header1")); + Assertions.assertTrue(this.response + .headerValues(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS) + .contains("header2")); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestAllOriginsAllowed() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + this.conf.addAllowedOrigin("*"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestWrongAllowedMethod() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("DELETE"); + this.conf.addAllowedOrigin("*"); + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } + + @Test + void preflightRequestMatchedAllowedMethod() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + this.conf.addAllowedOrigin("*"); + this.processor.process(this.conf, this.request, this.response); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + Assertions.assertArrayEquals( + new String[] {"GET", "HEAD"}, + this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_METHODS) + .toArray()); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + } + + @Test + void preflightRequestTestWithOriginButWithoutOtherHeaders() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } + + @Test + void preflightRequestWithoutRequestMethod() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } + + @Test + void preflightRequestWithRequestAndMethodHeaderButNoConfig() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } + + @Test + void preflightRequestValidRequestAndConfig() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + this.conf.addAllowedOrigin("*"); + this.conf.addAllowedMethod("GET"); + this.conf.addAllowedMethod("PUT"); + this.conf.addAllowedHeader("header1"); + this.conf.addAllowedHeader("header2"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("*", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_METHODS)); + Assertions.assertArrayEquals( + new String[] {"GET", "PUT"}, + this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_METHODS) + .toArray()); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_MAX_AGE)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestCredentials() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + this.conf.addAllowedOrigin("https://domain1.com"); + this.conf.addAllowedOrigin("https://domain2.com"); + this.conf.addAllowedOrigin("http://domain3.example"); + this.conf.addAllowedHeader("Header1"); + this.conf.setAllowCredentials(true); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestCredentialsWithWildcardOrigin() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example")); + this.conf.addAllowedHeader("Header1"); + this.conf.setAllowCredentials(true); + + Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); + + this.response = new DefaultHttpResponse(); + this.response.setStatus(HttpStatus.OK.getCode()); + this.conf.setAllowedOrigins(null); + this.conf.addAllowedOriginPattern("*"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestPrivateNetworkWithWildcardOrigin() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) + .thenReturn("true"); + this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example")); + this.conf.addAllowedHeader("Header1"); + this.conf.setAllowPrivateNetwork(true); + + Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); + + this.response = new DefaultHttpResponse(); + this.response.setStatus(HttpStatus.OK.getCode()); + this.conf.setAllowedOrigins(null); + this.conf.addAllowedOriginPattern("*"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); + Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestAllowedHeaders() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn(Arrays.asList("Header1", "Header2")); + this.conf.addAllowedHeader("Header1"); + this.conf.addAllowedHeader("Header2"); + this.conf.addAllowedHeader("Header3"); + this.conf.addAllowedOrigin("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header1")); + Assertions.assertTrue(this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header2")); + Assertions.assertFalse(this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header3")); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestAllowsAllHeaders() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn(Arrays.asList("Header1", "Header2")); + this.conf.addAllowedHeader("*"); + this.conf.addAllowedOrigin("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header1")); + Assertions.assertTrue(this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header2")); + Assertions.assertFalse(this.response + .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("*")); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestWithEmptyHeaders() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn(""); + this.conf.addAllowedHeader("*"); + this.conf.addAllowedOrigin("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestWithNullConfig() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + this.conf.addAllowedOrigin("*"); + + this.processor.process(null, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } + + @Test + void preventDuplicatedVaryHeaders() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + this.response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); + this.response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + this.response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + } + + @Test + void preflightRequestWithoutAccessControlRequestPrivateNetwork() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + this.conf.addAllowedHeader("*"); + this.conf.addAllowedOrigin("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestWithAccessControlRequestPrivateNetworkNotAllowed() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) + .thenReturn("true"); + this.conf.addAllowedHeader("*"); + this.conf.addAllowedOrigin("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } + + @Test + void preflightRequestWithAccessControlRequestPrivateNetworkAllowed() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) + .thenReturn("true"); + this.conf.addAllowedHeader("*"); + this.conf.addAllowedOrigin("https://domain2.com"); + this.conf.setAllowPrivateNetwork(true); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); + Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java new file mode 100644 index 00000000000..77986e5c026 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CORS_CONFIG_PREFIX; + +public class CorsUtilTest { + + @Test + void testResolveGlobalMetaInCommon() { + Configuration config = Mockito.mock(Configuration.class); + Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn("http://localhost:8080"); + Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn( "GET,POST,PUT,DELETE" ); + Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn( "Content-Type,Authorization" ); + Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn( "Content-Type,Authorization" ); + Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn( "3600" ); + Mockito.when(config.getString(RestConstants.ALLOW_CREDENTIALS)).thenReturn( "true" ); + Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn( "true" ); + Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).thenReturn( "true" ); + CorsMeta meta = CorsUtil.resolveGlobalMeta(config); + Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); + Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); + Assertions.assertTrue(meta.getAllowedHeaders().contains("Content-Type")); + Assertions.assertTrue(meta.getExposedHeaders().contains("Content-Type")); + Assertions.assertEquals(3600, meta.getMaxAge()); + Assertions.assertTrue(meta.getAllowCredentials()); + Assertions.assertTrue(meta.getAllowPrivateNetwork()); + } + + @Test + void testResolveGlobalMetaWithNullConfig() { + Configuration config = Mockito.mock(Configuration.class); + Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn(null); + Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn(null); + Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn(null); + Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn(null); + Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); + Mockito.when(config.getString(RestConstants.ALLOW_CREDENTIALS)).thenReturn(null); + Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn(null); + + CorsMeta meta = CorsUtil.resolveGlobalMeta(config); + Assertions.assertEquals(meta.getMaxAge(), CorsMeta.DEFAULT_MAX_AGE); + Assertions.assertEquals(meta.getAllowCredentials(), false); + Assertions.assertEquals(meta.getAllowPrivateNetwork(),false); + Assertions.assertEquals(meta.getAllowedOrigins(), CorsMeta.DEFAULT_PERMIT_ALL); + Assertions.assertEquals(meta.getAllowedMethods(), CorsMeta.DEFAULT_PERMIT_METHODS); + Assertions.assertEquals(meta.getAllowedHeaders(), CorsMeta.DEFAULT_PERMIT_ALL); + } + + @Test + void testGetPortWithDefaultValues() { + Assertions.assertEquals(80, CorsUtil.getPort(RestConstants.HTTP, -1)); + Assertions.assertEquals(80, CorsUtil.getPort(RestConstants.WS, -1)); + Assertions.assertEquals(443, CorsUtil.getPort(RestConstants.HTTPS, -1)); + Assertions.assertEquals(443, CorsUtil.getPort(RestConstants.WSS, -1)); + } + + @Test + void testGetPortWithCustomValues() { + Assertions.assertEquals(8080, CorsUtil.getPort(RestConstants.HTTP, 8080)); + Assertions.assertEquals(8443, CorsUtil.getPort(RestConstants.HTTPS, 8443)); + } +} From cf42edbfc3f85245203dac728cd10f18831ab5df Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 9 Apr 2024 20:51:39 +0800 Subject: [PATCH 06/62] fix(): fix CorsUtil bug --- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 4 ++-- .../mapping/DefaultRequestMappingRegistry.java | 2 +- .../protocol/tri/rest/cors/CorsUtilTest.java | 17 +++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 2aeeffb001d..0497733f302 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -261,10 +261,10 @@ public CorsMeta applyPermitDefaultValues() { if (this.maxAge == null) { this.maxAge = DEFAULT_MAX_AGE; } - if(this.allowCredentials == null){ + if (this.allowCredentials == null) { this.allowCredentials = false; } - if(this.allowPrivateNetwork == null){ + if (this.allowPrivateNetwork == null) { this.allowPrivateNetwork = false; } return this; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 833abb1d891..a400db3d0fc 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -216,7 +216,7 @@ public HandlerMeta lookup(HttpRequest request) { } private void loadGlobalCorsMeta(FrameworkModel frameworkModel) { - Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel); + Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); globalCorsMeta = CorsUtil.resolveGlobalMeta(config); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java index 77986e5c026..da275bdf2d4 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java @@ -31,13 +31,14 @@ public class CorsUtilTest { void testResolveGlobalMetaInCommon() { Configuration config = Mockito.mock(Configuration.class); Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn("http://localhost:8080"); - Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn( "GET,POST,PUT,DELETE" ); - Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn( "Content-Type,Authorization" ); - Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn( "Content-Type,Authorization" ); - Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn( "3600" ); - Mockito.when(config.getString(RestConstants.ALLOW_CREDENTIALS)).thenReturn( "true" ); - Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn( "true" ); - Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).thenReturn( "true" ); + Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn("GET,POST,PUT,DELETE"); + Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn("Content-Type,Authorization"); + Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); + Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); + Mockito.when(config.getString(RestConstants.ALLOW_CREDENTIALS)).thenReturn("true"); + Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn("true"); + Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) + .thenReturn("true"); CorsMeta meta = CorsUtil.resolveGlobalMeta(config); Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); @@ -62,7 +63,7 @@ void testResolveGlobalMetaWithNullConfig() { CorsMeta meta = CorsUtil.resolveGlobalMeta(config); Assertions.assertEquals(meta.getMaxAge(), CorsMeta.DEFAULT_MAX_AGE); Assertions.assertEquals(meta.getAllowCredentials(), false); - Assertions.assertEquals(meta.getAllowPrivateNetwork(),false); + Assertions.assertEquals(meta.getAllowPrivateNetwork(), false); Assertions.assertEquals(meta.getAllowedOrigins(), CorsMeta.DEFAULT_PERMIT_ALL); Assertions.assertEquals(meta.getAllowedMethods(), CorsMeta.DEFAULT_PERMIT_METHODS); Assertions.assertEquals(meta.getAllowedHeaders(), CorsMeta.DEFAULT_PERMIT_ALL); From 352e9741fb5bba70c0e506c89ad3915376d426e9 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 9 Apr 2024 20:56:47 +0800 Subject: [PATCH 07/62] fix(): fix CorsUtil bug --- .../dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java index da275bdf2d4..a7244532370 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java @@ -42,13 +42,19 @@ void testResolveGlobalMetaInCommon() { CorsMeta meta = CorsUtil.resolveGlobalMeta(config); Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); + Assertions.assertTrue(meta.getAllowedMethods().contains("POST")); + Assertions.assertTrue(meta.getAllowedMethods().contains("PUT")); + Assertions.assertTrue(meta.getAllowedMethods().contains("DELETE")); Assertions.assertTrue(meta.getAllowedHeaders().contains("Content-Type")); + Assertions.assertTrue(meta.getAllowedHeaders().contains("Authorization")); Assertions.assertTrue(meta.getExposedHeaders().contains("Content-Type")); + Assertions.assertTrue(meta.getExposedHeaders().contains("Authorization")); Assertions.assertEquals(3600, meta.getMaxAge()); Assertions.assertTrue(meta.getAllowCredentials()); Assertions.assertTrue(meta.getAllowPrivateNetwork()); } + @Test void testResolveGlobalMetaWithNullConfig() { Configuration config = Mockito.mock(Configuration.class); From 32015a38fb3025eeb94dd52180ded72a71d35eb3 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 10 Apr 2024 11:17:43 +0800 Subject: [PATCH 08/62] fix(): fix objectUtil bug --- .../spring/compatible/SpringMvcRestProtocolTest.java | 3 ++- .../support/spring/compatible/SpringRestDemoService.java | 4 ++-- .../apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java | 8 +++----- .../tri/rest/mapping/DefaultRequestMappingRegistry.java | 1 - .../dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java | 1 - pom.xml | 6 +++--- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java index 2b9bc6a8537..e3f26c77335 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java @@ -86,7 +86,7 @@ public Exporter getExport(URL url, SpringRestDemoService } @Test - void testCors(){ + void testCors() { SpringRestDemoService server = getServerImpl(); @@ -143,6 +143,7 @@ void testRestProtocolWithContextPath() { url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=" + SpringRestDemoService.class.getName()); Invoker invoker = protocol.refer(SpringRestDemoService.class, url); + SpringRestDemoService client = proxy.getProxy(invoker); String result = client.sayHello("haha"); Assertions.assertTrue(server.isCalled()); diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java index cb476267698..8a50664d1b5 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java @@ -27,7 +27,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -@CrossOrigin +@CrossOrigin(origins = "http://127.0.0.1:80") @RequestMapping("/demoService") public interface SpringRestDemoService { @RequestMapping(value = "/hello", method = RequestMethod.GET) @@ -73,6 +73,6 @@ public interface SpringRestDemoService { long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c); @RequestMapping(method = RequestMethod.GET, value = "/cors") - @CrossOrigin(origins = "http://localhowt:8080") + @CrossOrigin(origins = "http://128.0.0.1:80") String cors(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 0497733f302..bd40a095150 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -31,8 +31,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.apache.commons.lang3.ObjectUtils; - public class CorsMeta { public static final String ALL = "*"; @@ -377,7 +375,7 @@ public String checkOrigin(String origin) { return null; } String originToCheck = trimTrailingSlash(origin); - if (!ObjectUtils.isEmpty(this.allowedOrigins)) { + if (!this.allowedOrigins.isEmpty()) { if (this.allowedOrigins.contains(ALL)) { if (validateAllowCredentials() || validateAllowPrivateNetwork()) { return null; @@ -390,7 +388,7 @@ public String checkOrigin(String origin) { } } } - if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) { + if (!this.allowedOriginPatterns.isEmpty()) { for (OriginPattern p : this.allowedOriginPatterns) { if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(originToCheck).matches()) { @@ -418,7 +416,7 @@ public List checkHeaders(List requestHeaders) { if (requestHeaders.isEmpty()) { return Collections.emptyList(); } - if (ObjectUtils.isEmpty(this.allowedHeaders)) { + if (this.allowedHeaders.isEmpty()) { return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index a400db3d0fc..c7772470c21 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -86,7 +86,6 @@ public void register(Invoker invoker) { } if (classMapping != null) { methodMapping = classMapping.combine(methodMapping); - methodMapping.setCorsMeta(methodMapping.getCorsMeta().combine(globalCorsMeta)); } register0(methodMapping, buildHandlerMeta(invoker, methodMeta)); }); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java index a7244532370..6e3e72a9ed0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java @@ -54,7 +54,6 @@ void testResolveGlobalMetaInCommon() { Assertions.assertTrue(meta.getAllowPrivateNetwork()); } - @Test void testResolveGlobalMetaWithNullConfig() { Configuration config = Mockito.mock(Configuration.class); diff --git a/pom.xml b/pom.xml index 4dafbf47e67..20ad2a044e2 100644 --- a/pom.xml +++ b/pom.xml @@ -147,12 +147,12 @@ 3.22.3 1.54.0 - 2.43.0 + 2. 43.0 check 1.0.0 2.38.0 - 3.3.0-beta.2-SNAPSHOT + 3.3.0-beta.3-SNAPSHOT @@ -160,7 +160,7 @@ org.apache.dubbo dubbo-dependencies-bom - ${project.version} + 3.3.0-beta.2-SNAPSHOT pom import From c785a5e17145969163308876366a23cbd813e0a7 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 10 Apr 2024 14:25:51 +0800 Subject: [PATCH 09/62] fix(): fix corsmeta set bug --- .../dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java | 4 ++-- .../tri/rest/mapping/RestRequestHandlerMapping.java | 6 ++++-- pom.xml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 553fa61c778..ab82c2d2b78 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -45,7 +45,7 @@ public final class RequestMapping implements Condition3.22.3 1.54.0 - 2. 43.0 + 2.43.0 check 1.0.0 2.38.0 From 194bbc586d280fafc0b584cd169d4b8e00f90e0c Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 10 Apr 2024 17:53:16 +0800 Subject: [PATCH 10/62] fix(): fix config load fail bug --- .../mapping/DefaultRequestMappingRegistry.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index c7772470c21..fd02db6812a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -22,6 +22,7 @@ import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.model.ServiceDescriptor; @@ -61,7 +62,6 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); - loadGlobalCorsMeta(frameworkModel); } @Override @@ -76,7 +76,7 @@ public void register(Invoker invoker) { } RequestMapping classMapping = resolver.resolve(serviceMeta); if (classMapping != null) { - classMapping.setCorsMeta(classMapping.getCorsMeta().combine(globalCorsMeta)); + classMapping.setCorsMeta(classMapping.getCorsMeta().combine(getGlobalCorsMeta())); } consumer.accept((methods) -> { MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); @@ -214,9 +214,13 @@ public HandlerMeta lookup(HttpRequest request) { return handler; } - private void loadGlobalCorsMeta(FrameworkModel frameworkModel) { - Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); - globalCorsMeta = CorsUtil.resolveGlobalMeta(config); + private CorsMeta getGlobalCorsMeta() { + if (globalCorsMeta == null) { + Configuration globalConfiguration = + ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); + globalCorsMeta = CorsUtil.resolveGlobalMeta(globalConfiguration); + } + return globalCorsMeta; } private static final class Registration { From 995b87b1f2b45bcc462a4c224dd7e9b6664077d8 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 10 Apr 2024 20:23:47 +0800 Subject: [PATCH 11/62] fix(): option method can not be look fail --- .../protocol/tri/rest/cors/CorsProcessor.java | 2 +- .../DefaultRequestMappingRegistry.java | 35 +++++++++++++++++-- .../rest/mapping/RequestMappingRegistry.java | 3 +- .../mapping/RestRequestHandlerMapping.java | 13 +------ .../tri/rest/cors/CorsProcessorTest.java | 34 ++++++++++-------- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index db6a987ac44..f5b875b96be 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -136,7 +136,7 @@ private void reject(HttpResponse response) { response.setBody("Invalid CORS request"); } - private boolean isPreFlight(HttpRequest request) { + public static boolean isPreFlight(HttpRequest request) { // preflight request is a OPTIONS request with Access-Control-Request-Method header return request.method().equals(HttpMethods.OPTIONS.name()) && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index fd02db6812a..eeffda74a5e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -19,7 +19,11 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.utils.Assert; +import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.remoting.http12.exception.HttpStatusException; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.model.ApplicationModel; @@ -30,6 +34,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; @@ -57,11 +62,12 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist private final RadixTree tree = new RadixTree<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private CorsMeta globalCorsMeta; + private final CorsProcessor corsProcessor; public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); + corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); } @Override @@ -145,7 +151,7 @@ public void destroy() { } } - public HandlerMeta lookup(HttpRequest request) { + public HandlerMeta lookup(HttpRequest request, HttpResponse response) { String path = PathUtils.normalize(request.rawPath()); request.setAttribute(RestConstants.PATH_ATTRIBUTE, path); List> matches = new ArrayList<>(); @@ -160,6 +166,18 @@ public HandlerMeta lookup(HttpRequest request) { if (size == 0) { return null; } + + String method = null; + if (request.hasHeader(HttpMethods.OPTIONS.name())) { + if (CorsProcessor.isPreFlight(request)) { + method = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + request.setMethod(method); + } else { + throw new HttpStatusException( + HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); + } + } + List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { Match match = matches.get(i); @@ -199,6 +217,9 @@ public HandlerMeta lookup(HttpRequest request) { Candidate winner = candidates.get(0); RequestMapping mapping = winner.mapping; + + processCors(method, mapping, request, response); + HandlerMeta handler = winner.meta; request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler); @@ -211,9 +232,19 @@ public HandlerMeta lookup(HttpRequest request) { if (producesCondition != null) { request.setAttribute(RestConstants.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producesCondition.getMediaTypes()); } + return handler; } + private void processCors(String method, RequestMapping mapping, HttpRequest request, HttpResponse response) { + if (method != null) { + request.setMethod(HttpMethods.OPTIONS.name()); + } + if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { + throw new HttpStatusException(HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); + } + } + private CorsMeta getGlobalCorsMeta() { if (globalCorsMeta == null) { Configuration globalConfiguration = diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java index b7dfb559946..e3046bad41d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; @@ -29,7 +30,7 @@ public interface RequestMappingRegistry { void unregister(Invoker invoker); - HandlerMeta lookup(HttpRequest request); + HandlerMeta lookup(HttpRequest request, HttpResponse response); void destroy(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index 666051dbf32..7b3b81b9303 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -23,8 +23,6 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.HttpStatus; -import org.apache.dubbo.remoting.http12.exception.HttpStatusException; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -35,7 +33,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; @@ -50,7 +47,6 @@ public final class RestRequestHandlerMapping implements RequestHandlerMapping { private final TypeConverter typeConverter; private final ContentNegotiator contentNegotiator; private final CodecUtils codecUtils; - private final CorsProcessor corsProcessor; public RestRequestHandlerMapping(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; @@ -60,23 +56,16 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { typeConverter = beanFactory.getOrRegisterBean(GeneralTypeConverter.class); contentNegotiator = beanFactory.getOrRegisterBean(ContentNegotiator.class); codecUtils = beanFactory.getOrRegisterBean(CodecUtils.class); - corsProcessor = beanFactory.getOrRegisterBean(CorsProcessor.class); } @Override public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { - HandlerMeta meta = requestMappingRegistry.lookup(request); + HandlerMeta meta = requestMappingRegistry.lookup(request, response); if (meta == null) { return null; } - RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); - if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { - response.commit(); - throw new HttpStatusException(HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); - } - String requestMediaType = request.mediaType(); String responseMediaType = contentNegotiator.negotiate(request); if (responseMediaType != null) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index 796e988e105..ca9ed385bfe 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -80,21 +80,25 @@ void sameOriginRequest() { this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } - - @Test - void actualRequestWithOriginHeader() { - Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - - this.processor.process(this.conf, this.request, this.response); - Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); - Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); - } + // Although in this test it can pass, but In actual use, + // due to the implementation of Http12 error return, + // it is not yet possible to set the Vary request header while being intercepted. + // @Test + // void actualRequestWithOriginHeader() { + // Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + // Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + // + // this.processor.process(this.conf, this.request, this.response); + // Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + // Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + // Assertions.assertTrue( + // + // this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + // Assertions.assertTrue( + // + // this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + // Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + // } @Test void actualRequestWithOriginHeaderAndNullConfig() { From f45cc0c7f77a53a5bfbb51bd73bd727da81847eb Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 10 Apr 2024 20:57:41 +0800 Subject: [PATCH 12/62] fix(): CorsMeta method will null --- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 6 ++--- .../DefaultRequestMappingRegistry.java | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index bd40a095150..bf751dca11a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -375,7 +375,7 @@ public String checkOrigin(String origin) { return null; } String originToCheck = trimTrailingSlash(origin); - if (!this.allowedOrigins.isEmpty()) { + if (!(this.allowedOrigins == null || this.allowedOrigins.isEmpty())) { if (this.allowedOrigins.contains(ALL)) { if (validateAllowCredentials() || validateAllowPrivateNetwork()) { return null; @@ -388,7 +388,7 @@ public String checkOrigin(String origin) { } } } - if (!this.allowedOriginPatterns.isEmpty()) { + if (!(this.allowedOriginPatterns == null || this.allowedOriginPatterns.isEmpty())) { for (OriginPattern p : this.allowedOriginPatterns) { if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(originToCheck).matches()) { @@ -416,7 +416,7 @@ public List checkHeaders(List requestHeaders) { if (requestHeaders.isEmpty()) { return Collections.emptyList(); } - if (this.allowedHeaders.isEmpty()) { + if (this.allowedHeaders == null || this.allowedHeaders.isEmpty()) { return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index eeffda74a5e..35b81105d51 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -167,16 +168,7 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return null; } - String method = null; - if (request.hasHeader(HttpMethods.OPTIONS.name())) { - if (CorsProcessor.isPreFlight(request)) { - method = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - request.setMethod(method); - } else { - throw new HttpStatusException( - HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); - } - } + String method = preprocessingCors(request); List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { @@ -236,6 +228,20 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return handler; } + private String preprocessingCors(HttpRequest request) { + if (Objects.equals(request.method(), HttpMethods.OPTIONS.name())) { + if (CorsProcessor.isPreFlight(request)) { + String realMethod = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + request.setMethod(realMethod); + return realMethod; + } else { + throw new HttpStatusException( + HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); + } + } + return null; + } + private void processCors(String method, RequestMapping mapping, HttpRequest request, HttpResponse response) { if (method != null) { request.setMethod(HttpMethods.OPTIONS.name()); From 755d39bf0765e6cb04a987f4451d366cd688c6cf Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 10 Apr 2024 23:26:48 +0800 Subject: [PATCH 13/62] fix(): request-header not set will fail --- .../apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java | 1 - .../dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java | 8 ++++---- .../rpc/protocol/tri/rest/cors/CorsProcessorTest.java | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index bf751dca11a..e7020547619 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -419,7 +419,6 @@ public List checkHeaders(List requestHeaders) { if (this.allowedHeaders == null || this.allowedHeaders.isEmpty()) { return null; } - boolean allowAnyHeader = this.allowedHeaders.contains(ALL); List result = new ArrayList<>(requestHeaders.size()); for (String requestHeader : requestHeaders) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index f5b875b96be..2ee1c1d456e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -78,9 +78,9 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor reject(response); return false; } - - List allowHeaders = config.checkHeaders(getHttpHeaders(request, isPreLight)); - if (isPreLight && allowHeaders == null) { + List httpHeaders = getHttpHeaders(request, isPreLight); + List allowHeaders = config.checkHeaders(httpHeaders); + if (isPreLight && httpHeaders != null && allowHeaders == null) { reject(response); return false; } @@ -93,7 +93,7 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); } - if (isPreLight && !allowHeaders.isEmpty()) { + if (isPreLight && !(allowHeaders == null || allowHeaders.isEmpty())) { response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index ca9ed385bfe..16ea0e853c2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -226,14 +226,14 @@ void actualRequestExposedHeaders() { } @Test - void preflightRequestAllOriginsAllowed() { + void preflightRequestWithoutRequestheader() { Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn("GET"); this.conf.addAllowedOrigin("*"); - this.processor.process(this.conf, this.request, this.response); + this.processor.process(this.conf, request, this.response); Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); From 7efd5883a7009f0eb896ff86cc480289438a4ae6 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 11:33:41 +0800 Subject: [PATCH 14/62] refactor(): improve CorsMeta CorsProcess some code --- .../AbstractServerHttpChannelObserver.java | 10 ++++++- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 10 +++++-- .../protocol/tri/rest/cors/CorsProcessor.java | 30 ++++++++----------- .../rpc/protocol/tri/rest/cors/CorsUtil.java | 9 ++---- .../DefaultRequestMappingRegistry.java | 20 +++++++++---- 5 files changed, 46 insertions(+), 33 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java index a4086cc08f2..5dc7c5acf47 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java @@ -81,7 +81,14 @@ public void onNext(Object data) { if (!headerSent) { doSendHeaders(String.valueOf(result.getStatus()), result.getHeaders()); } - data = result.getBody(); + if (result.getStatus() != HttpStatus.OK.getCode()) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setStatus(String.valueOf(result.getStatus())); + errorResponse.setMessage(result.getBody().toString()); + data = errorResponse; + } else { + data = result.getBody(); + } } else if (!headerSent) { doSendHeaders(HttpStatus.OK.getStatusString(), null); } @@ -113,6 +120,7 @@ protected HttpMetadata encodeTrailers(Throwable throwable) { public void onError(Throwable throwable) { if (throwable instanceof HttpResultPayloadException) { onNext(((HttpResultPayloadException) throwable).getResult()); + doOnCompleted(null); return; } int httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR.getCode(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index e7020547619..a849c145ba3 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -33,6 +33,8 @@ public class CorsMeta { + // Default value + public static final String ALL = "*"; public static final List ALL_LIST = Collections.singletonList(ALL); @@ -51,6 +53,8 @@ public class CorsMeta { public static final Long DEFAULT_MAX_AGE = 1800L; + // Fields + private List allowedOrigins; private List allowedOriginPatterns; @@ -375,7 +379,7 @@ public String checkOrigin(String origin) { return null; } String originToCheck = trimTrailingSlash(origin); - if (!(this.allowedOrigins == null || this.allowedOrigins.isEmpty())) { + if (!CollectionUtils.isEmpty(this.allowedOrigins)) { if (this.allowedOrigins.contains(ALL)) { if (validateAllowCredentials() || validateAllowPrivateNetwork()) { return null; @@ -388,7 +392,7 @@ public String checkOrigin(String origin) { } } } - if (!(this.allowedOriginPatterns == null || this.allowedOriginPatterns.isEmpty())) { + if (!CollectionUtils.isEmpty(this.allowedOriginPatterns)) { for (OriginPattern p : this.allowedOriginPatterns) { if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(originToCheck).matches()) { @@ -416,7 +420,7 @@ public List checkHeaders(List requestHeaders) { if (requestHeaders.isEmpty()) { return Collections.emptyList(); } - if (this.allowedHeaders == null || this.allowedHeaders.isEmpty()) { + if (CollectionUtils.isEmpty(this.allowedHeaders)) { return null; } boolean allowAnyHeader = this.allowedHeaders.contains(ALL); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index 2ee1c1d456e..735c39b059b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -56,8 +56,7 @@ public boolean process(CorsMeta config, HttpRequest request, HttpResponse respon if (config == null) { // if no cors config and is a preflight request if (preFlight) { - reject(response); - return false; + return reject(response); } return true; } @@ -69,20 +68,17 @@ public boolean process(CorsMeta config, HttpRequest request, HttpResponse respon protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { String allowOrigin = config.checkOrigin(request.header(RestConstants.ORIGIN)); if (allowOrigin == null) { - reject(response); - return false; + return reject(response); } List allowHttpMethods = config.checkHttpMethods(getHttpMethods(request, isPreLight)); if (allowHttpMethods == null) { - reject(response); - return false; + return reject(response); } List httpHeaders = getHttpHeaders(request, isPreLight); List allowHeaders = config.checkHeaders(httpHeaders); if (isPreLight && httpHeaders != null && allowHeaders == null) { - reject(response); - return false; + return reject(response); } response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); @@ -91,10 +87,13 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor response.setHeader( RestConstants.ACCESS_CONTROL_ALLOW_METHODS, allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); - } - - if (isPreLight && !(allowHeaders == null || allowHeaders.isEmpty())) { - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); + if (!CollectionUtils.isEmpty(allowHeaders)) { + response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); + } + if (config.getMaxAge() != null) { + response.setHeader( + RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); + } } if (!CollectionUtils.isEmpty(config.getExposedHeaders())) { @@ -110,10 +109,6 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor response.setHeader(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.TRUE.toString()); } - if (isPreLight && config.getMaxAge() != null) { - response.setHeader( - RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); - } return true; } @@ -131,9 +126,10 @@ private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { return new ArrayList<>(request.headerNames()); } - private void reject(HttpResponse response) { + private boolean reject(HttpResponse response) { response.setStatus(HttpStatus.FORBIDDEN.getCode()); response.setBody("Invalid CORS request"); + return false; } public static boolean isPreFlight(HttpRequest request) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java index cc4fdee230d..6fa756746ba 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java @@ -19,8 +19,9 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class CorsUtil { @@ -62,10 +63,6 @@ private static List parseList(String value) { if (value == null) { return null; } - List list = new ArrayList<>(); - for (String item : value.split(",")) { - list.add(item.trim()); - } - return list; + return Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 35b81105d51..c47c8d7c6c6 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -22,8 +22,9 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpResult; import org.apache.dubbo.remoting.http12.HttpStatus; -import org.apache.dubbo.remoting.http12.exception.HttpStatusException; +import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.model.ApplicationModel; @@ -168,7 +169,7 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return null; } - String method = preprocessingCors(request); + String method = preprocessingCors(request, response); List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { @@ -228,15 +229,18 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return handler; } - private String preprocessingCors(HttpRequest request) { + private String preprocessingCors(HttpRequest request, HttpResponse response) { if (Objects.equals(request.method(), HttpMethods.OPTIONS.name())) { if (CorsProcessor.isPreFlight(request)) { String realMethod = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); request.setMethod(realMethod); return realMethod; } else { - throw new HttpStatusException( - HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body(response.body()) + .headers(response.headers()) + .build()); } } return null; @@ -247,7 +251,11 @@ private void processCors(String method, RequestMapping mapping, HttpRequest requ request.setMethod(HttpMethods.OPTIONS.name()); } if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { - throw new HttpStatusException(HttpStatus.FORBIDDEN.getCode(), " CORS request rejected: " + request.uri()); + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body(response.body()) + .headers(response.headers()) + .build()); } } From e743a54dcf16d4ea121ddd26ea6bc7494bbf706c Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 14:24:00 +0800 Subject: [PATCH 15/62] fix(): coreMeta combine priority --- .../compatible/SpringRestDemoService.java | 2 - .../rpc/protocol/tri/rest/cors/CorsMeta.java | 27 ++++---- .../DefaultRequestMappingRegistry.java | 2 +- .../protocol/tri/rest/cors/CorsMetaTest.java | 66 ++++++++++--------- .../tri/rest/cors/CorsProcessorTest.java | 34 +++++----- 5 files changed, 62 insertions(+), 69 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java index 8a50664d1b5..9a2dd273f98 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java @@ -27,7 +27,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -@CrossOrigin(origins = "http://127.0.0.1:80") @RequestMapping("/demoService") public interface SpringRestDemoService { @RequestMapping(value = "/hello", method = RequestMethod.GET) @@ -73,6 +72,5 @@ public interface SpringRestDemoService { long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c); @RequestMapping(method = RequestMethod.GET, value = "/cors") - @CrossOrigin(origins = "http://128.0.0.1:80") String cors(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index a849c145ba3..bd3dcf59312 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -296,7 +296,7 @@ public boolean validateAllowPrivateNetwork() { } /** - * the custom value always cover default value + * Combines two lists of strings, with a priority on this * @param other other * @return {@link CorsMeta} */ @@ -312,27 +312,24 @@ public CorsMeta combine(CorsMeta other) { config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods())); config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders())); config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders())); - Boolean allowCredentials = other.getAllowCredentials(); - if (allowCredentials != null) { - config.setAllowCredentials(allowCredentials); + if (this.allowCredentials == null) { + config.setAllowCredentials(other.getAllowCredentials()); } - Boolean allowPrivateNetwork = other.getAllowPrivateNetwork(); - if (allowPrivateNetwork != null) { - config.setAllowPrivateNetwork(allowPrivateNetwork); + if (this.allowPrivateNetwork == null) { + config.setAllowPrivateNetwork(other.allowPrivateNetwork); } - Long maxAge = other.getMaxAge(); - if (maxAge != null) { - config.setMaxAge(maxAge); + if (this.maxAge == null) { + config.setMaxAge(other.maxAge); } return config; } /** - * combine + * Combines two lists of strings, with a priority on the source list. * - * @param source source - * @param other other - * @return {@link List}<{@link String}> + * @param source The primary list of strings. + * @param other The secondary list of strings to be combined with the source. + * @return A combined list of strings, with the source list taking priority in case of conflicts. */ private List combine(List source, List other) { if (other == null) { @@ -343,7 +340,7 @@ private List combine(List source, List other) { } // save setting value at first if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) { - return other; + return source; } if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS) { return source; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index c47c8d7c6c6..3c20d2fb0c9 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -93,7 +93,7 @@ public void register(Invoker invoker) { return; } if (classMapping != null) { - methodMapping = classMapping.combine(methodMapping); + methodMapping = methodMapping.combine(classMapping); } register0(methodMapping, buildHandlerMeta(invoker, methodMeta)); }); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java index 6c112bc8144..509267828a9 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java @@ -121,24 +121,24 @@ void combineWithConfigWithNullProperties() { @Test void combineWithDefaultPermitValues() { - CorsMeta config = new CorsMeta().applyPermitDefaultValues(); + CorsMeta priority = new CorsMeta().applyPermitDefaultValues(); CorsMeta other = new CorsMeta(); other.addAllowedOrigin("https://domain.com"); other.addAllowedHeader("header1"); other.addAllowedMethod(HttpMethods.PUT.name()); - CorsMeta combinedConfig = config.combine(other); + CorsMeta combinedConfig = priority.combine(other); Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( - new String[] {"https://domain.com"}, - combinedConfig.getAllowedOrigins().toArray()); + new String[] {"*"}, combinedConfig.getAllowedOrigins().toArray()); Assertions.assertArrayEquals( - new String[] {"header1"}, combinedConfig.getAllowedHeaders().toArray()); + new String[] {"*"}, combinedConfig.getAllowedHeaders().toArray()); Assertions.assertArrayEquals( - new String[] {HttpMethods.PUT.name()}, + new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}, combinedConfig.getAllowedMethods().toArray()); Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); - combinedConfig = other.combine(config); + + combinedConfig = other.combine(priority); Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( new String[] {"https://domain.com"}, @@ -149,26 +149,26 @@ void combineWithDefaultPermitValues() { new String[] {HttpMethods.PUT.name()}, combinedConfig.getAllowedMethods().toArray()); Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); - combinedConfig = config.combine(new CorsMeta()); + combinedConfig = priority.combine(new CorsMeta()); Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( - new String[] {"*"}, config.getAllowedOrigins().toArray()); + new String[] {"*"}, priority.getAllowedOrigins().toArray()); Assertions.assertArrayEquals( - new String[] {"*"}, config.getAllowedHeaders().toArray()); + new String[] {"*"}, priority.getAllowedHeaders().toArray()); Assertions.assertArrayEquals( new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}, combinedConfig.getAllowedMethods().toArray()); Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); // Combine an empty config with config - combinedConfig = new CorsMeta().combine(config); + combinedConfig = new CorsMeta().combine(priority); // Assert the combined config Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( - new String[] {"*"}, config.getAllowedOrigins().toArray()); + new String[] {"*"}, priority.getAllowedOrigins().toArray()); Assertions.assertArrayEquals( - new String[] {"*"}, config.getAllowedHeaders().toArray()); + new String[] {"*"}, priority.getAllowedHeaders().toArray()); Assertions.assertArrayEquals( new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}, combinedConfig.getAllowedMethods().toArray()); @@ -341,15 +341,15 @@ void combineWithDuplicatedElements() { @Test void combine() { // Create a config with some values - CorsMeta config = new CorsMeta(); - config.addAllowedOrigin("https://domain1.com"); - config.addAllowedOriginPattern("http://*.domain1.com"); - config.addAllowedHeader("header1"); - config.addExposedHeader("header3"); - config.addAllowedMethod(HttpMethods.GET.name()); - config.setMaxAge(123L); - config.setAllowCredentials(true); - config.setAllowPrivateNetwork(true); + CorsMeta priority = new CorsMeta(); + priority.addAllowedOrigin("https://domain1.com"); + priority.addAllowedOriginPattern("http://*.domain1.com"); + priority.addAllowedHeader("header1"); + priority.addExposedHeader("header3"); + priority.addAllowedMethod(HttpMethods.GET.name()); + priority.setMaxAge(123L); + priority.setAllowCredentials(true); + priority.setAllowPrivateNetwork(true); // Create another config with some different values CorsMeta other = new CorsMeta(); @@ -363,26 +363,28 @@ void combine() { other.setAllowPrivateNetwork(false); // Combine the configs - config = config.combine(other); + priority = priority.combine(other); // Assert the combined config - Assertions.assertNotNull(config); + Assertions.assertNotNull(priority); Assertions.assertArrayEquals( new String[] {"https://domain1.com", "https://domain2.com"}, - config.getAllowedOrigins().toArray()); + priority.getAllowedOrigins().toArray()); Assertions.assertArrayEquals( - new String[] {"header1", "header2"}, config.getAllowedHeaders().toArray()); + new String[] {"header1", "header2"}, + priority.getAllowedHeaders().toArray()); Assertions.assertArrayEquals( - new String[] {"header3", "header4"}, config.getExposedHeaders().toArray()); + new String[] {"header3", "header4"}, + priority.getExposedHeaders().toArray()); Assertions.assertArrayEquals( new String[] {HttpMethods.GET.name(), HttpMethods.PUT.name()}, - config.getAllowedMethods().toArray()); - Assertions.assertEquals(Long.valueOf(456), config.getMaxAge()); - Assertions.assertFalse(config.getAllowCredentials()); - Assertions.assertFalse(config.getAllowPrivateNetwork()); + priority.getAllowedMethods().toArray()); + Assertions.assertEquals(Long.valueOf(123L), priority.getMaxAge()); + Assertions.assertTrue(priority.getAllowCredentials()); + Assertions.assertTrue(priority.getAllowPrivateNetwork()); Assertions.assertArrayEquals( new String[] {"http://*.domain1.com", "http://*.domain2.com"}, - config.getAllowedOriginPatterns().toArray()); + priority.getAllowedOriginPatterns().toArray()); } @Test diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index 16ea0e853c2..3eace82c53e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -80,25 +80,21 @@ void sameOriginRequest() { this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } - // Although in this test it can pass, but In actual use, - // due to the implementation of Http12 error return, - // it is not yet possible to set the Vary request header while being intercepted. - // @Test - // void actualRequestWithOriginHeader() { - // Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); - // Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - // - // this.processor.process(this.conf, this.request, this.response); - // Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - // Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); - // Assertions.assertTrue( - // - // this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - // Assertions.assertTrue( - // - // this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - // Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); - // } + + @Test + void actualRequestWithOriginHeader() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); + + this.processor.process(this.conf, this.request, this.response); + Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); + } @Test void actualRequestWithOriginHeaderAndNullConfig() { From 5445bbe3c723caf744da96218366f6b85671a0a5 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 14:53:41 +0800 Subject: [PATCH 16/62] test(): remove rest-spring cors test to sample --- .../spring/compatible/SpringDemoServiceImpl.java | 5 ----- .../compatible/SpringMvcRestProtocolTest.java | 14 -------------- .../spring/compatible/SpringRestDemoService.java | 4 ---- .../dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java | 9 +++++---- 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java index 3685b9d32e6..46498e5162e 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java @@ -106,9 +106,4 @@ public long primitiveByte(byte a, Long b) { public long primitiveShort(short a, Long b, int c) { return a + b; } - - @Override - public String cors() { - return "you should not pass"; - } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java index e3f26c77335..c7246ba9c4a 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java @@ -85,20 +85,6 @@ public Exporter getExport(URL url, SpringRestDemoService return tProtocol.export(proxy.getInvoker(server, getServerClass(), url)); } - @Test - void testCors() { - - SpringRestDemoService server = getServerImpl(); - - URL nettyUrl = this.registerProvider(getUrl(), server, SpringRestDemoService.class); - - Exporter exporter = getExport(nettyUrl, server); - - SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); - String cors = demoService.cors(); - Assertions.assertNotEquals("you should not pass", cors); - } - @Test void testRestProtocol() { int port = NetUtils.getAvailablePort(); diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java index 9a2dd273f98..4696ac3971b 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java @@ -20,7 +20,6 @@ import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -70,7 +69,4 @@ public interface SpringRestDemoService { @RequestMapping(method = RequestMethod.POST, value = "/primitiveShort") long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c); - - @RequestMapping(method = RequestMethod.GET, value = "/cors") - String cors(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index bd3dcf59312..47e0215871e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -339,12 +339,13 @@ private List combine(List source, List other) { return other; } // save setting value at first - if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) { - return source; - } - if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS) { + if (source == DEFAULT_PERMIT_ALL + || source == DEFAULT_PERMIT_METHODS + || other == DEFAULT_PERMIT_ALL + || other == DEFAULT_PERMIT_METHODS) { return source; } + if (source.contains(ALL) || other.contains(ALL)) { return ALL_LIST; } From d089ad3bda55ccfd3fbfdce0317308a10d5b9ea5 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 17:58:19 +0800 Subject: [PATCH 17/62] docs(): add docs --- .../src/main/java/org/apache/dubbo/config/RestConfig.java | 2 ++ .../tri/rest/mapping/DefaultRequestMappingRegistry.java | 2 +- .../dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java index dc7e9889286..413109fd490 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java @@ -72,6 +72,8 @@ public class RestConfig implements Serializable { /** * The config is used to set the Global CORS configuration properties. + * The default value can found in org.apache.dubbo.rpc.protocol.tri.rest. + * cors.CorsMeta.applyPermitDefaultValues() */ @Nested private CorsConfig cors; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 3c20d2fb0c9..c47c8d7c6c6 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -93,7 +93,7 @@ public void register(Invoker invoker) { return; } if (classMapping != null) { - methodMapping = methodMapping.combine(classMapping); + methodMapping = classMapping.combine(methodMapping); } register0(methodMapping, buildHandlerMeta(invoker, methodMeta)); }); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index ab82c2d2b78..548c7f70c31 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -87,7 +87,7 @@ public RequestMapping combine(RequestMapping other) { ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - CorsMeta meta = this.corsMeta.combine(other.corsMeta); + CorsMeta meta = other.corsMeta.combine(this.corsMeta); return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response, meta); } From e64fad4d0f0d8291b57cb263f66e3ced17efc4a5 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 18:23:43 +0800 Subject: [PATCH 18/62] revert(): test version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5acb630e9ae..d498577014b 100644 --- a/pom.xml +++ b/pom.xml @@ -152,7 +152,7 @@ 1.0.0 2.38.0 - 3.3.0-beta.3-SNAPSHOT + 3.3.0-beta.2-SNAPSHOT From 342c64f63085194ef50cdbd5e29dcea41a8702aa Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 20:06:33 +0800 Subject: [PATCH 19/62] fix(): getCorsMeta can be null --- .../mapping/DefaultRequestMappingRegistry.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index c47c8d7c6c6..86e4fd4f1eb 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -83,9 +83,7 @@ public void register(Invoker invoker) { continue; } RequestMapping classMapping = resolver.resolve(serviceMeta); - if (classMapping != null) { - classMapping.setCorsMeta(classMapping.getCorsMeta().combine(getGlobalCorsMeta())); - } + mergeGlobalCorsMeta(classMapping); consumer.accept((methods) -> { MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); RequestMapping methodMapping = resolver.resolve(methodMeta); @@ -133,6 +131,17 @@ private HandlerMeta buildHandlerMeta(Invoker invoker, MethodMeta methodMeta) serviceDescriptor); } + private void mergeGlobalCorsMeta(RequestMapping mapping) { + if (mapping != null) { + CorsMeta corsMeta = mapping.getCorsMeta(); + if (corsMeta != null) { + mapping.setCorsMeta(corsMeta.combine(getGlobalCorsMeta())); + } else { + mapping.setCorsMeta(getGlobalCorsMeta()); + } + } + } + @Override public void unregister(Invoker invoker) { lock.writeLock().lock(); From 81371671c84f9989088bd62d78fa86de28a7323e Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 21:45:33 +0800 Subject: [PATCH 20/62] fix(): combine can be null --- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 29 ++++++++++++------- .../DefaultRequestMappingRegistry.java | 2 +- .../tri/rest/mapping/RequestMapping.java | 2 +- .../protocol/tri/rest/cors/CorsMetaTest.java | 28 +++++++++--------- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 47e0215871e..0d8e349422c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -300,25 +300,32 @@ public boolean validateAllowPrivateNetwork() { * @param other other * @return {@link CorsMeta} */ - public CorsMeta combine(CorsMeta other) { + public static CorsMeta combine(CorsMeta priority, CorsMeta other) { + if (priority == null && other == null) { + return null; + } + if (priority == null) { + return other; + } if (other == null) { - return this; + return priority; } - CorsMeta config = new CorsMeta(this); - List origins = combine(getAllowedOrigins(), other.getAllowedOrigins()); - List patterns = combinePatterns(this.allowedOriginPatterns, other.allowedOriginPatterns); + CorsMeta config = new CorsMeta(priority); + List origins = priority.combine(priority.getAllowedOrigins(), other.getAllowedOrigins()); + List patterns = + priority.combinePatterns(priority.allowedOriginPatterns, other.allowedOriginPatterns); config.allowedOrigins = (origins == DEFAULT_PERMIT_ALL && !CollectionUtils.isEmpty(patterns) ? null : origins); config.allowedOriginPatterns = patterns; - config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods())); - config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders())); - config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders())); - if (this.allowCredentials == null) { + config.setAllowedMethods(priority.combine(priority.getAllowedMethods(), other.getAllowedMethods())); + config.setAllowedHeaders(priority.combine(priority.getAllowedHeaders(), other.getAllowedHeaders())); + config.setExposedHeaders(priority.combine(priority.getExposedHeaders(), other.getExposedHeaders())); + if (priority.allowCredentials == null) { config.setAllowCredentials(other.getAllowCredentials()); } - if (this.allowPrivateNetwork == null) { + if (priority.allowPrivateNetwork == null) { config.setAllowPrivateNetwork(other.allowPrivateNetwork); } - if (this.maxAge == null) { + if (priority.maxAge == null) { config.setMaxAge(other.maxAge); } return config; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 86e4fd4f1eb..7f11b7cd399 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -135,7 +135,7 @@ private void mergeGlobalCorsMeta(RequestMapping mapping) { if (mapping != null) { CorsMeta corsMeta = mapping.getCorsMeta(); if (corsMeta != null) { - mapping.setCorsMeta(corsMeta.combine(getGlobalCorsMeta())); + mapping.setCorsMeta(CorsMeta.combine(corsMeta, getGlobalCorsMeta())); } else { mapping.setCorsMeta(getGlobalCorsMeta()); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 548c7f70c31..5b832d303cd 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -87,7 +87,7 @@ public RequestMapping combine(RequestMapping other) { ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - CorsMeta meta = other.corsMeta.combine(this.corsMeta); + CorsMeta meta = CorsMeta.combine(other.corsMeta, this.corsMeta); return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response, meta); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java index 509267828a9..594c41fd9e8 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java @@ -81,7 +81,7 @@ void combineWithNull() { CorsMeta config = new CorsMeta(); config.setAllowedOrigins(Collections.singletonList("*")); - config.combine(null); + CorsMeta.combine(config, null); Assertions.assertArrayEquals( new String[] {"*"}, config.getAllowedOrigins().toArray()); Assertions.assertNull(config.getAllowedOriginPatterns()); @@ -99,7 +99,7 @@ void combineWithConfigWithNullProperties() { config.setAllowCredentials(true); config.setAllowPrivateNetwork(true); CorsMeta other = new CorsMeta(); - config = config.combine(other); + config = CorsMeta.combine(config, other); // Assert the combined config Assertions.assertNotNull(config); Assertions.assertArrayEquals( @@ -126,7 +126,7 @@ void combineWithDefaultPermitValues() { other.addAllowedOrigin("https://domain.com"); other.addAllowedHeader("header1"); other.addAllowedMethod(HttpMethods.PUT.name()); - CorsMeta combinedConfig = priority.combine(other); + CorsMeta combinedConfig = CorsMeta.combine(priority, other); Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( @@ -138,7 +138,7 @@ void combineWithDefaultPermitValues() { combinedConfig.getAllowedMethods().toArray()); Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); - combinedConfig = other.combine(priority); + combinedConfig = CorsMeta.combine(other, priority); Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( new String[] {"https://domain.com"}, @@ -149,7 +149,7 @@ void combineWithDefaultPermitValues() { new String[] {HttpMethods.PUT.name()}, combinedConfig.getAllowedMethods().toArray()); Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); - combinedConfig = priority.combine(new CorsMeta()); + combinedConfig = CorsMeta.combine(priority, new CorsMeta()); Assertions.assertNotNull(combinedConfig); Assertions.assertArrayEquals( new String[] {"*"}, priority.getAllowedOrigins().toArray()); @@ -161,7 +161,7 @@ void combineWithDefaultPermitValues() { Assertions.assertTrue(combinedConfig.getExposedHeaders().isEmpty()); // Combine an empty config with config - combinedConfig = new CorsMeta().combine(priority); + combinedConfig = CorsMeta.combine(new CorsMeta(), priority); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -185,7 +185,7 @@ void combinePatternWithDefaultPermitValues() { other.addAllowedOriginPattern("http://*.com"); // Combine the configs, with 'other' first - CorsMeta combinedConfig = other.combine(config); + CorsMeta combinedConfig = CorsMeta.combine(other, config); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -195,7 +195,7 @@ void combinePatternWithDefaultPermitValues() { combinedConfig.getAllowedOriginPatterns().toArray()); // Combine the configs, with 'config' first - combinedConfig = config.combine(other); + combinedConfig = CorsMeta.combine(config, other); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -216,7 +216,7 @@ void combinePatternWithDefaultPermitValuesAndCustomOrigin() { other.addAllowedOriginPattern("http://*.com"); // Combine the configs, with 'other' first - CorsMeta combinedConfig = other.combine(config); + CorsMeta combinedConfig = CorsMeta.combine(other, config); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -228,7 +228,7 @@ void combinePatternWithDefaultPermitValuesAndCustomOrigin() { combinedConfig.getAllowedOriginPatterns().toArray()); // Combine the configs, with 'config' first - combinedConfig = config.combine(other); + combinedConfig = CorsMeta.combine(config, other); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -261,7 +261,7 @@ void combineWithAsteriskWildCard() { other.addAllowedMethod(HttpMethods.PUT.name()); // Combine the configs, with 'config' first - CorsMeta combinedConfig = config.combine(other); + CorsMeta combinedConfig = CorsMeta.combine(config, other); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -277,7 +277,7 @@ void combineWithAsteriskWildCard() { new String[] {"*"}, combinedConfig.getAllowedMethods().toArray()); // Combine the configs, with 'other' first - combinedConfig = other.combine(config); + combinedConfig = CorsMeta.combine(other, config); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -317,7 +317,7 @@ void combineWithDuplicatedElements() { other.addAllowedMethod(HttpMethods.GET.name()); // Combine the configs - CorsMeta combinedConfig = config.combine(other); + CorsMeta combinedConfig = CorsMeta.combine(config, other); // Assert the combined config Assertions.assertNotNull(combinedConfig); @@ -363,7 +363,7 @@ void combine() { other.setAllowPrivateNetwork(false); // Combine the configs - priority = priority.combine(other); + priority = CorsMeta.combine(priority, other); // Assert the combined config Assertions.assertNotNull(priority); From 791c905ed3235572008b1d8ebe1483489f15e2e7 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 23:28:50 +0800 Subject: [PATCH 21/62] fix(): save option and vary bug --- .../protocol/tri/rest/cors/CorsProcessor.java | 20 ++- .../DefaultRequestMappingRegistry.java | 17 +-- .../tri/rest/cors/CorsProcessorTest.java | 134 +++++++++--------- 3 files changed, 85 insertions(+), 86 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index 735c39b059b..6d61c960fce 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -135,6 +135,7 @@ private boolean reject(HttpResponse response) { public static boolean isPreFlight(HttpRequest request) { // preflight request is a OPTIONS request with Access-Control-Request-Method header return request.method().equals(HttpMethods.OPTIONS.name()) + && request.header(RestConstants.ORIGIN) != null && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; } @@ -162,18 +163,25 @@ private boolean isCorsRequest(HttpRequest request) { private void setVaryHeaders(HttpResponse response) { List varyHeaders = response.headerValues(RestConstants.VARY); if (varyHeaders == null) { - response.addHeader(RestConstants.VARY, RestConstants.ORIGIN); - response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + + response.addHeader( + RestConstants.VARY, + RestConstants.ORIGIN + "," + RestConstants.ACCESS_CONTROL_REQUEST_METHOD + "," + + RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); } else { + StringBuilder builder = new StringBuilder(); if (!varyHeaders.contains(RestConstants.ORIGIN)) { - response.addHeader(RestConstants.VARY, RestConstants.ORIGIN); + builder.append(RestConstants.ORIGIN).append(","); } if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { - response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + builder.append(RestConstants.ACCESS_CONTROL_REQUEST_METHOD).append(","); } if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { - response.addHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); + builder.append(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS).append(","); + } + if (builder.length() > 0) { + builder.deleteCharAt(builder.length() - 1); + response.addHeader(RestConstants.VARY, builder.toString()); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 7f11b7cd399..492dd97fb21 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -51,7 +51,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -239,18 +238,10 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { } private String preprocessingCors(HttpRequest request, HttpResponse response) { - if (Objects.equals(request.method(), HttpMethods.OPTIONS.name())) { - if (CorsProcessor.isPreFlight(request)) { - String realMethod = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - request.setMethod(realMethod); - return realMethod; - } else { - throw new HttpResultPayloadException(HttpResult.builder() - .status(HttpStatus.FORBIDDEN) - .body(response.body()) - .headers(response.headers()) - .build()); - } + if (CorsProcessor.isPreFlight(request)) { + String realMethod = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); + request.setMethod(realMethod); + return realMethod; } return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index 3eace82c53e..a7f1582be06 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -59,11 +59,11 @@ void requestWithoutOriginHeader() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); this.processor.process(this.conf, this.request, this.response); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -73,11 +73,11 @@ void sameOriginRequest() { Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("http://domain1.example"); this.processor.process(this.conf, this.request, this.response); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -88,11 +88,11 @@ void actualRequestWithOriginHeader() { this.processor.process(this.conf, this.request, this.response); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); } @@ -117,11 +117,11 @@ void actualRequestWithOriginHeaderAndAllowedOrigin() { Assertions.assertEquals("*", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_MAX_AGE)); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -139,11 +139,11 @@ void actualRequestCredentials() { Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -166,11 +166,11 @@ void actualRequestCredentialsWithWildcardOrigin() { Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -213,11 +213,11 @@ void actualRequestExposedHeaders() { Assertions.assertTrue(this.response .headerValues(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS) .contains("header2")); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -230,11 +230,11 @@ void preflightRequestWithoutRequestheader() { this.conf.addAllowedOrigin("*"); this.processor.process(this.conf, request, this.response); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -246,11 +246,11 @@ void preflightRequestWrongAllowedMethod() { .thenReturn("DELETE"); this.conf.addAllowedOrigin("*"); this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); } @@ -268,11 +268,11 @@ void preflightRequestMatchedAllowedMethod() { this.response .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_METHODS) .toArray()); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test @@ -282,11 +282,11 @@ void preflightRequestTestWithOriginButWithoutOtherHeaders() { this.processor.process(this.conf, this.request, this.response); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); } @@ -298,11 +298,11 @@ void preflightRequestWithoutRequestMethod() { .thenReturn("Header1"); this.processor.process(this.conf, this.request, this.response); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); } @@ -317,11 +317,11 @@ void preflightRequestWithRequestAndMethodHeaderButNoConfig() { this.processor.process(this.conf, this.request, this.response); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), this.response.status()); } @@ -349,11 +349,11 @@ void preflightRequestValidRequestAndConfig() { .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_METHODS) .toArray()); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_MAX_AGE)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -376,11 +376,11 @@ void preflightRequestCredentials() { Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -406,11 +406,11 @@ void preflightRequestCredentialsWithWildcardOrigin() { this.processor.process(this.conf, this.request, this.response); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -439,11 +439,11 @@ void preflightRequestPrivateNetworkWithWildcardOrigin() { Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -472,11 +472,11 @@ void preflightRequestAllowedHeaders() { Assertions.assertFalse(this.response .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) .contains("Header3")); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -503,11 +503,11 @@ void preflightRequestAllowsAllHeaders() { Assertions.assertFalse(this.response .headerValues(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS) .contains("*")); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -525,11 +525,11 @@ void preflightRequestWithEmptyHeaders() { this.processor.process(this.conf, this.request, this.response); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @@ -549,16 +549,16 @@ void preflightRequestWithNullConfig() { @Test void preventDuplicatedVaryHeaders() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); - this.response.setHeader(RestConstants.VARY, RestConstants.ORIGIN); - this.response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - this.response.setHeader(RestConstants.VARY, RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - + this.response.setHeader( + RestConstants.VARY, + RestConstants.ORIGIN + "," + RestConstants.ACCESS_CONTROL_REQUEST_METHOD + "," + + RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.headerValues(RestConstants.VARY).contains(RestConstants.ORIGIN)); + Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.headerValues(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); + this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test From 81fff53af0ca9c076a9f853d26c1e1aae0e5fa42 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 11 Apr 2024 23:36:09 +0800 Subject: [PATCH 22/62] fix(): pom version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d498577014b..4dafbf47e67 100644 --- a/pom.xml +++ b/pom.xml @@ -160,7 +160,7 @@ org.apache.dubbo dubbo-dependencies-bom - 3.3.0-beta.2-SNAPSHOT + ${project.version} pom import From 8be498819b6683b1b2e43c6539bf8aee9b3e3979 Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 12 Apr 2024 00:17:57 +0800 Subject: [PATCH 23/62] fix(): spring version will cause allowPrivateWork resolve error --- .../support/spring/SpringMvcRequestMappingResolver.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 688f87b6620..18abefce169 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -131,8 +131,13 @@ private CorsMeta createCorsMeta(AnnotationMeta crossOrigin) { meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); String allowCredentials = crossOrigin.getString("allowCredentials"); meta.setAllowCredentials(allowCredentials != null ? Boolean.valueOf(allowCredentials) : null); - String allowPrivateNetwork = crossOrigin.getString("allowPrivateNetwork"); - meta.setAllowPrivateNetwork(allowPrivateNetwork != null ? Boolean.valueOf(allowPrivateNetwork) : null); + // Because allowPrivateNetwork does not exist in some spring versions, we need to catch the exception + try { + String allowPrivateNetwork = crossOrigin.getString("allowPrivateNetwork"); + meta.setAllowPrivateNetwork(allowPrivateNetwork != null ? Boolean.valueOf(allowPrivateNetwork) : null); + } catch (IllegalArgumentException e) { + meta.setAllowPrivateNetwork(null); + } return meta; } } From a93b3e8909bec521df2cbe8b93f4d0a919c874cc Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 12 Apr 2024 00:41:26 +0800 Subject: [PATCH 24/62] fix(): ci --- .../apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index 6d61c960fce..da5595dd0a1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -163,7 +163,6 @@ private boolean isCorsRequest(HttpRequest request) { private void setVaryHeaders(HttpResponse response) { List varyHeaders = response.headerValues(RestConstants.VARY); if (varyHeaders == null) { - response.addHeader( RestConstants.VARY, RestConstants.ORIGIN + "," + RestConstants.ACCESS_CONTROL_REQUEST_METHOD + "," From fa1020ed29624d8c0e0598b6e193a76f4dd8550d Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 12 Apr 2024 15:40:11 +0800 Subject: [PATCH 25/62] refactor(): delete useless code --- .../tri/rest/mapping/DefaultRequestMappingRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 492dd97fb21..df4e6d50256 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -177,7 +177,7 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return null; } - String method = preprocessingCors(request, response); + String method = preprocessingCors(request); List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { @@ -237,7 +237,7 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return handler; } - private String preprocessingCors(HttpRequest request, HttpResponse response) { + private String preprocessingCors(HttpRequest request) { if (CorsProcessor.isPreFlight(request)) { String realMethod = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); request.setMethod(realMethod); From 8a4c279f77da9ffebd5d5c28bc0aab4b9ec8d0cd Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 12 Apr 2024 22:53:46 +0800 Subject: [PATCH 26/62] refactor(): accept some sonarcloud issue --- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 69 ++++++++++++++----- .../rpc/protocol/tri/rest/cors/CorsUtil.java | 1 + .../tri/rest/mapping/RequestMapping.java | 4 +- .../protocol/tri/rest/cors/CorsMetaTest.java | 2 +- .../tri/rest/cors/CorsProcessorTest.java | 2 +- .../protocol/tri/rest/cors/CorsUtilTest.java | 14 ++-- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 0d8e349422c..97b47aa1fdb 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -152,8 +152,16 @@ public void addAllowedOriginPattern(String originPattern) { } public void setAllowedMethods(List allowedMethods) { - this.allowedMethods = (allowedMethods != null ? new ArrayList<>(allowedMethods) : null); - if (!CollectionUtils.isEmpty(allowedMethods)) { + if (allowedMethods != null) { + this.allowedMethods = new ArrayList<>(allowedMethods); + setResolvedMethods(allowedMethods); + } else { + this.allowedMethods = null; + } + } + + public void setResolvedMethods(List allowedMethods) { + if (!allowedMethods.isEmpty()) { this.resolvedMethods = new ArrayList<>(allowedMethods.size()); for (String method : allowedMethods) { if (ALL.equals(method)) { @@ -386,26 +394,41 @@ public String checkOrigin(String origin) { String originToCheck = trimTrailingSlash(origin); if (!CollectionUtils.isEmpty(this.allowedOrigins)) { if (this.allowedOrigins.contains(ALL)) { - if (validateAllowCredentials() || validateAllowPrivateNetwork()) { - return null; - } - return ALL; + return allOriginAllowed(); } - for (String allowedOrigin : this.allowedOrigins) { - if (originToCheck.equalsIgnoreCase(allowedOrigin)) { - return origin; - } + if (isOriginAllowed(originToCheck)) { + return origin; + } + } + if (isOriginPatternAllowed(originToCheck)) { + return origin; + } + return null; + } + + private String allOriginAllowed() { + return (validateAllowCredentials() || validateAllowPrivateNetwork() ? null : ALL); + } + + private boolean isOriginAllowed(String originToCheck) { + for (String allowedOrigin : this.allowedOrigins) { + if (originToCheck.equalsIgnoreCase(allowedOrigin)) { + return true; } } + return false; + } + + private boolean isOriginPatternAllowed(String originToCheck) { if (!CollectionUtils.isEmpty(this.allowedOriginPatterns)) { for (OriginPattern p : this.allowedOriginPatterns) { if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(originToCheck).matches()) { - return origin; + return true; } } } - return null; + return false; } public List checkHttpMethods(HttpMethods requestMethod) { @@ -430,22 +453,30 @@ public List checkHeaders(List requestHeaders) { } boolean allowAnyHeader = this.allowedHeaders.contains(ALL); List result = new ArrayList<>(requestHeaders.size()); + loadAllowedHeaders(requestHeaders, result, allowAnyHeader); + return (result.isEmpty() ? null : result); + } + + private void loadAllowedHeaders(List requestHeaders, List result, boolean allowAnyHeader) { for (String requestHeader : requestHeaders) { if (StringUtils.hasText(requestHeader)) { requestHeader = requestHeader.trim(); if (allowAnyHeader) { result.add(requestHeader); } else { - for (String allowedHeader : this.allowedHeaders) { - if (requestHeader.equalsIgnoreCase(allowedHeader)) { - result.add(requestHeader); - break; - } - } + loadAllowedHeaders(result, requestHeader); } } } - return (result.isEmpty() ? null : result); + } + + private void loadAllowedHeaders(List result, String requestHeader) { + for (String allowedHeader : this.allowedHeaders) { + if (requestHeader.equalsIgnoreCase(allowedHeader)) { + result.add(requestHeader); + break; + } + } } private static class OriginPattern { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java index 6fa756746ba..ee81bdb3778 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; public class CorsUtil { + private CorsUtil() {} public static int getPort(String scheme, int port) { if (port == -1) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 5b832d303cd..4066da157b9 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -395,7 +395,7 @@ public RequestMapping build() { ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); - CorsMeta corsMeta = this.corsMeta == null ? null : this.corsMeta; + CorsMeta cors = this.corsMeta == null ? null : this.corsMeta; return new RequestMapping( name, pathCondition, @@ -406,7 +406,7 @@ public RequestMapping build() { producesCondition, customCondition, response, - corsMeta); + cors); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java index 594c41fd9e8..55d2d2ed588 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class CorsMetaTest { +class CorsMetaTest { @Test void setNullValues() { CorsMeta config = new CorsMeta(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index a7f1582be06..7fadfb250f1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -public class CorsProcessorTest { +class CorsProcessorTest { private HttpRequest request; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java index 6e3e72a9ed0..635c23af9a0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java @@ -25,7 +25,7 @@ import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CORS_CONFIG_PREFIX; -public class CorsUtilTest { +class CorsUtilTest { @Test void testResolveGlobalMetaInCommon() { @@ -66,12 +66,12 @@ void testResolveGlobalMetaWithNullConfig() { Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn(null); CorsMeta meta = CorsUtil.resolveGlobalMeta(config); - Assertions.assertEquals(meta.getMaxAge(), CorsMeta.DEFAULT_MAX_AGE); - Assertions.assertEquals(meta.getAllowCredentials(), false); - Assertions.assertEquals(meta.getAllowPrivateNetwork(), false); - Assertions.assertEquals(meta.getAllowedOrigins(), CorsMeta.DEFAULT_PERMIT_ALL); - Assertions.assertEquals(meta.getAllowedMethods(), CorsMeta.DEFAULT_PERMIT_METHODS); - Assertions.assertEquals(meta.getAllowedHeaders(), CorsMeta.DEFAULT_PERMIT_ALL); + Assertions.assertEquals(CorsMeta.DEFAULT_MAX_AGE, meta.getMaxAge()); + Assertions.assertFalse(meta.getAllowCredentials()); + Assertions.assertFalse(meta.getAllowPrivateNetwork()); + Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedOrigins()); + Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_METHODS, meta.getAllowedMethods()); + Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedHeaders()); } @Test From 5b44226a05a5228366d21b4bdcd8d36c324167e8 Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 13 Apr 2024 00:59:47 +0800 Subject: [PATCH 27/62] refactor(): add @Nullable to point the CorsMeta Attributes --- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 53 ++++++++++++------- .../protocol/tri/rest/cors/CorsProcessor.java | 4 +- .../rpc/protocol/tri/rest/cors/CorsUtil.java | 4 +- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 97b47aa1fdb..3880a8ff43d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.cors; +import org.apache.dubbo.common.lang.Nullable; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpMethods; @@ -53,24 +54,32 @@ public class CorsMeta { public static final Long DEFAULT_MAX_AGE = 1800L; - // Fields + @Nullable private List allowedOrigins; + @Nullable private List allowedOriginPatterns; + @Nullable private List allowedMethods; + @Nullable private List resolvedMethods = DEFAULT_METHODS; + @Nullable private List allowedHeaders; + @Nullable private List exposedHeaders; + @Nullable private Boolean allowCredentials; + @Nullable private Boolean allowPrivateNetwork; + @Nullable private Long maxAge; public CorsMeta() {} @@ -87,7 +96,7 @@ public CorsMeta(CorsMeta other) { this.maxAge = other.maxAge; } - public void setAllowedOrigins(List origins) { + public void setAllowedOrigins(@Nullable List origins) { this.allowedOrigins = (origins == null ? null : origins.stream() @@ -104,7 +113,7 @@ public List getAllowedOrigins() { return this.allowedOrigins; } - public void addAllowedOrigin(String origin) { + public void addAllowedOrigin(@Nullable String origin) { if (origin == null) { return; } @@ -117,7 +126,7 @@ public void addAllowedOrigin(String origin) { this.allowedOrigins.add(origin); } - public void setAllowedOriginPatterns(List allowedOriginPatterns) { + public void setAllowedOriginPatterns(@Nullable List allowedOriginPatterns) { if (allowedOriginPatterns == null) { this.allowedOriginPatterns = null; } else { @@ -128,6 +137,7 @@ public void setAllowedOriginPatterns(List allowedOriginPatterns) { } } + @Nullable public List getAllowedOriginPatterns() { if (this.allowedOriginPatterns == null) { return null; @@ -137,7 +147,7 @@ public List getAllowedOriginPatterns() { .collect(Collectors.toList()); } - public void addAllowedOriginPattern(String originPattern) { + public void addAllowedOriginPattern(@Nullable String originPattern) { if (originPattern == null) { return; } @@ -151,7 +161,7 @@ public void addAllowedOriginPattern(String originPattern) { } } - public void setAllowedMethods(List allowedMethods) { + public void setAllowedMethods(@Nullable List allowedMethods) { if (allowedMethods != null) { this.allowedMethods = new ArrayList<>(allowedMethods); setResolvedMethods(allowedMethods); @@ -160,6 +170,7 @@ public void setAllowedMethods(List allowedMethods) { } } + @Nullable public void setResolvedMethods(List allowedMethods) { if (!allowedMethods.isEmpty()) { this.resolvedMethods = new ArrayList<>(allowedMethods.size()); @@ -200,7 +211,7 @@ public void addAllowedMethod(String method) { } } - public void setAllowedHeaders(List allowedHeaders) { + public void setAllowedHeaders(@Nullable List allowedHeaders) { this.allowedHeaders = (allowedHeaders != null ? new ArrayList<>(allowedHeaders) : null); } @@ -217,7 +228,7 @@ public void addAllowedHeader(String allowedHeader) { this.allowedHeaders.add(allowedHeader); } - public void setExposedHeaders(List exposedHeaders) { + public void setExposedHeaders(@Nullable List exposedHeaders) { this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null); } @@ -232,10 +243,10 @@ public void addExposedHeader(String exposedHeader) { this.exposedHeaders.add(exposedHeader); } - public void setAllowCredentials(Boolean allowCredentials) { + public void setAllowCredentials(@Nullable Boolean allowCredentials) { this.allowCredentials = allowCredentials; } - + @Nullable public Boolean getAllowCredentials() { return this.allowCredentials; } @@ -248,10 +259,11 @@ public Boolean getAllowPrivateNetwork() { return this.allowPrivateNetwork; } - public void setMaxAge(Long maxAge) { + public void setMaxAge(@Nullable Long maxAge) { this.maxAge = maxAge; } + @Nullable public Long getMaxAge() { return this.maxAge; } @@ -308,9 +320,9 @@ public boolean validateAllowPrivateNetwork() { * @param other other * @return {@link CorsMeta} */ - public static CorsMeta combine(CorsMeta priority, CorsMeta other) { + public static CorsMeta combine(@Nullable CorsMeta priority,@Nullable CorsMeta other) { if (priority == null && other == null) { - return null; + return new CorsMeta(); } if (priority == null) { return other; @@ -346,7 +358,7 @@ public static CorsMeta combine(CorsMeta priority, CorsMeta other) { * @param other The secondary list of strings to be combined with the source. * @return A combined list of strings, with the source list taking priority in case of conflicts. */ - private List combine(List source, List other) { + private List combine(@Nullable List source,@Nullable List other) { if (other == null) { return (source != null ? source : Collections.emptyList()); } @@ -370,7 +382,8 @@ private List combine(List source, List other) { return new ArrayList<>(combined); } - private List combinePatterns(List source, List other) { + private List combinePatterns(@Nullable List source, + @Nullable List other) { if (other == null) { return (source != null ? source : Collections.emptyList()); @@ -387,7 +400,7 @@ private List combinePatterns(List source, List(combined); } - public String checkOrigin(String origin) { + public String checkOrigin(@Nullable String origin) { if (!StringUtils.hasText(origin)) { return null; } @@ -430,8 +443,8 @@ private boolean isOriginPatternAllowed(String originToCheck) { } return false; } - - public List checkHttpMethods(HttpMethods requestMethod) { + @Nullable + public List checkHttpMethods( @Nullable HttpMethods requestMethod) { if (requestMethod == null) { return null; } @@ -440,8 +453,8 @@ public List checkHttpMethods(HttpMethods requestMethod) { } return (this.resolvedMethods.contains(requestMethod) ? this.resolvedMethods : null); } - - public List checkHeaders(List requestHeaders) { + @Nullable + public List checkHeaders(@Nullable List requestHeaders) { if (requestHeaders == null) { return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index da5595dd0a1..66df5b934bc 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -113,14 +113,14 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor } private HttpMethods getHttpMethods(HttpRequest request, Boolean isPreLight) { - if (isPreLight) { + if (Boolean.TRUE.equals(isPreLight)) { return HttpMethods.valueOf(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); } return HttpMethods.valueOf(request.method()); } private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { - if (isPreLight) { + if (Boolean.TRUE.equals(isPreLight)) { return request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); } return new ArrayList<>(request.headerNames()); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java index ee81bdb3778..0401007e9ad 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri.rest.cors; import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.lang.Nullable; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import java.util.Arrays; @@ -60,7 +61,8 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { return meta.applyPermitDefaultValues(); } - private static List parseList(String value) { + @Nullable + private static List parseList(@Nullable String value) { if (value == null) { return null; } From c3c07712a475ceeb92a149e7a9a8b409996cc68d Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 13 Apr 2024 01:01:34 +0800 Subject: [PATCH 28/62] refactor(): style --- .../dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 3880a8ff43d..3d2c7a847f2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -54,7 +54,6 @@ public class CorsMeta { public static final Long DEFAULT_MAX_AGE = 1800L; - @Nullable private List allowedOrigins; @@ -246,6 +245,7 @@ public void addExposedHeader(String exposedHeader) { public void setAllowCredentials(@Nullable Boolean allowCredentials) { this.allowCredentials = allowCredentials; } + @Nullable public Boolean getAllowCredentials() { return this.allowCredentials; @@ -320,7 +320,7 @@ public boolean validateAllowPrivateNetwork() { * @param other other * @return {@link CorsMeta} */ - public static CorsMeta combine(@Nullable CorsMeta priority,@Nullable CorsMeta other) { + public static CorsMeta combine(@Nullable CorsMeta priority, @Nullable CorsMeta other) { if (priority == null && other == null) { return new CorsMeta(); } @@ -358,7 +358,7 @@ public static CorsMeta combine(@Nullable CorsMeta priority,@Nullable CorsMeta ot * @param other The secondary list of strings to be combined with the source. * @return A combined list of strings, with the source list taking priority in case of conflicts. */ - private List combine(@Nullable List source,@Nullable List other) { + private List combine(@Nullable List source, @Nullable List other) { if (other == null) { return (source != null ? source : Collections.emptyList()); } @@ -382,8 +382,8 @@ private List combine(@Nullable List source,@Nullable List(combined); } - private List combinePatterns(@Nullable List source, - @Nullable List other) { + private List combinePatterns( + @Nullable List source, @Nullable List other) { if (other == null) { return (source != null ? source : Collections.emptyList()); @@ -443,8 +443,9 @@ private boolean isOriginPatternAllowed(String originToCheck) { } return false; } + @Nullable - public List checkHttpMethods( @Nullable HttpMethods requestMethod) { + public List checkHttpMethods(@Nullable HttpMethods requestMethod) { if (requestMethod == null) { return null; } @@ -453,6 +454,7 @@ public List checkHttpMethods( @Nullable HttpMethods requestMethod) } return (this.resolvedMethods.contains(requestMethod) ? this.resolvedMethods : null); } + @Nullable public List checkHeaders(@Nullable List requestHeaders) { if (requestHeaders == null) { From 1875011e1e0a3fef2f29ede7f21f3dbb6fd1fc03 Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 19 Apr 2024 10:29:17 +0800 Subject: [PATCH 29/62] fix(): fix prelight logic --- .../DefaultRequestMappingRegistry.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index df4e6d50256..217c01827b7 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -19,7 +19,6 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.utils.Assert; -import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.remoting.http12.HttpResult; @@ -177,8 +176,6 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return null; } - String method = preprocessingCors(request); - List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { Match match = matches.get(i); @@ -219,7 +216,7 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { Candidate winner = candidates.get(0); RequestMapping mapping = winner.mapping; - processCors(method, mapping, request, response); + processCors(mapping, request, response); HandlerMeta handler = winner.meta; request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); @@ -237,19 +234,7 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return handler; } - private String preprocessingCors(HttpRequest request) { - if (CorsProcessor.isPreFlight(request)) { - String realMethod = request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD); - request.setMethod(realMethod); - return realMethod; - } - return null; - } - - private void processCors(String method, RequestMapping mapping, HttpRequest request, HttpResponse response) { - if (method != null) { - request.setMethod(HttpMethods.OPTIONS.name()); - } + private void processCors(RequestMapping mapping, HttpRequest request, HttpResponse response) { if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { throw new HttpResultPayloadException(HttpResult.builder() .status(HttpStatus.FORBIDDEN) From a7912fa0d29296df0bbe7d651be75890cdb0dc14 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 00:37:10 +0800 Subject: [PATCH 30/62] fix(): remove credential & privateNetWork report --- .../org/apache/dubbo/config/CorsConfig.java | 20 ------ .../SpringMvcRequestMappingResolver.java | 17 ++--- .../rpc/protocol/tri/rest/RestConstants.java | 6 -- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 62 +------------------ .../protocol/tri/rest/cors/CorsProcessor.java | 11 ---- .../rpc/protocol/tri/rest/cors/CorsUtil.java | 13 ++-- .../protocol/tri/rest/cors/CorsMetaTest.java | 25 +------- .../tri/rest/cors/CorsProcessorTest.java | 7 +-- .../protocol/tri/rest/cors/CorsUtilTest.java | 20 ++---- 9 files changed, 22 insertions(+), 159 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java index 3c8eeec4d6e..603fff44114 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -29,10 +29,6 @@ public class CorsConfig implements Serializable { private String exposedHeaders; - private Boolean allowCredentials; - - private Boolean allowPrivateNetWork; - private Long maxAge; public String getAllowedOrigins() { @@ -67,14 +63,6 @@ public void setExposedHeaders(String exposedHeaders) { this.exposedHeaders = exposedHeaders; } - public Boolean getAllowCredentials() { - return allowCredentials; - } - - public void setAllowCredentials(Boolean allowCredentials) { - this.allowCredentials = allowCredentials; - } - public Long getMaxAge() { return maxAge; } @@ -82,12 +70,4 @@ public Long getMaxAge() { public void setMaxAge(Long maxAge) { this.maxAge = maxAge; } - - public Boolean getAllowPrivateNetWork() { - return allowPrivateNetWork; - } - - public void setAllowPrivateNetWork(Boolean allowPrivateNetWork) { - this.allowPrivateNetWork = allowPrivateNetWork; - } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 18abefce169..65f5ce3838a 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -70,7 +70,7 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { return builder(requestMapping, responseStatus) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) - .cors(createCorsMeta(crossOrigin)) + .cors(buildCorsMeta(crossOrigin)) .build(); } @@ -92,7 +92,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { .name(methodMeta.getMethod().getName()) .contextPath(serviceMeta.getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(createCorsMeta(crossOrigin)) + .cors(buildCorsMeta(crossOrigin)) .build(); } @@ -114,10 +114,10 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp .produce(requestMapping.getStringArray("produces")); } - private CorsMeta createCorsMeta(AnnotationMeta crossOrigin) { + private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { CorsMeta meta = new CorsMeta(); if (crossOrigin == null) { - return meta; + return null; } String[] allowedHeaders = crossOrigin.getStringArray("allowedHeaders"); meta.setAllowedHeaders(allowedHeaders != null ? Arrays.asList(allowedHeaders) : Collections.emptyList()); @@ -129,15 +129,6 @@ private CorsMeta createCorsMeta(AnnotationMeta crossOrigin) { meta.setExposedHeaders(exposedHeaders != null ? Arrays.asList(exposedHeaders) : Collections.emptyList()); String maxAge = crossOrigin.getString("maxAge"); meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); - String allowCredentials = crossOrigin.getString("allowCredentials"); - meta.setAllowCredentials(allowCredentials != null ? Boolean.valueOf(allowCredentials) : null); - // Because allowPrivateNetwork does not exist in some spring versions, we need to catch the exception - try { - String allowPrivateNetwork = crossOrigin.getString("allowPrivateNetwork"); - meta.setAllowPrivateNetwork(allowPrivateNetwork != null ? Boolean.valueOf(allowPrivateNetwork) : null); - } catch (IllegalArgumentException e) { - meta.setAllowPrivateNetwork(null); - } return meta; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 101e13390ac..94154f29a87 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -59,10 +59,6 @@ public final class RestConstants { public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; - public static final String HTTP = "http"; - public static final String HTTPS = "https"; - public static final String WS = "ws"; - public static final String WSS = "wss"; public static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network"; public static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network"; @@ -80,8 +76,6 @@ public final class RestConstants { public static final String ALLOWED_HEADERS = CORS_CONFIG_PREFIX + "allowed-headers"; public static final String EXPOSED_HEADERS = CORS_CONFIG_PREFIX + "exposed-headers"; public static final String MAX_AGE = CORS_CONFIG_PREFIX + "max-age"; - public static final String ALLOW_CREDENTIALS = CORS_CONFIG_PREFIX + "allow-credentials"; - public static final String ALLOW_PRIVATE_NETWORK = CORS_CONFIG_PREFIX + "allow-private-network"; private RestConstants() {} } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java index 3d2c7a847f2..842ce6ef347 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java @@ -72,12 +72,6 @@ public class CorsMeta { @Nullable private List exposedHeaders; - @Nullable - private Boolean allowCredentials; - - @Nullable - private Boolean allowPrivateNetwork; - @Nullable private Long maxAge; @@ -90,8 +84,6 @@ public CorsMeta(CorsMeta other) { this.resolvedMethods = other.resolvedMethods; this.allowedHeaders = other.allowedHeaders; this.exposedHeaders = other.exposedHeaders; - this.allowCredentials = other.allowCredentials; - this.allowPrivateNetwork = other.allowPrivateNetwork; this.maxAge = other.maxAge; } @@ -242,23 +234,6 @@ public void addExposedHeader(String exposedHeader) { this.exposedHeaders.add(exposedHeader); } - public void setAllowCredentials(@Nullable Boolean allowCredentials) { - this.allowCredentials = allowCredentials; - } - - @Nullable - public Boolean getAllowCredentials() { - return this.allowCredentials; - } - - public void setAllowPrivateNetwork(Boolean allowPrivateNetwork) { - this.allowPrivateNetwork = allowPrivateNetwork; - } - - public Boolean getAllowPrivateNetwork() { - return this.allowPrivateNetwork; - } - public void setMaxAge(@Nullable Long maxAge) { this.maxAge = maxAge; } @@ -283,38 +258,9 @@ public CorsMeta applyPermitDefaultValues() { if (this.maxAge == null) { this.maxAge = DEFAULT_MAX_AGE; } - if (this.allowCredentials == null) { - this.allowCredentials = false; - } - if (this.allowPrivateNetwork == null) { - this.allowPrivateNetwork = false; - } return this; } - public boolean validateAllowCredentials() { - // When allowCredentials is true, allowedOrigins cannot contain the special value \"*\" - // since that cannot be set on the \"Access-Control-Allow-Origin\" response header. - // To allow credentials to a set of origins, list them explicitly - // or consider using \"allowedOriginPatterns\" instead. - return this.allowCredentials != null - && this.allowCredentials.equals(Boolean.TRUE) - && this.allowedOrigins != null - && this.allowedOrigins.contains(ALL); - } - - public boolean validateAllowPrivateNetwork() { - - // When allowPrivateNetwork is true, allowedOrigins cannot contain the special value \"*\" - // as it is not recommended from a security perspective. - // To allow private network access to a set of origins, list them explicitly - // or consider using \"allowedOriginPatterns\" instead. - return this.allowPrivateNetwork != null - && this.allowPrivateNetwork.equals(Boolean.TRUE) - && this.allowedOrigins != null - && this.allowedOrigins.contains(ALL); - } - /** * Combines two lists of strings, with a priority on this * @param other other @@ -339,12 +285,6 @@ public static CorsMeta combine(@Nullable CorsMeta priority, @Nullable CorsMeta o config.setAllowedMethods(priority.combine(priority.getAllowedMethods(), other.getAllowedMethods())); config.setAllowedHeaders(priority.combine(priority.getAllowedHeaders(), other.getAllowedHeaders())); config.setExposedHeaders(priority.combine(priority.getExposedHeaders(), other.getExposedHeaders())); - if (priority.allowCredentials == null) { - config.setAllowCredentials(other.getAllowCredentials()); - } - if (priority.allowPrivateNetwork == null) { - config.setAllowPrivateNetwork(other.allowPrivateNetwork); - } if (priority.maxAge == null) { config.setMaxAge(other.maxAge); } @@ -420,7 +360,7 @@ public String checkOrigin(@Nullable String origin) { } private String allOriginAllowed() { - return (validateAllowCredentials() || validateAllowPrivateNetwork() ? null : ALL); + return ALL; } private boolean isOriginAllowed(String originToCheck) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index 66df5b934bc..141fb8b050c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -32,8 +32,6 @@ import java.util.Objects; import java.util.stream.Collectors; -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK; -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK; import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil.getPort; public class CorsProcessor { @@ -100,15 +98,6 @@ protected boolean handleInternal(HttpRequest request, HttpResponse response, Cor response.setHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS, config.getExposedHeaders()); } - if (Boolean.TRUE.equals(config.getAllowCredentials())) { - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); - } - - if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) - && Boolean.parseBoolean(request.header(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) { - response.setHeader(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.TRUE.toString()); - } - return true; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java index 0401007e9ad..41bcc0ce411 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java @@ -25,13 +25,18 @@ import java.util.stream.Collectors; public class CorsUtil { + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + public static final String WS = "ws"; + public static final String WSS = "wss"; + private CorsUtil() {} public static int getPort(String scheme, int port) { if (port == -1) { - if (RestConstants.HTTP.equals(scheme) || RestConstants.WS.equals(scheme)) { + if (HTTP.equals(scheme) || WS.equals(scheme)) { port = 80; - } else if (RestConstants.HTTPS.equals(scheme) || RestConstants.WSS.equals(scheme)) { + } else if (HTTPS.equals(scheme) || WSS.equals(scheme)) { port = 443; } } @@ -46,8 +51,6 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); String maxAge = config.getString(RestConstants.MAX_AGE); - String allowCredentials = config.getString(RestConstants.ALLOW_CREDENTIALS); - String allowPrivateNetwork = config.getString(RestConstants.ALLOW_PRIVATE_NETWORK); // Create a new CorsMeta object and set the properties. CorsMeta meta = new CorsMeta(); meta.setAllowedOrigins(parseList(allowOrigins)); @@ -55,8 +58,6 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { meta.setAllowedHeaders(parseList(allowHeaders)); meta.setExposedHeaders(parseList(exposeHeaders)); meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); - meta.setAllowCredentials(allowCredentials == null ? null : Boolean.valueOf(allowCredentials)); - meta.setAllowPrivateNetwork(allowPrivateNetwork == null ? null : Boolean.valueOf(allowPrivateNetwork)); // Return the CorsMeta object. return meta.applyPermitDefaultValues(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java index 55d2d2ed588..800a336f7a8 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java @@ -34,16 +34,12 @@ void setNullValues() { config.setAllowedHeaders(null); config.setAllowedMethods(null); config.setExposedHeaders(null); - config.setAllowCredentials(null); config.setMaxAge(null); - config.setAllowPrivateNetwork(null); Assertions.assertNull(config.getAllowedOrigins()); Assertions.assertNull(config.getAllowedOriginPatterns()); Assertions.assertNull(config.getAllowedHeaders()); Assertions.assertNull(config.getAllowedMethods()); Assertions.assertNull(config.getExposedHeaders()); - Assertions.assertNull(config.getAllowCredentials()); - Assertions.assertNull(config.getAllowPrivateNetwork()); Assertions.assertNull(config.getMaxAge()); } @@ -57,8 +53,7 @@ void setValues() { config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.addExposedHeader("*"); - config.setAllowCredentials(true); - config.setAllowPrivateNetwork(true); + config.setMaxAge(123L); Assertions.assertArrayEquals( new String[] {"*"}, config.getAllowedOrigins().toArray()); @@ -71,8 +66,6 @@ void setValues() { new String[] {"*"}, config.getAllowedMethods().toArray()); Assertions.assertArrayEquals( new String[] {"*"}, config.getExposedHeaders().toArray()); - Assertions.assertTrue(config.getAllowCredentials()); - Assertions.assertTrue(config.getAllowPrivateNetwork()); Assertions.assertEquals(123L, config.getMaxAge().longValue()); } @@ -96,8 +89,6 @@ void combineWithConfigWithNullProperties() { config.addExposedHeader("header3"); config.addAllowedMethod(HttpMethods.GET.name()); config.setMaxAge(123L); - config.setAllowCredentials(true); - config.setAllowPrivateNetwork(true); CorsMeta other = new CorsMeta(); config = CorsMeta.combine(config, other); // Assert the combined config @@ -115,8 +106,6 @@ void combineWithConfigWithNullProperties() { new String[] {HttpMethods.GET.name()}, config.getAllowedMethods().toArray()); Assertions.assertEquals(123L, config.getMaxAge().longValue()); - Assertions.assertTrue(config.getAllowCredentials()); - Assertions.assertTrue(config.getAllowPrivateNetwork()); } @Test @@ -348,8 +337,6 @@ void combine() { priority.addExposedHeader("header3"); priority.addAllowedMethod(HttpMethods.GET.name()); priority.setMaxAge(123L); - priority.setAllowCredentials(true); - priority.setAllowPrivateNetwork(true); // Create another config with some different values CorsMeta other = new CorsMeta(); @@ -359,8 +346,6 @@ void combine() { other.addExposedHeader("header4"); other.addAllowedMethod(HttpMethods.PUT.name()); other.setMaxAge(456L); - other.setAllowCredentials(false); - other.setAllowPrivateNetwork(false); // Combine the configs priority = CorsMeta.combine(priority, other); @@ -380,8 +365,7 @@ void combine() { new String[] {HttpMethods.GET.name(), HttpMethods.PUT.name()}, priority.getAllowedMethods().toArray()); Assertions.assertEquals(Long.valueOf(123L), priority.getMaxAge()); - Assertions.assertTrue(priority.getAllowCredentials()); - Assertions.assertTrue(priority.getAllowPrivateNetwork()); + Assertions.assertArrayEquals( new String[] {"http://*.domain1.com", "http://*.domain2.com"}, priority.getAllowedOriginPatterns().toArray()); @@ -395,7 +379,7 @@ void checkOriginAllowed() { Assertions.assertEquals("*", config.checkOrigin("https://domain.com")); // "*" does not match together with allowCredentials - config.setAllowCredentials(true); + Assertions.assertNull(config.checkOrigin("https://domain.com")); // specific origin matches Origin header with or without trailing "/" @@ -408,7 +392,6 @@ void checkOriginAllowed() { Assertions.assertEquals("https://domain.com", config.checkOrigin("https://domain.com")); Assertions.assertEquals("https://domain.com/", config.checkOrigin("https://domain.com/")); - config.setAllowCredentials(false); Assertions.assertEquals("https://domain.com", config.checkOrigin("https://domain.com")); } @@ -436,7 +419,6 @@ void checkOriginPatternAllowed() { config.applyPermitDefaultValues(); Assertions.assertEquals("*", config.checkOrigin("https://domain.com")); - config.setAllowCredentials(true); Assertions.assertNull(config.checkOrigin("https://domain.com")); config.addAllowedOriginPattern("https://*.domain.com"); Assertions.assertEquals("https://example.domain.com", config.checkOrigin("https://example.domain.com")); @@ -454,7 +436,6 @@ void checkOriginPatternAllowed() { "https://example.specific.port.com:8081", config.checkOrigin("https://example.specific.port.com:8081")); Assertions.assertNull(config.checkOrigin("https://example.specific.port.com:1234")); - config.setAllowCredentials(false); Assertions.assertEquals("https://example.domain.com", config.checkOrigin("https://example.domain.com")); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index 7fadfb250f1..a38a0883c2a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -132,7 +132,6 @@ void actualRequestCredentials() { this.conf.addAllowedOrigin("https://domain1.com"); this.conf.addAllowedOrigin("https://domain2.com"); this.conf.addAllowedOrigin("http://domain3.example"); - this.conf.setAllowCredentials(true); this.processor.process(this.conf, this.request, this.response); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); @@ -153,7 +152,7 @@ void actualRequestCredentialsWithWildcardOrigin() { Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); this.conf.addAllowedOrigin("*"); - this.conf.setAllowCredentials(true); + Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); this.response = new DefaultHttpResponse(); @@ -369,7 +368,6 @@ void preflightRequestCredentials() { this.conf.addAllowedOrigin("https://domain2.com"); this.conf.addAllowedOrigin("http://domain3.example"); this.conf.addAllowedHeader("Header1"); - this.conf.setAllowCredentials(true); this.processor.process(this.conf, this.request, this.response); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); @@ -394,7 +392,6 @@ void preflightRequestCredentialsWithWildcardOrigin() { .thenReturn("Header1"); this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example")); this.conf.addAllowedHeader("Header1"); - this.conf.setAllowCredentials(true); Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); @@ -426,7 +423,6 @@ void preflightRequestPrivateNetworkWithWildcardOrigin() { .thenReturn("true"); this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example")); this.conf.addAllowedHeader("Header1"); - this.conf.setAllowPrivateNetwork(true); Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); @@ -603,7 +599,6 @@ void preflightRequestWithAccessControlRequestPrivateNetworkAllowed() { .thenReturn("true"); this.conf.addAllowedHeader("*"); this.conf.addAllowedOrigin("https://domain2.com"); - this.conf.setAllowPrivateNetwork(true); this.processor.process(this.conf, this.request, this.response); Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java index 635c23af9a0..6113f8e4913 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java @@ -35,8 +35,6 @@ void testResolveGlobalMetaInCommon() { Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn("Content-Type,Authorization"); Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); - Mockito.when(config.getString(RestConstants.ALLOW_CREDENTIALS)).thenReturn("true"); - Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn("true"); Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) .thenReturn("true"); CorsMeta meta = CorsUtil.resolveGlobalMeta(config); @@ -50,8 +48,6 @@ void testResolveGlobalMetaInCommon() { Assertions.assertTrue(meta.getExposedHeaders().contains("Content-Type")); Assertions.assertTrue(meta.getExposedHeaders().contains("Authorization")); Assertions.assertEquals(3600, meta.getMaxAge()); - Assertions.assertTrue(meta.getAllowCredentials()); - Assertions.assertTrue(meta.getAllowPrivateNetwork()); } @Test @@ -62,13 +58,9 @@ void testResolveGlobalMetaWithNullConfig() { Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn(null); Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn(null); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); - Mockito.when(config.getString(RestConstants.ALLOW_CREDENTIALS)).thenReturn(null); - Mockito.when(config.getString(RestConstants.ALLOW_PRIVATE_NETWORK)).thenReturn(null); CorsMeta meta = CorsUtil.resolveGlobalMeta(config); Assertions.assertEquals(CorsMeta.DEFAULT_MAX_AGE, meta.getMaxAge()); - Assertions.assertFalse(meta.getAllowCredentials()); - Assertions.assertFalse(meta.getAllowPrivateNetwork()); Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedOrigins()); Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_METHODS, meta.getAllowedMethods()); Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedHeaders()); @@ -76,15 +68,15 @@ void testResolveGlobalMetaWithNullConfig() { @Test void testGetPortWithDefaultValues() { - Assertions.assertEquals(80, CorsUtil.getPort(RestConstants.HTTP, -1)); - Assertions.assertEquals(80, CorsUtil.getPort(RestConstants.WS, -1)); - Assertions.assertEquals(443, CorsUtil.getPort(RestConstants.HTTPS, -1)); - Assertions.assertEquals(443, CorsUtil.getPort(RestConstants.WSS, -1)); + Assertions.assertEquals(80, CorsUtil.getPort(CorsUtil.HTTP, -1)); + Assertions.assertEquals(80, CorsUtil.getPort(CorsUtil.WS, -1)); + Assertions.assertEquals(443, CorsUtil.getPort(CorsUtil.HTTPS, -1)); + Assertions.assertEquals(443, CorsUtil.getPort(CorsUtil.WSS, -1)); } @Test void testGetPortWithCustomValues() { - Assertions.assertEquals(8080, CorsUtil.getPort(RestConstants.HTTP, 8080)); - Assertions.assertEquals(8443, CorsUtil.getPort(RestConstants.HTTPS, 8443)); + Assertions.assertEquals(8080, CorsUtil.getPort(CorsUtil.HTTP, 8080)); + Assertions.assertEquals(8443, CorsUtil.getPort(CorsUtil.HTTPS, 8443)); } } From 6f4b817ef9d646c3583cee9b631c504a42aabada Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 01:02:11 +0800 Subject: [PATCH 31/62] refactor(): Move globalMetaMerge in RequestMappingResolver --- .../jaxrs/JaxrsRequestMappingResolver.java | 2 ++ .../SpringMvcRequestMappingResolver.java | 10 +++++++- .../protocol/tri/rest/cors/CorsProcessor.java | 2 +- .../cors/{CorsUtil.java => CorsUtils.java} | 18 ++++++++++++-- .../DefaultRequestMappingRegistry.java | 24 +++---------------- .../{CorsUtilTest.java => CorsUtilsTest.java} | 18 +++++++------- 6 files changed, 40 insertions(+), 34 deletions(-) rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/{CorsUtil.java => CorsUtils.java} (82%) rename dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/{CorsUtilTest.java => CorsUtilsTest.java} (86%) diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index 9d7dc5a1432..b446966b4c1 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -18,6 +18,7 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; @@ -69,6 +70,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .cors(CorsUtils.getGlobalCorsMeta()) .build(); } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 65f5ce3838a..3bd86398eda 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -20,6 +20,7 @@ import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; @@ -70,7 +71,7 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { return builder(requestMapping, responseStatus) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) - .cors(buildCorsMeta(crossOrigin)) + .cors(buildCorsMetaWithGlobal(crossOrigin)) .build(); } @@ -131,4 +132,11 @@ private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); return meta; } + private CorsMeta buildCorsMetaWithGlobal(AnnotationMeta crossOrigin) { + CorsMeta corsMeta = buildCorsMeta(crossOrigin); + if (corsMeta != null) { + return CorsMeta.combine(corsMeta, CorsUtils.getGlobalCorsMeta()); + } + return CorsUtils.getGlobalCorsMeta(); + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java index 141fb8b050c..3a49b422999 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java @@ -32,7 +32,7 @@ import java.util.Objects; import java.util.stream.Collectors; -import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil.getPort; +import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils.getPort; public class CorsProcessor { private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(CorsProcessor.class); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java similarity index 82% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 41bcc0ce411..041f5619f96 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -17,20 +17,25 @@ package org.apache.dubbo.rpc.protocol.tri.rest.cors; import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.lang.Nullable; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -public class CorsUtil { +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; + +public class CorsUtils { + private static CorsMeta globalCorsMeta; public static final String HTTP = "http"; public static final String HTTPS = "https"; public static final String WS = "ws"; public static final String WSS = "wss"; - private CorsUtil() {} + private CorsUtils() {} public static int getPort(String scheme, int port) { if (port == -1) { @@ -62,6 +67,15 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { return meta.applyPermitDefaultValues(); } + public static CorsMeta getGlobalCorsMeta() { + if (globalCorsMeta == null) { + Configuration globalConfiguration = + ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); + globalCorsMeta = resolveGlobalMeta(globalConfiguration); + } + return globalCorsMeta; + } + @Nullable private static List parseList(@Nullable String value) { if (value == null) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 217c01827b7..f4376cc65db 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -35,7 +35,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtil; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; @@ -62,7 +62,6 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist private final RadixTree tree = new RadixTree<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private CorsMeta globalCorsMeta; private final CorsProcessor corsProcessor; public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { @@ -81,7 +80,6 @@ public void register(Invoker invoker) { continue; } RequestMapping classMapping = resolver.resolve(serviceMeta); - mergeGlobalCorsMeta(classMapping); consumer.accept((methods) -> { MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); RequestMapping methodMapping = resolver.resolve(methodMeta); @@ -129,16 +127,7 @@ private HandlerMeta buildHandlerMeta(Invoker invoker, MethodMeta methodMeta) serviceDescriptor); } - private void mergeGlobalCorsMeta(RequestMapping mapping) { - if (mapping != null) { - CorsMeta corsMeta = mapping.getCorsMeta(); - if (corsMeta != null) { - mapping.setCorsMeta(CorsMeta.combine(corsMeta, getGlobalCorsMeta())); - } else { - mapping.setCorsMeta(getGlobalCorsMeta()); - } - } - } + @Override public void unregister(Invoker invoker) { @@ -244,14 +233,7 @@ private void processCors(RequestMapping mapping, HttpRequest request, HttpRespon } } - private CorsMeta getGlobalCorsMeta() { - if (globalCorsMeta == null) { - Configuration globalConfiguration = - ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); - globalCorsMeta = CorsUtil.resolveGlobalMeta(globalConfiguration); - } - return globalCorsMeta; - } + private static final class Registration { RequestMapping mapping; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java similarity index 86% rename from dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java rename to dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index 6113f8e4913..9246c5fa0dc 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -25,7 +25,7 @@ import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CORS_CONFIG_PREFIX; -class CorsUtilTest { +class CorsUtilsTest { @Test void testResolveGlobalMetaInCommon() { @@ -37,7 +37,7 @@ void testResolveGlobalMetaInCommon() { Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) .thenReturn("true"); - CorsMeta meta = CorsUtil.resolveGlobalMeta(config); + CorsMeta meta = CorsUtils.resolveGlobalMeta(config); Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); Assertions.assertTrue(meta.getAllowedMethods().contains("POST")); @@ -59,7 +59,7 @@ void testResolveGlobalMetaWithNullConfig() { Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn(null); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); - CorsMeta meta = CorsUtil.resolveGlobalMeta(config); + CorsMeta meta = CorsUtils.resolveGlobalMeta(config); Assertions.assertEquals(CorsMeta.DEFAULT_MAX_AGE, meta.getMaxAge()); Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedOrigins()); Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_METHODS, meta.getAllowedMethods()); @@ -68,15 +68,15 @@ void testResolveGlobalMetaWithNullConfig() { @Test void testGetPortWithDefaultValues() { - Assertions.assertEquals(80, CorsUtil.getPort(CorsUtil.HTTP, -1)); - Assertions.assertEquals(80, CorsUtil.getPort(CorsUtil.WS, -1)); - Assertions.assertEquals(443, CorsUtil.getPort(CorsUtil.HTTPS, -1)); - Assertions.assertEquals(443, CorsUtil.getPort(CorsUtil.WSS, -1)); + Assertions.assertEquals(80, CorsUtils.getPort(CorsUtils.HTTP, -1)); + Assertions.assertEquals(80, CorsUtils.getPort(CorsUtils.WS, -1)); + Assertions.assertEquals(443, CorsUtils.getPort(CorsUtils.HTTPS, -1)); + Assertions.assertEquals(443, CorsUtils.getPort(CorsUtils.WSS, -1)); } @Test void testGetPortWithCustomValues() { - Assertions.assertEquals(8080, CorsUtil.getPort(CorsUtil.HTTP, 8080)); - Assertions.assertEquals(8443, CorsUtil.getPort(CorsUtil.HTTPS, 8443)); + Assertions.assertEquals(8080, CorsUtils.getPort(CorsUtils.HTTP, 8080)); + Assertions.assertEquals(8443, CorsUtils.getPort(CorsUtils.HTTPS, 8443)); } } From 3cb3af72778ce7680c83c5ebc91eb811f80d4cf6 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 10:41:54 +0800 Subject: [PATCH 32/62] refactor(): use array replace corsConfig string --- .../org/apache/dubbo/config/CorsConfig.java | 24 ++++++------- .../SpringMvcRequestMappingResolver.java | 3 +- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 34 ++++++------------- .../DefaultRequestMappingRegistry.java | 9 ----- .../protocol/tri/rest/cors/CorsUtilsTest.java | 10 +++--- 5 files changed, 29 insertions(+), 51 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java index 603fff44114..06446861594 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -21,45 +21,45 @@ public class CorsConfig implements Serializable { private static final long serialVersionUID = 1L; - private String allowedOrigins; + private String[] allowedOrigins; - private String allowedMethods; + private String[] allowedMethods; - private String allowedHeaders; + private String[] allowedHeaders; - private String exposedHeaders; + private String[] exposedHeaders; private Long maxAge; - public String getAllowedOrigins() { + public String[] getAllowedOrigins() { return allowedOrigins; } - public void setAllowedOrigins(String allowedOrigins) { + public void setAllowedOrigins(String[] allowedOrigins) { this.allowedOrigins = allowedOrigins; } - public String getAllowedMethods() { + public String[] getAllowedMethods() { return allowedMethods; } - public void setAllowedMethods(String allowedMethods) { + public void setAllowedMethods(String[] allowedMethods) { this.allowedMethods = allowedMethods; } - public String getAllowedHeaders() { + public String[] getAllowedHeaders() { return allowedHeaders; } - public void setAllowedHeaders(String allowedHeaders) { + public void setAllowedHeaders(String[] allowedHeaders) { this.allowedHeaders = allowedHeaders; } - public String getExposedHeaders() { + public String[] getExposedHeaders() { return exposedHeaders; } - public void setExposedHeaders(String exposedHeaders) { + public void setExposedHeaders(String[] exposedHeaders) { this.exposedHeaders = exposedHeaders; } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 3bd86398eda..b8e481be496 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -132,10 +132,11 @@ private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); return meta; } + private CorsMeta buildCorsMetaWithGlobal(AnnotationMeta crossOrigin) { CorsMeta corsMeta = buildCorsMeta(crossOrigin); if (corsMeta != null) { - return CorsMeta.combine(corsMeta, CorsUtils.getGlobalCorsMeta()); + return CorsMeta.combine(corsMeta, CorsUtils.getGlobalCorsMeta()); } return CorsUtils.getGlobalCorsMeta(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 041f5619f96..5968247a865 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -18,15 +18,10 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; -import org.apache.dubbo.common.lang.Nullable; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; public class CorsUtils { private static CorsMeta globalCorsMeta; @@ -49,20 +44,19 @@ public static int getPort(String scheme, int port) { } public static CorsMeta resolveGlobalMeta(Configuration config) { - // Get the CORS configuration properties from the configuration object. - String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); - String allowMethods = config.getString(RestConstants.ALLOWED_METHODS); - String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); - String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); - String maxAge = config.getString(RestConstants.MAX_AGE); + String[] allowOrigins = config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null); + String[] allowMethods = config.convert(String[].class, RestConstants.ALLOWED_METHODS, null); + String[] allowHeaders = config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null); + String[] exposeHeaders = config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null); + Object maxAge = config.getProperty(RestConstants.MAX_AGE); // Create a new CorsMeta object and set the properties. CorsMeta meta = new CorsMeta(); - meta.setAllowedOrigins(parseList(allowOrigins)); - meta.setAllowedMethods(parseList(allowMethods)); - meta.setAllowedHeaders(parseList(allowHeaders)); - meta.setExposedHeaders(parseList(exposeHeaders)); - meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); + meta.setAllowedOrigins(Arrays.asList(allowOrigins)); + meta.setAllowedMethods(Arrays.asList(allowMethods)); + meta.setAllowedHeaders(Arrays.asList(allowHeaders)); + meta.setExposedHeaders(Arrays.asList(exposeHeaders)); + meta.setMaxAge(maxAge == null ? null : (Long) maxAge); // Return the CorsMeta object. return meta.applyPermitDefaultValues(); } @@ -75,12 +69,4 @@ public static CorsMeta getGlobalCorsMeta() { } return globalCorsMeta; } - - @Nullable - private static List parseList(@Nullable String value) { - if (value == null) { - return null; - } - return Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index f4376cc65db..cc6641754a0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -16,8 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; -import org.apache.dubbo.common.config.Configuration; -import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.utils.Assert; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; @@ -26,16 +24,13 @@ import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; @@ -127,8 +122,6 @@ private HandlerMeta buildHandlerMeta(Invoker invoker, MethodMeta methodMeta) serviceDescriptor); } - - @Override public void unregister(Invoker invoker) { lock.writeLock().lock(); @@ -233,8 +226,6 @@ private void processCors(RequestMapping mapping, HttpRequest request, HttpRespon } } - - private static final class Registration { RequestMapping mapping; HandlerMeta meta; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index 9246c5fa0dc..71d6cc2f795 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -30,11 +30,11 @@ class CorsUtilsTest { @Test void testResolveGlobalMetaInCommon() { Configuration config = Mockito.mock(Configuration.class); - Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn("http://localhost:8080"); - Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn("GET,POST,PUT,DELETE"); - Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn("Content-Type,Authorization"); - Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); - Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); + Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null)).thenReturn(new String[]{"http://localhost:8080"}); + Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_METHODS, null)).thenReturn(new String[]{"GET,POST,PUT,DELETE"}); + Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null)).thenReturn(new String[]{"Content-Type,Authorization"}); + Mockito.when(config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null)).thenReturn(new String[]{"Content-Type,Authorization"}); + Mockito.when(config.getProperty(RestConstants.MAX_AGE)).thenReturn(3600); Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) .thenReturn("true"); CorsMeta meta = CorsUtils.resolveGlobalMeta(config); From 4eb05467b1c9602eee58a1cb3c1697bf16ec2509 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 13:01:06 +0800 Subject: [PATCH 33/62] refactor(): move CorsProcessor to CorsHeaderFilterAdapter --- .../rest/cors/CorsHeaderFilterAdapter.java | 60 +++++++++++++++++++ .../rest/filter/RestHeaderFilterAdapter.java | 4 +- .../DefaultRequestMappingRegistry.java | 18 ------ .../tri/route/DefaultRequestRouter.java | 1 + .../internal/org.apache.dubbo.rpc.Filter | 3 +- .../protocol/tri/rest/cors/CorsUtilsTest.java | 12 ++-- 6 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java new file mode 100644 index 00000000000..293705a05d7 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; + +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpResult; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; + +@Activate(group = CommonConstants.PROVIDER, order = 1000) +public class CorsHeaderFilterAdapter extends RestHeaderFilterAdapter { + private CorsProcessor corsProcessor; + + public CorsHeaderFilterAdapter(FrameworkModel frameworkModel) { + corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); + } + + @Override + protected RpcInvocation invoke( + Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) + throws RpcException { + RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); + processCors(mapping, request, response); + return invocation; + } + + private void processCors(RequestMapping mapping, HttpRequest request, HttpResponse response) { + if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body(response.body()) + .headers(response.headers()) + .build()); + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java index abbf4a7a5ab..ebc08e6540b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java @@ -19,7 +19,6 @@ import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.HeaderFilter; -import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.RpcInvocation; @@ -38,5 +37,6 @@ public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws } protected abstract RpcInvocation invoke( - Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) throws RpcException; + Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) + throws RpcException; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index cc6641754a0..ea1d707caec 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -19,9 +19,6 @@ import org.apache.dubbo.common.utils.Assert; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.HttpResult; -import org.apache.dubbo.remoting.http12.HttpStatus; -import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -30,7 +27,6 @@ import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; @@ -57,11 +53,9 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist private final RadixTree tree = new RadixTree<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final CorsProcessor corsProcessor; public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); - corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); } @Override @@ -198,8 +192,6 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { Candidate winner = candidates.get(0); RequestMapping mapping = winner.mapping; - processCors(mapping, request, response); - HandlerMeta handler = winner.meta; request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler); @@ -216,16 +208,6 @@ public HandlerMeta lookup(HttpRequest request, HttpResponse response) { return handler; } - private void processCors(RequestMapping mapping, HttpRequest request, HttpResponse response) { - if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { - throw new HttpResultPayloadException(HttpResult.builder() - .status(HttpStatus.FORBIDDEN) - .body(response.body()) - .headers(response.headers()) - .build()); - } - } - private static final class Registration { RequestMapping mapping; HandlerMeta meta; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java index f93c667a336..8e91375f8a2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java @@ -52,6 +52,7 @@ public RpcInvocationBuildContext route(URL url, RequestMetadata metadata, HttpCh continue; } handler.setAttribute(TripleConstant.HANDLER_TYPE_KEY, mapping.getType()); + ; handler.setAttribute(TripleConstant.HTTP_REQUEST_KEY, request); handler.setAttribute(TripleConstant.HTTP_RESPONSE_KEY, response); return handler; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter index 204897e33ff..0071d4dcc9b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter @@ -1,2 +1,3 @@ http-context=org.apache.dubbo.rpc.protocol.tri.HttpContextFilter -rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter \ No newline at end of file +rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter +rest-cors-extension=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilterAdapter diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index 71d6cc2f795..b1caba7f4af 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -30,10 +30,14 @@ class CorsUtilsTest { @Test void testResolveGlobalMetaInCommon() { Configuration config = Mockito.mock(Configuration.class); - Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null)).thenReturn(new String[]{"http://localhost:8080"}); - Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_METHODS, null)).thenReturn(new String[]{"GET,POST,PUT,DELETE"}); - Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null)).thenReturn(new String[]{"Content-Type,Authorization"}); - Mockito.when(config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null)).thenReturn(new String[]{"Content-Type,Authorization"}); + Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null)) + .thenReturn(new String[] {"http://localhost:8080"}); + Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_METHODS, null)) + .thenReturn(new String[] {"GET,POST,PUT,DELETE"}); + Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null)) + .thenReturn(new String[] {"Content-Type,Authorization"}); + Mockito.when(config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null)) + .thenReturn(new String[] {"Content-Type,Authorization"}); Mockito.when(config.getProperty(RestConstants.MAX_AGE)).thenReturn(3600); Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) .thenReturn("true"); From ff60f3dea31a20e78732c1cbfe71d9b3e5a12f77 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 13:25:04 +0800 Subject: [PATCH 34/62] fix(): fix unit test --- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 8 +- .../protocol/tri/rest/cors/CorsMetaTest.java | 5 - .../tri/rest/cors/CorsProcessorTest.java | 176 ------------------ .../protocol/tri/rest/cors/CorsUtilsTest.java | 8 +- 4 files changed, 8 insertions(+), 189 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 5968247a865..60282208663 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -52,10 +52,10 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { Object maxAge = config.getProperty(RestConstants.MAX_AGE); // Create a new CorsMeta object and set the properties. CorsMeta meta = new CorsMeta(); - meta.setAllowedOrigins(Arrays.asList(allowOrigins)); - meta.setAllowedMethods(Arrays.asList(allowMethods)); - meta.setAllowedHeaders(Arrays.asList(allowHeaders)); - meta.setExposedHeaders(Arrays.asList(exposeHeaders)); + meta.setAllowedOrigins(allowOrigins == null?null:Arrays.asList(allowOrigins)); + meta.setAllowedMethods(allowMethods == null?null:Arrays.asList(allowMethods)); + meta.setAllowedHeaders(allowHeaders == null?null:Arrays.asList(allowHeaders)); + meta.setExposedHeaders(exposeHeaders == null?null:Arrays.asList(exposeHeaders)); meta.setMaxAge(maxAge == null ? null : (Long) maxAge); // Return the CorsMeta object. return meta.applyPermitDefaultValues(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java index 800a336f7a8..1e5e1453e76 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMetaTest.java @@ -378,10 +378,6 @@ void checkOriginAllowed() { config.addAllowedOrigin("*"); Assertions.assertEquals("*", config.checkOrigin("https://domain.com")); - // "*" does not match together with allowCredentials - - Assertions.assertNull(config.checkOrigin("https://domain.com")); - // specific origin matches Origin header with or without trailing "/" config.setAllowedOrigins(Collections.singletonList("https://domain.com")); Assertions.assertEquals("https://domain.com", config.checkOrigin("https://domain.com")); @@ -419,7 +415,6 @@ void checkOriginPatternAllowed() { config.applyPermitDefaultValues(); Assertions.assertEquals("*", config.checkOrigin("https://domain.com")); - Assertions.assertNull(config.checkOrigin("https://domain.com")); config.addAllowedOriginPattern("https://*.domain.com"); Assertions.assertEquals("https://example.domain.com", config.checkOrigin("https://example.domain.com")); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index a38a0883c2a..a2dbbaa3da1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -125,53 +125,6 @@ void actualRequestWithOriginHeaderAndAllowedOrigin() { Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } - @Test - void actualRequestCredentials() { - Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - this.conf.addAllowedOrigin("https://domain1.com"); - this.conf.addAllowedOrigin("https://domain2.com"); - this.conf.addAllowedOrigin("http://domain3.example"); - - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } - - @Test - void actualRequestCredentialsWithWildcardOrigin() { - Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - - this.conf.addAllowedOrigin("*"); - - Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); - - this.response = new DefaultHttpResponse(); - this.response.setStatus(HttpStatus.OK.getCode()); - this.conf.setAllowedOrigins(null); - this.conf.addAllowedOriginPattern("*"); - - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } @Test void actualRequestCaseInsensitiveOriginMatch() { @@ -356,92 +309,6 @@ void preflightRequestValidRequestAndConfig() { Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } - @Test - void preflightRequestCredentials() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) - .thenReturn("Header1"); - this.conf.addAllowedOrigin("https://domain1.com"); - this.conf.addAllowedOrigin("https://domain2.com"); - this.conf.addAllowedOrigin("http://domain3.example"); - this.conf.addAllowedHeader("Header1"); - - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertEquals("true", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_CREDENTIALS)); - Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } - - @Test - void preflightRequestCredentialsWithWildcardOrigin() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) - .thenReturn("Header1"); - this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example")); - this.conf.addAllowedHeader("Header1"); - - Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); - - this.response = new DefaultHttpResponse(); - this.response.setStatus(HttpStatus.OK.getCode()); - this.conf.setAllowedOrigins(null); - this.conf.addAllowedOriginPattern("*"); - - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } - - @Test - void preflightRequestPrivateNetworkWithWildcardOrigin() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) - .thenReturn("Header1"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) - .thenReturn("true"); - this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example")); - this.conf.addAllowedHeader("Header1"); - - Assertions.assertFalse(this.processor.process(this.conf, this.request, this.response)); - - this.response = new DefaultHttpResponse(); - this.response.setStatus(HttpStatus.OK.getCode()); - this.conf.setAllowedOrigins(null); - this.conf.addAllowedOriginPattern("*"); - - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); - Assertions.assertEquals("https://domain2.com", this.response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.header(RestConstants.VARY).contains(RestConstants.ORIGIN)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } @Test void preflightRequestAllowedHeaders() { @@ -557,52 +424,9 @@ void preventDuplicatedVaryHeaders() { this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); } - @Test - void preflightRequestWithoutAccessControlRequestPrivateNetwork() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - this.conf.addAllowedHeader("*"); - this.conf.addAllowedOrigin("https://domain2.com"); - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } - @Test - void preflightRequestWithAccessControlRequestPrivateNetworkNotAllowed() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) - .thenReturn("true"); - this.conf.addAllowedHeader("*"); - this.conf.addAllowedOrigin("https://domain2.com"); - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertFalse(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } - @Test - void preflightRequestWithAccessControlRequestPrivateNetworkAllowed() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(RestConstants.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - Mockito.when(request.header(RestConstants.ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) - .thenReturn("true"); - this.conf.addAllowedHeader("*"); - this.conf.addAllowedOrigin("https://domain2.com"); - this.processor.process(this.conf, this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index b1caba7f4af..6f094c3238a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -33,12 +33,12 @@ void testResolveGlobalMetaInCommon() { Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null)) .thenReturn(new String[] {"http://localhost:8080"}); Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_METHODS, null)) - .thenReturn(new String[] {"GET,POST,PUT,DELETE"}); + .thenReturn(new String[] {"GET","POST","PUT","DELETE"}); Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null)) - .thenReturn(new String[] {"Content-Type,Authorization"}); + .thenReturn(new String[] {"Content-Type","Authorization"}); Mockito.when(config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null)) - .thenReturn(new String[] {"Content-Type,Authorization"}); - Mockito.when(config.getProperty(RestConstants.MAX_AGE)).thenReturn(3600); + .thenReturn(new String[] {"Content-Type","Authorization"}); + Mockito.when(config.getProperty(RestConstants.MAX_AGE)).thenReturn(Long.valueOf(3600)); Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) .thenReturn("true"); CorsMeta meta = CorsUtils.resolveGlobalMeta(config); From 1cfab7fd372283ee359dd505f9459683e9f929ad Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 17:21:06 +0800 Subject: [PATCH 35/62] fix(): fix test failure --- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 32 +++++++++++++------ .../CorsHeaderFilterAdapter.java | 4 +-- .../internal/org.apache.dubbo.rpc.Filter | 1 - .../org.apache.dubbo.rpc.HeaderFilter | 1 + .../tri/rest/cors/CorsProcessorTest.java | 8 ----- .../protocol/tri/rest/cors/CorsUtilsTest.java | 19 ++++------- 6 files changed, 31 insertions(+), 34 deletions(-) rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/{cors => filter}/CorsHeaderFilterAdapter.java (95%) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 60282208663..0abc24dca4d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -18,10 +18,13 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.common.lang.Nullable; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; public class CorsUtils { private static CorsMeta globalCorsMeta; @@ -44,23 +47,32 @@ public static int getPort(String scheme, int port) { } public static CorsMeta resolveGlobalMeta(Configuration config) { + // Get the CORS configuration properties from the configuration object. - String[] allowOrigins = config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null); - String[] allowMethods = config.convert(String[].class, RestConstants.ALLOWED_METHODS, null); - String[] allowHeaders = config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null); - String[] exposeHeaders = config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null); - Object maxAge = config.getProperty(RestConstants.MAX_AGE); + String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); + String allowMethods = config.getString(RestConstants.ALLOWED_METHODS); + String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); + String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); + String maxAge = config.getString(RestConstants.MAX_AGE); // Create a new CorsMeta object and set the properties. CorsMeta meta = new CorsMeta(); - meta.setAllowedOrigins(allowOrigins == null?null:Arrays.asList(allowOrigins)); - meta.setAllowedMethods(allowMethods == null?null:Arrays.asList(allowMethods)); - meta.setAllowedHeaders(allowHeaders == null?null:Arrays.asList(allowHeaders)); - meta.setExposedHeaders(exposeHeaders == null?null:Arrays.asList(exposeHeaders)); - meta.setMaxAge(maxAge == null ? null : (Long) maxAge); + meta.setAllowedOrigins(parseList(allowOrigins)); + meta.setAllowedMethods(parseList(allowMethods)); + meta.setAllowedHeaders(parseList(allowHeaders)); + meta.setExposedHeaders(parseList(exposeHeaders)); + meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); // Return the CorsMeta object. return meta.applyPermitDefaultValues(); } + @Nullable + private static List parseList(@Nullable String value) { + if (value == null) { + return null; + } + return Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); + } + public static CorsMeta getGlobalCorsMeta() { if (globalCorsMeta == null) { Configuration globalConfiguration = diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java similarity index 95% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java index 293705a05d7..d2ed7a06cda 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri.rest.cors; +package org.apache.dubbo.rpc.protocol.tri.rest.filter; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; @@ -28,7 +28,7 @@ import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; -import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; @Activate(group = CommonConstants.PROVIDER, order = 1000) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter index 0071d4dcc9b..8821eb7a155 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter @@ -1,3 +1,2 @@ http-context=org.apache.dubbo.rpc.protocol.tri.HttpContextFilter rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter -rest-cors-extension=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilterAdapter diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter new file mode 100644 index 00000000000..ad0dea4dc8c --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter @@ -0,0 +1 @@ +rest-cors-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.CorsHeaderFilterAdapter diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java index a2dbbaa3da1..8920b7ac6f9 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessorTest.java @@ -125,7 +125,6 @@ void actualRequestWithOriginHeaderAndAllowedOrigin() { Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } - @Test void actualRequestCaseInsensitiveOriginMatch() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); @@ -309,7 +308,6 @@ void preflightRequestValidRequestAndConfig() { Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } - @Test void preflightRequestAllowedHeaders() { Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); @@ -423,10 +421,4 @@ void preventDuplicatedVaryHeaders() { Assertions.assertTrue( this.response.header(RestConstants.VARY).contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)); } - - - - - - } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index 6f094c3238a..f55c3fcbc71 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -23,24 +23,17 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.CORS_CONFIG_PREFIX; - class CorsUtilsTest { @Test void testResolveGlobalMetaInCommon() { Configuration config = Mockito.mock(Configuration.class); - Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_ORIGINS, null)) - .thenReturn(new String[] {"http://localhost:8080"}); - Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_METHODS, null)) - .thenReturn(new String[] {"GET","POST","PUT","DELETE"}); - Mockito.when(config.convert(String[].class, RestConstants.ALLOWED_HEADERS, null)) - .thenReturn(new String[] {"Content-Type","Authorization"}); - Mockito.when(config.convert(String[].class, RestConstants.EXPOSED_HEADERS, null)) - .thenReturn(new String[] {"Content-Type","Authorization"}); - Mockito.when(config.getProperty(RestConstants.MAX_AGE)).thenReturn(Long.valueOf(3600)); - Mockito.when(config.getString(CORS_CONFIG_PREFIX + RestConstants.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)) - .thenReturn("true"); + Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn("http://localhost:8080"); + Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn("GET,POST,PUT,DELETE"); + Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn("Content-Type,Authorization"); + Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); + Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); + Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); CorsMeta meta = CorsUtils.resolveGlobalMeta(config); Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); From 4fcb1cc15e492baa413668abdcd4c1a617db5302 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 17:29:27 +0800 Subject: [PATCH 36/62] fix(): delete useless param --- .../tri/rest/mapping/DefaultRequestMappingRegistry.java | 3 +-- .../rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java | 3 +-- .../protocol/tri/rest/mapping/RestRequestHandlerMapping.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index ea1d707caec..eabef994a59 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -18,7 +18,6 @@ import org.apache.dubbo.common.utils.Assert; import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -136,7 +135,7 @@ public void destroy() { } } - public HandlerMeta lookup(HttpRequest request, HttpResponse response) { + public HandlerMeta lookup(HttpRequest request) { String path = PathUtils.normalize(request.rawPath()); request.setAttribute(RestConstants.PATH_ATTRIBUTE, path); List> matches = new ArrayList<>(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java index e3046bad41d..b7dfb559946 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java @@ -17,7 +17,6 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; @@ -30,7 +29,7 @@ public interface RequestMappingRegistry { void unregister(Invoker invoker); - HandlerMeta lookup(HttpRequest request, HttpResponse response); + HandlerMeta lookup(HttpRequest request); void destroy(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index 7b3b81b9303..b3ceaa155b3 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -61,7 +61,7 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { @Override public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { - HandlerMeta meta = requestMappingRegistry.lookup(request, response); + HandlerMeta meta = requestMappingRegistry.lookup(request); if (meta == null) { return null; } From 639ca849d2e431f96663baa2f1ee149a09e70463 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 24 Apr 2024 18:22:27 +0800 Subject: [PATCH 37/62] fix(): fix sonarcloud --- .../dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java index 8e91375f8a2..f93c667a336 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java @@ -52,7 +52,6 @@ public RpcInvocationBuildContext route(URL url, RequestMetadata metadata, HttpCh continue; } handler.setAttribute(TripleConstant.HANDLER_TYPE_KEY, mapping.getType()); - ; handler.setAttribute(TripleConstant.HTTP_REQUEST_KEY, request); handler.setAttribute(TripleConstant.HTTP_RESPONSE_KEY, response); return handler; From 73951bd5cac6d08897e2fb3fc282e8b13eff66ee Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 25 Apr 2024 15:22:59 +0800 Subject: [PATCH 38/62] fix(): fix wrong class place & naming --- .../CorsHeaderFilter.java} | 10 +++++----- .../dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java | 2 +- .../rest/mapping/DefaultRequestMappingRegistry.java | 3 ++- .../dubbo/internal/org.apache.dubbo.rpc.HeaderFilter | 2 +- .../rpc/protocol/tri/rest/cors/CorsUtilsTest.java | 8 ++++---- 5 files changed, 13 insertions(+), 12 deletions(-) rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/{filter/CorsHeaderFilterAdapter.java => cors/CorsHeaderFilter.java} (89%) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java similarity index 89% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index d2ed7a06cda..cfd10d5a334 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri.rest.filter; +package org.apache.dubbo.rpc.protocol.tri.rest.cors; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; @@ -28,14 +28,14 @@ import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; @Activate(group = CommonConstants.PROVIDER, order = 1000) -public class CorsHeaderFilterAdapter extends RestHeaderFilterAdapter { - private CorsProcessor corsProcessor; +public class CorsHeaderFilter extends RestHeaderFilterAdapter { + private final CorsProcessor corsProcessor; - public CorsHeaderFilterAdapter(FrameworkModel frameworkModel) { + public CorsHeaderFilter(FrameworkModel frameworkModel) { corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 0abc24dca4d..ee9db63f2d2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -62,7 +62,7 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { meta.setExposedHeaders(parseList(exposeHeaders)); meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); // Return the CorsMeta object. - return meta.applyPermitDefaultValues(); + return meta; } @Nullable diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index eabef994a59..407bf1c53b1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -61,7 +61,8 @@ public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { public void register(Invoker invoker) { Object service = invoker.getUrl().getServiceModel().getProxyObject(); new MethodWalker().walk(service.getClass(), (classes, consumer) -> { - for (RequestMappingResolver resolver : resolvers) { + for (int i = 0, size = resolvers.size(); i < size; i++) { + RequestMappingResolver resolver = resolvers.get(i); RestToolKit toolKit = resolver.getRestToolKit(); ServiceMeta serviceMeta = new ServiceMeta(classes, service, invoker.getUrl(), toolKit); if (!resolver.accept(serviceMeta)) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter index ad0dea4dc8c..d0ecc752d0f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter @@ -1 +1 @@ -rest-cors-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.CorsHeaderFilterAdapter +rest-cors=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilter diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index f55c3fcbc71..0b040bed5f1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -57,10 +57,10 @@ void testResolveGlobalMetaWithNullConfig() { Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); CorsMeta meta = CorsUtils.resolveGlobalMeta(config); - Assertions.assertEquals(CorsMeta.DEFAULT_MAX_AGE, meta.getMaxAge()); - Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedOrigins()); - Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_METHODS, meta.getAllowedMethods()); - Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedHeaders()); + Assertions.assertNull(meta.getMaxAge()); + Assertions.assertNull(meta.getAllowedOrigins()); + Assertions.assertNull(meta.getAllowedMethods()); + Assertions.assertNull(meta.getAllowedHeaders()); } @Test From 1326607d714d86b66e9afd85af80813bddd27537 Mon Sep 17 00:00:00 2001 From: liu Date: Thu, 25 Apr 2024 15:22:59 +0800 Subject: [PATCH 39/62] fix(): fix wrong static global corsMeta --- .../support/jaxrs/JaxrsRequestMappingResolver.java | 4 +++- .../spring/SpringMvcRequestMappingResolver.java | 6 ++++-- .../CorsHeaderFilter.java} | 10 +++++----- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 10 +++++----- .../mapping/DefaultRequestMappingRegistry.java | 3 ++- .../internal/org.apache.dubbo.rpc.HeaderFilter | 2 +- .../rpc/protocol/tri/rest/cors/CorsUtilsTest.java | 14 ++++++++------ 7 files changed, 28 insertions(+), 21 deletions(-) rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/{filter/CorsHeaderFilterAdapter.java => cors/CorsHeaderFilter.java} (89%) diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index b446966b4c1..0d414cea9a9 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -33,9 +33,11 @@ public class JaxrsRequestMappingResolver implements RequestMappingResolver { private final RestToolKit toolKit; + private final CorsUtils corsUtils; public JaxrsRequestMappingResolver(FrameworkModel frameworkModel) { toolKit = new JaxrsRestToolKit(frameworkModel); + corsUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CorsUtils.class); } @Override @@ -70,7 +72,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(CorsUtils.getGlobalCorsMeta()) + .cors(corsUtils.getGlobalCorsMeta()) .build(); } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index b8e481be496..33dfc2bc394 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -40,9 +40,11 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver { private final FrameworkModel frameworkModel; private volatile RestToolKit toolKit; + private final CorsUtils corsUtils; public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; + corsUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CorsUtils.class); } @Override @@ -136,8 +138,8 @@ private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { private CorsMeta buildCorsMetaWithGlobal(AnnotationMeta crossOrigin) { CorsMeta corsMeta = buildCorsMeta(crossOrigin); if (corsMeta != null) { - return CorsMeta.combine(corsMeta, CorsUtils.getGlobalCorsMeta()); + return CorsMeta.combine(corsMeta, corsUtils.getGlobalCorsMeta()); } - return CorsUtils.getGlobalCorsMeta(); + return corsUtils.getGlobalCorsMeta(); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java similarity index 89% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index d2ed7a06cda..cfd10d5a334 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/CorsHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri.rest.filter; +package org.apache.dubbo.rpc.protocol.tri.rest.cors; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; @@ -28,14 +28,14 @@ import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsProcessor; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; @Activate(group = CommonConstants.PROVIDER, order = 1000) -public class CorsHeaderFilterAdapter extends RestHeaderFilterAdapter { - private CorsProcessor corsProcessor; +public class CorsHeaderFilter extends RestHeaderFilterAdapter { + private final CorsProcessor corsProcessor; - public CorsHeaderFilterAdapter(FrameworkModel frameworkModel) { + public CorsHeaderFilter(FrameworkModel frameworkModel) { corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 0abc24dca4d..1972e1bd70c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -27,13 +27,13 @@ import java.util.stream.Collectors; public class CorsUtils { - private static CorsMeta globalCorsMeta; + private CorsMeta globalCorsMeta; public static final String HTTP = "http"; public static final String HTTPS = "https"; public static final String WS = "ws"; public static final String WSS = "wss"; - private CorsUtils() {} + public CorsUtils() {} public static int getPort(String scheme, int port) { if (port == -1) { @@ -46,7 +46,7 @@ public static int getPort(String scheme, int port) { return port; } - public static CorsMeta resolveGlobalMeta(Configuration config) { + public CorsMeta resolveGlobalMeta(Configuration config) { // Get the CORS configuration properties from the configuration object. String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); @@ -62,7 +62,7 @@ public static CorsMeta resolveGlobalMeta(Configuration config) { meta.setExposedHeaders(parseList(exposeHeaders)); meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); // Return the CorsMeta object. - return meta.applyPermitDefaultValues(); + return meta; } @Nullable @@ -73,7 +73,7 @@ private static List parseList(@Nullable String value) { return Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); } - public static CorsMeta getGlobalCorsMeta() { + public CorsMeta getGlobalCorsMeta() { if (globalCorsMeta == null) { Configuration globalConfiguration = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index eabef994a59..407bf1c53b1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -61,7 +61,8 @@ public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { public void register(Invoker invoker) { Object service = invoker.getUrl().getServiceModel().getProxyObject(); new MethodWalker().walk(service.getClass(), (classes, consumer) -> { - for (RequestMappingResolver resolver : resolvers) { + for (int i = 0, size = resolvers.size(); i < size; i++) { + RequestMappingResolver resolver = resolvers.get(i); RestToolKit toolKit = resolver.getRestToolKit(); ServiceMeta serviceMeta = new ServiceMeta(classes, service, invoker.getUrl(), toolKit); if (!resolver.accept(serviceMeta)) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter index ad0dea4dc8c..d0ecc752d0f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter @@ -1 +1 @@ -rest-cors-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.CorsHeaderFilterAdapter +rest-cors=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilter diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index f55c3fcbc71..5aafc258ca3 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -27,6 +27,7 @@ class CorsUtilsTest { @Test void testResolveGlobalMetaInCommon() { + CorsUtils utils = new CorsUtils(); Configuration config = Mockito.mock(Configuration.class); Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn("http://localhost:8080"); Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn("GET,POST,PUT,DELETE"); @@ -34,7 +35,7 @@ void testResolveGlobalMetaInCommon() { Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); - CorsMeta meta = CorsUtils.resolveGlobalMeta(config); + CorsMeta meta = utils.resolveGlobalMeta(config); Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); Assertions.assertTrue(meta.getAllowedMethods().contains("POST")); @@ -49,6 +50,7 @@ void testResolveGlobalMetaInCommon() { @Test void testResolveGlobalMetaWithNullConfig() { + CorsUtils utils = new CorsUtils(); Configuration config = Mockito.mock(Configuration.class); Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn(null); Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn(null); @@ -56,11 +58,11 @@ void testResolveGlobalMetaWithNullConfig() { Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn(null); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); - CorsMeta meta = CorsUtils.resolveGlobalMeta(config); - Assertions.assertEquals(CorsMeta.DEFAULT_MAX_AGE, meta.getMaxAge()); - Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedOrigins()); - Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_METHODS, meta.getAllowedMethods()); - Assertions.assertEquals(CorsMeta.DEFAULT_PERMIT_ALL, meta.getAllowedHeaders()); + CorsMeta meta = utils.resolveGlobalMeta(config); + Assertions.assertNull(meta.getMaxAge()); + Assertions.assertNull(meta.getAllowedOrigins()); + Assertions.assertNull(meta.getAllowedMethods()); + Assertions.assertNull(meta.getAllowedHeaders()); } @Test From 70f040496dfeb9f55ad942f32e65048aff28ba57 Mon Sep 17 00:00:00 2001 From: liu Date: Fri, 17 May 2024 23:07:09 +0800 Subject: [PATCH 40/62] fix(): refactor CorsUtil from sonar issue --- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 23 ++++++++----------- .../protocol/tri/rest/cors/CorsUtilsTest.java | 7 +++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 1972e1bd70c..a716caad384 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -33,7 +33,9 @@ public class CorsUtils { public static final String WS = "ws"; public static final String WSS = "wss"; - public CorsUtils() {} + public CorsUtils() { + globalCorsMeta = new CorsMeta(); + } public static int getPort(String scheme, int port) { if (port == -1) { @@ -46,23 +48,18 @@ public static int getPort(String scheme, int port) { return port; } - public CorsMeta resolveGlobalMeta(Configuration config) { - + public void resolveGlobalMeta(Configuration config) { // Get the CORS configuration properties from the configuration object. String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); String allowMethods = config.getString(RestConstants.ALLOWED_METHODS); String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); String maxAge = config.getString(RestConstants.MAX_AGE); - // Create a new CorsMeta object and set the properties. - CorsMeta meta = new CorsMeta(); - meta.setAllowedOrigins(parseList(allowOrigins)); - meta.setAllowedMethods(parseList(allowMethods)); - meta.setAllowedHeaders(parseList(allowHeaders)); - meta.setExposedHeaders(parseList(exposeHeaders)); - meta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); - // Return the CorsMeta object. - return meta; + globalCorsMeta.setAllowedOrigins(parseList(allowOrigins)); + globalCorsMeta.setAllowedMethods(parseList(allowMethods)); + globalCorsMeta.setAllowedHeaders(parseList(allowHeaders)); + globalCorsMeta.setExposedHeaders(parseList(exposeHeaders)); + globalCorsMeta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); } @Nullable @@ -77,7 +74,7 @@ public CorsMeta getGlobalCorsMeta() { if (globalCorsMeta == null) { Configuration globalConfiguration = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); - globalCorsMeta = resolveGlobalMeta(globalConfiguration); + resolveGlobalMeta(globalConfiguration); } return globalCorsMeta; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java index 5aafc258ca3..500a5290232 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java @@ -35,7 +35,8 @@ void testResolveGlobalMetaInCommon() { Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); - CorsMeta meta = utils.resolveGlobalMeta(config); + utils.resolveGlobalMeta(config); + CorsMeta meta = utils.getGlobalCorsMeta(); Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); Assertions.assertTrue(meta.getAllowedMethods().contains("POST")); @@ -57,8 +58,8 @@ void testResolveGlobalMetaWithNullConfig() { Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn(null); Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn(null); Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); - - CorsMeta meta = utils.resolveGlobalMeta(config); + utils.resolveGlobalMeta(config); + CorsMeta meta = utils.getGlobalCorsMeta(); Assertions.assertNull(meta.getMaxAge()); Assertions.assertNull(meta.getAllowedOrigins()); Assertions.assertNull(meta.getAllowedMethods()); From 854c9bb3d886d907a927f32962ffb475baa432cc Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Sat, 18 May 2024 11:19:37 +0800 Subject: [PATCH 41/62] feat(rest): refine cors support --- .../org/apache/dubbo/config/CorsConfig.java | 10 + .../jaxrs/JaxrsRequestMappingResolver.java | 7 +- .../SpringMvcRequestMappingResolver.java | 43 +- .../compatible/SpringMvcRestProtocolTest.java | 1 - .../dubbo/remoting/http12/HttpStatus.java | 1 + .../org/apache/dubbo/rpc/HeaderFilter.java | 2 +- .../dubbo/rpc/filter/TokenHeaderFilter.java | 3 +- .../rpc/protocol/tri/rest/RestConstants.java | 17 +- .../tri/rest/cors/CorsHeaderFilter.java | 226 +++++++- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 497 ------------------ .../protocol/tri/rest/cors/CorsProcessor.java | 176 ------- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 68 +-- .../rest/filter/RestHeaderFilterAdapter.java | 7 +- .../DefaultRequestMappingRegistry.java | 2 - .../tri/rest/mapping/RequestMapping.java | 77 ++- .../mapping/RestRequestHandlerMapping.java | 1 - .../internal/org.apache.dubbo.rpc.Filter | 2 +- .../org.apache.dubbo.rpc.HeaderFilter | 2 +- 18 files changed, 303 insertions(+), 839 deletions(-) delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java index 06446861594..deabbcc98bb 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -29,6 +29,8 @@ public class CorsConfig implements Serializable { private String[] exposedHeaders; + private Boolean allowCredentials; + private Long maxAge; public String[] getAllowedOrigins() { @@ -63,6 +65,14 @@ public void setExposedHeaders(String[] exposedHeaders) { this.exposedHeaders = exposedHeaders; } + public Boolean getAllowCredentials() { + return allowCredentials; + } + + public void setAllowCredentials(Boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } + public Long getMaxAge() { return maxAge; } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index 0d414cea9a9..bb20ccea154 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -25,6 +25,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationSupport; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; @@ -33,11 +34,11 @@ public class JaxrsRequestMappingResolver implements RequestMappingResolver { private final RestToolKit toolKit; - private final CorsUtils corsUtils; + private final CorsMeta globalCorsMeta; public JaxrsRequestMappingResolver(FrameworkModel frameworkModel) { toolKit = new JaxrsRestToolKit(frameworkModel); - corsUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CorsUtils.class); + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } @Override @@ -72,7 +73,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(corsUtils.getGlobalCorsMeta()) + .cors(globalCorsMeta) .build(); } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 33dfc2bc394..8250de680de 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -19,32 +19,29 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; -import java.util.Arrays; -import java.util.Collections; - import org.springframework.http.HttpStatus; @Activate(onClass = "org.springframework.web.bind.annotation.RequestMapping") public class SpringMvcRequestMappingResolver implements RequestMappingResolver { private final FrameworkModel frameworkModel; + private final CorsMeta globalCorsMeta; private volatile RestToolKit toolKit; - private final CorsUtils corsUtils; public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - corsUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CorsUtils.class); + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } @Override @@ -73,7 +70,7 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { return builder(requestMapping, responseStatus) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) - .cors(buildCorsMetaWithGlobal(crossOrigin)) + .cors(buildCorsMeta(crossOrigin)) .build(); } @@ -89,7 +86,6 @@ public RequestMapping resolve(MethodMeta methodMeta) { } ServiceMeta serviceMeta = methodMeta.getServiceMeta(); AnnotationMeta responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus); - AnnotationMeta crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin); return builder(requestMapping, responseStatus) .name(methodMeta.getMethod().getName()) @@ -118,28 +114,17 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp } private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { - CorsMeta meta = new CorsMeta(); if (crossOrigin == null) { - return null; + return globalCorsMeta; } - String[] allowedHeaders = crossOrigin.getStringArray("allowedHeaders"); - meta.setAllowedHeaders(allowedHeaders != null ? Arrays.asList(allowedHeaders) : Collections.emptyList()); - String[] methods = crossOrigin.getStringArray("methods"); - meta.setAllowedMethods(methods != null ? Arrays.asList(methods) : Collections.emptyList()); - String[] origins = crossOrigin.getStringArray("origins"); - meta.setAllowedOrigins(origins != null ? Arrays.asList(origins) : Collections.emptyList()); - String[] exposedHeaders = crossOrigin.getStringArray("exposedHeaders"); - meta.setExposedHeaders(exposedHeaders != null ? Arrays.asList(exposedHeaders) : Collections.emptyList()); - String maxAge = crossOrigin.getString("maxAge"); - meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); - return meta; - } - - private CorsMeta buildCorsMetaWithGlobal(AnnotationMeta crossOrigin) { - CorsMeta corsMeta = buildCorsMeta(crossOrigin); - if (corsMeta != null) { - return CorsMeta.combine(corsMeta, corsUtils.getGlobalCorsMeta()); - } - return corsUtils.getGlobalCorsMeta(); + CorsMeta corsMeta = CorsMeta.builder() + .allowedOrigins(crossOrigin.getStringArray("origins")) + .allowedMethods(crossOrigin.getStringArray("methods")) + .allowedHeaders(crossOrigin.getStringArray("allowedHeaders")) + .exposedHeaders(crossOrigin.getStringArray("exposedHeaders")) + .allowCredentials(crossOrigin.getBoolean("allowCredentials")) + .maxAge(crossOrigin.getNumber("maxAge")) + .build(); + return globalCorsMeta == null ? corsMeta : globalCorsMeta.combine(corsMeta); } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java index c7246ba9c4a..6e124fe2ae8 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java @@ -129,7 +129,6 @@ void testRestProtocolWithContextPath() { url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=" + SpringRestDemoService.class.getName()); Invoker invoker = protocol.refer(SpringRestDemoService.class, url); - SpringRestDemoService client = proxy.getProxy(invoker); String result = client.sayHello("haha"); Assertions.assertTrue(server.isCalled()); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java index f91b6280280..f28eb9422fb 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java @@ -20,6 +20,7 @@ public enum HttpStatus { OK(200), CREATED(201), ACCEPTED(202), + NO_CONTENT(204), FOUND(302), BAD_REQUEST(400), UNAUTHORIZED(401), diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java index 983c8c5d8b0..04dd983305d 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java @@ -22,5 +22,5 @@ @SPI(scope = ExtensionScope.FRAMEWORK) public interface HeaderFilter { - RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException; + void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException; } diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java index c8d8c6e1da9..3b6abb02d28 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java @@ -31,7 +31,7 @@ @Activate public class TokenHeaderFilter implements HeaderFilter { @Override - public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { String token = invoker.getUrl().getParameter(TOKEN_KEY); if (ConfigUtils.isNotEmpty(token)) { Class serviceType = invoker.getInterface(); @@ -46,6 +46,5 @@ public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws + ", consumer incorrect token is " + remoteToken); } } - return invocation; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 94154f29a87..7c512278bb5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -48,20 +48,6 @@ public final class RestConstants { public static final String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = "org.springframework.web.servlet.HandlerMapping.producibleMediaTypes"; - /* Cors Config */ - public static final String VARY = "Vary"; - public static final String ORIGIN = "Origin"; - public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; - public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; - public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; - public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; - public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; - public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; - public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; - public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; - public static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network"; - public static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network"; - /* Configuration Key */ public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size"; @@ -70,10 +56,13 @@ public final class RestConstants { public static final String TRAILING_SLASH_MATCH_KEY = CONFIG_PREFIX + "trailing-slash-match"; public static final String CASE_SENSITIVE_MATCH_KEY = CONFIG_PREFIX + "case-sensitive-match"; public static final String FORMAT_PARAMETER_NAME_KEY = CONFIG_PREFIX + "format-parameter-name"; + + /* Cors Configuration Key */ public static final String CORS_CONFIG_PREFIX = CONFIG_PREFIX + "cors."; public static final String ALLOWED_ORIGINS = CORS_CONFIG_PREFIX + "allowed-origins"; public static final String ALLOWED_METHODS = CORS_CONFIG_PREFIX + "allowed-methods"; public static final String ALLOWED_HEADERS = CORS_CONFIG_PREFIX + "allowed-headers"; + public static final String ALLOW_CREDENTIALS = CORS_CONFIG_PREFIX + "allow-credentials"; public static final String EXPOSED_HEADERS = CORS_CONFIG_PREFIX + "exposed-headers"; public static final String MAX_AGE = CORS_CONFIG_PREFIX + "max-age"; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index cfd10d5a334..6601c1030d8 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -18,6 +18,9 @@ import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.ArrayUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.remoting.http12.HttpResult; @@ -26,35 +29,232 @@ import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; + +/** + * See: DefaultCorsProcessor + */ @Activate(group = CommonConstants.PROVIDER, order = 1000) public class CorsHeaderFilter extends RestHeaderFilterAdapter { - private final CorsProcessor corsProcessor; - public CorsHeaderFilter(FrameworkModel frameworkModel) { - corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); - } + public static final String VARY = "Vary"; + public static final String ORIGIN = "Origin"; + public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; @Override - protected RpcInvocation invoke( - Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) + protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) throws RpcException { RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); - processCors(mapping, request, response); - return invocation; + CorsMeta cors = mapping.getCors(); + String origin = request.header(ORIGIN); + if (cors == null) { + if (isPreFlightRequest(request, origin)) { + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body("Invalid CORS request") + .build()); + } + return; + } + + if (process(cors, request, response)) { + return; + } + + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body("Invalid CORS request") + .headers(response.headers()) + .build()); } - private void processCors(RequestMapping mapping, HttpRequest request, HttpResponse response) { - if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { + private boolean process(CorsMeta cors, HttpRequest request, HttpResponse response) { + List varyHeaders = response.headerValues(VARY); + for (String header : new String[] {ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS}) { + if (varyHeaders == null || !varyHeaders.contains(header)) { + response.addHeader(VARY, header); + } + } + + String origin = request.header(ORIGIN); + if (isNotCorsRequest(request, origin)) { + return true; + } + + if (response.header(ACCESS_CONTROL_ALLOW_ORIGIN) != null) { + return true; + } + + String allowOrigin = checkOrigin(cors, origin); + if (allowOrigin == null) { + return false; + } + + boolean preFlight = isPreFlightRequest(request, origin); + + List allowMethods = + checkMethods(cors, preFlight ? request.header(ACCESS_CONTROL_REQUEST_METHOD) : request.method()); + if (allowMethods == null) { + return false; + } + + List allowHeaders = checkHeaders(cors, request.headerValues(ACCESS_CONTROL_REQUEST_HEADERS)); + if (preFlight && allowHeaders == null) { + return false; + } + + response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); + + if (ArrayUtils.isNotEmpty(cors.getExposedHeaders())) { + response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, StringUtils.join(cors.getExposedHeaders(), ",")); + } + + if (Boolean.TRUE.equals(cors.getAllowCredentials())) { + response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); + } + + if (preFlight) { + response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.join(allowMethods, ",")); + + if (!allowHeaders.isEmpty()) { + response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(allowHeaders, ",")); + } + if (cors.getMaxAge() != null) { + response.setHeader(ACCESS_CONTROL_MAX_AGE, cors.getMaxAge().toString()); + } throw new HttpResultPayloadException(HttpResult.builder() - .status(HttpStatus.FORBIDDEN) - .body(response.body()) + .status(HttpStatus.NO_CONTENT) .headers(response.headers()) .build()); } + + return true; + } + + private static String checkOrigin(CorsMeta cors, String origin) { + if (StringUtils.isBlank(origin)) { + return null; + } + origin = CorsUtils.formatOrigin(origin); + String[] allowedOrigins = cors.getAllowedOrigins(); + if (ArrayUtils.isNotEmpty(allowedOrigins)) { + if (ArrayUtils.contains(allowedOrigins, ANY_VALUE)) { + if (Boolean.TRUE.equals(cors.getAllowCredentials())) { + throw new IllegalArgumentException( + "When allowCredentials is true, allowedOrigins cannot contain the special value \"*\""); + } + return ANY_VALUE; + } + for (String allowedOrigin : allowedOrigins) { + if (origin.equalsIgnoreCase(allowedOrigin)) { + return origin; + } + } + } + if (ArrayUtils.isNotEmpty(cors.getAllowedOriginsPatterns())) { + for (Pattern pattern : cors.getAllowedOriginsPatterns()) { + if (pattern.matcher(origin).matches()) { + return origin; + } + } + } + return null; + } + + private static List checkMethods(CorsMeta cors, String method) { + if (method == null) { + return null; + } + String[] allowedMethods = cors.getAllowedMethods(); + if (ArrayUtils.contains(allowedMethods, ANY_VALUE)) { + return Collections.singletonList(method); + } + for (String allowedMethod : allowedMethods) { + if (method.equalsIgnoreCase(allowedMethod)) { + return Collections.singletonList(method); + } + } + return null; + } + + private static List checkHeaders(CorsMeta cors, Collection headers) { + if (headers == null) { + return null; + } + if (headers.isEmpty()) { + return Collections.emptyList(); + } + String[] allowedHeaders = cors.getAllowedHeaders(); + if (ArrayUtils.isEmpty(allowedHeaders)) { + return null; + } + + boolean allowAny = ArrayUtils.contains(allowedHeaders, ANY_VALUE); + List result = new ArrayList<>(headers.size()); + for (String header : headers) { + if (allowAny) { + result.add(header); + continue; + } + for (String allowedHeader : allowedHeaders) { + if (header.equalsIgnoreCase(allowedHeader)) { + result.add(header); + break; + } + } + } + return result.isEmpty() ? null : result; + } + + private static boolean isNotCorsRequest(HttpRequest request, String origin) { + if (origin == null) { + return true; + } + try { + URI uri = new URI(origin); + return request.scheme().equals(uri.getScheme()) + && request.serverName().equals(uri.getHost()) + && getPort(request.scheme(), request.serverPort()) == getPort(uri.getScheme(), uri.getPort()); + } catch (URISyntaxException e) { + return false; + } + } + + private static boolean isPreFlightRequest(HttpRequest request, String origin) { + return request.method().equals(HttpMethods.OPTIONS.name()) + && origin != null + && request.hasHeader(ACCESS_CONTROL_REQUEST_METHOD); + } + + private static int getPort(String scheme, int port) { + if (port == -1) { + if ("http".equals(scheme)) { + return 80; + } + if ("https".equals(scheme)) { + return 443; + } + } + return port; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java deleted file mode 100644 index 842ce6ef347..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; - -import org.apache.dubbo.common.lang.Nullable; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.remoting.http12.HttpMethods; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class CorsMeta { - - // Default value - - public static final String ALL = "*"; - - public static final List ALL_LIST = Collections.singletonList(ALL); - - private static final OriginPattern ALL_PATTERN = new OriginPattern("*"); - - private static final List ALL_PATTERN_LIST = Collections.singletonList(ALL_PATTERN); - - public static final List DEFAULT_PERMIT_ALL = Collections.singletonList(ALL); - - public static final List DEFAULT_METHODS = - Collections.unmodifiableList(Arrays.asList(HttpMethods.GET, HttpMethods.HEAD)); - - public static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( - Arrays.asList(HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name())); - - public static final Long DEFAULT_MAX_AGE = 1800L; - - @Nullable - private List allowedOrigins; - - @Nullable - private List allowedOriginPatterns; - - @Nullable - private List allowedMethods; - - @Nullable - private List resolvedMethods = DEFAULT_METHODS; - - @Nullable - private List allowedHeaders; - - @Nullable - private List exposedHeaders; - - @Nullable - private Long maxAge; - - public CorsMeta() {} - - public CorsMeta(CorsMeta other) { - this.allowedOrigins = other.allowedOrigins; - this.allowedOriginPatterns = other.allowedOriginPatterns; - this.allowedMethods = other.allowedMethods; - this.resolvedMethods = other.resolvedMethods; - this.allowedHeaders = other.allowedHeaders; - this.exposedHeaders = other.exposedHeaders; - this.maxAge = other.maxAge; - } - - public void setAllowedOrigins(@Nullable List origins) { - this.allowedOrigins = (origins == null - ? null - : origins.stream() - .filter(Objects::nonNull) - .map(this::trimTrailingSlash) - .collect(Collectors.toList())); - } - - private String trimTrailingSlash(String origin) { - return (origin.endsWith("/") ? origin.substring(0, origin.length() - 1) : origin); - } - - public List getAllowedOrigins() { - return this.allowedOrigins; - } - - public void addAllowedOrigin(@Nullable String origin) { - if (origin == null) { - return; - } - if (this.allowedOrigins == null) { - this.allowedOrigins = new ArrayList<>(4); - } else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { - setAllowedOrigins(DEFAULT_PERMIT_ALL); - } - origin = trimTrailingSlash(origin); - this.allowedOrigins.add(origin); - } - - public void setAllowedOriginPatterns(@Nullable List allowedOriginPatterns) { - if (allowedOriginPatterns == null) { - this.allowedOriginPatterns = null; - } else { - this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size()); - for (String patternValue : allowedOriginPatterns) { - addAllowedOriginPattern(patternValue); - } - } - } - - @Nullable - public List getAllowedOriginPatterns() { - if (this.allowedOriginPatterns == null) { - return null; - } - return this.allowedOriginPatterns.stream() - .map(OriginPattern::getDeclaredPattern) - .collect(Collectors.toList()); - } - - public void addAllowedOriginPattern(@Nullable String originPattern) { - if (originPattern == null) { - return; - } - if (this.allowedOriginPatterns == null) { - this.allowedOriginPatterns = new ArrayList<>(4); - } - originPattern = trimTrailingSlash(originPattern); - this.allowedOriginPatterns.add(new OriginPattern(originPattern)); - if (this.allowedOrigins == DEFAULT_PERMIT_ALL) { - this.allowedOrigins = null; - } - } - - public void setAllowedMethods(@Nullable List allowedMethods) { - if (allowedMethods != null) { - this.allowedMethods = new ArrayList<>(allowedMethods); - setResolvedMethods(allowedMethods); - } else { - this.allowedMethods = null; - } - } - - @Nullable - public void setResolvedMethods(List allowedMethods) { - if (!allowedMethods.isEmpty()) { - this.resolvedMethods = new ArrayList<>(allowedMethods.size()); - for (String method : allowedMethods) { - if (ALL.equals(method)) { - this.resolvedMethods = null; - break; - } - this.resolvedMethods.add(HttpMethods.valueOf(method)); - } - } else { - this.resolvedMethods = DEFAULT_METHODS; - } - } - - public List getAllowedMethods() { - return this.allowedMethods; - } - - public void addAllowedMethod(HttpMethods method) { - addAllowedMethod(method.name()); - } - - public void addAllowedMethod(String method) { - if (StringUtils.hasText(method)) { - if (this.allowedMethods == null) { - this.allowedMethods = new ArrayList<>(4); - this.resolvedMethods = new ArrayList<>(4); - } else if (this.allowedMethods == DEFAULT_PERMIT_METHODS) { - setAllowedMethods(DEFAULT_PERMIT_METHODS); - } - this.allowedMethods.add(method); - if (ALL.equals(method)) { - this.resolvedMethods = null; - } else if (this.resolvedMethods != null) { - this.resolvedMethods.add(HttpMethods.valueOf(method)); - } - } - } - - public void setAllowedHeaders(@Nullable List allowedHeaders) { - this.allowedHeaders = (allowedHeaders != null ? new ArrayList<>(allowedHeaders) : null); - } - - public List getAllowedHeaders() { - return this.allowedHeaders; - } - - public void addAllowedHeader(String allowedHeader) { - if (this.allowedHeaders == null) { - this.allowedHeaders = new ArrayList<>(4); - } else if (this.allowedHeaders == DEFAULT_PERMIT_ALL) { - setAllowedHeaders(DEFAULT_PERMIT_ALL); - } - this.allowedHeaders.add(allowedHeader); - } - - public void setExposedHeaders(@Nullable List exposedHeaders) { - this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null); - } - - public List getExposedHeaders() { - return this.exposedHeaders; - } - - public void addExposedHeader(String exposedHeader) { - if (this.exposedHeaders == null) { - this.exposedHeaders = new ArrayList<>(4); - } - this.exposedHeaders.add(exposedHeader); - } - - public void setMaxAge(@Nullable Long maxAge) { - this.maxAge = maxAge; - } - - @Nullable - public Long getMaxAge() { - return this.maxAge; - } - - public CorsMeta applyPermitDefaultValues() { - if (this.allowedOrigins == null && this.allowedOriginPatterns == null) { - this.allowedOrigins = DEFAULT_PERMIT_ALL; - } - if (this.allowedMethods == null) { - this.allowedMethods = DEFAULT_PERMIT_METHODS; - this.resolvedMethods = - DEFAULT_PERMIT_METHODS.stream().map(HttpMethods::valueOf).collect(Collectors.toList()); - } - if (this.allowedHeaders == null) { - this.allowedHeaders = DEFAULT_PERMIT_ALL; - } - if (this.maxAge == null) { - this.maxAge = DEFAULT_MAX_AGE; - } - return this; - } - - /** - * Combines two lists of strings, with a priority on this - * @param other other - * @return {@link CorsMeta} - */ - public static CorsMeta combine(@Nullable CorsMeta priority, @Nullable CorsMeta other) { - if (priority == null && other == null) { - return new CorsMeta(); - } - if (priority == null) { - return other; - } - if (other == null) { - return priority; - } - CorsMeta config = new CorsMeta(priority); - List origins = priority.combine(priority.getAllowedOrigins(), other.getAllowedOrigins()); - List patterns = - priority.combinePatterns(priority.allowedOriginPatterns, other.allowedOriginPatterns); - config.allowedOrigins = (origins == DEFAULT_PERMIT_ALL && !CollectionUtils.isEmpty(patterns) ? null : origins); - config.allowedOriginPatterns = patterns; - config.setAllowedMethods(priority.combine(priority.getAllowedMethods(), other.getAllowedMethods())); - config.setAllowedHeaders(priority.combine(priority.getAllowedHeaders(), other.getAllowedHeaders())); - config.setExposedHeaders(priority.combine(priority.getExposedHeaders(), other.getExposedHeaders())); - if (priority.maxAge == null) { - config.setMaxAge(other.maxAge); - } - return config; - } - - /** - * Combines two lists of strings, with a priority on the source list. - * - * @param source The primary list of strings. - * @param other The secondary list of strings to be combined with the source. - * @return A combined list of strings, with the source list taking priority in case of conflicts. - */ - private List combine(@Nullable List source, @Nullable List other) { - if (other == null) { - return (source != null ? source : Collections.emptyList()); - } - if (source == null) { - return other; - } - // save setting value at first - if (source == DEFAULT_PERMIT_ALL - || source == DEFAULT_PERMIT_METHODS - || other == DEFAULT_PERMIT_ALL - || other == DEFAULT_PERMIT_METHODS) { - return source; - } - - if (source.contains(ALL) || other.contains(ALL)) { - return ALL_LIST; - } - Set combined = new LinkedHashSet<>(source.size() + other.size()); - combined.addAll(source); - combined.addAll(other); - return new ArrayList<>(combined); - } - - private List combinePatterns( - @Nullable List source, @Nullable List other) { - - if (other == null) { - return (source != null ? source : Collections.emptyList()); - } - if (source == null) { - return other; - } - if (source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) { - return ALL_PATTERN_LIST; - } - Set combined = new LinkedHashSet<>(source.size() + other.size()); - combined.addAll(source); - combined.addAll(other); - return new ArrayList<>(combined); - } - - public String checkOrigin(@Nullable String origin) { - if (!StringUtils.hasText(origin)) { - return null; - } - String originToCheck = trimTrailingSlash(origin); - if (!CollectionUtils.isEmpty(this.allowedOrigins)) { - if (this.allowedOrigins.contains(ALL)) { - return allOriginAllowed(); - } - if (isOriginAllowed(originToCheck)) { - return origin; - } - } - if (isOriginPatternAllowed(originToCheck)) { - return origin; - } - return null; - } - - private String allOriginAllowed() { - return ALL; - } - - private boolean isOriginAllowed(String originToCheck) { - for (String allowedOrigin : this.allowedOrigins) { - if (originToCheck.equalsIgnoreCase(allowedOrigin)) { - return true; - } - } - return false; - } - - private boolean isOriginPatternAllowed(String originToCheck) { - if (!CollectionUtils.isEmpty(this.allowedOriginPatterns)) { - for (OriginPattern p : this.allowedOriginPatterns) { - if (p.getDeclaredPattern().equals(ALL) - || p.getPattern().matcher(originToCheck).matches()) { - return true; - } - } - } - return false; - } - - @Nullable - public List checkHttpMethods(@Nullable HttpMethods requestMethod) { - if (requestMethod == null) { - return null; - } - if (this.resolvedMethods == null) { - return Collections.singletonList(requestMethod); - } - return (this.resolvedMethods.contains(requestMethod) ? this.resolvedMethods : null); - } - - @Nullable - public List checkHeaders(@Nullable List requestHeaders) { - if (requestHeaders == null) { - return null; - } - if (requestHeaders.isEmpty()) { - return Collections.emptyList(); - } - if (CollectionUtils.isEmpty(this.allowedHeaders)) { - return null; - } - boolean allowAnyHeader = this.allowedHeaders.contains(ALL); - List result = new ArrayList<>(requestHeaders.size()); - loadAllowedHeaders(requestHeaders, result, allowAnyHeader); - return (result.isEmpty() ? null : result); - } - - private void loadAllowedHeaders(List requestHeaders, List result, boolean allowAnyHeader) { - for (String requestHeader : requestHeaders) { - if (StringUtils.hasText(requestHeader)) { - requestHeader = requestHeader.trim(); - if (allowAnyHeader) { - result.add(requestHeader); - } else { - loadAllowedHeaders(result, requestHeader); - } - } - } - } - - private void loadAllowedHeaders(List result, String requestHeader) { - for (String allowedHeader : this.allowedHeaders) { - if (requestHeader.equalsIgnoreCase(allowedHeader)) { - result.add(requestHeader); - break; - } - } - } - - private static class OriginPattern { - - private static final Pattern PORTS_PATTERN = Pattern.compile("(.*):\\[(\\*|\\d+(,\\d+)*)]"); - - private final String declaredPattern; - - private final Pattern pattern; - - OriginPattern(String declaredPattern) { - this.declaredPattern = declaredPattern; - this.pattern = initPattern(declaredPattern); - } - - private static Pattern initPattern(String patternValue) { - String portList = null; - Matcher matcher = PORTS_PATTERN.matcher(patternValue); - if (matcher.matches()) { - patternValue = matcher.group(1); - portList = matcher.group(2); - } - - patternValue = "\\Q" + patternValue + "\\E"; - patternValue = patternValue.replace("*", "\\E.*\\Q"); - - if (portList != null) { - patternValue += (portList.equals(ALL) ? "(:\\d+)?" : ":(" + portList.replace(',', '|') + ")"); - } - - return Pattern.compile(patternValue); - } - - public String getDeclaredPattern() { - return this.declaredPattern; - } - - public Pattern getPattern() { - return this.pattern; - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || !getClass().equals(other.getClass())) { - return false; - } - return Objects.equals(this.declaredPattern, ((OriginPattern) other).declaredPattern); - } - - @Override - public int hashCode() { - return this.declaredPattern.hashCode(); - } - - @Override - public String toString() { - return this.declaredPattern; - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java deleted file mode 100644 index 3a49b422999..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; - -import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.remoting.http12.HttpMethods; -import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.HttpStatus; -import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils.getPort; - -public class CorsProcessor { - private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(CorsProcessor.class); - - public boolean process(CorsMeta config, HttpRequest request, HttpResponse response) { - // set vary header - setVaryHeaders(response); - - // skip if is not a cors request - if (!isCorsRequest(request)) { - return true; - } - - // skip if origin already contains in Access-Control-Allow-Origin header - if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) != null) { - return true; - } - boolean preFlight = isPreFlight(request); - if (config == null) { - // if no cors config and is a preflight request - if (preFlight) { - return reject(response); - } - return true; - } - - // handle cors request - return handleInternal(request, response, config, preFlight); - } - - protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { - String allowOrigin = config.checkOrigin(request.header(RestConstants.ORIGIN)); - if (allowOrigin == null) { - return reject(response); - } - - List allowHttpMethods = config.checkHttpMethods(getHttpMethods(request, isPreLight)); - if (allowHttpMethods == null) { - return reject(response); - } - List httpHeaders = getHttpHeaders(request, isPreLight); - List allowHeaders = config.checkHeaders(httpHeaders); - if (isPreLight && httpHeaders != null && allowHeaders == null) { - return reject(response); - } - - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); - - if (isPreLight) { - response.setHeader( - RestConstants.ACCESS_CONTROL_ALLOW_METHODS, - allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); - if (!CollectionUtils.isEmpty(allowHeaders)) { - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); - } - if (config.getMaxAge() != null) { - response.setHeader( - RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); - } - } - - if (!CollectionUtils.isEmpty(config.getExposedHeaders())) { - response.setHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS, config.getExposedHeaders()); - } - - return true; - } - - private HttpMethods getHttpMethods(HttpRequest request, Boolean isPreLight) { - if (Boolean.TRUE.equals(isPreLight)) { - return HttpMethods.valueOf(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - } - return HttpMethods.valueOf(request.method()); - } - - private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { - if (Boolean.TRUE.equals(isPreLight)) { - return request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } - return new ArrayList<>(request.headerNames()); - } - - private boolean reject(HttpResponse response) { - response.setStatus(HttpStatus.FORBIDDEN.getCode()); - response.setBody("Invalid CORS request"); - return false; - } - - public static boolean isPreFlight(HttpRequest request) { - // preflight request is a OPTIONS request with Access-Control-Request-Method header - return request.method().equals(HttpMethods.OPTIONS.name()) - && request.header(RestConstants.ORIGIN) != null - && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; - } - - private boolean isCorsRequest(HttpRequest request) { - // skip if request has no origin header - String origin = request.header(RestConstants.ORIGIN); - if (origin == null) { - return false; - } - - try { - URI uri = new URI(origin); - - // return true if origin is not the same as request's scheme, host and port - return !(Objects.equals(uri.getScheme(), request.scheme()) - && uri.getHost().equals(request.serverName()) - && getPort(uri.getScheme(), uri.getPort()) == getPort(request.scheme(), request.serverPort())); - } catch (URISyntaxException e) { - LOGGER.debug("Origin header is not a valid URI: " + origin); - // skip if origin is not a valid URI - return true; - } - } - - private void setVaryHeaders(HttpResponse response) { - List varyHeaders = response.headerValues(RestConstants.VARY); - if (varyHeaders == null) { - response.addHeader( - RestConstants.VARY, - RestConstants.ORIGIN + "," + RestConstants.ACCESS_CONTROL_REQUEST_METHOD + "," - + RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } else { - StringBuilder builder = new StringBuilder(); - if (!varyHeaders.contains(RestConstants.ORIGIN)) { - builder.append(RestConstants.ORIGIN).append(","); - } - if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { - builder.append(RestConstants.ACCESS_CONTROL_REQUEST_METHOD).append(","); - } - if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { - builder.append(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS).append(","); - } - if (builder.length() > 0) { - builder.deleteCharAt(builder.length() - 1); - response.addHeader(RestConstants.VARY, builder.toString()); - } - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index a716caad384..34458c4a491 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -18,64 +18,36 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; -import org.apache.dubbo.common.lang.Nullable; -import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; public class CorsUtils { - private CorsMeta globalCorsMeta; - public static final String HTTP = "http"; - public static final String HTTPS = "https"; - public static final String WS = "ws"; - public static final String WSS = "wss"; - public CorsUtils() { - globalCorsMeta = new CorsMeta(); - } + private CorsUtils() {} - public static int getPort(String scheme, int port) { - if (port == -1) { - if (HTTP.equals(scheme) || WS.equals(scheme)) { - port = 80; - } else if (HTTPS.equals(scheme) || WSS.equals(scheme)) { - port = 443; - } - } - return port; - } + public static CorsMeta getGlobalCorsMeta(FrameworkModel frameworkModel) { + Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); - public void resolveGlobalMeta(Configuration config) { - // Get the CORS configuration properties from the configuration object. - String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); - String allowMethods = config.getString(RestConstants.ALLOWED_METHODS); - String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); - String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); String maxAge = config.getString(RestConstants.MAX_AGE); - globalCorsMeta.setAllowedOrigins(parseList(allowOrigins)); - globalCorsMeta.setAllowedMethods(parseList(allowMethods)); - globalCorsMeta.setAllowedHeaders(parseList(allowHeaders)); - globalCorsMeta.setExposedHeaders(parseList(exposeHeaders)); - globalCorsMeta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); + return CorsMeta.builder() + .allowedOrigins(getValues(config, RestConstants.ALLOWED_ORIGINS)) + .allowedMethods(getValues(config, RestConstants.ALLOWED_METHODS)) + .allowedHeaders(getValues(config, RestConstants.ALLOWED_HEADERS)) + .allowCredentials(config.getBoolean(RestConstants.ALLOW_CREDENTIALS)) + .exposedHeaders(getValues(config, RestConstants.EXPOSED_HEADERS)) + .maxAge(maxAge == null ? null : Long.valueOf(maxAge)) + .build(); } - @Nullable - private static List parseList(@Nullable String value) { - if (value == null) { - return null; - } - return Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); + private static String[] getValues(Configuration config, String key) { + return StringUtils.tokenize(config.getString(key), ','); } - public CorsMeta getGlobalCorsMeta() { - if (globalCorsMeta == null) { - Configuration globalConfiguration = - ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); - resolveGlobalMeta(globalConfiguration); - } - return globalCorsMeta; + public static String formatOrigin(String value) { + value = value.trim(); + int last = value.length() - 1; + return last > -1 && value.charAt(last) == '/' ? value.substring(0, last) : value; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java index ebc08e6540b..c96c1469728 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java @@ -27,16 +27,15 @@ public abstract class RestHeaderFilterAdapter implements HeaderFilter { @Override - public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); - return invoke(invoker, invocation, request, response); + invoke(invoker, invocation, request, response); } - return invocation; } - protected abstract RpcInvocation invoke( + protected abstract void invoke( Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) throws RpcException; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 407bf1c53b1..81680ac4f24 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -151,7 +151,6 @@ public HandlerMeta lookup(HttpRequest request) { if (size == 0) { return null; } - List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { Match match = matches.get(i); @@ -191,7 +190,6 @@ public HandlerMeta lookup(HttpRequest request) { Candidate winner = candidates.get(0); RequestMapping mapping = winner.mapping; - HandlerMeta handler = winner.meta; request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 4066da157b9..f81389d6be5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -18,7 +18,6 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.Condition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConditionWrapper; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConsumesCondition; @@ -28,6 +27,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ResponseMeta; import java.util.Objects; @@ -44,8 +44,8 @@ public final class RequestMapping implements Condition customCondition, - ResponseMeta response, - CorsMeta corsMeta) { + CorsMeta cors, + ResponseMeta response) { this.name = name; this.pathCondition = pathCondition; this.methodsCondition = methodsCondition; @@ -68,8 +68,8 @@ private RequestMapping( this.consumesCondition = consumesCondition; this.producesCondition = producesCondition; this.customCondition = customCondition == null ? null : new ConditionWrapper(customCondition); + this.cors = cors; this.response = response; - this.corsMeta = corsMeta; } public static Builder builder() { @@ -86,9 +86,9 @@ public RequestMapping combine(RequestMapping other) { ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); + CorsMeta cors = this.cors == null ? other.cors : this.cors.combine(other.cors); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - CorsMeta meta = CorsMeta.combine(other.corsMeta, this.corsMeta); - return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response, meta); + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } private > T combine(T value, T other) { @@ -160,8 +160,8 @@ private RequestMapping doMatch(HttpRequest request, PathCondition pathCondition) return null; } } - return new RequestMapping( - name, paths, methods, params, headers, consumes, produces, custom, response, corsMeta); + + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } public String getName() { @@ -176,16 +176,12 @@ public ProducesCondition getProducesCondition() { return producesCondition; } - public ResponseMeta getResponse() { - return response; + public CorsMeta getCors() { + return cors; } - public CorsMeta getCorsMeta() { - return corsMeta; - } - - public void setCorsMeta(CorsMeta corsMeta) { - this.corsMeta = corsMeta; + public ResponseMeta getResponse() { + return response; } @Override @@ -237,7 +233,6 @@ public int compareTo(RequestMapping other, HttpRequest request) { result = customCondition.compareTo(other.customCondition, request); return result; } - return 0; } @@ -252,8 +247,7 @@ public int hashCode() { headersCondition, consumesCondition, producesCondition, - customCondition, - corsMeta); + customCondition); this.hashCode = hashCode; } return hashCode; @@ -274,8 +268,7 @@ public boolean equals(Object obj) { && Objects.equals(headersCondition, other.headersCondition) && Objects.equals(consumesCondition, other.consumesCondition) && Objects.equals(producesCondition, other.producesCondition) - && Objects.equals(customCondition, other.customCondition) - && Objects.equals(corsMeta, other.corsMeta); + && Objects.equals(customCondition, other.customCondition); } @Override @@ -306,8 +299,8 @@ public String toString() { if (response != null) { sb.append(", response=").append(response); } - if (corsMeta != null) { - sb.append(", corsMeta=").append(corsMeta); + if (cors != null) { + sb.append(", cors=").append(cors); } sb.append('}'); return sb.toString(); @@ -323,9 +316,9 @@ public static final class Builder { private String[] consumes; private String[] produces; private Condition customCondition; + private CorsMeta corsMeta; private Integer responseStatus; private String responseReason; - private CorsMeta corsMeta; public Builder name(String name) { this.name = name; @@ -372,6 +365,11 @@ public Builder custom(Condition customCondition) { return this; } + public Builder cors(CorsMeta corsMeta) { + this.corsMeta = corsMeta; + return this; + } + public Builder responseStatus(int status) { responseStatus = status; return this; @@ -382,31 +380,18 @@ public Builder responseReason(String reason) { return this; } - public Builder cors(CorsMeta corsMeta) { - this.corsMeta = corsMeta; - return this; - } - public RequestMapping build() { - PathCondition pathCondition = isEmpty(paths) ? null : new PathCondition(contextPath, paths); - MethodsCondition methodsCondition = isEmpty(methods) ? null : new MethodsCondition(methods); - ParamsCondition paramsCondition = isEmpty(params) ? null : new ParamsCondition(params); - HeadersCondition headersCondition = isEmpty(headers) ? null : new HeadersCondition(headers); - ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); - ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); - ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); - CorsMeta cors = this.corsMeta == null ? null : this.corsMeta; return new RequestMapping( name, - pathCondition, - methodsCondition, - paramsCondition, - headersCondition, - consumesCondition, - producesCondition, + isEmpty(paths) ? null : new PathCondition(contextPath, paths), + isEmpty(methods) ? null : new MethodsCondition(methods), + isEmpty(params) ? null : new ParamsCondition(params), + isEmpty(headers) ? null : new HeadersCondition(headers), + isEmpty(consumes) ? null : new ConsumesCondition(consumes), + isEmpty(produces) ? null : new ProducesCondition(produces), customCondition, - response, - cors); + corsMeta == null ? null : corsMeta, + responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason)); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index b3ceaa155b3..e04b9794de1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -60,7 +60,6 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { @Override public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { - HandlerMeta meta = requestMappingRegistry.lookup(request); if (meta == null) { return null; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter index 8821eb7a155..204897e33ff 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter @@ -1,2 +1,2 @@ http-context=org.apache.dubbo.rpc.protocol.tri.HttpContextFilter -rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter +rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter index d0ecc752d0f..fa26599c253 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter @@ -1 +1 @@ -rest-cors=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilter +rest-cors=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilter \ No newline at end of file From 5f9bfd755d7673764ec86e37e2608182e4184467 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Sat, 18 May 2024 11:19:37 +0800 Subject: [PATCH 42/62] feat(rest): refine cors support --- .../org/apache/dubbo/config/CorsConfig.java | 10 + .../jaxrs/JaxrsRequestMappingResolver.java | 7 +- .../SpringMvcRequestMappingResolver.java | 43 +- .../compatible/SpringMvcRestProtocolTest.java | 1 - .../dubbo/remoting/http12/HttpStatus.java | 1 + .../org/apache/dubbo/rpc/HeaderFilter.java | 2 +- .../dubbo/rpc/filter/TokenHeaderFilter.java | 3 +- .../rpc/protocol/tri/rest/RestConstants.java | 17 +- .../tri/rest/cors/CorsHeaderFilter.java | 226 +++++++- .../rpc/protocol/tri/rest/cors/CorsMeta.java | 497 ------------------ .../protocol/tri/rest/cors/CorsProcessor.java | 176 ------- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 68 +-- .../rest/filter/RestHeaderFilterAdapter.java | 7 +- .../DefaultRequestMappingRegistry.java | 2 - .../tri/rest/mapping/RequestMapping.java | 77 ++- .../mapping/RestRequestHandlerMapping.java | 1 - .../tri/rest/mapping/meta/CorsMeta.java | 230 ++++++++ .../internal/org.apache.dubbo.rpc.Filter | 2 +- .../org.apache.dubbo.rpc.HeaderFilter | 2 +- 19 files changed, 533 insertions(+), 839 deletions(-) delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java index 06446861594..deabbcc98bb 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -29,6 +29,8 @@ public class CorsConfig implements Serializable { private String[] exposedHeaders; + private Boolean allowCredentials; + private Long maxAge; public String[] getAllowedOrigins() { @@ -63,6 +65,14 @@ public void setExposedHeaders(String[] exposedHeaders) { this.exposedHeaders = exposedHeaders; } + public Boolean getAllowCredentials() { + return allowCredentials; + } + + public void setAllowCredentials(Boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } + public Long getMaxAge() { return maxAge; } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index 0d414cea9a9..bb20ccea154 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -25,6 +25,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationSupport; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; @@ -33,11 +34,11 @@ public class JaxrsRequestMappingResolver implements RequestMappingResolver { private final RestToolKit toolKit; - private final CorsUtils corsUtils; + private final CorsMeta globalCorsMeta; public JaxrsRequestMappingResolver(FrameworkModel frameworkModel) { toolKit = new JaxrsRestToolKit(frameworkModel); - corsUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CorsUtils.class); + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } @Override @@ -72,7 +73,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(corsUtils.getGlobalCorsMeta()) + .cors(globalCorsMeta) .build(); } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 33dfc2bc394..8250de680de 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -19,32 +19,29 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; -import java.util.Arrays; -import java.util.Collections; - import org.springframework.http.HttpStatus; @Activate(onClass = "org.springframework.web.bind.annotation.RequestMapping") public class SpringMvcRequestMappingResolver implements RequestMappingResolver { private final FrameworkModel frameworkModel; + private final CorsMeta globalCorsMeta; private volatile RestToolKit toolKit; - private final CorsUtils corsUtils; public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - corsUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CorsUtils.class); + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } @Override @@ -73,7 +70,7 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { return builder(requestMapping, responseStatus) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) - .cors(buildCorsMetaWithGlobal(crossOrigin)) + .cors(buildCorsMeta(crossOrigin)) .build(); } @@ -89,7 +86,6 @@ public RequestMapping resolve(MethodMeta methodMeta) { } ServiceMeta serviceMeta = methodMeta.getServiceMeta(); AnnotationMeta responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus); - AnnotationMeta crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin); return builder(requestMapping, responseStatus) .name(methodMeta.getMethod().getName()) @@ -118,28 +114,17 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp } private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { - CorsMeta meta = new CorsMeta(); if (crossOrigin == null) { - return null; + return globalCorsMeta; } - String[] allowedHeaders = crossOrigin.getStringArray("allowedHeaders"); - meta.setAllowedHeaders(allowedHeaders != null ? Arrays.asList(allowedHeaders) : Collections.emptyList()); - String[] methods = crossOrigin.getStringArray("methods"); - meta.setAllowedMethods(methods != null ? Arrays.asList(methods) : Collections.emptyList()); - String[] origins = crossOrigin.getStringArray("origins"); - meta.setAllowedOrigins(origins != null ? Arrays.asList(origins) : Collections.emptyList()); - String[] exposedHeaders = crossOrigin.getStringArray("exposedHeaders"); - meta.setExposedHeaders(exposedHeaders != null ? Arrays.asList(exposedHeaders) : Collections.emptyList()); - String maxAge = crossOrigin.getString("maxAge"); - meta.setMaxAge(maxAge != null ? Long.valueOf(maxAge) : null); - return meta; - } - - private CorsMeta buildCorsMetaWithGlobal(AnnotationMeta crossOrigin) { - CorsMeta corsMeta = buildCorsMeta(crossOrigin); - if (corsMeta != null) { - return CorsMeta.combine(corsMeta, corsUtils.getGlobalCorsMeta()); - } - return corsUtils.getGlobalCorsMeta(); + CorsMeta corsMeta = CorsMeta.builder() + .allowedOrigins(crossOrigin.getStringArray("origins")) + .allowedMethods(crossOrigin.getStringArray("methods")) + .allowedHeaders(crossOrigin.getStringArray("allowedHeaders")) + .exposedHeaders(crossOrigin.getStringArray("exposedHeaders")) + .allowCredentials(crossOrigin.getBoolean("allowCredentials")) + .maxAge(crossOrigin.getNumber("maxAge")) + .build(); + return globalCorsMeta == null ? corsMeta : globalCorsMeta.combine(corsMeta); } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java index c7246ba9c4a..6e124fe2ae8 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java @@ -129,7 +129,6 @@ void testRestProtocolWithContextPath() { url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=" + SpringRestDemoService.class.getName()); Invoker invoker = protocol.refer(SpringRestDemoService.class, url); - SpringRestDemoService client = proxy.getProxy(invoker); String result = client.sayHello("haha"); Assertions.assertTrue(server.isCalled()); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java index f91b6280280..f28eb9422fb 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java @@ -20,6 +20,7 @@ public enum HttpStatus { OK(200), CREATED(201), ACCEPTED(202), + NO_CONTENT(204), FOUND(302), BAD_REQUEST(400), UNAUTHORIZED(401), diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java index 983c8c5d8b0..04dd983305d 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java @@ -22,5 +22,5 @@ @SPI(scope = ExtensionScope.FRAMEWORK) public interface HeaderFilter { - RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException; + void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException; } diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java index c8d8c6e1da9..3b6abb02d28 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java @@ -31,7 +31,7 @@ @Activate public class TokenHeaderFilter implements HeaderFilter { @Override - public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { String token = invoker.getUrl().getParameter(TOKEN_KEY); if (ConfigUtils.isNotEmpty(token)) { Class serviceType = invoker.getInterface(); @@ -46,6 +46,5 @@ public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws + ", consumer incorrect token is " + remoteToken); } } - return invocation; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 94154f29a87..7c512278bb5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -48,20 +48,6 @@ public final class RestConstants { public static final String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = "org.springframework.web.servlet.HandlerMapping.producibleMediaTypes"; - /* Cors Config */ - public static final String VARY = "Vary"; - public static final String ORIGIN = "Origin"; - public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; - public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; - public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; - public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; - public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; - public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; - public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; - public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; - public static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network"; - public static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network"; - /* Configuration Key */ public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size"; @@ -70,10 +56,13 @@ public final class RestConstants { public static final String TRAILING_SLASH_MATCH_KEY = CONFIG_PREFIX + "trailing-slash-match"; public static final String CASE_SENSITIVE_MATCH_KEY = CONFIG_PREFIX + "case-sensitive-match"; public static final String FORMAT_PARAMETER_NAME_KEY = CONFIG_PREFIX + "format-parameter-name"; + + /* Cors Configuration Key */ public static final String CORS_CONFIG_PREFIX = CONFIG_PREFIX + "cors."; public static final String ALLOWED_ORIGINS = CORS_CONFIG_PREFIX + "allowed-origins"; public static final String ALLOWED_METHODS = CORS_CONFIG_PREFIX + "allowed-methods"; public static final String ALLOWED_HEADERS = CORS_CONFIG_PREFIX + "allowed-headers"; + public static final String ALLOW_CREDENTIALS = CORS_CONFIG_PREFIX + "allow-credentials"; public static final String EXPOSED_HEADERS = CORS_CONFIG_PREFIX + "exposed-headers"; public static final String MAX_AGE = CORS_CONFIG_PREFIX + "max-age"; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index cfd10d5a334..6601c1030d8 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -18,6 +18,9 @@ import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.ArrayUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.remoting.http12.HttpResult; @@ -26,35 +29,232 @@ import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; + +/** + * See: DefaultCorsProcessor + */ @Activate(group = CommonConstants.PROVIDER, order = 1000) public class CorsHeaderFilter extends RestHeaderFilterAdapter { - private final CorsProcessor corsProcessor; - public CorsHeaderFilter(FrameworkModel frameworkModel) { - corsProcessor = frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class); - } + public static final String VARY = "Vary"; + public static final String ORIGIN = "Origin"; + public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; @Override - protected RpcInvocation invoke( - Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) + protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) throws RpcException { RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); - processCors(mapping, request, response); - return invocation; + CorsMeta cors = mapping.getCors(); + String origin = request.header(ORIGIN); + if (cors == null) { + if (isPreFlightRequest(request, origin)) { + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body("Invalid CORS request") + .build()); + } + return; + } + + if (process(cors, request, response)) { + return; + } + + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.FORBIDDEN) + .body("Invalid CORS request") + .headers(response.headers()) + .build()); } - private void processCors(RequestMapping mapping, HttpRequest request, HttpResponse response) { - if (!corsProcessor.process(mapping.getCorsMeta(), request, response)) { + private boolean process(CorsMeta cors, HttpRequest request, HttpResponse response) { + List varyHeaders = response.headerValues(VARY); + for (String header : new String[] {ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS}) { + if (varyHeaders == null || !varyHeaders.contains(header)) { + response.addHeader(VARY, header); + } + } + + String origin = request.header(ORIGIN); + if (isNotCorsRequest(request, origin)) { + return true; + } + + if (response.header(ACCESS_CONTROL_ALLOW_ORIGIN) != null) { + return true; + } + + String allowOrigin = checkOrigin(cors, origin); + if (allowOrigin == null) { + return false; + } + + boolean preFlight = isPreFlightRequest(request, origin); + + List allowMethods = + checkMethods(cors, preFlight ? request.header(ACCESS_CONTROL_REQUEST_METHOD) : request.method()); + if (allowMethods == null) { + return false; + } + + List allowHeaders = checkHeaders(cors, request.headerValues(ACCESS_CONTROL_REQUEST_HEADERS)); + if (preFlight && allowHeaders == null) { + return false; + } + + response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); + + if (ArrayUtils.isNotEmpty(cors.getExposedHeaders())) { + response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, StringUtils.join(cors.getExposedHeaders(), ",")); + } + + if (Boolean.TRUE.equals(cors.getAllowCredentials())) { + response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); + } + + if (preFlight) { + response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.join(allowMethods, ",")); + + if (!allowHeaders.isEmpty()) { + response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(allowHeaders, ",")); + } + if (cors.getMaxAge() != null) { + response.setHeader(ACCESS_CONTROL_MAX_AGE, cors.getMaxAge().toString()); + } throw new HttpResultPayloadException(HttpResult.builder() - .status(HttpStatus.FORBIDDEN) - .body(response.body()) + .status(HttpStatus.NO_CONTENT) .headers(response.headers()) .build()); } + + return true; + } + + private static String checkOrigin(CorsMeta cors, String origin) { + if (StringUtils.isBlank(origin)) { + return null; + } + origin = CorsUtils.formatOrigin(origin); + String[] allowedOrigins = cors.getAllowedOrigins(); + if (ArrayUtils.isNotEmpty(allowedOrigins)) { + if (ArrayUtils.contains(allowedOrigins, ANY_VALUE)) { + if (Boolean.TRUE.equals(cors.getAllowCredentials())) { + throw new IllegalArgumentException( + "When allowCredentials is true, allowedOrigins cannot contain the special value \"*\""); + } + return ANY_VALUE; + } + for (String allowedOrigin : allowedOrigins) { + if (origin.equalsIgnoreCase(allowedOrigin)) { + return origin; + } + } + } + if (ArrayUtils.isNotEmpty(cors.getAllowedOriginsPatterns())) { + for (Pattern pattern : cors.getAllowedOriginsPatterns()) { + if (pattern.matcher(origin).matches()) { + return origin; + } + } + } + return null; + } + + private static List checkMethods(CorsMeta cors, String method) { + if (method == null) { + return null; + } + String[] allowedMethods = cors.getAllowedMethods(); + if (ArrayUtils.contains(allowedMethods, ANY_VALUE)) { + return Collections.singletonList(method); + } + for (String allowedMethod : allowedMethods) { + if (method.equalsIgnoreCase(allowedMethod)) { + return Collections.singletonList(method); + } + } + return null; + } + + private static List checkHeaders(CorsMeta cors, Collection headers) { + if (headers == null) { + return null; + } + if (headers.isEmpty()) { + return Collections.emptyList(); + } + String[] allowedHeaders = cors.getAllowedHeaders(); + if (ArrayUtils.isEmpty(allowedHeaders)) { + return null; + } + + boolean allowAny = ArrayUtils.contains(allowedHeaders, ANY_VALUE); + List result = new ArrayList<>(headers.size()); + for (String header : headers) { + if (allowAny) { + result.add(header); + continue; + } + for (String allowedHeader : allowedHeaders) { + if (header.equalsIgnoreCase(allowedHeader)) { + result.add(header); + break; + } + } + } + return result.isEmpty() ? null : result; + } + + private static boolean isNotCorsRequest(HttpRequest request, String origin) { + if (origin == null) { + return true; + } + try { + URI uri = new URI(origin); + return request.scheme().equals(uri.getScheme()) + && request.serverName().equals(uri.getHost()) + && getPort(request.scheme(), request.serverPort()) == getPort(uri.getScheme(), uri.getPort()); + } catch (URISyntaxException e) { + return false; + } + } + + private static boolean isPreFlightRequest(HttpRequest request, String origin) { + return request.method().equals(HttpMethods.OPTIONS.name()) + && origin != null + && request.hasHeader(ACCESS_CONTROL_REQUEST_METHOD); + } + + private static int getPort(String scheme, int port) { + if (port == -1) { + if ("http".equals(scheme)) { + return 80; + } + if ("https".equals(scheme)) { + return 443; + } + } + return port; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java deleted file mode 100644 index 842ce6ef347..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsMeta.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; - -import org.apache.dubbo.common.lang.Nullable; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.remoting.http12.HttpMethods; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class CorsMeta { - - // Default value - - public static final String ALL = "*"; - - public static final List ALL_LIST = Collections.singletonList(ALL); - - private static final OriginPattern ALL_PATTERN = new OriginPattern("*"); - - private static final List ALL_PATTERN_LIST = Collections.singletonList(ALL_PATTERN); - - public static final List DEFAULT_PERMIT_ALL = Collections.singletonList(ALL); - - public static final List DEFAULT_METHODS = - Collections.unmodifiableList(Arrays.asList(HttpMethods.GET, HttpMethods.HEAD)); - - public static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( - Arrays.asList(HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name())); - - public static final Long DEFAULT_MAX_AGE = 1800L; - - @Nullable - private List allowedOrigins; - - @Nullable - private List allowedOriginPatterns; - - @Nullable - private List allowedMethods; - - @Nullable - private List resolvedMethods = DEFAULT_METHODS; - - @Nullable - private List allowedHeaders; - - @Nullable - private List exposedHeaders; - - @Nullable - private Long maxAge; - - public CorsMeta() {} - - public CorsMeta(CorsMeta other) { - this.allowedOrigins = other.allowedOrigins; - this.allowedOriginPatterns = other.allowedOriginPatterns; - this.allowedMethods = other.allowedMethods; - this.resolvedMethods = other.resolvedMethods; - this.allowedHeaders = other.allowedHeaders; - this.exposedHeaders = other.exposedHeaders; - this.maxAge = other.maxAge; - } - - public void setAllowedOrigins(@Nullable List origins) { - this.allowedOrigins = (origins == null - ? null - : origins.stream() - .filter(Objects::nonNull) - .map(this::trimTrailingSlash) - .collect(Collectors.toList())); - } - - private String trimTrailingSlash(String origin) { - return (origin.endsWith("/") ? origin.substring(0, origin.length() - 1) : origin); - } - - public List getAllowedOrigins() { - return this.allowedOrigins; - } - - public void addAllowedOrigin(@Nullable String origin) { - if (origin == null) { - return; - } - if (this.allowedOrigins == null) { - this.allowedOrigins = new ArrayList<>(4); - } else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { - setAllowedOrigins(DEFAULT_PERMIT_ALL); - } - origin = trimTrailingSlash(origin); - this.allowedOrigins.add(origin); - } - - public void setAllowedOriginPatterns(@Nullable List allowedOriginPatterns) { - if (allowedOriginPatterns == null) { - this.allowedOriginPatterns = null; - } else { - this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size()); - for (String patternValue : allowedOriginPatterns) { - addAllowedOriginPattern(patternValue); - } - } - } - - @Nullable - public List getAllowedOriginPatterns() { - if (this.allowedOriginPatterns == null) { - return null; - } - return this.allowedOriginPatterns.stream() - .map(OriginPattern::getDeclaredPattern) - .collect(Collectors.toList()); - } - - public void addAllowedOriginPattern(@Nullable String originPattern) { - if (originPattern == null) { - return; - } - if (this.allowedOriginPatterns == null) { - this.allowedOriginPatterns = new ArrayList<>(4); - } - originPattern = trimTrailingSlash(originPattern); - this.allowedOriginPatterns.add(new OriginPattern(originPattern)); - if (this.allowedOrigins == DEFAULT_PERMIT_ALL) { - this.allowedOrigins = null; - } - } - - public void setAllowedMethods(@Nullable List allowedMethods) { - if (allowedMethods != null) { - this.allowedMethods = new ArrayList<>(allowedMethods); - setResolvedMethods(allowedMethods); - } else { - this.allowedMethods = null; - } - } - - @Nullable - public void setResolvedMethods(List allowedMethods) { - if (!allowedMethods.isEmpty()) { - this.resolvedMethods = new ArrayList<>(allowedMethods.size()); - for (String method : allowedMethods) { - if (ALL.equals(method)) { - this.resolvedMethods = null; - break; - } - this.resolvedMethods.add(HttpMethods.valueOf(method)); - } - } else { - this.resolvedMethods = DEFAULT_METHODS; - } - } - - public List getAllowedMethods() { - return this.allowedMethods; - } - - public void addAllowedMethod(HttpMethods method) { - addAllowedMethod(method.name()); - } - - public void addAllowedMethod(String method) { - if (StringUtils.hasText(method)) { - if (this.allowedMethods == null) { - this.allowedMethods = new ArrayList<>(4); - this.resolvedMethods = new ArrayList<>(4); - } else if (this.allowedMethods == DEFAULT_PERMIT_METHODS) { - setAllowedMethods(DEFAULT_PERMIT_METHODS); - } - this.allowedMethods.add(method); - if (ALL.equals(method)) { - this.resolvedMethods = null; - } else if (this.resolvedMethods != null) { - this.resolvedMethods.add(HttpMethods.valueOf(method)); - } - } - } - - public void setAllowedHeaders(@Nullable List allowedHeaders) { - this.allowedHeaders = (allowedHeaders != null ? new ArrayList<>(allowedHeaders) : null); - } - - public List getAllowedHeaders() { - return this.allowedHeaders; - } - - public void addAllowedHeader(String allowedHeader) { - if (this.allowedHeaders == null) { - this.allowedHeaders = new ArrayList<>(4); - } else if (this.allowedHeaders == DEFAULT_PERMIT_ALL) { - setAllowedHeaders(DEFAULT_PERMIT_ALL); - } - this.allowedHeaders.add(allowedHeader); - } - - public void setExposedHeaders(@Nullable List exposedHeaders) { - this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null); - } - - public List getExposedHeaders() { - return this.exposedHeaders; - } - - public void addExposedHeader(String exposedHeader) { - if (this.exposedHeaders == null) { - this.exposedHeaders = new ArrayList<>(4); - } - this.exposedHeaders.add(exposedHeader); - } - - public void setMaxAge(@Nullable Long maxAge) { - this.maxAge = maxAge; - } - - @Nullable - public Long getMaxAge() { - return this.maxAge; - } - - public CorsMeta applyPermitDefaultValues() { - if (this.allowedOrigins == null && this.allowedOriginPatterns == null) { - this.allowedOrigins = DEFAULT_PERMIT_ALL; - } - if (this.allowedMethods == null) { - this.allowedMethods = DEFAULT_PERMIT_METHODS; - this.resolvedMethods = - DEFAULT_PERMIT_METHODS.stream().map(HttpMethods::valueOf).collect(Collectors.toList()); - } - if (this.allowedHeaders == null) { - this.allowedHeaders = DEFAULT_PERMIT_ALL; - } - if (this.maxAge == null) { - this.maxAge = DEFAULT_MAX_AGE; - } - return this; - } - - /** - * Combines two lists of strings, with a priority on this - * @param other other - * @return {@link CorsMeta} - */ - public static CorsMeta combine(@Nullable CorsMeta priority, @Nullable CorsMeta other) { - if (priority == null && other == null) { - return new CorsMeta(); - } - if (priority == null) { - return other; - } - if (other == null) { - return priority; - } - CorsMeta config = new CorsMeta(priority); - List origins = priority.combine(priority.getAllowedOrigins(), other.getAllowedOrigins()); - List patterns = - priority.combinePatterns(priority.allowedOriginPatterns, other.allowedOriginPatterns); - config.allowedOrigins = (origins == DEFAULT_PERMIT_ALL && !CollectionUtils.isEmpty(patterns) ? null : origins); - config.allowedOriginPatterns = patterns; - config.setAllowedMethods(priority.combine(priority.getAllowedMethods(), other.getAllowedMethods())); - config.setAllowedHeaders(priority.combine(priority.getAllowedHeaders(), other.getAllowedHeaders())); - config.setExposedHeaders(priority.combine(priority.getExposedHeaders(), other.getExposedHeaders())); - if (priority.maxAge == null) { - config.setMaxAge(other.maxAge); - } - return config; - } - - /** - * Combines two lists of strings, with a priority on the source list. - * - * @param source The primary list of strings. - * @param other The secondary list of strings to be combined with the source. - * @return A combined list of strings, with the source list taking priority in case of conflicts. - */ - private List combine(@Nullable List source, @Nullable List other) { - if (other == null) { - return (source != null ? source : Collections.emptyList()); - } - if (source == null) { - return other; - } - // save setting value at first - if (source == DEFAULT_PERMIT_ALL - || source == DEFAULT_PERMIT_METHODS - || other == DEFAULT_PERMIT_ALL - || other == DEFAULT_PERMIT_METHODS) { - return source; - } - - if (source.contains(ALL) || other.contains(ALL)) { - return ALL_LIST; - } - Set combined = new LinkedHashSet<>(source.size() + other.size()); - combined.addAll(source); - combined.addAll(other); - return new ArrayList<>(combined); - } - - private List combinePatterns( - @Nullable List source, @Nullable List other) { - - if (other == null) { - return (source != null ? source : Collections.emptyList()); - } - if (source == null) { - return other; - } - if (source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) { - return ALL_PATTERN_LIST; - } - Set combined = new LinkedHashSet<>(source.size() + other.size()); - combined.addAll(source); - combined.addAll(other); - return new ArrayList<>(combined); - } - - public String checkOrigin(@Nullable String origin) { - if (!StringUtils.hasText(origin)) { - return null; - } - String originToCheck = trimTrailingSlash(origin); - if (!CollectionUtils.isEmpty(this.allowedOrigins)) { - if (this.allowedOrigins.contains(ALL)) { - return allOriginAllowed(); - } - if (isOriginAllowed(originToCheck)) { - return origin; - } - } - if (isOriginPatternAllowed(originToCheck)) { - return origin; - } - return null; - } - - private String allOriginAllowed() { - return ALL; - } - - private boolean isOriginAllowed(String originToCheck) { - for (String allowedOrigin : this.allowedOrigins) { - if (originToCheck.equalsIgnoreCase(allowedOrigin)) { - return true; - } - } - return false; - } - - private boolean isOriginPatternAllowed(String originToCheck) { - if (!CollectionUtils.isEmpty(this.allowedOriginPatterns)) { - for (OriginPattern p : this.allowedOriginPatterns) { - if (p.getDeclaredPattern().equals(ALL) - || p.getPattern().matcher(originToCheck).matches()) { - return true; - } - } - } - return false; - } - - @Nullable - public List checkHttpMethods(@Nullable HttpMethods requestMethod) { - if (requestMethod == null) { - return null; - } - if (this.resolvedMethods == null) { - return Collections.singletonList(requestMethod); - } - return (this.resolvedMethods.contains(requestMethod) ? this.resolvedMethods : null); - } - - @Nullable - public List checkHeaders(@Nullable List requestHeaders) { - if (requestHeaders == null) { - return null; - } - if (requestHeaders.isEmpty()) { - return Collections.emptyList(); - } - if (CollectionUtils.isEmpty(this.allowedHeaders)) { - return null; - } - boolean allowAnyHeader = this.allowedHeaders.contains(ALL); - List result = new ArrayList<>(requestHeaders.size()); - loadAllowedHeaders(requestHeaders, result, allowAnyHeader); - return (result.isEmpty() ? null : result); - } - - private void loadAllowedHeaders(List requestHeaders, List result, boolean allowAnyHeader) { - for (String requestHeader : requestHeaders) { - if (StringUtils.hasText(requestHeader)) { - requestHeader = requestHeader.trim(); - if (allowAnyHeader) { - result.add(requestHeader); - } else { - loadAllowedHeaders(result, requestHeader); - } - } - } - } - - private void loadAllowedHeaders(List result, String requestHeader) { - for (String allowedHeader : this.allowedHeaders) { - if (requestHeader.equalsIgnoreCase(allowedHeader)) { - result.add(requestHeader); - break; - } - } - } - - private static class OriginPattern { - - private static final Pattern PORTS_PATTERN = Pattern.compile("(.*):\\[(\\*|\\d+(,\\d+)*)]"); - - private final String declaredPattern; - - private final Pattern pattern; - - OriginPattern(String declaredPattern) { - this.declaredPattern = declaredPattern; - this.pattern = initPattern(declaredPattern); - } - - private static Pattern initPattern(String patternValue) { - String portList = null; - Matcher matcher = PORTS_PATTERN.matcher(patternValue); - if (matcher.matches()) { - patternValue = matcher.group(1); - portList = matcher.group(2); - } - - patternValue = "\\Q" + patternValue + "\\E"; - patternValue = patternValue.replace("*", "\\E.*\\Q"); - - if (portList != null) { - patternValue += (portList.equals(ALL) ? "(:\\d+)?" : ":(" + portList.replace(',', '|') + ")"); - } - - return Pattern.compile(patternValue); - } - - public String getDeclaredPattern() { - return this.declaredPattern; - } - - public Pattern getPattern() { - return this.pattern; - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || !getClass().equals(other.getClass())) { - return false; - } - return Objects.equals(this.declaredPattern, ((OriginPattern) other).declaredPattern); - } - - @Override - public int hashCode() { - return this.declaredPattern.hashCode(); - } - - @Override - public String toString() { - return this.declaredPattern; - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java deleted file mode 100644 index 3a49b422999..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsProcessor.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; - -import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.remoting.http12.HttpMethods; -import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.HttpStatus; -import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils.getPort; - -public class CorsProcessor { - private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(CorsProcessor.class); - - public boolean process(CorsMeta config, HttpRequest request, HttpResponse response) { - // set vary header - setVaryHeaders(response); - - // skip if is not a cors request - if (!isCorsRequest(request)) { - return true; - } - - // skip if origin already contains in Access-Control-Allow-Origin header - if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) != null) { - return true; - } - boolean preFlight = isPreFlight(request); - if (config == null) { - // if no cors config and is a preflight request - if (preFlight) { - return reject(response); - } - return true; - } - - // handle cors request - return handleInternal(request, response, config, preFlight); - } - - protected boolean handleInternal(HttpRequest request, HttpResponse response, CorsMeta config, boolean isPreLight) { - String allowOrigin = config.checkOrigin(request.header(RestConstants.ORIGIN)); - if (allowOrigin == null) { - return reject(response); - } - - List allowHttpMethods = config.checkHttpMethods(getHttpMethods(request, isPreLight)); - if (allowHttpMethods == null) { - return reject(response); - } - List httpHeaders = getHttpHeaders(request, isPreLight); - List allowHeaders = config.checkHeaders(httpHeaders); - if (isPreLight && httpHeaders != null && allowHeaders == null) { - return reject(response); - } - - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); - - if (isPreLight) { - response.setHeader( - RestConstants.ACCESS_CONTROL_ALLOW_METHODS, - allowHttpMethods.stream().map(Enum::name).collect(Collectors.toList())); - if (!CollectionUtils.isEmpty(allowHeaders)) { - response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); - } - if (config.getMaxAge() != null) { - response.setHeader( - RestConstants.ACCESS_CONTROL_MAX_AGE, config.getMaxAge().toString()); - } - } - - if (!CollectionUtils.isEmpty(config.getExposedHeaders())) { - response.setHeader(RestConstants.ACCESS_CONTROL_EXPOSE_HEADERS, config.getExposedHeaders()); - } - - return true; - } - - private HttpMethods getHttpMethods(HttpRequest request, Boolean isPreLight) { - if (Boolean.TRUE.equals(isPreLight)) { - return HttpMethods.valueOf(request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)); - } - return HttpMethods.valueOf(request.method()); - } - - private List getHttpHeaders(HttpRequest request, Boolean isPreLight) { - if (Boolean.TRUE.equals(isPreLight)) { - return request.headerValues(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } - return new ArrayList<>(request.headerNames()); - } - - private boolean reject(HttpResponse response) { - response.setStatus(HttpStatus.FORBIDDEN.getCode()); - response.setBody("Invalid CORS request"); - return false; - } - - public static boolean isPreFlight(HttpRequest request) { - // preflight request is a OPTIONS request with Access-Control-Request-Method header - return request.method().equals(HttpMethods.OPTIONS.name()) - && request.header(RestConstants.ORIGIN) != null - && request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null; - } - - private boolean isCorsRequest(HttpRequest request) { - // skip if request has no origin header - String origin = request.header(RestConstants.ORIGIN); - if (origin == null) { - return false; - } - - try { - URI uri = new URI(origin); - - // return true if origin is not the same as request's scheme, host and port - return !(Objects.equals(uri.getScheme(), request.scheme()) - && uri.getHost().equals(request.serverName()) - && getPort(uri.getScheme(), uri.getPort()) == getPort(request.scheme(), request.serverPort())); - } catch (URISyntaxException e) { - LOGGER.debug("Origin header is not a valid URI: " + origin); - // skip if origin is not a valid URI - return true; - } - } - - private void setVaryHeaders(HttpResponse response) { - List varyHeaders = response.headerValues(RestConstants.VARY); - if (varyHeaders == null) { - response.addHeader( - RestConstants.VARY, - RestConstants.ORIGIN + "," + RestConstants.ACCESS_CONTROL_REQUEST_METHOD + "," - + RestConstants.ACCESS_CONTROL_REQUEST_HEADERS); - } else { - StringBuilder builder = new StringBuilder(); - if (!varyHeaders.contains(RestConstants.ORIGIN)) { - builder.append(RestConstants.ORIGIN).append(","); - } - if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_METHOD)) { - builder.append(RestConstants.ACCESS_CONTROL_REQUEST_METHOD).append(","); - } - if (!varyHeaders.contains(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS)) { - builder.append(RestConstants.ACCESS_CONTROL_REQUEST_HEADERS).append(","); - } - if (builder.length() > 0) { - builder.deleteCharAt(builder.length() - 1); - response.addHeader(RestConstants.VARY, builder.toString()); - } - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index a716caad384..34458c4a491 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -18,64 +18,36 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; -import org.apache.dubbo.common.lang.Nullable; -import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; public class CorsUtils { - private CorsMeta globalCorsMeta; - public static final String HTTP = "http"; - public static final String HTTPS = "https"; - public static final String WS = "ws"; - public static final String WSS = "wss"; - public CorsUtils() { - globalCorsMeta = new CorsMeta(); - } + private CorsUtils() {} - public static int getPort(String scheme, int port) { - if (port == -1) { - if (HTTP.equals(scheme) || WS.equals(scheme)) { - port = 80; - } else if (HTTPS.equals(scheme) || WSS.equals(scheme)) { - port = 443; - } - } - return port; - } + public static CorsMeta getGlobalCorsMeta(FrameworkModel frameworkModel) { + Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); - public void resolveGlobalMeta(Configuration config) { - // Get the CORS configuration properties from the configuration object. - String allowOrigins = config.getString(RestConstants.ALLOWED_ORIGINS); - String allowMethods = config.getString(RestConstants.ALLOWED_METHODS); - String allowHeaders = config.getString(RestConstants.ALLOWED_HEADERS); - String exposeHeaders = config.getString(RestConstants.EXPOSED_HEADERS); String maxAge = config.getString(RestConstants.MAX_AGE); - globalCorsMeta.setAllowedOrigins(parseList(allowOrigins)); - globalCorsMeta.setAllowedMethods(parseList(allowMethods)); - globalCorsMeta.setAllowedHeaders(parseList(allowHeaders)); - globalCorsMeta.setExposedHeaders(parseList(exposeHeaders)); - globalCorsMeta.setMaxAge(maxAge == null ? null : Long.valueOf(maxAge)); + return CorsMeta.builder() + .allowedOrigins(getValues(config, RestConstants.ALLOWED_ORIGINS)) + .allowedMethods(getValues(config, RestConstants.ALLOWED_METHODS)) + .allowedHeaders(getValues(config, RestConstants.ALLOWED_HEADERS)) + .allowCredentials(config.getBoolean(RestConstants.ALLOW_CREDENTIALS)) + .exposedHeaders(getValues(config, RestConstants.EXPOSED_HEADERS)) + .maxAge(maxAge == null ? null : Long.valueOf(maxAge)) + .build(); } - @Nullable - private static List parseList(@Nullable String value) { - if (value == null) { - return null; - } - return Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()); + private static String[] getValues(Configuration config, String key) { + return StringUtils.tokenize(config.getString(key), ','); } - public CorsMeta getGlobalCorsMeta() { - if (globalCorsMeta == null) { - Configuration globalConfiguration = - ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel()); - resolveGlobalMeta(globalConfiguration); - } - return globalCorsMeta; + public static String formatOrigin(String value) { + value = value.trim(); + int last = value.length() - 1; + return last > -1 && value.charAt(last) == '/' ? value.substring(0, last) : value; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java index ebc08e6540b..c96c1469728 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java @@ -27,16 +27,15 @@ public abstract class RestHeaderFilterAdapter implements HeaderFilter { @Override - public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); - return invoke(invoker, invocation, request, response); + invoke(invoker, invocation, request, response); } - return invocation; } - protected abstract RpcInvocation invoke( + protected abstract void invoke( Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) throws RpcException; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 407bf1c53b1..81680ac4f24 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -151,7 +151,6 @@ public HandlerMeta lookup(HttpRequest request) { if (size == 0) { return null; } - List candidates = new ArrayList<>(size); for (int i = 0; i < size; i++) { Match match = matches.get(i); @@ -191,7 +190,6 @@ public HandlerMeta lookup(HttpRequest request) { Candidate winner = candidates.get(0); RequestMapping mapping = winner.mapping; - HandlerMeta handler = winner.meta; request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 4066da157b9..f81389d6be5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -18,7 +18,6 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.Condition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConditionWrapper; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConsumesCondition; @@ -28,6 +27,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ResponseMeta; import java.util.Objects; @@ -44,8 +44,8 @@ public final class RequestMapping implements Condition customCondition, - ResponseMeta response, - CorsMeta corsMeta) { + CorsMeta cors, + ResponseMeta response) { this.name = name; this.pathCondition = pathCondition; this.methodsCondition = methodsCondition; @@ -68,8 +68,8 @@ private RequestMapping( this.consumesCondition = consumesCondition; this.producesCondition = producesCondition; this.customCondition = customCondition == null ? null : new ConditionWrapper(customCondition); + this.cors = cors; this.response = response; - this.corsMeta = corsMeta; } public static Builder builder() { @@ -86,9 +86,9 @@ public RequestMapping combine(RequestMapping other) { ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); + CorsMeta cors = this.cors == null ? other.cors : this.cors.combine(other.cors); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - CorsMeta meta = CorsMeta.combine(other.corsMeta, this.corsMeta); - return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response, meta); + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } private > T combine(T value, T other) { @@ -160,8 +160,8 @@ private RequestMapping doMatch(HttpRequest request, PathCondition pathCondition) return null; } } - return new RequestMapping( - name, paths, methods, params, headers, consumes, produces, custom, response, corsMeta); + + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } public String getName() { @@ -176,16 +176,12 @@ public ProducesCondition getProducesCondition() { return producesCondition; } - public ResponseMeta getResponse() { - return response; + public CorsMeta getCors() { + return cors; } - public CorsMeta getCorsMeta() { - return corsMeta; - } - - public void setCorsMeta(CorsMeta corsMeta) { - this.corsMeta = corsMeta; + public ResponseMeta getResponse() { + return response; } @Override @@ -237,7 +233,6 @@ public int compareTo(RequestMapping other, HttpRequest request) { result = customCondition.compareTo(other.customCondition, request); return result; } - return 0; } @@ -252,8 +247,7 @@ public int hashCode() { headersCondition, consumesCondition, producesCondition, - customCondition, - corsMeta); + customCondition); this.hashCode = hashCode; } return hashCode; @@ -274,8 +268,7 @@ public boolean equals(Object obj) { && Objects.equals(headersCondition, other.headersCondition) && Objects.equals(consumesCondition, other.consumesCondition) && Objects.equals(producesCondition, other.producesCondition) - && Objects.equals(customCondition, other.customCondition) - && Objects.equals(corsMeta, other.corsMeta); + && Objects.equals(customCondition, other.customCondition); } @Override @@ -306,8 +299,8 @@ public String toString() { if (response != null) { sb.append(", response=").append(response); } - if (corsMeta != null) { - sb.append(", corsMeta=").append(corsMeta); + if (cors != null) { + sb.append(", cors=").append(cors); } sb.append('}'); return sb.toString(); @@ -323,9 +316,9 @@ public static final class Builder { private String[] consumes; private String[] produces; private Condition customCondition; + private CorsMeta corsMeta; private Integer responseStatus; private String responseReason; - private CorsMeta corsMeta; public Builder name(String name) { this.name = name; @@ -372,6 +365,11 @@ public Builder custom(Condition customCondition) { return this; } + public Builder cors(CorsMeta corsMeta) { + this.corsMeta = corsMeta; + return this; + } + public Builder responseStatus(int status) { responseStatus = status; return this; @@ -382,31 +380,18 @@ public Builder responseReason(String reason) { return this; } - public Builder cors(CorsMeta corsMeta) { - this.corsMeta = corsMeta; - return this; - } - public RequestMapping build() { - PathCondition pathCondition = isEmpty(paths) ? null : new PathCondition(contextPath, paths); - MethodsCondition methodsCondition = isEmpty(methods) ? null : new MethodsCondition(methods); - ParamsCondition paramsCondition = isEmpty(params) ? null : new ParamsCondition(params); - HeadersCondition headersCondition = isEmpty(headers) ? null : new HeadersCondition(headers); - ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); - ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); - ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); - CorsMeta cors = this.corsMeta == null ? null : this.corsMeta; return new RequestMapping( name, - pathCondition, - methodsCondition, - paramsCondition, - headersCondition, - consumesCondition, - producesCondition, + isEmpty(paths) ? null : new PathCondition(contextPath, paths), + isEmpty(methods) ? null : new MethodsCondition(methods), + isEmpty(params) ? null : new ParamsCondition(params), + isEmpty(headers) ? null : new HeadersCondition(headers), + isEmpty(consumes) ? null : new ConsumesCondition(consumes), + isEmpty(produces) ? null : new ProducesCondition(produces), customCondition, - response, - cors); + corsMeta == null ? null : corsMeta, + responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason)); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index b3ceaa155b3..e04b9794de1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -60,7 +60,6 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { @Override public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { - HandlerMeta meta = requestMappingRegistry.lookup(request); if (meta == null) { return null; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java new file mode 100644 index 00000000000..a1fa6c2b7dc --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; + +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Function; + +import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; +import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING_ARRAY; + +public class CorsMeta { + + private final String[] allowedOrigins; + private final Pattern[] allowedOriginsPatterns; + private final String[] allowedMethods; + private final String[] allowedHeaders; + private final String[] exposedHeaders; + private final Boolean allowCredentials; + private final Long maxAge; + + private CorsMeta( + String[] allowedOrigins, + Pattern[] allowedOriginsPatterns, + String[] allowedMethods, + String[] allowedHeaders, + String[] exposedHeaders, + Boolean allowCredentials, + Long maxAge) { + this.allowedOrigins = allowedOrigins; + this.allowedOriginsPatterns = allowedOriginsPatterns; + this.allowedMethods = allowedMethods; + this.allowedHeaders = allowedHeaders; + this.exposedHeaders = exposedHeaders; + this.allowCredentials = allowCredentials; + this.maxAge = maxAge; + } + + public static Builder builder() { + return new Builder(); + } + + public String[] getAllowedOrigins() { + return allowedOrigins; + } + + public Pattern[] getAllowedOriginsPatterns() { + return allowedOriginsPatterns; + } + + public String[] getAllowedMethods() { + return allowedMethods; + } + + public String[] getAllowedHeaders() { + return allowedHeaders; + } + + public String[] getExposedHeaders() { + return exposedHeaders; + } + + public Boolean getAllowCredentials() { + return allowCredentials; + } + + public Long getMaxAge() { + return maxAge; + } + + public CorsMeta combine(CorsMeta other) { + if (other == null) { + return this; + } + return builder() + .allowedOrigins(allowedOrigins) + .allowedOrigins(other.allowedOrigins) + .allowedMethods(allowedMethods) + .allowedMethods(other.allowedMethods) + .allowedHeaders(allowedHeaders) + .allowedHeaders(other.allowedHeaders) + .exposedHeaders(exposedHeaders) + .exposedHeaders(other.exposedHeaders) + .allowCredentials(other.allowCredentials == null ? allowCredentials : other.allowCredentials) + .maxAge(other.maxAge == null ? maxAge : other.maxAge) + .build(); + } + + @Override + public String toString() { + return "CorsMeta{" + + "allowedOrigins=" + Arrays.toString(allowedOrigins) + + ", allowedOriginsPatterns=" + Arrays.toString(allowedOriginsPatterns) + + ", allowedMethods=" + Arrays.toString(allowedMethods) + + ", allowedHeaders=" + Arrays.toString(allowedHeaders) + + ", exposedHeaders=" + Arrays.toString(exposedHeaders) + + ", allowCredentials=" + allowCredentials + + ", maxAge=" + maxAge + + '}'; + } + + public static final class Builder { + + private static final String[] DEFAULT_METHODS = new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name()}; + private static final Pattern PORTS_PATTERN = Pattern.compile("(.*):\\[(\\*|\\d+(,\\d+)*)]"); + + private final Set allowedOrigins = new LinkedHashSet<>(); + private final Set allowedMethods = new LinkedHashSet<>(); + private final Set allowedHeaders = new LinkedHashSet<>(); + private final Set exposedHeaders = new LinkedHashSet<>(); + private Boolean allowCredentials; + private Long maxAge; + + public Builder allowedOrigins(String... origins) { + addValues(allowedOrigins, CorsUtils::formatOrigin, origins); + return this; + } + + public Builder allowedMethods(String... methods) { + addValues(allowedMethods, v -> v.trim().toUpperCase(), methods); + return this; + } + + public Builder allowedHeaders(String... headers) { + addValues(allowedHeaders, String::trim, headers); + return this; + } + + public Builder exposedHeaders(String... headers) { + addValues(exposedHeaders, String::trim, headers); + return this; + } + + private static void addValues(Set set, Function fn, String... values) { + if (values == null || set.contains(ANY_VALUE)) { + return; + } + for (String value : values) { + if (value == null) { + continue; + } + value = fn.apply(value); + if (value.isEmpty()) { + continue; + } + if (ANY_VALUE.equals(value)) { + set.clear(); + set.add(ANY_VALUE); + return; + } + set.add(value); + } + } + + private static Pattern initPattern(String patternValue) { + String ports = null; + Matcher matcher = PORTS_PATTERN.matcher(patternValue); + if (matcher.matches()) { + patternValue = matcher.group(1); + ports = matcher.group(2); + } + patternValue = "\\Q" + patternValue + "\\E"; + patternValue = patternValue.replace("*", "\\E.*\\Q"); + if (ports != null) { + patternValue += (ANY_VALUE.equals(ports) ? "(:\\d+)?" : ":(" + ports.replace(',', '|') + ")"); + } + return Pattern.compile(patternValue); + } + + public Builder allowCredentials(Boolean allowCredentials) { + this.allowCredentials = allowCredentials; + return this; + } + + public Builder maxAge(Long maxAge) { + this.maxAge = maxAge; + return this; + } + + public CorsMeta build() { + if (allowedOrigins.isEmpty() + && allowedMethods.isEmpty() + && allowedHeaders.isEmpty() + && exposedHeaders.isEmpty() + && allowCredentials == null + && maxAge == null) { + return null; + } + + int len = allowedOrigins.size(); + String[] origins = new String[len]; + Pattern[] originsPatterns = new Pattern[len]; + int i = 0; + for (String origin : allowedOrigins) { + origins[i] = origin; + originsPatterns[i] = ANY_VALUE.equals(origin) ? null : initPattern(origin); + i++; + } + return new CorsMeta( + origins, + originsPatterns, + allowedMethods.isEmpty() ? DEFAULT_METHODS : allowedMethods.toArray(EMPTY_STRING_ARRAY), + allowedHeaders.toArray(EMPTY_STRING_ARRAY), + exposedHeaders.toArray(EMPTY_STRING_ARRAY), + allowCredentials, + maxAge); + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter index 8821eb7a155..204897e33ff 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter @@ -1,2 +1,2 @@ http-context=org.apache.dubbo.rpc.protocol.tri.HttpContextFilter -rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter +rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter index d0ecc752d0f..fa26599c253 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter @@ -1 +1 @@ -rest-cors=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilter +rest-cors=org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsHeaderFilter \ No newline at end of file From 4d84717a04cfb76b9ad0ee205879b4c48581ecf5 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Sat, 18 May 2024 14:50:25 +0800 Subject: [PATCH 43/62] feat(rest): refine cors support bugfix --- .../org/apache/dubbo/config/CorsConfig.java | 34 +++++++++++++++++++ .../org/apache/dubbo/config/RestConfig.java | 2 -- .../SpringMvcRequestMappingResolver.java | 17 ++++++---- .../h12/AbstractServerTransportListener.java | 4 +-- .../tri/rest/cors/CorsHeaderFilter.java | 15 ++++---- .../rpc/protocol/tri/rest/cors/CorsUtils.java | 2 +- .../mapping/condition/MethodsCondition.java | 7 ++++ .../tri/rest/mapping/meta/CorsMeta.java | 34 +++++++++++++++++-- 8 files changed, 94 insertions(+), 21 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java index deabbcc98bb..657f20d039c 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java @@ -21,16 +21,50 @@ public class CorsConfig implements Serializable { private static final long serialVersionUID = 1L; + /** + * A list of origins for which cross-origin requests are allowed. Values may be a specific domain, e.g. + * {@code "https://domain1.com"}, or the CORS defined special value {@code "*"} for all origins. + *

By default this is not set which means that no origins are allowed. + * However, an instance of this class is often initialized further, e.g. for {@code @CrossOrigin}, via + * {@code org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta.Builder#applyDefault()}. + */ private String[] allowedOrigins; + /** + * Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, + * {@code "PUT"}, etc. The special value {@code "*"} allows all methods. + *

If not set, only {@code "GET"} and {@code "HEAD"} are allowed. + *

By default this is not set. + */ private String[] allowedMethods; + /** + * /** + * Set the list of headers that a pre-flight request can list as allowed + * for use during an actual request. The special value {@code "*"} allows + * actual requests to send any header. + *

By default this is not set. + */ private String[] allowedHeaders; + /** + * Set the list of response headers that an actual response might have + * and can be exposed to the client. The special value {@code "*"} + * allows all headers to be exposed. + *

By default this is not set. + */ private String[] exposedHeaders; + /** + * Whether user credentials are supported. + *

By default this is not set (i.e. user credentials are not supported). + */ private Boolean allowCredentials; + /** + * Configure how long, as a duration, the response from a pre-flight request + * can be cached by clients. + */ private Long maxAge; public String[] getAllowedOrigins() { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java index 413109fd490..dc7e9889286 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java @@ -72,8 +72,6 @@ public class RestConfig implements Serializable { /** * The config is used to set the Global CORS configuration properties. - * The default value can found in org.apache.dubbo.rpc.protocol.tri.rest. - * cors.CorsMeta.applyPermitDefaultValues() */ @Nested private CorsConfig cors; diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 8250de680de..6b52353d799 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -67,10 +67,12 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { } AnnotationMeta responseStatus = serviceMeta.findMergedAnnotation(Annotations.ResponseStatus); AnnotationMeta crossOrigin = serviceMeta.findMergedAnnotation(Annotations.CrossOrigin); + String[] methods = requestMapping.getStringArray("method"); return builder(requestMapping, responseStatus) + .method(methods) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) - .cors(buildCorsMeta(crossOrigin)) + .cors(buildCorsMeta(crossOrigin, methods)) .build(); } @@ -87,11 +89,13 @@ public RequestMapping resolve(MethodMeta methodMeta) { ServiceMeta serviceMeta = methodMeta.getServiceMeta(); AnnotationMeta responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus); AnnotationMeta crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin); + String[] methods = requestMapping.getStringArray("method"); return builder(requestMapping, responseStatus) + .method(methods) .name(methodMeta.getMethod().getName()) .contextPath(serviceMeta.getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(buildCorsMeta(crossOrigin)) + .cors(buildCorsMeta(crossOrigin, methods)) .build(); } @@ -106,24 +110,25 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp } } return builder.path(requestMapping.getValueArray()) - .method(requestMapping.getStringArray("method")) .param(requestMapping.getStringArray("params")) .header(requestMapping.getStringArray("headers")) .consume(requestMapping.getStringArray("consumes")) .produce(requestMapping.getStringArray("produces")); } - private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { + private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin, String[] methods) { if (crossOrigin == null) { return globalCorsMeta; } + String[] allowedMethods = crossOrigin.getStringArray("methods"); CorsMeta corsMeta = CorsMeta.builder() .allowedOrigins(crossOrigin.getStringArray("origins")) - .allowedMethods(crossOrigin.getStringArray("methods")) + .allowedMethods(allowedMethods.length == 0 ? methods : allowedMethods) .allowedHeaders(crossOrigin.getStringArray("allowedHeaders")) .exposedHeaders(crossOrigin.getStringArray("exposedHeaders")) - .allowCredentials(crossOrigin.getBoolean("allowCredentials")) + .allowCredentials(crossOrigin.getString("allowCredentials")) .maxAge(crossOrigin.getNumber("maxAge")) + .applyDefault() .build(); return globalCorsMeta == null ? corsMeta : globalCorsMeta.combine(corsMeta); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java index 5d74b831b04..b9b66c1ce87 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java @@ -160,10 +160,10 @@ protected void onDataCompletion(MESSAGE message) { protected void logError(Throwable t) { if (t instanceof HttpStatusException) { HttpStatusException e = (HttpStatusException) t; - if (e.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.getCode()) { + if (e.getStatusCode() >= HttpStatus.BAD_REQUEST.getCode()) { LOGGER.debug("http status exception", e); - return; } + return; } LOGGER.error(INTERNAL_ERROR, "", "", "server internal error", t); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index 6601c1030d8..20c5c9a1882 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -37,6 +37,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -118,9 +119,12 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons return false; } - List allowHeaders = checkHeaders(cors, request.headerValues(ACCESS_CONTROL_REQUEST_HEADERS)); - if (preFlight && allowHeaders == null) { - return false; + List allowHeaders = null; + if (preFlight) { + allowHeaders = checkHeaders(cors, request.headerValues(ACCESS_CONTROL_REQUEST_HEADERS)); + if (allowHeaders == null) { + return false; + } } response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); @@ -191,7 +195,7 @@ private static List checkMethods(CorsMeta cors, String method) { } for (String allowedMethod : allowedMethods) { if (method.equalsIgnoreCase(allowedMethod)) { - return Collections.singletonList(method); + return Arrays.asList(allowedMethods); } } return null; @@ -199,9 +203,6 @@ private static List checkMethods(CorsMeta cors, String method) { private static List checkHeaders(CorsMeta cors, Collection headers) { if (headers == null) { - return null; - } - if (headers.isEmpty()) { return Collections.emptyList(); } String[] allowedHeaders = cors.getAllowedHeaders(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 34458c4a491..a9aab0bd91d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -35,7 +35,7 @@ public static CorsMeta getGlobalCorsMeta(FrameworkModel frameworkModel) { .allowedOrigins(getValues(config, RestConstants.ALLOWED_ORIGINS)) .allowedMethods(getValues(config, RestConstants.ALLOWED_METHODS)) .allowedHeaders(getValues(config, RestConstants.ALLOWED_HEADERS)) - .allowCredentials(config.getBoolean(RestConstants.ALLOW_CREDENTIALS)) + .allowCredentials(config.getString(RestConstants.ALLOW_CREDENTIALS)) .exposedHeaders(getValues(config, RestConstants.EXPOSED_HEADERS)) .maxAge(maxAge == null ? null : Long.valueOf(maxAge)) .build(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java index fdde62dc61d..fa01f67194e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java @@ -24,6 +24,7 @@ import static org.apache.dubbo.remoting.http12.HttpMethods.GET; import static org.apache.dubbo.remoting.http12.HttpMethods.HEAD; +import static org.apache.dubbo.remoting.http12.HttpMethods.OPTIONS; public final class MethodsCondition implements Condition { @@ -53,6 +54,12 @@ public MethodsCondition match(HttpRequest request) { if (HEAD.name().equals(method) && methods.contains(GET.name())) { return new MethodsCondition(GET.name()); } + if (OPTIONS.name().equals(method) + && request.hasHeader("origin") + && request.hasHeader("access-control-request-method")) { + return new MethodsCondition(OPTIONS.name()); + } + return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index a1fa6c2b7dc..5c16d19c161 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -122,7 +122,6 @@ public String toString() { public static final class Builder { - private static final String[] DEFAULT_METHODS = new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name()}; private static final Pattern PORTS_PATTERN = Pattern.compile("(.*):\\[(\\*|\\d+(,\\d+)*)]"); private final Set allowedOrigins = new LinkedHashSet<>(); @@ -193,8 +192,32 @@ public Builder allowCredentials(Boolean allowCredentials) { return this; } + public Builder allowCredentials(String allowCredentials) { + if ("true".equals(allowCredentials)) { + this.allowCredentials = true; + } else if ("false".equals(allowCredentials)) { + this.allowCredentials = false; + } + return this; + } + public Builder maxAge(Long maxAge) { - this.maxAge = maxAge; + if (maxAge != null && maxAge > -1) { + this.maxAge = maxAge; + } + return this; + } + + public Builder applyDefault() { + if (allowedOrigins.isEmpty()) { + allowedOrigins.add(ANY_VALUE); + } + if (allowedHeaders.isEmpty()) { + allowedHeaders.add(ANY_VALUE); + } + if (maxAge == null) { + maxAge = 1800L; + } return this; } @@ -217,10 +240,15 @@ public CorsMeta build() { originsPatterns[i] = ANY_VALUE.equals(origin) ? null : initPattern(origin); i++; } + if (allowedMethods.isEmpty()) { + allowedMethods.add(HttpMethods.GET.name()); + allowedMethods.add(HttpMethods.HEAD.name()); + allowedMethods.add(HttpMethods.POST.name()); + } return new CorsMeta( origins, originsPatterns, - allowedMethods.isEmpty() ? DEFAULT_METHODS : allowedMethods.toArray(EMPTY_STRING_ARRAY), + allowedMethods.toArray(EMPTY_STRING_ARRAY), allowedHeaders.toArray(EMPTY_STRING_ARRAY), exposedHeaders.toArray(EMPTY_STRING_ARRAY), allowCredentials, From d3b3eafc936116f6ef64270dc90545f4ca14f916 Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 18 May 2024 19:16:21 +0800 Subject: [PATCH 44/62] fix(): getBoolean will throw exception when null --- .../apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java index 34458c4a491..c1ab8ddf0cb 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtils.java @@ -29,13 +29,13 @@ private CorsUtils() {} public static CorsMeta getGlobalCorsMeta(FrameworkModel frameworkModel) { Configuration config = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); - String maxAge = config.getString(RestConstants.MAX_AGE); + String allowCredential = config.getString(RestConstants.ALLOW_CREDENTIALS); return CorsMeta.builder() .allowedOrigins(getValues(config, RestConstants.ALLOWED_ORIGINS)) .allowedMethods(getValues(config, RestConstants.ALLOWED_METHODS)) .allowedHeaders(getValues(config, RestConstants.ALLOWED_HEADERS)) - .allowCredentials(config.getBoolean(RestConstants.ALLOW_CREDENTIALS)) + .allowCredentials(allowCredential == null ? null : Boolean.valueOf(allowCredential)) .exposedHeaders(getValues(config, RestConstants.EXPOSED_HEADERS)) .maxAge(maxAge == null ? null : Long.valueOf(maxAge)) .build(); From c8dcb9e4ca7497e49ac958f50c91f2a05861f254 Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 18 May 2024 19:34:12 +0800 Subject: [PATCH 45/62] fix(rest-spring): fix crossOrigin allowCredentials is string --- .../rest/support/spring/SpringMvcRequestMappingResolver.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 8250de680de..9455f38f4a2 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -117,12 +117,13 @@ private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { if (crossOrigin == null) { return globalCorsMeta; } + String allowCredentials = crossOrigin.getString("allowCredentials"); CorsMeta corsMeta = CorsMeta.builder() .allowedOrigins(crossOrigin.getStringArray("origins")) .allowedMethods(crossOrigin.getStringArray("methods")) .allowedHeaders(crossOrigin.getStringArray("allowedHeaders")) .exposedHeaders(crossOrigin.getStringArray("exposedHeaders")) - .allowCredentials(crossOrigin.getBoolean("allowCredentials")) + .allowCredentials(allowCredentials == null ? null : Boolean.valueOf(allowCredentials)) .maxAge(crossOrigin.getNumber("maxAge")) .build(); return globalCorsMeta == null ? corsMeta : globalCorsMeta.combine(corsMeta); From 87f6423b22e2cab6532857541d6059675483775d Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 18 May 2024 20:04:31 +0800 Subject: [PATCH 46/62] fix(): fix globalCorsMeta load null --- .../support/jaxrs/JaxrsRequestMappingResolver.java | 14 +++++++++++--- .../spring/SpringMvcRequestMappingResolver.java | 6 ++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index bb20ccea154..b01d25cdd88 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -33,12 +33,13 @@ @Activate(onClass = "javax.ws.rs.Path") public class JaxrsRequestMappingResolver implements RequestMappingResolver { + private final FrameworkModel frameworkModel; private final RestToolKit toolKit; - private final CorsMeta globalCorsMeta; + private CorsMeta globalCorsMeta; public JaxrsRequestMappingResolver(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; toolKit = new JaxrsRestToolKit(frameworkModel); - globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } @Override @@ -73,10 +74,17 @@ public RequestMapping resolve(MethodMeta methodMeta) { .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(globalCorsMeta) + .cors(getGlobalCorsMeta()) .build(); } + private CorsMeta getGlobalCorsMeta() { + if (globalCorsMeta == null) { + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); + } + return globalCorsMeta; + } + private Builder builder(AnnotationSupport meta, AnnotationMeta path, AnnotationMeta httpMethod) { Builder builder = RequestMapping.builder().path(path.getValue()); if (httpMethod == null) { diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 9455f38f4a2..96bf3bd7388 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -36,12 +36,11 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver { private final FrameworkModel frameworkModel; - private final CorsMeta globalCorsMeta; + private CorsMeta globalCorsMeta; private volatile RestToolKit toolKit; public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } @Override @@ -115,6 +114,9 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin) { if (crossOrigin == null) { + if (globalCorsMeta == null) { + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); + } return globalCorsMeta; } String allowCredentials = crossOrigin.getString("allowCredentials"); From 360e9e845edff643469657d2c2b71c8f7bc0c545 Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 18 May 2024 20:15:55 +0800 Subject: [PATCH 47/62] fix(): fix vary header bug --- .../rpc/protocol/tri/rest/cors/CorsHeaderFilter.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index 6601c1030d8..326b86e96ce 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -90,11 +90,20 @@ protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest private boolean process(CorsMeta cors, HttpRequest request, HttpResponse response) { List varyHeaders = response.headerValues(VARY); + + StringBuilder varyBuilder = new StringBuilder(); for (String header : new String[] {ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS}) { if (varyHeaders == null || !varyHeaders.contains(header)) { - response.addHeader(VARY, header); + if (varyBuilder.length() > 0) { + varyBuilder.append(", "); + } + varyBuilder.append(header); } } + if (varyBuilder.length() > 0) { + response.setHeader(VARY, varyBuilder.toString()); + } + String origin = request.header(ORIGIN); if (isNotCorsRequest(request, origin)) { From 9be50136348b0d38f8fdeef4fa5ef6d7bb8b6588 Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 18 May 2024 22:07:50 +0800 Subject: [PATCH 48/62] fix(): fix unit test && Fix cors specification --- .../tri/rest/cors/CorsHeaderFilter.java | 30 +++++++++---------- .../tri/rest/cors/CorsHeaderFilterTest.java | 10 ++----- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index 85ff9ba950d..19a013d0a5f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -40,7 +40,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; @@ -90,20 +92,16 @@ protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest } private boolean process(CorsMeta cors, HttpRequest request, HttpResponse response) { - List varyHeaders = response.headerValues(VARY); - StringBuilder varyBuilder = new StringBuilder(); - for (String header : new String[] {ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS}) { - if (varyHeaders == null || !varyHeaders.contains(header)) { - if (varyBuilder.length() > 0) { - varyBuilder.append(", "); - } - varyBuilder.append(header); - } - } - if (varyBuilder.length() > 0) { - response.setHeader(VARY, varyBuilder.toString()); + Set varHeadersSet = new LinkedHashSet<>(); + List varyHeaders = response.headerValues(VARY); + if (varyHeaders != null) { + varHeadersSet.addAll(varyHeaders); } + varHeadersSet.add(ORIGIN); + varHeadersSet.add(ACCESS_CONTROL_REQUEST_METHOD); + varHeadersSet.add(ACCESS_CONTROL_REQUEST_HEADERS); + response.setHeader(VARY, StringUtils.join(varHeadersSet, ", ")); String origin = request.header(ORIGIN); if (isNotCorsRequest(request, origin)) { @@ -138,7 +136,7 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); if (ArrayUtils.isNotEmpty(cors.getExposedHeaders())) { - response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, StringUtils.join(cors.getExposedHeaders(), ",")); + response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, StringUtils.join(cors.getExposedHeaders(), ", ")); } if (Boolean.TRUE.equals(cors.getAllowCredentials())) { @@ -146,10 +144,10 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons } if (preFlight) { - response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.join(allowMethods, ",")); + response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.join(allowMethods, ", ")); if (!allowHeaders.isEmpty()) { - response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(allowHeaders, ",")); + response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(allowHeaders, ", ")); } if (cors.getMaxAge() != null) { response.setHeader(ACCESS_CONTROL_MAX_AGE, cors.getMaxAge().toString()); @@ -210,7 +208,7 @@ private static List checkMethods(CorsMeta cors, String method) { } private static List checkHeaders(CorsMeta cors, Collection headers) { - if (headers == null) { + if (headers == null || headers.isEmpty()) { return Collections.emptyList(); } String[] allowedHeaders = cors.getAllowedHeaders(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index a559f1fc078..0035af725db 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -243,7 +243,8 @@ void preflightRequestMatchedAllowedMethod() { Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); Mockito.when(build.getCors()) - .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); + .thenReturn( + CorsMeta.builder().allowedOrigins("*").applyDefault().build()); try { this.processor.process(this.request, this.response); Assertions.fail(); @@ -339,11 +340,7 @@ void preflightRequestValidRequestAndConfig() { Assertions.assertEquals("*", this.response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); log.info("{}", this.response.headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertArrayEquals( - new String[] {"GET"}, - this.response - .headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS) - .toArray()); + Assertions.assertEquals("GET, PUT", this.response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( @@ -468,7 +465,6 @@ void preflightRequestWithEmptyHeaders() { this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); } @Test From e90185fa0c27ca7e95bc6483d20319ac39351031 Mon Sep 17 00:00:00 2001 From: liu Date: Sat, 18 May 2024 22:16:11 +0800 Subject: [PATCH 49/62] fix(): fix pom --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c0a4edf8856..b93069ee272 100644 --- a/pom.xml +++ b/pom.xml @@ -160,7 +160,7 @@ org.apache.dubbo dubbo-dependencies-bom - 3.3.0-beta.2-SNAPSHOT + ${project.version} pom import From 43855324427876b7e8cb242ea449349d4c2090a7 Mon Sep 17 00:00:00 2001 From: liu Date: Sun, 19 May 2024 16:11:30 +0800 Subject: [PATCH 50/62] fix(): fix combine bug --- .../SpringMvcRequestMappingResolver.java | 3 +- .../tri/rest/mapping/RequestMapping.java | 2 +- .../tri/rest/mapping/meta/CorsMeta.java | 74 ++++++++++++++----- .../protocol/tri/rest/cors/CorsUtilsTest.java | 60 --------------- 4 files changed, 58 insertions(+), 81 deletions(-) delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 065f201b84e..da22ec22e4c 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -130,8 +130,7 @@ private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin, String[] methods) .exposedHeaders(crossOrigin.getStringArray("exposedHeaders")) .allowCredentials(crossOrigin.getString("allowCredentials")) .maxAge(crossOrigin.getNumber("maxAge")) - .applyDefault() .build(); - return globalCorsMeta == null ? corsMeta : globalCorsMeta.combine(corsMeta); + return corsMeta == null ? globalCorsMeta : corsMeta.combine(globalCorsMeta); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index f81389d6be5..faf30214a33 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -86,7 +86,7 @@ public RequestMapping combine(RequestMapping other) { ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); - CorsMeta cors = this.cors == null ? other.cors : this.cors.combine(other.cors); + CorsMeta cors = other.cors == null ? this.cors : other.cors.combineDefault(this.cors); ResponseMeta response = ResponseMeta.combine(this.response, other.response); return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 5c16d19c161..7d03cf45e74 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,6 +41,10 @@ public class CorsMeta { private final Boolean allowCredentials; private final Long maxAge; + public static final String[] DEFAULT_ALLOWED_METHODS = { + HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name() + }; + private CorsMeta( String[] allowedOrigins, Pattern[] allowedOriginsPatterns, @@ -89,22 +94,40 @@ public Long getMaxAge() { return maxAge; } - public CorsMeta combine(CorsMeta other) { - if (other == null) { - return this; - } + public CorsMeta combine(CorsMeta secondary) { + return secondary == null ? this : combine0(secondary).build(); + } + + public CorsMeta combineDefault(CorsMeta secondary) { + return secondary == null ? this : combine0(secondary).applyDefault().build(); + } + + private Builder combine0(CorsMeta secondary) { return builder() - .allowedOrigins(allowedOrigins) - .allowedOrigins(other.allowedOrigins) - .allowedMethods(allowedMethods) - .allowedMethods(other.allowedMethods) - .allowedHeaders(allowedHeaders) - .allowedHeaders(other.allowedHeaders) - .exposedHeaders(exposedHeaders) - .exposedHeaders(other.exposedHeaders) - .allowCredentials(other.allowCredentials == null ? allowCredentials : other.allowCredentials) - .maxAge(other.maxAge == null ? maxAge : other.maxAge) - .build(); + .allowedOrigins(combine(this.allowedOrigins, secondary.allowedOrigins)) + .allowedHeaders(combine(this.allowedHeaders, secondary.allowedHeaders)) + .allowedMethods(combine(this.allowedMethods, secondary.allowedMethods)) + .exposedHeaders(this.exposedHeaders) + .exposedHeaders(secondary.exposedHeaders) + .allowCredentials( + secondary.allowCredentials == null ? this.allowCredentials : secondary.allowCredentials) + .maxAge(secondary.maxAge == null ? this.maxAge : secondary.maxAge); + } + + private String[] combine(String[] major, String[] secondary) { + if (major == null || major.length == 0) { + return secondary == null ? new String[0] : secondary; + } + if (secondary == null + || secondary.length == 0 + || Objects.equals(secondary[0], ANY_VALUE) + || Objects.equals(major[0], ANY_VALUE)) { + return major; + } + Set combined = new LinkedHashSet<>(major.length + secondary.length); + combined.addAll(Arrays.asList(major)); + combined.addAll(Arrays.asList(secondary)); + return combined.toArray(new String[0]); } @Override @@ -221,6 +244,15 @@ public Builder applyDefault() { return this; } + public Boolean enabledCors() { + return !(allowedOrigins.isEmpty() + && allowedMethods.isEmpty() + && allowedHeaders.isEmpty() + && exposedHeaders.isEmpty() + && allowCredentials == null + && maxAge == null); + } + public CorsMeta build() { if (allowedOrigins.isEmpty() && allowedMethods.isEmpty() @@ -241,9 +273,7 @@ public CorsMeta build() { i++; } if (allowedMethods.isEmpty()) { - allowedMethods.add(HttpMethods.GET.name()); - allowedMethods.add(HttpMethods.HEAD.name()); - allowedMethods.add(HttpMethods.POST.name()); + allowedMethods.addAll(Arrays.asList(DEFAULT_ALLOWED_METHODS)); } return new CorsMeta( origins, @@ -254,5 +284,13 @@ public CorsMeta build() { allowCredentials, maxAge); } + + public CorsMeta buildDefault() { + if (enabledCors()) { + return applyDefault().build(); + } else { + return null; + } + } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java deleted file mode 100644 index ead518f63d8..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsUtilsTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.dubbo.rpc.protocol.tri.rest.cors; - -class CorsUtilsTest { - - // @Test - // void testResolveGlobalMetaInCommon() { - // Configuration config = Mockito.mock(Configuration.class); - // Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn("http://localhost:8080"); - // Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn("GET,POST,PUT,DELETE"); - // Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn("Content-Type,Authorization"); - // Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn("Content-Type,Authorization"); - // Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); - // Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn("3600"); - // FrameworkModel frameworkModel = Mockito.mock(FrameworkModel.class); - // - // CorsMeta meta = CorsUtils.getGlobalCorsMeta(); - // Assertions.assertTrue(meta.getAllowedOrigins().contains("http://localhost:8080")); - // Assertions.assertTrue(meta.getAllowedMethods().contains("GET")); - // Assertions.assertTrue(meta.getAllowedMethods().contains("POST")); - // Assertions.assertTrue(meta.getAllowedMethods().contains("PUT")); - // Assertions.assertTrue(meta.getAllowedMethods().contains("DELETE")); - // Assertions.assertTrue(meta.getAllowedHeaders().contains("Content-Type")); - // Assertions.assertTrue(meta.getAllowedHeaders().contains("Authorization")); - // Assertions.assertTrue(meta.getExposedHeaders().contains("Content-Type")); - // Assertions.assertTrue(meta.getExposedHeaders().contains("Authorization")); - // Assertions.assertEquals(3600, meta.getMaxAge()); - // } - // - // @Test - // void testResolveGlobalMetaWithNullConfig() { - // Configuration config = Mockito.mock(Configuration.class); - // Mockito.when(config.getString(RestConstants.ALLOWED_ORIGINS)).thenReturn(null); - // Mockito.when(config.getString(RestConstants.ALLOWED_METHODS)).thenReturn(null); - // Mockito.when(config.getString(RestConstants.ALLOWED_HEADERS)).thenReturn(null); - // Mockito.when(config.getString(RestConstants.EXPOSED_HEADERS)).thenReturn(null); - // Mockito.when(config.getString(RestConstants.MAX_AGE)).thenReturn(null); - // CorsMeta meta = CorsUtils.getGlobalCorsMeta() - // Assertions.assertNull(meta.getMaxAge()); - // Assertions.assertNull(meta.getAllowedOrigins()); - // Assertions.assertNull(meta.getAllowedMethods()); - // Assertions.assertNull(meta.getAllowedHeaders()); - // } - -} From 00aa06adce1c4a119469b1bf9dd1e34483fab837 Mon Sep 17 00:00:00 2001 From: liu Date: Sun, 19 May 2024 17:27:28 +0800 Subject: [PATCH 51/62] fix(): fix some sonar issue --- .../tri/rest/cors/CorsHeaderFilter.java | 23 +++++++++++-------- .../tri/rest/mapping/meta/CorsMeta.java | 18 +-------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index 19a013d0a5f..07532a60689 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -93,15 +93,7 @@ protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest private boolean process(CorsMeta cors, HttpRequest request, HttpResponse response) { - Set varHeadersSet = new LinkedHashSet<>(); - List varyHeaders = response.headerValues(VARY); - if (varyHeaders != null) { - varHeadersSet.addAll(varyHeaders); - } - varHeadersSet.add(ORIGIN); - varHeadersSet.add(ACCESS_CONTROL_REQUEST_METHOD); - varHeadersSet.add(ACCESS_CONTROL_REQUEST_HEADERS); - response.setHeader(VARY, StringUtils.join(varHeadersSet, ", ")); + setVaryHeader(response); String origin = request.header(ORIGIN); if (isNotCorsRequest(request, origin)) { @@ -161,6 +153,19 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons return true; } + private static void setVaryHeader(HttpResponse response){ + Set varHeadersSet = new LinkedHashSet<>(); + List varyHeaders = response.headerValues(VARY); + if (varyHeaders != null) { + varHeadersSet.addAll(varyHeaders); + } + varHeadersSet.add(ORIGIN); + varHeadersSet.add(ACCESS_CONTROL_REQUEST_METHOD); + varHeadersSet.add(ACCESS_CONTROL_REQUEST_HEADERS); + response.setHeader(VARY, StringUtils.join(varHeadersSet, ", ")); + + } + private static String checkOrigin(CorsMeta cors, String origin) { if (StringUtils.isBlank(origin)) { return null; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 7d03cf45e74..65283b55415 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -41,7 +41,7 @@ public class CorsMeta { private final Boolean allowCredentials; private final Long maxAge; - public static final String[] DEFAULT_ALLOWED_METHODS = { + protected static final String[] DEFAULT_ALLOWED_METHODS = { HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name() }; @@ -244,15 +244,6 @@ public Builder applyDefault() { return this; } - public Boolean enabledCors() { - return !(allowedOrigins.isEmpty() - && allowedMethods.isEmpty() - && allowedHeaders.isEmpty() - && exposedHeaders.isEmpty() - && allowCredentials == null - && maxAge == null); - } - public CorsMeta build() { if (allowedOrigins.isEmpty() && allowedMethods.isEmpty() @@ -285,12 +276,5 @@ public CorsMeta build() { maxAge); } - public CorsMeta buildDefault() { - if (enabledCors()) { - return applyDefault().build(); - } else { - return null; - } - } } } From c4b501a53be7c22555b389f82f9286a778b0d688 Mon Sep 17 00:00:00 2001 From: liu Date: Sun, 19 May 2024 17:37:01 +0800 Subject: [PATCH 52/62] fix(): fix style --- .../dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java | 3 +-- .../dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index 07532a60689..bad856a4805 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -153,7 +153,7 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons return true; } - private static void setVaryHeader(HttpResponse response){ + private static void setVaryHeader(HttpResponse response) { Set varHeadersSet = new LinkedHashSet<>(); List varyHeaders = response.headerValues(VARY); if (varyHeaders != null) { @@ -163,7 +163,6 @@ private static void setVaryHeader(HttpResponse response){ varHeadersSet.add(ACCESS_CONTROL_REQUEST_METHOD); varHeadersSet.add(ACCESS_CONTROL_REQUEST_HEADERS); response.setHeader(VARY, StringUtils.join(varHeadersSet, ", ")); - } private static String checkOrigin(CorsMeta cors, String origin) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 65283b55415..440584881ca 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -275,6 +275,5 @@ public CorsMeta build() { allowCredentials, maxAge); } - } } From 8bae639712308fc6b502a2e462d38b1542d2e4df Mon Sep 17 00:00:00 2001 From: oxsean Date: Mon, 20 May 2024 03:51:08 +0000 Subject: [PATCH 53/62] feat(rest): refine cors support --- .../jaxrs/JaxrsRequestMappingResolver.java | 12 +- .../SpringMvcRequestMappingResolver.java | 19 +- .../tri/rest/cors/CorsHeaderFilter.java | 25 +- .../tri/rest/mapping/RequestMapping.java | 12 +- .../tri/rest/mapping/meta/CorsMeta.java | 145 +++++----- .../tri/rest/cors/CorsHeaderFilterTest.java | 259 +++++++++--------- 6 files changed, 249 insertions(+), 223 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index b01d25cdd88..72cb3253513 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -70,21 +70,17 @@ public RequestMapping resolve(MethodMeta methodMeta) { return null; } ServiceMeta serviceMeta = methodMeta.getServiceMeta(); + if (globalCorsMeta == null) { + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); + } return builder(methodMeta, path, httpMethod) .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(getGlobalCorsMeta()) + .cors(globalCorsMeta) .build(); } - private CorsMeta getGlobalCorsMeta() { - if (globalCorsMeta == null) { - globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); - } - return globalCorsMeta; - } - private Builder builder(AnnotationSupport meta, AnnotationMeta path, AnnotationMeta httpMethod) { Builder builder = RequestMapping.builder().path(path.getValue()); if (httpMethod == null) { diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index da22ec22e4c..e6de4f568bd 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.support.spring; +import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -36,8 +37,8 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver { private final FrameworkModel frameworkModel; - private CorsMeta globalCorsMeta; private volatile RestToolKit toolKit; + private CorsMeta globalCorsMeta; public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; @@ -116,21 +117,27 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp } private CorsMeta buildCorsMeta(AnnotationMeta crossOrigin, String[] methods) { + if (globalCorsMeta == null) { + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); + } if (crossOrigin == null) { - if (globalCorsMeta == null) { - globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); - } return globalCorsMeta; } String[] allowedMethods = crossOrigin.getStringArray("methods"); + if (allowedMethods.length == 0) { + allowedMethods = methods; + if (allowedMethods.length == 0) { + allowedMethods = new String[] {CommonConstants.ANY_VALUE}; + } + } CorsMeta corsMeta = CorsMeta.builder() .allowedOrigins(crossOrigin.getStringArray("origins")) - .allowedMethods(allowedMethods.length == 0 ? methods : allowedMethods) + .allowedMethods(allowedMethods) .allowedHeaders(crossOrigin.getStringArray("allowedHeaders")) .exposedHeaders(crossOrigin.getStringArray("exposedHeaders")) .allowCredentials(crossOrigin.getString("allowCredentials")) .maxAge(crossOrigin.getNumber("maxAge")) .build(); - return corsMeta == null ? globalCorsMeta : corsMeta.combine(globalCorsMeta); + return globalCorsMeta.combine(corsMeta); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index bad856a4805..d4c4164a86a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -63,6 +63,7 @@ public class CorsHeaderFilter extends RestHeaderFilterAdapter { public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + public static final String SEP = ", "; @Override protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest request, HttpResponse response) @@ -92,7 +93,6 @@ protected void invoke(Invoker invoker, RpcInvocation invocation, HttpRequest } private boolean process(CorsMeta cors, HttpRequest request, HttpResponse response) { - setVaryHeader(response); String origin = request.header(ORIGIN); @@ -128,7 +128,7 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin); if (ArrayUtils.isNotEmpty(cors.getExposedHeaders())) { - response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, StringUtils.join(cors.getExposedHeaders(), ", ")); + response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, StringUtils.join(cors.getExposedHeaders(), SEP)); } if (Boolean.TRUE.equals(cors.getAllowCredentials())) { @@ -136,10 +136,10 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons } if (preFlight) { - response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.join(allowMethods, ", ")); + response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.join(allowMethods, SEP)); if (!allowHeaders.isEmpty()) { - response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(allowHeaders, ", ")); + response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(allowHeaders, SEP)); } if (cors.getMaxAge() != null) { response.setHeader(ACCESS_CONTROL_MAX_AGE, cors.getMaxAge().toString()); @@ -154,15 +154,18 @@ private boolean process(CorsMeta cors, HttpRequest request, HttpResponse respons } private static void setVaryHeader(HttpResponse response) { - Set varHeadersSet = new LinkedHashSet<>(); List varyHeaders = response.headerValues(VARY); - if (varyHeaders != null) { - varHeadersSet.addAll(varyHeaders); + String varyValue; + if (varyHeaders == null) { + varyValue = ORIGIN + SEP + ACCESS_CONTROL_REQUEST_METHOD + SEP + ACCESS_CONTROL_REQUEST_HEADERS; + } else { + Set varHeadersSet = new LinkedHashSet<>(varyHeaders); + varHeadersSet.add(ORIGIN); + varHeadersSet.add(ACCESS_CONTROL_REQUEST_METHOD); + varHeadersSet.add(ACCESS_CONTROL_REQUEST_HEADERS); + varyValue = StringUtils.join(varHeadersSet, SEP); } - varHeadersSet.add(ORIGIN); - varHeadersSet.add(ACCESS_CONTROL_REQUEST_METHOD); - varHeadersSet.add(ACCESS_CONTROL_REQUEST_HEADERS); - response.setHeader(VARY, StringUtils.join(varHeadersSet, ", ")); + response.setHeader(VARY, varyValue); } private static String checkOrigin(CorsMeta cors, String origin) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index faf30214a33..d6a2b1f76c2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -86,13 +86,19 @@ public RequestMapping combine(RequestMapping other) { ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); - CorsMeta cors = other.cors == null ? this.cors : other.cors.combineDefault(this.cors); + CorsMeta cors = combine(this.cors, other.cors); ResponseMeta response = ResponseMeta.combine(this.response, other.response); return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } - private > T combine(T value, T other) { - return value == null ? other : other == null ? value : value.combine(other); + private > T combine(T source, T other) { + return source == null ? other : other == null ? source : source.combine(other); + } + + private CorsMeta combine(CorsMeta source, CorsMeta other) { + return source == null || source.isEmpty() + ? other == null || other.isEmpty() ? null : other.applyDefault() + : source.combine(other).applyDefault(); } public RequestMapping match(HttpRequest request, PathExpression path) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 440584881ca..9b6c5d81583 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -16,12 +16,15 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; +import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashSet; -import java.util.Objects; +import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,10 +44,6 @@ public class CorsMeta { private final Boolean allowCredentials; private final Long maxAge; - protected static final String[] DEFAULT_ALLOWED_METHODS = { - HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name() - }; - private CorsMeta( String[] allowedOrigins, Pattern[] allowedOriginsPatterns, @@ -94,40 +93,86 @@ public Long getMaxAge() { return maxAge; } - public CorsMeta combine(CorsMeta secondary) { - return secondary == null ? this : combine0(secondary).build(); + public boolean isEmpty() { + return allowedOrigins.length == 0 + && allowedMethods.length == 0 + && allowedHeaders.length == 0 + && exposedHeaders.length == 0 + && allowCredentials == null + && maxAge == null; } - public CorsMeta combineDefault(CorsMeta secondary) { - return secondary == null ? this : combine0(secondary).applyDefault().build(); + public CorsMeta applyDefault() { + String[] allowedOrigins = null; + Pattern[] allowedOriginsPatterns = null; + if (this.allowedOrigins.length == 0) { + allowedOrigins = new String[] {ANY_VALUE}; + allowedOriginsPatterns = new Pattern[] {null}; + } + String[] allowedMethods = null; + if (this.allowedMethods.length == 0) { + allowedMethods = new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}; + } + String[] allowedHeaders = null; + if (this.allowedHeaders.length == 0) { + allowedHeaders = new String[] {ANY_VALUE}; + } + Long maxAge = null; + if (this.maxAge == null) { + maxAge = 1800L; + } + if (allowedOrigins == null && allowedMethods == null && allowedHeaders == null && maxAge == null) { + return this; + } + return new CorsMeta( + allowedOrigins == null ? this.allowedOrigins : allowedOrigins, + allowedOriginsPatterns == null ? this.allowedOriginsPatterns : allowedOriginsPatterns, + allowedMethods == null ? this.allowedMethods : allowedMethods, + allowedHeaders == null ? this.allowedHeaders : allowedHeaders, + exposedHeaders, + allowCredentials, + maxAge); } - private Builder combine0(CorsMeta secondary) { - return builder() - .allowedOrigins(combine(this.allowedOrigins, secondary.allowedOrigins)) - .allowedHeaders(combine(this.allowedHeaders, secondary.allowedHeaders)) - .allowedMethods(combine(this.allowedMethods, secondary.allowedMethods)) - .exposedHeaders(this.exposedHeaders) - .exposedHeaders(secondary.exposedHeaders) - .allowCredentials( - secondary.allowCredentials == null ? this.allowCredentials : secondary.allowCredentials) - .maxAge(secondary.maxAge == null ? this.maxAge : secondary.maxAge); + public CorsMeta combine(CorsMeta other) { + if (other == null || other.isEmpty()) { + return this; + } + return new CorsMeta( + combine(allowedOrigins, other.allowedOrigins), merge(allowedOriginsPatterns, + other.allowedOriginsPatterns).toArray(new Pattern[0]), + combine(allowedMethods, other.allowedMethods), + combine(allowedHeaders, other.allowedHeaders), + combine(exposedHeaders, other.exposedHeaders), + other.allowCredentials == null ? allowCredentials : other.allowCredentials, + other.maxAge == null ? maxAge : other.maxAge); } - private String[] combine(String[] major, String[] secondary) { - if (major == null || major.length == 0) { - return secondary == null ? new String[0] : secondary; + private static String[] combine(String[] source, String[] other) { + if (other.length == 0) { + return source; + } + if (source.length == 0) { + return other; } - if (secondary == null - || secondary.length == 0 - || Objects.equals(secondary[0], ANY_VALUE) - || Objects.equals(major[0], ANY_VALUE)) { - return major; + if (source[0].equals(ANY_VALUE)) { + return other; } - Set combined = new LinkedHashSet<>(major.length + secondary.length); - combined.addAll(Arrays.asList(major)); - combined.addAll(Arrays.asList(secondary)); - return combined.toArray(new String[0]); + if (other[0].equals(ANY_VALUE)) { + return source; + } + return merge(source, other).toArray(EMPTY_STRING_ARRAY); + } + + private static Set merge(T[] source, T[] other) { + int size = source.length + other.length; + if (size == 0) { + return Collections.emptySet(); + } + Set merged = CollectionUtils.newLinkedHashSet(size); + Collections.addAll(merged, source); + Collections.addAll(merged, other); + return merged; } @Override @@ -231,44 +276,20 @@ public Builder maxAge(Long maxAge) { return this; } - public Builder applyDefault() { - if (allowedOrigins.isEmpty()) { - allowedOrigins.add(ANY_VALUE); - } - if (allowedHeaders.isEmpty()) { - allowedHeaders.add(ANY_VALUE); - } - if (maxAge == null) { - maxAge = 1800L; - } - return this; - } - public CorsMeta build() { - if (allowedOrigins.isEmpty() - && allowedMethods.isEmpty() - && allowedHeaders.isEmpty() - && exposedHeaders.isEmpty() - && allowCredentials == null - && maxAge == null) { - return null; - } - int len = allowedOrigins.size(); String[] origins = new String[len]; - Pattern[] originsPatterns = new Pattern[len]; + List originsPatterns = new ArrayList<>(len); int i = 0; for (String origin : allowedOrigins) { - origins[i] = origin; - originsPatterns[i] = ANY_VALUE.equals(origin) ? null : initPattern(origin); - i++; - } - if (allowedMethods.isEmpty()) { - allowedMethods.addAll(Arrays.asList(DEFAULT_ALLOWED_METHODS)); + origins[i++] = origin; + if (ANY_VALUE.equals(origin)) { + continue; + } + originsPatterns.add(initPattern(origin)); } return new CorsMeta( - origins, - originsPatterns, + origins, originsPatterns.toArray(new Pattern[0]), allowedMethods.toArray(EMPTY_STRING_ARRAY), allowedHeaders.toArray(EMPTY_STRING_ARRAY), exposedHeaders.toArray(EMPTY_STRING_ARRAY), diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index 0035af725db..218e5229f24 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -38,6 +38,7 @@ class CorsHeaderFilterTest { private static final Logger log = LoggerFactory.getLogger(CorsHeaderFilterTest.class); + private HttpRequest request; private HttpResponse response; @@ -46,29 +47,29 @@ class CorsHeaderFilterTest { private RequestMapping build; - class MockCorsHeaderFilter extends CorsHeaderFilter { + static class MockCorsHeaderFilter extends CorsHeaderFilter { public void process(HttpRequest request, HttpResponse response) { invoke(null, null, request, response); } } private CorsMeta defaultCorsMeta() { - return CorsMeta.builder().maxAge((long) 1000.0).build(); + return CorsMeta.builder().maxAge(1000L).build(); } @BeforeEach public void setup() { build = Mockito.mock(RequestMapping.class); - this.request = Mockito.mock(HttpRequest.class); - Mockito.when(this.request.attribute(RestConstants.MAPPING_ATTRIBUTE)).thenReturn(build); - Mockito.when(this.request.uri()).thenReturn("/test.html"); - Mockito.when(this.request.serverName()).thenReturn("domain1.example"); - Mockito.when(this.request.scheme()).thenReturn("http"); - Mockito.when(this.request.serverPort()).thenReturn(80); - Mockito.when(this.request.remoteHost()).thenReturn("127.0.0.1"); - this.response = new DefaultHttpResponse(); - this.response.setStatus(HttpStatus.OK.getCode()); - this.processor = new MockCorsHeaderFilter(); + request = Mockito.mock(HttpRequest.class); + Mockito.when(request.attribute(RestConstants.MAPPING_ATTRIBUTE)).thenReturn(build); + Mockito.when(request.uri()).thenReturn("/test.html"); + Mockito.when(request.serverName()).thenReturn("domain1.example"); + Mockito.when(request.scheme()).thenReturn("http"); + Mockito.when(request.serverPort()).thenReturn(80); + Mockito.when(request.remoteHost()).thenReturn("127.0.0.1"); + response = new DefaultHttpResponse(); + response.setStatus(HttpStatus.OK.getCode()); + processor = new MockCorsHeaderFilter(); } @Test @@ -76,14 +77,14 @@ void requestWithoutOriginHeader() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(build.getCors()).thenReturn(CorsMeta.builder().build()); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); - this.processor.process(this.request, this.response); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + processor.process(request, response); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test @@ -91,14 +92,14 @@ void sameOriginRequest() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("http://domain1.example"); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); - this.processor.process(this.request, this.response); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + processor.process(request, response); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test @@ -106,9 +107,7 @@ void actualRequestWithOriginHeader() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); - Assertions.assertThrows(HttpResultPayloadException.class, () -> { - this.processor.process(this.request, this.response); - }); + Assertions.assertThrows(HttpResultPayloadException.class, () -> processor.process(request, response)); } @Test @@ -116,28 +115,27 @@ void actualRequestWithOriginHeaderAndNullConfig() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); Mockito.when(build.getCors()).thenReturn(null); - this.processor.process(this.request, this.response); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + processor.process(request, response); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test void actualRequestWithOriginHeaderAndAllowedOrigin() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(build.getCors()) - .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); - this.processor.process(this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("*", this.response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS)); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Mockito.when(build.getCors()).thenReturn(CorsMeta.builder().build().applyDefault()); + processor.process(request, response); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("*", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test @@ -145,11 +143,13 @@ void actualRequestCaseInsensitiveOriginMatch() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); Mockito.when(build.getCors()) - .thenReturn( - CorsMeta.builder().allowedOrigins("https://DOMAIN2.com").build()); - this.processor.process(this.request, this.response); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + .thenReturn(CorsMeta.builder() + .allowedOrigins("https://DOMAIN2.com") + .build() + .applyDefault()); + processor.process(request, response); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test @@ -159,10 +159,11 @@ void actualRequestTrailingSlashOriginMatch() { Mockito.when(build.getCors()) .thenReturn(CorsMeta.builder() .allowedOrigins("https://domain2.com/") - .build()); - this.processor.process(this.request, this.response); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + .build() + .applyDefault()); + processor.process(request, response); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test @@ -172,26 +173,24 @@ void actualRequestExposedHeaders() { Mockito.doReturn(CorsMeta.builder() .allowedOrigins("https://domain2.com") .exposedHeaders("header1", "header2") - .build()) + .build() + .applyDefault()) .when(build) .getCors(); - this.processor.process(this.request, this.response); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals( - "https://domain2.com", this.response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS)); - Assertions.assertTrue(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS) - .contains("header1")); - Assertions.assertTrue(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS) - .contains("header2")); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + processor.process(request, response); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS).contains("header1")); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.ACCESS_CONTROL_EXPOSE_HEADERS).contains("header2")); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test @@ -202,10 +201,9 @@ void preflightRequestWithoutRequestheader() { .thenReturn("GET"); Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); - Mockito.when(build.getCors()) - .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); + Mockito.when(build.getCors()).thenReturn(CorsMeta.builder().build().applyDefault()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); @@ -225,7 +223,7 @@ void preflightRequestWrongAllowedMethod() { Mockito.when(build.getCors()) .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); @@ -242,11 +240,9 @@ void preflightRequestMatchedAllowedMethod() { .thenReturn("GET"); Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); - Mockito.when(build.getCors()) - .thenReturn( - CorsMeta.builder().allowedOrigins("*").applyDefault().build()); + Mockito.when(build.getCors()).thenReturn(CorsMeta.builder().build().applyDefault()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); @@ -263,7 +259,7 @@ void preflightRequestTestWithOriginButWithoutOtherHeaders() { .thenReturn(true); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); @@ -282,7 +278,7 @@ void preflightRequestWithoutRequestMethod() { .thenReturn(true); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); @@ -303,7 +299,7 @@ void preflightRequestWithRequestAndMethodHeaderButNoConfig() { .thenReturn(true); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); @@ -329,24 +325,24 @@ void preflightRequestValidRequestAndConfig() { .allowedHeaders("Header1", "Header2") .build()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("*", this.response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - log.info("{}", this.response.headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertEquals("GET, PUT", this.response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("*", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); + log.info("{}", response.headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); + Assertions.assertEquals("GET, PUT", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test @@ -363,34 +359,32 @@ void preflightRequestAllowedHeaders() { Mockito.doReturn(CorsMeta.builder() .allowedOrigins("https://domain2.com") .allowedHeaders("Header1", "Header2") - .build()) + .build() + .applyDefault()) .when(build) .getCors(); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header1")); - Assertions.assertTrue(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header2")); - Assertions.assertFalse(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header3")); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); + Assertions.assertTrue( + response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); + Assertions.assertFalse( + response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header3")); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), this.response.status()); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test @@ -408,31 +402,29 @@ void preflightRequestAllowsAllHeaders() { .thenReturn(CorsMeta.builder() .allowedOrigins("https://domain2.com") .allowedHeaders("*") - .build()); + .build() + .applyDefault()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header1")); - Assertions.assertTrue(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header2")); - Assertions.assertFalse(this.response - .header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("*")); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); + Assertions.assertFalse( + response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("*")); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test @@ -449,22 +441,23 @@ void preflightRequestWithEmptyHeaders() { .thenReturn(CorsMeta.builder() .allowedOrigins("https://domain2.com") .allowedHeaders("*") - .build()); + .build() + .applyDefault()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertFalse(this.response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test @@ -476,7 +469,7 @@ void preflightRequestWithNullConfig() { Mockito.when(build.getCors()) .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); try { - this.processor.process(this.request, this.response); + processor.process(request, response); Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); @@ -488,15 +481,15 @@ void preflightRequestWithNullConfig() { @Test void preventDuplicatedVaryHeaders() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); - this.response.setHeader( + response.setHeader( CorsHeaderFilter.VARY, CorsHeaderFilter.ORIGIN + "," + CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD + "," + CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS); - this.processor.process(this.request, this.response); - Assertions.assertTrue(this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + processor.process(request, response); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); Assertions.assertTrue( - this.response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } } From 88ebc5a92971f23bc5fd9cedda6c6bf0c24f15fb Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 20 May 2024 12:26:32 +0800 Subject: [PATCH 54/62] fix(): fix style --- .../dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 9b6c5d81583..8a850dea4c0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -139,8 +139,8 @@ public CorsMeta combine(CorsMeta other) { return this; } return new CorsMeta( - combine(allowedOrigins, other.allowedOrigins), merge(allowedOriginsPatterns, - other.allowedOriginsPatterns).toArray(new Pattern[0]), + combine(allowedOrigins, other.allowedOrigins), + merge(allowedOriginsPatterns, other.allowedOriginsPatterns).toArray(new Pattern[0]), combine(allowedMethods, other.allowedMethods), combine(allowedHeaders, other.allowedHeaders), combine(exposedHeaders, other.exposedHeaders), @@ -289,7 +289,8 @@ public CorsMeta build() { originsPatterns.add(initPattern(origin)); } return new CorsMeta( - origins, originsPatterns.toArray(new Pattern[0]), + origins, + originsPatterns.toArray(new Pattern[0]), allowedMethods.toArray(EMPTY_STRING_ARRAY), allowedHeaders.toArray(EMPTY_STRING_ARRAY), exposedHeaders.toArray(EMPTY_STRING_ARRAY), From 12d1b8744d87df98fa51f1f46f1c18245d4dc1c6 Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 20 May 2024 13:48:30 +0800 Subject: [PATCH 55/62] fix(): fix needed sonar issue --- .../tri/rest/mapping/RequestMapping.java | 5 ++-- .../tri/rest/mapping/meta/CorsMeta.java | 27 +++++++++---------- .../tri/rest/cors/CorsHeaderFilterTest.java | 19 ------------- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index d6a2b1f76c2..5d00ad8eac3 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -86,9 +86,10 @@ public RequestMapping combine(RequestMapping other) { ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); - CorsMeta cors = combine(this.cors, other.cors); + CorsMeta corsMeta = combine(this.cors, other.cors); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); + return new RequestMapping( + name, paths, methods, params, headers, consumes, produces, custom, corsMeta, response); } private > T combine(T source, T other) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 8a850dea4c0..2d019bb0813 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; @@ -26,11 +27,10 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.common.base.Function; - import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING_ARRAY; @@ -224,19 +224,18 @@ private static void addValues(Set set, Function fn, Stri return; } for (String value : values) { - if (value == null) { - continue; - } - value = fn.apply(value); - if (value.isEmpty()) { - continue; - } - if (ANY_VALUE.equals(value)) { - set.clear(); - set.add(ANY_VALUE); - return; + if (StringUtils.isNotEmpty(value)) { + value = fn.apply(value); + if (value.isEmpty()) { + continue; + } + if (ANY_VALUE.equals(value)) { + set.clear(); + set.add(ANY_VALUE); + return; + } + set.add(value); } - set.add(value); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index 218e5229f24..23a9c6c2865 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -193,25 +193,6 @@ void actualRequestExposedHeaders() { Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } - @Test - void preflightRequestWithoutRequestheader() { - Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); - Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); - Mockito.when(request.header(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn("GET"); - Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) - .thenReturn(true); - Mockito.when(build.getCors()).thenReturn(CorsMeta.builder().build().applyDefault()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } - } - @Test void preflightRequestWrongAllowedMethod() { Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); From c04a61f9616c85466d1cac4f9f2a109f8a3f1bf5 Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 20 May 2024 17:58:11 +0800 Subject: [PATCH 56/62] refactor(): refactor CorsMeta.combine() and add comment --- .../rpc/protocol/tri/rest/mapping/meta/CorsMeta.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 2d019bb0813..ee5e8b02d09 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -148,19 +148,16 @@ public CorsMeta combine(CorsMeta other) { other.maxAge == null ? maxAge : other.maxAge); } + /** + * Merge two arrays of CORS config values, with the other array having higher priority. + */ private static String[] combine(String[] source, String[] other) { if (other.length == 0) { return source; } - if (source.length == 0) { + if (source.length == 0 || source[0].equals(ANY_VALUE) || other[0].equals(ANY_VALUE)) { return other; } - if (source[0].equals(ANY_VALUE)) { - return other; - } - if (other[0].equals(ANY_VALUE)) { - return source; - } return merge(source, other).toArray(EMPTY_STRING_ARRAY); } From c61a130ac4acc6ccbd34bfe69ce49ab7caa66da8 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 21 May 2024 13:00:04 +0800 Subject: [PATCH 57/62] fix(): Replenish license --- .licenserc.yaml | 1 + .../protocol/tri/rest/cors/CorsHeaderFilter.java | 13 ++++++------- pom.xml | 2 ++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.licenserc.yaml b/.licenserc.yaml index e497f4b10e7..98957a25f7d 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -79,6 +79,7 @@ header: - 'dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java' - 'dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/utils/TestSocketUtils.java' - 'dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriHttp2RemoteFlowController.java' + - 'dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java' - 'dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/serial/SerializingExecutor.java' - 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractAotMojo.java' - 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractDependencyFilterMojo.java' diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index d4c4164a86a..f3dbe3eb622 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -1,12 +1,11 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 + * Copyright 2002-2024 the original author or authors. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 + * + * https://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, diff --git a/pom.xml b/pom.xml index b93069ee272..a8a3b1d694c 100644 --- a/pom.xml +++ b/pom.xml @@ -571,6 +571,7 @@ **/org/apache/dubbo/test/common/utils/TestSocketUtils.java, **/org/apache/dubbo/triple/TripleWrapper.java, **/org/apache/dubbo/rpc/protocol/tri/TriHttp2RemoteFlowController.java, + **/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java, **/org/apache/dubbo/metrics/aggregate/DubboMergingDigest.java, **/org/apache/dubbo/metrics/aggregate/DubboAbstractTDigest.java, **/org/apache/dubbo/common/logger/helpers/FormattingTuple.java, @@ -842,6 +843,7 @@ src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java src/main/java/org/apache/dubbo/test/common/utils/TestSocketUtils.java src/main/java/org/apache/dubbo/rpc/protocol/tri/TriHttp2RemoteFlowController.java + src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java src/main/java/org/apache/dubbo/common/threadpool/serial/SerializingExecutor.java src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractAotMojo.java src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractDependencyFilterMojo.java From 6151c6789c3062b5c33d539724510f2b0d5d54a3 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 21 May 2024 14:19:36 +0800 Subject: [PATCH 58/62] fix(): update test --- .../tri/rest/cors/CorsHeaderFilterTest.java | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index 23a9c6c2865..cc76b5eb262 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -310,20 +310,20 @@ void preflightRequestValidRequestAndConfig() { Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("*", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); + log.info("{}", response.headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); + Assertions.assertEquals("GET, PUT", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("*", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - log.info("{}", response.headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertEquals("GET, PUT", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test @@ -349,23 +349,23 @@ void preflightRequestAllowedHeaders() { Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header1")); + Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header2")); + Assertions.assertFalse(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header3")); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); - Assertions.assertTrue( - response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); - Assertions.assertFalse( - response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header3")); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } @Test @@ -390,22 +390,22 @@ void preflightRequestAllowsAllHeaders() { Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header1")); + Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("Header2")); + Assertions.assertFalse(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) + .contains("*")); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); - Assertions.assertTrue( - response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); - Assertions.assertFalse( - response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS).contains("*")); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test @@ -429,16 +429,16 @@ void preflightRequestWithEmptyHeaders() { Assertions.fail(); } catch (HttpResultPayloadException e) { Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); + Assertions.assertTrue( + response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } catch (Exception e) { Assertions.fail(); } - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } @Test From b7cb8c7efbf2351ab8d6be8bf1fb7b753068d8c4 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 21 May 2024 15:06:03 +0800 Subject: [PATCH 59/62] test(): Refactor the test class and add credential test cases --- .../tri/rest/cors/CorsHeaderFilterTest.java | 198 +++++++----------- 1 file changed, 73 insertions(+), 125 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index cc76b5eb262..8e0911f4779 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -51,6 +51,17 @@ static class MockCorsHeaderFilter extends CorsHeaderFilter { public void process(HttpRequest request, HttpResponse response) { invoke(null, null, request, response); } + + public void preLightProcess(HttpRequest request, HttpResponse response, int code) { + try { + process(request, response); + Assertions.fail(); + } catch (HttpResultPayloadException e) { + Assertions.assertEquals(code, e.getStatusCode()); + } catch (Exception e) { + Assertions.fail(); + } + } } private CorsMeta defaultCorsMeta() { @@ -193,6 +204,41 @@ void actualRequestExposedHeaders() { Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); } + @Test + void actualRequestCredentials() { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.doReturn(CorsMeta.builder() + .allowedOrigins( "https://domain1.com","https://domain2.com") + .allowCredentials(true) + .build() + .applyDefault()) + .when(build) + .getCors(); + processor.process(request, response); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertEquals("https://domain2.com", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); + Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertEquals("true", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS)); + Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); + Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); + } + + @Test + void actualRequestCredentialsWithWildcardOrigin() throws Exception { + Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); + Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.doReturn(CorsMeta.builder() + .allowedOrigins("*") + .allowCredentials(true) + .build() + .applyDefault()) + .when(build) + .getCors(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> processor.process(request, response)); + } + @Test void preflightRequestWrongAllowedMethod() { Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); @@ -203,14 +249,7 @@ void preflightRequestWrongAllowedMethod() { .thenReturn(true); Mockito.when(build.getCors()) .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.FORBIDDEN.getCode()); } @Test @@ -222,14 +261,7 @@ void preflightRequestMatchedAllowedMethod() { Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); Mockito.when(build.getCors()).thenReturn(CorsMeta.builder().build().applyDefault()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @Test @@ -239,14 +271,7 @@ void preflightRequestTestWithOriginButWithoutOtherHeaders() { Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.FORBIDDEN.getCode()); } @Test @@ -258,14 +283,7 @@ void preflightRequestWithoutRequestMethod() { Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.FORBIDDEN.getCode()); } @Test @@ -279,14 +297,7 @@ void preflightRequestWithRequestAndMethodHeaderButNoConfig() { Mockito.when(request.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) .thenReturn(true); Mockito.when(build.getCors()).thenReturn(defaultCorsMeta()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.FORBIDDEN.getCode()); } @Test @@ -305,25 +316,7 @@ void preflightRequestValidRequestAndConfig() { .allowedMethods("GET", "PUT") .allowedHeaders("Header1", "Header2") .build()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertEquals("*", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - log.info("{}", response.headerValues(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertEquals("GET, PUT", response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_METHODS)); - Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_MAX_AGE)); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @Test @@ -344,28 +337,7 @@ void preflightRequestAllowedHeaders() { .applyDefault()) .when(build) .getCors(); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header1")); - Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header2")); - Assertions.assertFalse(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header3")); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - Assertions.assertEquals(HttpStatus.OK.getCode(), response.status()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @Test @@ -385,27 +357,7 @@ void preflightRequestAllowsAllHeaders() { .allowedHeaders("*") .build() .applyDefault()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header1")); - Assertions.assertTrue(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("Header2")); - Assertions.assertFalse(response.header(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS) - .contains("*")); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @Test @@ -424,21 +376,7 @@ void preflightRequestWithEmptyHeaders() { .allowedHeaders("*") .build() .applyDefault()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.NO_CONTENT.getCode(), e.getStatusCode()); - Assertions.assertTrue(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_ORIGIN)); - Assertions.assertFalse(response.hasHeader(CorsHeaderFilter.ACCESS_CONTROL_ALLOW_HEADERS)); - Assertions.assertTrue(response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ORIGIN)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)); - Assertions.assertTrue( - response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @Test @@ -449,14 +387,22 @@ void preflightRequestWithNullConfig() { .thenReturn("GET"); Mockito.when(build.getCors()) .thenReturn(CorsMeta.builder().allowedOrigins("*").build()); - try { - processor.process(request, response); - Assertions.fail(); - } catch (HttpResultPayloadException e) { - Assertions.assertEquals(HttpStatus.FORBIDDEN.getCode(), e.getStatusCode()); - } catch (Exception e) { - Assertions.fail(); - } + processor.preLightProcess(request, response, HttpStatus.FORBIDDEN.getCode()); + } + + @Test + void preflightRequestCredentials() { + Mockito.when(request.method()).thenReturn(HttpMethods.OPTIONS.name()); + Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); + Mockito.when(request.header(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD)) + .thenReturn("GET"); + Mockito.doReturn(true).when(request).hasHeader(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_METHOD); + Mockito.when(request.header(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Header1"); + Mockito.doReturn(CorsMeta.builder() + .allowedOrigins("https://domain1.com", "https://domain2.com", "http://domain3.example") + .allowedHeaders("Header1").allowCredentials(true).build().applyDefault()).when(build).getCors(); + processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @Test @@ -473,4 +419,6 @@ void preventDuplicatedVaryHeaders() { Assertions.assertTrue( response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } + + } From db4503bb56a472902781ef7de7308926a6b24e45 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 21 May 2024 15:06:40 +0800 Subject: [PATCH 60/62] test(): Refactor the test class and add credential test cases --- .../tri/rest/cors/CorsHeaderFilterTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index 8e0911f4779..74c0ddd271c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -209,7 +209,7 @@ void actualRequestCredentials() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); Mockito.doReturn(CorsMeta.builder() - .allowedOrigins( "https://domain1.com","https://domain2.com") + .allowedOrigins("https://domain1.com", "https://domain2.com") .allowCredentials(true) .build() .applyDefault()) @@ -400,8 +400,13 @@ void preflightRequestCredentials() { Mockito.when(request.header(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)) .thenReturn("Header1"); Mockito.doReturn(CorsMeta.builder() - .allowedOrigins("https://domain1.com", "https://domain2.com", "http://domain3.example") - .allowedHeaders("Header1").allowCredentials(true).build().applyDefault()).when(build).getCors(); + .allowedOrigins("https://domain1.com", "https://domain2.com", "http://domain3.example") + .allowedHeaders("Header1") + .allowCredentials(true) + .build() + .applyDefault()) + .when(build) + .getCors(); processor.preLightProcess(request, response, HttpStatus.NO_CONTENT.getCode()); } @@ -419,6 +424,4 @@ void preventDuplicatedVaryHeaders() { Assertions.assertTrue( response.header(CorsHeaderFilter.VARY).contains(CorsHeaderFilter.ACCESS_CONTROL_REQUEST_HEADERS)); } - - } From edf93638f76b41721a19378f6f2f54f20a14f671 Mon Sep 17 00:00:00 2001 From: oxsean Date: Wed, 22 May 2024 03:41:37 +0000 Subject: [PATCH 61/62] fix(rest): revert api HeaderFilter --- .../src/main/java/org/apache/dubbo/rpc/HeaderFilter.java | 2 +- .../java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java | 3 ++- .../rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java index 04dd983305d..983c8c5d8b0 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/HeaderFilter.java @@ -22,5 +22,5 @@ @SPI(scope = ExtensionScope.FRAMEWORK) public interface HeaderFilter { - void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException; + RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException; } diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java index 3b6abb02d28..c8d8c6e1da9 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/TokenHeaderFilter.java @@ -31,7 +31,7 @@ @Activate public class TokenHeaderFilter implements HeaderFilter { @Override - public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { String token = invoker.getUrl().getParameter(TOKEN_KEY); if (ConfigUtils.isNotEmpty(token)) { Class serviceType = invoker.getInterface(); @@ -46,5 +46,6 @@ public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcExcep + ", consumer incorrect token is " + remoteToken); } } + return invocation; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java index c96c1469728..bb6f0927fbb 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java @@ -27,12 +27,13 @@ public abstract class RestHeaderFilterAdapter implements HeaderFilter { @Override - public void invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); invoke(invoker, invocation, request, response); } + return invocation; } protected abstract void invoke( From 852ad86c59719448b95ef957bc86e800ecfad877 Mon Sep 17 00:00:00 2001 From: liu Date: Wed, 22 May 2024 12:40:07 +0800 Subject: [PATCH 62/62] fix(): accept sonar issue --- .../tri/rest/mapping/meta/CorsMeta.java | 41 +++++++++++-------- .../tri/rest/cors/CorsHeaderFilterTest.java | 6 +-- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index ee5e8b02d09..d5545c5858a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -103,35 +103,44 @@ public boolean isEmpty() { } public CorsMeta applyDefault() { - String[] allowedOrigins = null; - Pattern[] allowedOriginsPatterns = null; + String[] allowedOriginArray = null; + Pattern[] allowedOriginPatternArray = null; if (this.allowedOrigins.length == 0) { - allowedOrigins = new String[] {ANY_VALUE}; - allowedOriginsPatterns = new Pattern[] {null}; + allowedOriginArray = new String[] {ANY_VALUE}; + allowedOriginPatternArray = new Pattern[] {null}; } - String[] allowedMethods = null; + + String[] allowedMethodArray = null; if (this.allowedMethods.length == 0) { - allowedMethods = new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}; + allowedMethodArray = + new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}; } - String[] allowedHeaders = null; + + String[] allowedHeaderArray = null; if (this.allowedHeaders.length == 0) { - allowedHeaders = new String[] {ANY_VALUE}; + allowedHeaderArray = new String[] {ANY_VALUE}; } - Long maxAge = null; + + Long maxAgeValue = null; if (this.maxAge == null) { - maxAge = 1800L; + maxAgeValue = 1800L; } - if (allowedOrigins == null && allowedMethods == null && allowedHeaders == null && maxAge == null) { + + if (allowedOriginArray == null + && allowedMethodArray == null + && allowedHeaderArray == null + && maxAgeValue == null) { return this; } + return new CorsMeta( - allowedOrigins == null ? this.allowedOrigins : allowedOrigins, - allowedOriginsPatterns == null ? this.allowedOriginsPatterns : allowedOriginsPatterns, - allowedMethods == null ? this.allowedMethods : allowedMethods, - allowedHeaders == null ? this.allowedHeaders : allowedHeaders, + allowedOriginArray == null ? this.allowedOrigins : allowedOriginArray, + allowedOriginPatternArray == null ? this.allowedOriginsPatterns : allowedOriginPatternArray, + allowedMethodArray == null ? this.allowedMethods : allowedMethodArray, + allowedHeaderArray == null ? this.allowedHeaders : allowedHeaderArray, exposedHeaders, allowCredentials, - maxAge); + maxAgeValue); } public CorsMeta combine(CorsMeta other) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java index 74c0ddd271c..70ef1144834 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilterTest.java @@ -32,13 +32,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; class CorsHeaderFilterTest { - private static final Logger log = LoggerFactory.getLogger(CorsHeaderFilterTest.class); - private HttpRequest request; private HttpResponse response; @@ -225,7 +221,7 @@ void actualRequestCredentials() { } @Test - void actualRequestCredentialsWithWildcardOrigin() throws Exception { + void actualRequestCredentialsWithWildcardOrigin() { Mockito.when(request.method()).thenReturn(HttpMethods.GET.name()); Mockito.when(request.header(CorsHeaderFilter.ORIGIN)).thenReturn("https://domain2.com"); Mockito.doReturn(CorsMeta.builder()