V1.1 Legacy-API-Key-Fallback und Base-URL-Validierung korrigiert

This commit is contained in:
2026-04-09 06:29:42 +02:00
parent 5099ff4aca
commit 8fd9e350e5
4 changed files with 210 additions and 7 deletions
@@ -64,6 +64,15 @@ public class MultiProviderConfigurationParser {
/** Environment variable for the OpenAI-compatible provider API key. */
static final String ENV_OPENAI_API_KEY = "OPENAI_COMPATIBLE_API_KEY";
/**
* Legacy environment variable for the OpenAI-compatible provider API key.
* <p>
* Accepted as a fallback when {@code OPENAI_COMPATIBLE_API_KEY} is not set.
* Existing installations that set this variable continue to work without change.
* New installations should prefer {@code OPENAI_COMPATIBLE_API_KEY}.
*/
static final String ENV_LEGACY_OPENAI_API_KEY = "PDF_UMBENENNER_API_KEY";
/** Environment variable for the Anthropic Claude provider API key. */
static final String ENV_CLAUDE_API_KEY = "ANTHROPIC_API_KEY";
@@ -131,7 +140,7 @@ public class MultiProviderConfigurationParser {
String model = getOptionalString(props, PROP_OPENAI_MODEL);
int timeout = parseTimeoutSeconds(props, PROP_OPENAI_TIMEOUT);
String baseUrl = getOptionalString(props, PROP_OPENAI_BASE_URL);
String apiKey = resolveApiKey(props, PROP_OPENAI_API_KEY, ENV_OPENAI_API_KEY);
String apiKey = resolveOpenAiApiKey(props);
return new ProviderConfiguration(model, timeout, baseUrl, apiKey);
}
@@ -179,6 +188,33 @@ public class MultiProviderConfigurationParser {
}
}
/**
* Resolves the effective API key for the OpenAI-compatible provider.
* <p>
* Resolution order:
* <ol>
* <li>{@code OPENAI_COMPATIBLE_API_KEY} environment variable</li>
* <li>{@code PDF_UMBENENNER_API_KEY} environment variable (legacy fallback;
* accepted for backward compatibility with existing installations)</li>
* <li>{@code ai.provider.openai-compatible.apiKey} property</li>
* </ol>
*
* @param props the configuration properties
* @return the resolved API key; never {@code null}, but may be blank
*/
private String resolveOpenAiApiKey(Properties props) {
String primary = environmentLookup.apply(ENV_OPENAI_API_KEY);
if (primary != null && !primary.isBlank()) {
return primary.trim();
}
String legacy = environmentLookup.apply(ENV_LEGACY_OPENAI_API_KEY);
if (legacy != null && !legacy.isBlank()) {
return legacy.trim();
}
String propsValue = props.getProperty(PROP_OPENAI_API_KEY);
return (propsValue != null) ? propsValue.trim() : "";
}
/**
* Resolves the effective API key for a provider family.
* <p>
@@ -5,6 +5,7 @@ import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.config.provider.MultiProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfiguration;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
@@ -16,8 +17,9 @@ import java.util.List;
* <li>{@code ai.provider.active} refers to a recognised provider family.</li>
* <li>{@code model} is non-blank.</li>
* <li>{@code timeoutSeconds} is a positive integer.</li>
* <li>{@code baseUrl} is non-blank (required for the OpenAI-compatible family;
* the Claude family always has a default).</li>
* <li>{@code baseUrl} is a syntactically valid absolute URI with scheme {@code http} or
* {@code https} (required for the OpenAI-compatible family; the Claude family always
* has a default, but it is validated with the same rules).</li>
* <li>{@code apiKey} is non-blank after environment-variable precedence has been applied
* by {@link MultiProviderConfigurationParser}.</li>
* </ul>
@@ -83,16 +85,40 @@ public class MultiProviderConfigurationValidator {
}
/**
* Validates base URL presence.
* Validates the base URL of the active provider.
* <p>
* The URL must be:
* <ul>
* <li>non-blank</li>
* <li>a syntactically valid URI</li>
* <li>an absolute URI (has a scheme component)</li>
* <li>using scheme {@code http} or {@code https}</li>
* </ul>
* The OpenAI-compatible family requires an explicit base URL.
* The Claude family always has a default ({@code https://api.anthropic.com}) applied by the
* parser, so this check is a safety net rather than a primary enforcement mechanism.
* parser, so this check serves both as a primary and safety-net enforcement.
*/
private void validateBaseUrl(AiProviderFamily family, ProviderConfiguration config,
String providerLabel, List<String> errors) {
if (config.baseUrl() == null || config.baseUrl().isBlank()) {
String baseUrl = config.baseUrl();
if (baseUrl == null || baseUrl.isBlank()) {
errors.add("- " + providerLabel + ".baseUrl: must not be blank");
return;
}
try {
URI uri = URI.create(baseUrl);
if (!uri.isAbsolute()) {
errors.add("- " + providerLabel + ".baseUrl: must be an absolute URI with http or https scheme, got: '"
+ baseUrl + "'");
return;
}
String scheme = uri.getScheme();
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
errors.add("- " + providerLabel + ".baseUrl: scheme must be http or https, got: '"
+ scheme + "' in '" + baseUrl + "'");
}
} catch (IllegalArgumentException e) {
errors.add("- " + providerLabel + ".baseUrl: not a valid URI: '" + baseUrl + "' (" + e.getMessage() + ")");
}
}