Skip to content

Commit 8f8b637

Browse files
authored
Enabled NullAway for spnego and webdav packages (AsyncHttpClient#1886)
* Enabled NullAway for WebDav package * Enabled NullAway for spnego package
1 parent 6f1fb81 commit 8f8b637

File tree

7 files changed

+58
-34
lines changed

7 files changed

+58
-34
lines changed

client/src/main/java/org/asynchttpclient/spnego/NamePasswordCallbackHandler.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.asynchttpclient.spnego;
1717

18+
import org.jetbrains.annotations.Nullable;
1819
import org.slf4j.Logger;
1920
import org.slf4j.LoggerFactory;
2021

@@ -32,14 +33,14 @@ public class NamePasswordCallbackHandler implements CallbackHandler {
3233
private static final Class<?>[] PASSWORD_CALLBACK_TYPES = new Class<?>[]{Object.class, char[].class, String.class};
3334

3435
private final String username;
35-
private final String password;
36-
private final String passwordCallbackName;
36+
private final @Nullable String password;
37+
private final @Nullable String passwordCallbackName;
3738

38-
public NamePasswordCallbackHandler(String username, String password) {
39+
public NamePasswordCallbackHandler(String username, @Nullable String password) {
3940
this(username, password, null);
4041
}
4142

42-
public NamePasswordCallbackHandler(String username, String password, String passwordCallbackName) {
43+
public NamePasswordCallbackHandler(String username, @Nullable String password, @Nullable String passwordCallbackName) {
4344
this.username = username;
4445
this.password = password;
4546
this.passwordCallbackName = passwordCallbackName;
@@ -54,7 +55,7 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback
5455
((NameCallback) callback).setName(username);
5556
} else if (callback instanceof PasswordCallback) {
5657
PasswordCallback pwCallback = (PasswordCallback) callback;
57-
pwCallback.setPassword(password.toCharArray());
58+
pwCallback.setPassword(password != null ? password.toCharArray() : null);
5859
} else if (!invokePasswordCallback(callback)) {
5960
String errorMsg = "Unsupported callback type " + callback.getClass().getName();
6061
log.info(errorMsg);
@@ -80,7 +81,7 @@ private boolean invokePasswordCallback(Callback callback) {
8081
try {
8182
Method method = callback.getClass().getMethod(cbname, arg);
8283
Object[] args = {
83-
arg == String.class ? password : password.toCharArray()
84+
arg == String.class ? password : password != null ? password.toCharArray() : null
8485
};
8586
method.invoke(callback, args);
8687
return true;

client/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java

+19-16
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.ietf.jgss.GSSManager;
4343
import org.ietf.jgss.GSSName;
4444
import org.ietf.jgss.Oid;
45+
import org.jetbrains.annotations.Nullable;
4546
import org.slf4j.Logger;
4647
import org.slf4j.LoggerFactory;
4748

@@ -70,17 +71,19 @@ public class SpnegoEngine {
7071
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
7172
private static final Map<String, SpnegoEngine> instances = new HashMap<>();
7273
private final Logger log = LoggerFactory.getLogger(getClass());
73-
private final SpnegoTokenGenerator spnegoGenerator;
74-
private final String username;
75-
private final String password;
76-
private final String servicePrincipalName;
77-
private final String realmName;
74+
private final @Nullable SpnegoTokenGenerator spnegoGenerator;
75+
private final @Nullable String username;
76+
private final @Nullable String password;
77+
private final @Nullable String servicePrincipalName;
78+
private final @Nullable String realmName;
7879
private final boolean useCanonicalHostname;
79-
private final String loginContextName;
80-
private final Map<String, String> customLoginConfig;
80+
private final @Nullable String loginContextName;
81+
private final @Nullable Map<String, String> customLoginConfig;
8182

82-
public SpnegoEngine(final String username, final String password, final String servicePrincipalName, final String realmName, final boolean useCanonicalHostname,
83-
final Map<String, String> customLoginConfig, final String loginContextName, final SpnegoTokenGenerator spnegoGenerator) {
83+
public SpnegoEngine(final @Nullable String username, final @Nullable String password,
84+
final @Nullable String servicePrincipalName, final @Nullable String realmName,
85+
final boolean useCanonicalHostname, final @Nullable Map<String, String> customLoginConfig,
86+
final @Nullable String loginContextName, final @Nullable SpnegoTokenGenerator spnegoGenerator) {
8487
this.username = username;
8588
this.password = password;
8689
this.servicePrincipalName = servicePrincipalName;
@@ -95,8 +98,10 @@ public SpnegoEngine() {
9598
this(null, null, null, null, true, null, null, null);
9699
}
97100

98-
public static SpnegoEngine instance(final String username, final String password, final String servicePrincipalName, final String realmName,
99-
final boolean useCanonicalHostname, final Map<String, String> customLoginConfig, final String loginContextName) {
101+
public static SpnegoEngine instance(final @Nullable String username, final @Nullable String password,
102+
final @Nullable String servicePrincipalName, final @Nullable String realmName,
103+
final boolean useCanonicalHostname, final @Nullable Map<String, String> customLoginConfig,
104+
final @Nullable String loginContextName) {
100105
String key = "";
101106
if (customLoginConfig != null && !customLoginConfig.isEmpty()) {
102107
StringBuilder customLoginConfigKeyValues = new StringBuilder();
@@ -151,7 +156,6 @@ public String generateToken(String host) throws SpnegoEngineException {
151156
// Try SPNEGO by default, fall back to Kerberos later if error
152157
negotiationOid = new Oid(SPNEGO_OID);
153158

154-
boolean tryKerberos = false;
155159
String spn = getCompleteServicePrincipalName(host);
156160
try {
157161
GSSManager manager = GSSManager.getInstance();
@@ -181,13 +185,12 @@ public String generateToken(String host) throws SpnegoEngineException {
181185
// Rethrow any other exception.
182186
if (ex.getMajor() == GSSException.BAD_MECH) {
183187
log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
184-
tryKerberos = true;
185188
} else {
186189
throw ex;
187190
}
188191

189192
}
190-
if (tryKerberos) {
193+
if (gssContext == null) {
191194
/* Kerberos v5 GSS-API mechanism defined in RFC 1964. */
192195
log.debug("Using Kerberos MECH {}", KERBEROS_OID);
193196
negotiationOid = new Oid(KERBEROS_OID);
@@ -270,14 +273,14 @@ private String getCanonicalHostname(String hostname) {
270273
return canonicalHostname;
271274
}
272275

273-
private CallbackHandler getUsernamePasswordHandler() {
276+
private @Nullable CallbackHandler getUsernamePasswordHandler() {
274277
if (username == null) {
275278
return null;
276279
}
277280
return new NamePasswordCallbackHandler(username, password);
278281
}
279282

280-
public Configuration getLoginConfiguration() {
283+
public @Nullable Configuration getLoginConfiguration() {
281284
if (customLoginConfig != null && !customLoginConfig.isEmpty()) {
282285
return new Configuration() {
283286
@Override

client/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,20 @@
1515
*/
1616
package org.asynchttpclient.spnego;
1717

18+
import org.jetbrains.annotations.Nullable;
19+
1820
/**
1921
* Signals SPNEGO protocol failure.
2022
*/
2123
public class SpnegoEngineException extends Exception {
2224

2325
private static final long serialVersionUID = -3123799505052881438L;
2426

25-
public SpnegoEngineException(String message) {
27+
public SpnegoEngineException(@Nullable String message) {
2628
super(message);
2729
}
2830

29-
public SpnegoEngineException(String message, Throwable cause) {
31+
public SpnegoEngineException(@Nullable String message, Throwable cause) {
3032
super(message, cause);
3133
}
3234
}

client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.asynchttpclient.HttpResponseStatus;
1919
import org.asynchttpclient.Response;
2020
import org.asynchttpclient.netty.NettyResponse;
21+
import org.jetbrains.annotations.Nullable;
2122
import org.slf4j.Logger;
2223
import org.slf4j.LoggerFactory;
2324
import org.w3c.dom.Document;
@@ -45,8 +46,8 @@ public abstract class WebDavCompletionHandlerBase<T> implements AsyncHandler<T>
4546
private static final Logger LOGGER = LoggerFactory.getLogger(WebDavCompletionHandlerBase.class);
4647
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
4748
private final List<HttpResponseBodyPart> bodyParts = Collections.synchronizedList(new ArrayList<>());
48-
private HttpResponseStatus status;
49-
private HttpHeaders headers;
49+
private @Nullable HttpResponseStatus status;
50+
private @Nullable HttpHeaders headers;
5051

5152
static {
5253
DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
@@ -78,19 +79,20 @@ public final State onHeadersReceived(final HttpHeaders headers) {
7879
return State.CONTINUE;
7980
}
8081

81-
private Document readXMLResponse(InputStream stream) {
82+
private Document readXMLResponse(InputStream stream, HttpResponseStatus initialStatus) {
8283
Document document;
8384
try {
8485
document = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse(stream);
85-
parse(document);
86+
status = parse(document, initialStatus);
8687
} catch (SAXException | IOException | ParserConfigurationException e) {
8788
LOGGER.error(e.getMessage(), e);
8889
throw new RuntimeException(e);
8990
}
9091
return document;
9192
}
9293

93-
private void parse(Document document) {
94+
private static HttpResponseStatus parse(Document document, HttpResponseStatus initialStatus) {
95+
HttpResponseStatus status = initialStatus;
9496
Element element = document.getDocumentElement();
9597
NodeList statusNode = element.getElementsByTagName("status");
9698
for (int i = 0; i < statusNode.getLength(); i++) {
@@ -101,14 +103,15 @@ private void parse(Document document) {
101103
String statusText = value.substring(value.lastIndexOf(' '));
102104
status = new HttpStatusWrapper(status, statusText, statusCode);
103105
}
106+
return status;
104107
}
105108

106109
@Override
107110
public final T onCompleted() throws Exception {
108111
if (status != null) {
109112
Document document = null;
110113
if (status.getStatusCode() == 207) {
111-
document = readXMLResponse(new NettyResponse(status, headers, bodyParts).getResponseBodyAsStream());
114+
document = readXMLResponse(new NettyResponse(status, headers, bodyParts).getResponseBodyAsStream(), status);
112115
}
113116
// recompute response as readXMLResponse->parse might have updated it
114117
return onCompleted(new WebDavResponse(new NettyResponse(status, headers, bodyParts), document));

client/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.netty.handler.codec.http.cookie.Cookie;
1717
import org.asynchttpclient.Response;
1818
import org.asynchttpclient.uri.Uri;
19+
import org.jetbrains.annotations.Nullable;
1920
import org.w3c.dom.Document;
2021

2122
import java.io.InputStream;
@@ -30,9 +31,9 @@
3031
public class WebDavResponse implements Response {
3132

3233
private final Response response;
33-
private final Document document;
34+
private final @Nullable Document document;
3435

35-
WebDavResponse(Response response, Document document) {
36+
WebDavResponse(Response response, @Nullable Document document) {
3637
this.response = response;
3738
this.document = document;
3839
}
@@ -132,7 +133,7 @@ public SocketAddress getLocalAddress() {
132133
return response.getLocalAddress();
133134
}
134135

135-
public Document getBodyAsXML() {
136+
public @Nullable Document getBodyAsXML() {
136137
return document;
137138
}
138139
}

client/src/test/java/org/asynchttpclient/spnego/SpnegoEngineTest.java

+14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.asynchttpclient.AbstractBasicTest;
2222
import org.junit.jupiter.api.AfterEach;
2323
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
2425

2526
import java.io.File;
2627
import java.util.HashMap;
@@ -96,6 +97,19 @@ public void testSpnegoGenerateTokenWithUsernamePassword() throws Exception {
9697
assertTrue(token.startsWith("YII"));
9798
}
9899

100+
@Test
101+
public void testSpnegoGenerateTokenWithNullPasswordFail() {
102+
SpnegoEngine spnegoEngine = new SpnegoEngine("alice",
103+
null,
104+
"bob",
105+
"service.ws.apache.org",
106+
false,
107+
null,
108+
"alice",
109+
null);
110+
assertThrows(SpnegoEngineException.class, () -> spnegoEngine.generateToken("localhost"), "No password provided");
111+
}
112+
99113
@RepeatedIfExceptionsTest(repeats = 5)
100114
public void testSpnegoGenerateTokenWithUsernamePasswordFail() throws Exception {
101115
SpnegoEngine spnegoEngine = new SpnegoEngine("alice",

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
-Xep:NullOptional:ERROR
234234
-XepExcludedPaths:.*/src/test/java/.*
235235
-XepOpt:NullAway:AnnotatedPackages=org.asynchttpclient
236-
-XepOpt:NullAway:UnannotatedSubPackages=org.asynchttpclient.netty,org.asynchttpclient.ntlm,org.asynchttpclient.request,org.asynchttpclient.spnego,org.asynchttpclient.webdav,org.asynchttpclient.ws
236+
-XepOpt:NullAway:UnannotatedSubPackages=org.asynchttpclient.netty,org.asynchttpclient.ntlm,org.asynchttpclient.request,org.asynchttpclient.ws
237237
-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true
238238
-Xep:NullAway:ERROR
239239
</arg>

0 commit comments

Comments
 (0)