Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.solr.security;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableSet;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.protocol.HttpContext;
import org.apache.solr.client.solrj.impl.Http2SolrClient;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SpecProvider;
import org.apache.solr.common.StringUtils;
import org.apache.solr.common.util.Base64;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.Utils;
import org.apache.solr.common.util.ValidatingJsonMap;
import org.apache.solr.security.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode;
import org.eclipse.jetty.client.api.Request;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.HttpsJwks;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.InvalidJwtSignatureException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Authenticaion plugin that finds logged in user by validating the signature of a JWT token
*/
public class JWTAuthPlugin extends AuthenticationPlugin implements SpecProvider, ConfigEditablePlugin {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String PARAM_BLOCK_UNKNOWN = "blockUnknown";
private static final String PARAM_REQUIRE_SUBJECT = "requireSub";
private static final String PARAM_REQUIRE_ISSUER = "requireIss";
private static final String PARAM_PRINCIPAL_CLAIM = "principalClaim";
private static final String PARAM_REQUIRE_EXPIRATIONTIME = "requireExp";
private static final String PARAM_ALG_WHITELIST = "algWhitelist";
private static final String PARAM_JWK_CACHE_DURATION = "jwkCacheDur";
private static final String PARAM_CLAIMS_MATCH = "claimsMatch";
private static final String PARAM_SCOPE = "scope";
private static final String PARAM_ADMINUI_SCOPE = "adminUiScope";
private static final String PARAM_REDIRECT_URIS = "redirectUris";
private static final String PARAM_ISSUERS = "issuers";
private static final String PARAM_REALM = "realm";
private static final String DEFAULT_AUTH_REALM = "solr-jwt";
private static final String CLAIM_SCOPE = "scope";
private static final long RETRY_INIT_DELAY_SECONDS = 30;
private static final long DEFAULT_REFRESH_REPRIEVE_THRESHOLD = 5000;
static final String PRIMARY_ISSUER = "PRIMARY";
private static final Set PROPS = ImmutableSet.of(PARAM_BLOCK_UNKNOWN,
PARAM_REQUIRE_SUBJECT, PARAM_PRINCIPAL_CLAIM, PARAM_REQUIRE_EXPIRATIONTIME, PARAM_ALG_WHITELIST,
PARAM_JWK_CACHE_DURATION, PARAM_CLAIMS_MATCH, PARAM_SCOPE, PARAM_REALM,
PARAM_ADMINUI_SCOPE, PARAM_REDIRECT_URIS, PARAM_REQUIRE_ISSUER, PARAM_ISSUERS,
// These keys are supported for now to enable PRIMARY issuer config through top-level keys
JWTIssuerConfig.PARAM_JWK_URL, JWTIssuerConfig.PARAM_JWKS_URL, JWTIssuerConfig.PARAM_JWK, JWTIssuerConfig.PARAM_ISSUER,
JWTIssuerConfig.PARAM_CLIENT_ID, JWTIssuerConfig.PARAM_WELL_KNOWN_URL, JWTIssuerConfig.PARAM_AUDIENCE,
JWTIssuerConfig.PARAM_AUTHORIZATION_ENDPOINT);
private JwtConsumer jwtConsumer;
private boolean requireExpirationTime;
private List algWhitelist;
private String principalClaim;
private HashMap claimsMatchCompiled;
private boolean blockUnknown;
private List requiredScopes = new ArrayList<>();
private Map pluginConfig;
private Instant lastInitTime = Instant.now();
private String adminUiScope;
private List redirectUris;
private List issuerConfigs;
private boolean requireIssuer;
private JWTVerificationkeyResolver verificationKeyResolver;
String realm;
/**
* Initialize plugin
*/
public JWTAuthPlugin() {}
@SuppressWarnings("unchecked")
@Override
public void init(Map pluginConfig) {
this.pluginConfig = pluginConfig;
this.issuerConfigs = null;
List unknownKeys = pluginConfig.keySet().stream().filter(k -> !PROPS.contains(k)).collect(Collectors.toList());
unknownKeys.remove("class");
unknownKeys.remove("");
if (!unknownKeys.isEmpty()) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Invalid JwtAuth configuration parameter " + unknownKeys);
}
blockUnknown = Boolean.parseBoolean(String.valueOf(pluginConfig.getOrDefault(PARAM_BLOCK_UNKNOWN, false)));
requireIssuer = Boolean.parseBoolean(String.valueOf(pluginConfig.getOrDefault(PARAM_REQUIRE_ISSUER, "true")));
requireExpirationTime = Boolean.parseBoolean(String.valueOf(pluginConfig.getOrDefault(PARAM_REQUIRE_EXPIRATIONTIME, "true")));
if (pluginConfig.get(PARAM_REQUIRE_SUBJECT) != null) {
log.warn("Parameter {} is no longer used and may generate error in a later version. A subject claim is now always required",
PARAM_REQUIRE_SUBJECT);
}
principalClaim = (String) pluginConfig.getOrDefault(PARAM_PRINCIPAL_CLAIM, "sub");
algWhitelist = (List) pluginConfig.get(PARAM_ALG_WHITELIST);
realm = (String) pluginConfig.getOrDefault(PARAM_REALM, DEFAULT_AUTH_REALM);
Map claimsMatch = (Map) pluginConfig.get(PARAM_CLAIMS_MATCH);
claimsMatchCompiled = new HashMap<>();
if (claimsMatch != null) {
for (Map.Entry entry : claimsMatch.entrySet()) {
claimsMatchCompiled.put(entry.getKey(), Pattern.compile(entry.getValue()));
}
}
String requiredScopesStr = (String) pluginConfig.get(PARAM_SCOPE);
if (!StringUtils.isEmpty(requiredScopesStr)) {
requiredScopes = Arrays.asList(requiredScopesStr.split("\\s+"));
}
long jwkCacheDuration = Long.parseLong((String) pluginConfig.getOrDefault(PARAM_JWK_CACHE_DURATION, "3600"));
JWTIssuerConfig.setHttpsJwksFactory(new JWTIssuerConfig.HttpsJwksFactory(jwkCacheDuration, DEFAULT_REFRESH_REPRIEVE_THRESHOLD));
issuerConfigs = new ArrayList<>();
// Try to parse an issuer from top level config, and add first (primary issuer)
Optional topLevelIssuer = parseIssuerFromTopLevelConfig(pluginConfig);
topLevelIssuer.ifPresent(ic -> {
issuerConfigs.add(ic);
log.warn("JWTAuthPlugin issuer is configured using top-level configuration keys. Please consider using the 'issuers' array instead.");
});
// Add issuers from 'issuers' key
issuerConfigs.addAll(parseIssuers(pluginConfig));
verificationKeyResolver = new JWTVerificationkeyResolver(issuerConfigs, requireIssuer);
if (issuerConfigs.size() > 0 && getPrimaryIssuer().getAuthorizationEndpoint() != null) {
adminUiScope = (String) pluginConfig.get(PARAM_ADMINUI_SCOPE);
if (adminUiScope == null && requiredScopes.size() > 0) {
adminUiScope = requiredScopes.get(0);
log.warn("No adminUiScope given, using first scope in 'scope' list as required scope for accessing Admin UI");
}
if (adminUiScope == null) {
adminUiScope = "solr";
log.info("No adminUiScope provided, fallback to 'solr' as required scope for Admin UI login may not work");
}
Object redirectUrisObj = pluginConfig.get(PARAM_REDIRECT_URIS);
redirectUris = Collections.emptyList();
if (redirectUrisObj != null) {
if (redirectUrisObj instanceof String) {
redirectUris = Collections.singletonList((String) redirectUrisObj);
} else if (redirectUrisObj instanceof List) {
redirectUris = (List) redirectUrisObj;
}
}
}
initConsumer();
lastInitTime = Instant.now();
}
@SuppressWarnings("unchecked")
private Optional parseIssuerFromTopLevelConfig(Map conf) {
try {
if (conf.get(JWTIssuerConfig.PARAM_JWK_URL) != null) {
log.warn("Configuration uses deprecated key {}. Please use {} instead", JWTIssuerConfig.PARAM_JWK_URL, JWTIssuerConfig.PARAM_JWKS_URL);
}
JWTIssuerConfig primary = new JWTIssuerConfig(PRIMARY_ISSUER)
.setIss((String) conf.get(JWTIssuerConfig.PARAM_ISSUER))
.setAud((String) conf.get(JWTIssuerConfig.PARAM_AUDIENCE))
.setJwksUrl(conf.get(JWTIssuerConfig.PARAM_JWKS_URL) != null ? conf.get(JWTIssuerConfig.PARAM_JWKS_URL) : conf.get(JWTIssuerConfig.PARAM_JWK_URL))
.setAuthorizationEndpoint((String) conf.get(JWTIssuerConfig.PARAM_AUTHORIZATION_ENDPOINT))
.setClientId((String) conf.get(JWTIssuerConfig.PARAM_CLIENT_ID))
.setWellKnownUrl((String) conf.get(JWTIssuerConfig.PARAM_WELL_KNOWN_URL));
if (conf.get(JWTIssuerConfig.PARAM_JWK) != null) {
primary.setJsonWebKeySet(JWTIssuerConfig.parseJwkSet((Map) conf.get(JWTIssuerConfig.PARAM_JWK)));
}
if (primary.isValid()) {
log.debug("Found issuer in top level config");
primary.init();
return Optional.of(primary);
} else {
log.debug("No issuer configured in top level config");
return Optional.empty();
}
} catch (JoseException je) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Failed parsing issuer from top level config", je);
}
}
/**
* Fetch the primary issuer to be used for Admin UI authentication. Callers of this method must ensure that at least
* one issuer is configured. The primary issuer is defined as the first issuer configured in the list.
* @return JWTIssuerConfig object for the primary issuer
*/
JWTIssuerConfig getPrimaryIssuer() {
if (issuerConfigs.size() == 0) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "No issuers configured");
}
return issuerConfigs.get(0);
}
/**
* Initialize optional additional issuers configured in 'issuers' config map
* @param pluginConfig the main config object
* @return a list of parsed {@link JWTIssuerConfig} objects
*/
@SuppressWarnings("unchecked")
List parseIssuers(Map pluginConfig) {
List configs = new ArrayList<>();
try {
List