001/*
002 * Copyright 2022-2025 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import javax.annotation.Nonnull;
020import java.util.Map;
021import java.util.Optional;
022import java.util.Set;
023import java.util.function.Function;
024
025import static java.util.Objects.requireNonNull;
026
027/**
028 * Contract for types that authorize <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> requests.
029 * <p>
030 * Standard threadsafe implementations can be acquired via these factory methods:
031 * <ul>
032 *   <li>{@link #withRejectAllPolicy()} (don't permit CORS requests)</li>
033 *   <li>{@link #withAcceptAllPolicy()} (permit all CORS requests, not recommended for production)</li>
034 *   <li>{@link #withWhitelistedOrigins(Set)} (permit whitelisted origins only)</li>
035 *   <li>{@link #withWhitelistedOrigins(Set, Function)}  (permit whitelisted origins only + control credentials behavior)</li>
036 *   <li>{@link #withWhitelistAuthorizer(Function)} (permit origins via function)</li>
037 *   <li>{@link #withWhitelistAuthorizer(Function, Function)} (permit origins via function + control credentials behavior)</li>
038 * </ul>
039 * See <a href="https://www.soklet.com/docs/cors">https://www.soklet.com/docs/cors</a> for detailed documentation.
040 *
041 * @author <a href="https://www.revetkn.com">Mark Allen</a>
042 */
043public interface CorsAuthorizer {
044        /**
045         * Authorizes a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">non-preflight CORS</a> request.
046         *
047         * @param request the request to authorize
048         * @param cors    the CORS data provided in the request
049         * @return a {@link CorsResponse} if authorized, or {@link Optional#empty()} if not authorized
050         */
051        @Nonnull
052        Optional<CorsResponse> authorize(@Nonnull Request request,
053                                                                                                                                         @Nonnull Cors cors);
054
055        /**
056         * Authorizes a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request">CORS preflight</a> request.
057         *
058         * @param request                              the preflight request to authorize
059         * @param corsPreflight                        the CORS preflight data provided in the request
060         * @param availableResourceMethodsByHttpMethod <em>Resource Methods</em> that are available to serve requests according to parameters specified by the preflight data
061         * @return a {@link CorsPreflightResponse} if authorized, or {@link Optional#empty()} if not authorized
062         */
063        @Nonnull
064        Optional<CorsPreflightResponse> authorizePreflight(@Nonnull Request request,
065                                                                                                                                                                                                                 @Nonnull CorsPreflight corsPreflight,
066                                                                                                                                                                                                                 @Nonnull Map<HttpMethod, ResourceMethod> availableResourceMethodsByHttpMethod);
067
068        /**
069         * Acquires a threadsafe {@link CorsAuthorizer} configured to permit all cross-domain requests <strong>regardless of {@code Origin}</strong>.
070         * <p>
071         * The returned instance is guaranteed to be a JVM-wide singleton.
072         * <p>
073         * <strong>Note: the returned instance is generally unsafe for production - prefer {@link #withWhitelistedOrigins(Set)} or {@link #withWhitelistAuthorizer(Function)} for production systems.</strong>
074         *
075         * @return a {@code CorsAuthorizer} configured to permit all cross-domain requests
076         */
077        @Nonnull
078        static CorsAuthorizer withAcceptAllPolicy() {
079                return AllOriginsCorsAuthorizer.defaultInstance();
080        }
081
082        /**
083         * Acquires a threadsafe {@link CorsAuthorizer} configured to reject all cross-domain requests <strong>regardless of {@code Origin}</strong>.
084         * <p>
085         * The returned instance is guaranteed to be a JVM-wide singleton.
086         *
087         * @return a {@code CorsAuthorizer} configured to reject all cross-domain requests
088         */
089        @Nonnull
090        static CorsAuthorizer withRejectAllPolicy() {
091                return NoOriginsCorsAuthorizer.defaultInstance();
092        }
093
094        /**
095         * Acquires a threadsafe {@link CorsAuthorizer} configured to accept only those cross-domain requests whose {@code Origin} matches a value in the provided set of {@code whitelistedOrigins}.
096         * <p>
097         * The returned {@link CorsAuthorizer} will set {@code Access-Control-Allow-Credentials} header to {@code true}. This behavior can be customized via {@link #withWhitelistedOrigins(Set, Function)}.
098         * <p>
099         * Callers should not rely on reference identity; this method may return a new or cached instance.
100         *
101         * @param whitelistedOrigins the set of whitelisted origins
102         * @return a credentials-allowed {@code CorsAuthorizer} configured to accept only the specified {@code whitelistedOrigins}
103         */
104        @Nonnull
105        static CorsAuthorizer withWhitelistedOrigins(@Nonnull Set<String> whitelistedOrigins) {
106                requireNonNull(whitelistedOrigins);
107                return WhitelistedOriginsCorsAuthorizer.withOrigins(whitelistedOrigins, (origin) -> false);
108        }
109
110        /**
111         * Acquires a threadsafe {@link CorsAuthorizer} configured to accept only those cross-domain requests whose {@code Origin} matches a value in the provided set of {@code whitelistedOrigins}.
112         * <p>
113         * The provided {@code allowCredentialsResolver} is used to control the value of {@code Access-Control-Allow-Credentials}: it's a function which takes a normalized {@code Origin} as input and should return {@code true} if clients are permitted to include credentials in cross-origin HTTP requests and {@code false} otherwise.
114         * <p>
115         * The returned {@link CorsAuthorizer} will omit the {@code Access-Control-Allow-Credentials} response header to reduce CSRF attack surface area. This behavior can be customized via {@link #withWhitelistAuthorizer(Function, Function)}.
116         * <p>
117         * Callers should not rely on reference identity; this method may return a new or cached instance.
118         *
119         * @param whitelistedOrigins       the set of whitelisted origins
120         * @param allowCredentialsResolver function which takes a normalized {@code Origin} as input and should return {@code true} if clients are permitted to include credentials in cross-origin HTTP requests and {@code false} otherwise
121         * @return a {@code CorsAuthorizer} configured to accept only the specified {@code whitelistedOrigins}, with {@code allowCredentialsResolver} dictating whether credentials are allowed
122         */
123        @Nonnull
124        static CorsAuthorizer withWhitelistedOrigins(@Nonnull Set<String> whitelistedOrigins,
125                                                                                                                                                                                         @Nonnull Function<String, Boolean> allowCredentialsResolver) {
126                requireNonNull(whitelistedOrigins);
127                requireNonNull(allowCredentialsResolver);
128
129                return WhitelistedOriginsCorsAuthorizer.withOrigins(whitelistedOrigins, allowCredentialsResolver);
130        }
131
132        /**
133         * Acquires a threadsafe {@link CorsAuthorizer} configured to accept only those cross-domain requests whose {@code Origin} is allowed by the provided {@code whitelistAuthorizer} function.
134         * <p>
135         * The {@code whitelistAuthorizer} function should return {@code true} if the supplied {@code Origin} is acceptable and {@code false} otherwise.
136         * <p>
137         * The returned {@link CorsAuthorizer} will omit the {@code Access-Control-Allow-Credentials} response header to reduce CSRF attack surface area. This behavior can be customized via {@link #withWhitelistAuthorizer(Function, Function)}.
138         * <p>
139         * Callers should not rely on reference identity; this method may return a new or cached instance.
140         *
141         * @param whitelistAuthorizer a function that returns {@code true} if the input is a whitelisted origin and {@code false} otherwise
142         * @return a credentials-allowed {@code CorsAuthorizer} configured to accept only the origins permitted by {@code whitelistAuthorizer}
143         */
144        @Nonnull
145        static CorsAuthorizer withWhitelistAuthorizer(@Nonnull Function<String, Boolean> whitelistAuthorizer) {
146                requireNonNull(whitelistAuthorizer);
147                return WhitelistedOriginsCorsAuthorizer.withAuthorizer(whitelistAuthorizer, (origin) -> false);
148        }
149
150        /**
151         * Acquires a threadsafe {@link CorsAuthorizer} configured to accept only those cross-domain requests whose {@code Origin} is allowed by the provided {@code whitelistAuthorizer} function.
152         * <p>
153         * The {@code whitelistAuthorizer} function should return {@code true} if the supplied {@code Origin} is acceptable and {@code false} otherwise.
154         * <p>
155         * The provided {@code allowCredentialsResolver} is used to control the value of {@code Access-Control-Allow-Credentials}: it's a function which takes a normalized {@code Origin} as input and should return {@code true} if clients are permitted to include credentials in cross-origin HTTP requests and {@code false} otherwise.
156         * <p>
157         * Callers should not rely on reference identity; this method may return a new or cached instance.
158         *
159         * @param whitelistAuthorizer      a function that returns {@code true} if the input is a whitelisted origin and {@code false} otherwise
160         * @param allowCredentialsResolver function which takes a normalized {@code Origin} as input and should return {@code true} if clients are permitted to include credentials in cross-origin HTTP requests and {@code false} otherwise
161         * @return a {@code CorsAuthorizer} configured to accept only the origins permitted by {@code whitelistAuthorizer}, with {@code allowCredentialsResolver} dictating whether credentials are allowed
162         */
163        @Nonnull
164        static CorsAuthorizer withWhitelistAuthorizer(@Nonnull Function<String, Boolean> whitelistAuthorizer,
165                                                                                                                                                                                                @Nonnull Function<String, Boolean> allowCredentialsResolver) {
166                requireNonNull(whitelistAuthorizer);
167                requireNonNull(allowCredentialsResolver);
168
169                return WhitelistedOriginsCorsAuthorizer.withAuthorizer(whitelistAuthorizer, allowCredentialsResolver);
170        }
171}