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}