stoty commented on code in PR #577:
URL:
https://github.com/apache/httpcomponents-client/pull/577#discussion_r1747053464
##########
httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java:
##########
@@ -60,69 +60,167 @@
*
* @since 4.2
*
- * @deprecated Do not use. The GGS based experimental authentication schemes
are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
*/
-@Deprecated
-public abstract class GGSSchemeBase implements AuthScheme {
+// FIXME The class name looks like a Typo. Rename in 6.0 ?
+public abstract class GGSSchemeBase implements AuthSchemeV2 {
enum State {
UNINITIATED,
- CHALLENGE_RECEIVED,
- TOKEN_GENERATED,
+ TOKEN_READY,
+ TOKEN_SENT,
+ SUCCEEDED,
FAILED,
}
private static final Logger LOG =
LoggerFactory.getLogger(GGSSchemeBase.class);
private static final String NO_TOKEN = "";
private static final String KERBEROS_SCHEME = "HTTP";
- private final org.apache.hc.client5.http.auth.KerberosConfig config;
+
+ // The GSS spec does not specify how long the conversation can be. This
should be plenty.
+ // Realistically, we get one initial token, then one maybe one more for
mutual authentication.
+ private static final int MAX_GSS_CHALLENGES = 3;
+ private final KerberosConfig config;
private final DnsResolver dnsResolver;
+ private final boolean mutualAuth;
+ private int challengesLeft = MAX_GSS_CHALLENGES;
/** Authentication process state */
private State state;
private GSSCredential gssCredential;
+ private GSSContext gssContext;
private String challenge;
- private byte[] token;
+ private byte[] queuedToken = new byte[0];
- GGSSchemeBase(final org.apache.hc.client5.http.auth.KerberosConfig config,
final DnsResolver dnsResolver) {
+ GGSSchemeBase(final KerberosConfig config, final DnsResolver dnsResolver) {
super();
- this.config = config != null ? config :
org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT;
+ this.config = config != null ? config : KerberosConfig.DEFAULT;
this.dnsResolver = dnsResolver != null ? dnsResolver :
SystemDefaultDnsResolver.INSTANCE;
+ this.mutualAuth = config.getRequestMutualAuth() ==
KerberosConfig.Option.ENABLE;
this.state = State.UNINITIATED;
}
- GGSSchemeBase(final org.apache.hc.client5.http.auth.KerberosConfig config)
{
+ GGSSchemeBase(final KerberosConfig config) {
this(config, SystemDefaultDnsResolver.INSTANCE);
}
GGSSchemeBase() {
- this(org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT,
SystemDefaultDnsResolver.INSTANCE);
+ this(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
}
@Override
public String getRealm() {
return null;
}
+ // The AuthScheme API maps awkwardly to GSSAPI, where proccessChallange
and generateAuthResponse
+ // map to the same single method call. Hence the generated token is only
stored in this method.
@Override
public void processChallenge(
+ final HttpHost host,
final AuthChallenge authChallenge,
- final HttpContext context) throws MalformedChallengeException {
- Args.notNull(authChallenge, "AuthChallenge");
-
- this.challenge = authChallenge.getValue() != null ?
authChallenge.getValue() : NO_TOKEN;
+ final HttpContext context,
+ final boolean challenged) throws AuthenticationException {
- if (state == State.UNINITIATED) {
- token = Base64.decodeBase64(challenge.getBytes());
- state = State.CHALLENGE_RECEIVED;
- } else {
+ if (challengesLeft-- <= 0 ) {
if (LOG.isDebugEnabled()) {
final HttpClientContext clientContext =
HttpClientContext.cast(context);
final String exchangeId = clientContext.getExchangeId();
- LOG.debug("{} Authentication already attempted", exchangeId);
+ LOG.debug("{} GSS error: too many challenges received.
Infinite loop ?", exchangeId);
}
+ // TODO: Should we throw an exception ? There is a test for this
behaviour.
state = State.FAILED;
+ return;
+ }
+
+ final byte[] challengeToken = Base64.decodeBase64(authChallenge== null
? null : authChallenge.getValue());
+
+ final String authServer;
+ String hostname = host.getHostName();
+ if (config.getUseCanonicalHostname() != KerberosConfig.Option.DISABLE){
+ try {
+ hostname =
dnsResolver.resolveCanonicalHostname(host.getHostName());
+ } catch (final UnknownHostException ignore){
+ }
+ }
+ if (config.getStripPort() != KerberosConfig.Option.DISABLE) {
+ authServer = hostname;
+ } else {
+ authServer = hostname + ":" + host.getPort();
+ }
+
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext =
HttpClientContext.adapt(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} GSS init {}", exchangeId, authServer);
+ }
+ try {
+ queuedToken = generateToken(challengeToken, KERBEROS_SCHEME,
authServer);
+ switch (state) {
+ case UNINITIATED:
+ if (challenge != NO_TOKEN) {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext =
HttpClientContext.adapt(context);
+ final String exchangeId =
clientContext.getExchangeId();
+ LOG.debug("{} Internal GSS error: token received when
none was sent yet: {}", exchangeId, challengeToken);
+ }
+ // TODO Should we fail ? That would break existing tests
that send a token
+ // in the first response, which is against the RFC.
+ }
+ state = State.TOKEN_READY;
+ break;
+ case TOKEN_SENT:
+ if (challenged) {
+ state = State.TOKEN_READY;
+ } else if (mutualAuth){
+ // We should have received a valid mutualAuth token
+ if (!gssContext.isEstablished()) {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext =
+ HttpClientContext.adapt(context);
+ final String exchangeId =
clientContext.getExchangeId();
+ LOG.debug("{} GSSContext is not established ",
exchangeId);
+ }
+ state = State.FAILED;
+ // TODO should we have specific exception(s) for these
?
+ throw new AuthenticationException(
+ "requireMutualAuth is set but GSSContext is
not established");
+ } else if (!gssContext.getMutualAuthState()) {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext =
+ HttpClientContext.adapt(context);
+ final String exchangeId =
clientContext.getExchangeId();
+ LOG.debug("{} requireMutualAuth is set but
GSSAUthContext does not have"
+ + " mutualAuthState set", exchangeId);
+ }
+ state = State.FAILED;
+ throw new AuthenticationException(
+ "requireMutualAuth is set but GSSContext
mutualAuthState is not set");
+ } else {
+ state = State.SUCCEEDED;
+ }
+ }
+ break;
+ default:
+ state = State.FAILED;
+ throw new IllegalStateException("Illegal state: " + state);
+
+ }
+ } catch (final GSSException gsse) {
+ state = State.FAILED;
+ if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
+ || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
+ throw new InvalidCredentialsException(gsse.getMessage(), gsse);
+ }
+ if (gsse.getMajor() == GSSException.NO_CRED) {
+ throw new InvalidCredentialsException(gsse.getMessage(), gsse);
+ }
+ if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
+ || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
+ || gsse.getMajor() == GSSException.OLD_TOKEN) {
+ throw new AuthenticationException(gsse.getMessage(), gsse);
+ }
+ // other error
+ throw new AuthenticationException(gsse.getMessage(), gsse);
Review Comment:
Not really.
But the Auth. execption would have the same message as the cause.
We can write some new message for the Auth. exception, I just really hate
when a library swallows the original exception instead of wrapping it.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]