001/*
002 * Copyright 2022-2026 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 org.jspecify.annotations.NonNull;
020
021import java.util.Map;
022import java.util.Optional;
023import java.util.Set;
024import java.util.function.Function;
025
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Contract for types that authorize <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> requests.
030 * <p>
031 * Standard threadsafe implementations can be acquired via these factory methods:
032 * <ul>
033 *   <li>{@link #rejectAllInstance()} (don't permit CORS requests)</li>
034 *   <li>{@link #acceptAllInstance()} (permit all CORS requests, not recommended for production)</li>
035 *   <li>{@link #fromWhitelistedOrigins(Set)} (permit whitelisted origins only)</li>
036 *   <li>{@link #fromWhitelistedOrigins(Set, Function)}  (permit whitelisted origins only + control credentials behavior)</li>
037 *   <li>{@link #fromWhitelistAuthorizer(Function)} (permit origins via function)</li>
038 *   <li>{@link #fromWhitelistAuthorizer(Function, Function)} (permit origins via function + control credentials behavior)</li>
039 * </ul>
040 * See <a href="https://www.soklet.com/docs/cors">https://www.soklet.com/docs/cors</a> for detailed documentation.
041 *
042 * @author <a href="https://www.revetkn.com">Mark Allen</a>
043 */
044public interface CorsAuthorizer {
045        /**
046         * Authorizes a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">non-preflight CORS</a> request.
047         *
048         * @param request the request to authorize
049         * @param cors    the CORS data provided in the request
050         * @return a {@link CorsResponse} if authorized, or {@link Optional#empty()} if not authorized
051         */
052        @NonNull
053        Optional<CorsResponse> authorize(@NonNull Request request,
054                                                                                                                                         @NonNull Cors cors);
055
056        /**
057         * Authorizes a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request">CORS preflight</a> request.
058         *
059         * @param request                              the preflight request to authorize
060         * @param corsPreflight                        the CORS preflight data provided in the request
061         * @param availableResourceMethodsByHttpMethod <em>Resource Methods</em> that are available to serve requests according to parameters specified by the preflight data
062         * @return a {@link CorsPreflightResponse} if authorized, or {@link Optional#empty()} if not authorized
063         */
064        @NonNull
065        Optional<CorsPreflightResponse> authorizePreflight(@NonNull Request request,
066                                                                                                                                                                                                                 @NonNull CorsPreflight corsPreflight,
067                                                                                                                                                                                                                 @NonNull Map<@NonNull HttpMethod, @NonNull ResourceMethod> availableResourceMethodsByHttpMethod);
068
069        /**
070         * Acquires a threadsafe {@link CorsAuthorizer} configured to permit all cross-domain requests <strong>regardless of {@code Origin}</strong>.
071         * <p>
072         * The returned instance is guaranteed to be a JVM-wide singleton.
073         * <p>
074 * <strong>Note: the returned instance is generally unsafe for production - prefer {@link #fromWhitelistedOrigins(Set)} or {@link #fromWhitelistAuthorizer(Function)} for production systems.</strong>
075         *
076         * @return a {@code CorsAuthorizer} configured to permit all cross-domain requests
077         */
078        @NonNull
079        static CorsAuthorizer acceptAllInstance() {
080                return AllOriginsCorsAuthorizer.defaultInstance();
081        }
082
083        /**
084         * Acquires a threadsafe {@link CorsAuthorizer} configured to reject all cross-domain requests <strong>regardless of {@code Origin}</strong>.
085         * <p>
086         * The returned instance is guaranteed to be a JVM-wide singleton.
087         *
088         * @return a {@code CorsAuthorizer} configured to reject all cross-domain requests
089         */
090        @NonNull
091        static CorsAuthorizer rejectAllInstance() {
092                return NoOriginsCorsAuthorizer.defaultInstance();
093        }
094
095        /**
096         * 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}.
097         * <p>
098         * The returned {@link CorsAuthorizer} will set {@code Access-Control-Allow-Credentials} header to {@code true}. This behavior can be customized via {@link #fromWhitelistedOrigins(Set, Function)}.
099         * <p>
100         * Callers should not rely on reference identity; this method may return a new or cached instance.
101         *
102         * @param whitelistedOrigins the set of whitelisted origins
103         * @return a credentials-allowed {@code CorsAuthorizer} configured to accept only the specified {@code whitelistedOrigins}
104         */
105        @NonNull
106        static CorsAuthorizer fromWhitelistedOrigins(@NonNull Set<@NonNull String> whitelistedOrigins) {
107                requireNonNull(whitelistedOrigins);
108                return WhitelistedOriginsCorsAuthorizer.fromOrigins(whitelistedOrigins, (origin) -> false);
109        }
110
111        /**
112         * 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}.
113         * <p>
114         * 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.
115         * <p>
116         * 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 #fromWhitelistAuthorizer(Function, Function)}.
117         * <p>
118         * Callers should not rely on reference identity; this method may return a new or cached instance.
119         *
120         * @param whitelistedOrigins       the set of whitelisted origins
121         * @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
122         * @return a {@code CorsAuthorizer} configured to accept only the specified {@code whitelistedOrigins}, with {@code allowCredentialsResolver} dictating whether credentials are allowed
123         */
124        @NonNull
125        static CorsAuthorizer fromWhitelistedOrigins(@NonNull Set<@NonNull String> whitelistedOrigins,
126                                                                                                                                                                                         @NonNull Function<String, Boolean> allowCredentialsResolver) {
127                requireNonNull(whitelistedOrigins);
128                requireNonNull(allowCredentialsResolver);
129
130                return WhitelistedOriginsCorsAuthorizer.fromOrigins(whitelistedOrigins, allowCredentialsResolver);
131        }
132
133        /**
134         * Acquires a threadsafe {@link CorsAuthorizer} configured to accept only those cross-domain requests whose {@code Origin} is allowed by the provided {@code whitelistAuthorizer} function.
135         * <p>
136         * The {@code whitelistAuthorizer} function should return {@code true} if the supplied {@code Origin} is acceptable and {@code false} otherwise.
137         * <p>
138         * 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 #fromWhitelistAuthorizer(Function, Function)}.
139         * <p>
140         * Callers should not rely on reference identity; this method may return a new or cached instance.
141         *
142         * @param whitelistAuthorizer a function that returns {@code true} if the input is a whitelisted origin and {@code false} otherwise
143         * @return a credentials-allowed {@code CorsAuthorizer} configured to accept only the origins permitted by {@code whitelistAuthorizer}
144         */
145        @NonNull
146        static CorsAuthorizer fromWhitelistAuthorizer(@NonNull Function<String, Boolean> whitelistAuthorizer) {
147                requireNonNull(whitelistAuthorizer);
148                return WhitelistedOriginsCorsAuthorizer.fromAuthorizer(whitelistAuthorizer, (origin) -> false);
149        }
150
151        /**
152         * Acquires a threadsafe {@link CorsAuthorizer} configured to accept only those cross-domain requests whose {@code Origin} is allowed by the provided {@code whitelistAuthorizer} function.
153         * <p>
154         * The {@code whitelistAuthorizer} function should return {@code true} if the supplied {@code Origin} is acceptable and {@code false} otherwise.
155         * <p>
156         * 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.
157         * <p>
158         * Callers should not rely on reference identity; this method may return a new or cached instance.
159         *
160         * @param whitelistAuthorizer      a function that returns {@code true} if the input is a whitelisted origin and {@code false} otherwise
161         * @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
162         * @return a {@code CorsAuthorizer} configured to accept only the origins permitted by {@code whitelistAuthorizer}, with {@code allowCredentialsResolver} dictating whether credentials are allowed
163         */
164        @NonNull
165        static CorsAuthorizer fromWhitelistAuthorizer(@NonNull Function<String, Boolean> whitelistAuthorizer,
166                                                                                                                                                                                                        @NonNull Function<String, Boolean> allowCredentialsResolver) {
167                requireNonNull(whitelistAuthorizer);
168                requireNonNull(allowCredentialsResolver);
169
170                return WhitelistedOriginsCorsAuthorizer.fromAuthorizer(whitelistAuthorizer, allowCredentialsResolver);
171        }
172}