diff --git a/rsocket/spring-security-rsocket.gradle b/rsocket/spring-security-rsocket.gradle index 6560a2f0b6..f0994bf593 100644 --- a/rsocket/spring-security-rsocket.gradle +++ b/rsocket/spring-security-rsocket.gradle @@ -1,3 +1,7 @@ +plugins { + id 'security-nullability' +} + apply plugin: 'io.spring.convention.spring-module' dependencies { diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/api/package-info.java b/rsocket/src/main/java/org/springframework/security/rsocket/api/package-info.java new file mode 100644 index 0000000000..19d4773322 --- /dev/null +++ b/rsocket/src/main/java/org/springframework/security/rsocket/api/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * Spring Security RSocket APIs. + */ +@NullMarked +package org.springframework.security.rsocket.api; + +import org.jspecify.annotations.NullMarked; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java index e5df1be987..322e9924e8 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadExchangeConverter.java @@ -24,6 +24,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.metadata.AuthMetadataCodec; import io.rsocket.metadata.WellKnownAuthType; import io.rsocket.metadata.WellKnownMimeType; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.codec.ByteArrayDecoder; @@ -66,7 +67,7 @@ public class AuthenticationPayloadExchangeConverter implements PayloadExchangeAu .flatMap((metadata) -> Mono.justOrEmpty(authentication(metadata))); } - private Authentication authentication(Map metadata) { + private @Nullable Authentication authentication(Map metadata) { byte[] authenticationMetadata = (byte[]) metadata.get("authentication"); if (authenticationMetadata == null) { return null; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/package-info.java b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/package-info.java new file mode 100644 index 0000000000..ecbb7cd03b --- /dev/null +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * Spring Security RSocket Authentication integration. + */ +@NullMarked +package org.springframework.security.rsocket.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authorization/package-info.java b/rsocket/src/main/java/org/springframework/security/rsocket/authorization/package-info.java new file mode 100644 index 0000000000..8146a049da --- /dev/null +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authorization/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * Spring Security RSocket authorization integration. + */ +@NullMarked +package org.springframework.security.rsocket.authorization; + +import org.jspecify.annotations.NullMarked; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/core/ContextPayloadInterceptorChain.java b/rsocket/src/main/java/org/springframework/security/rsocket/core/ContextPayloadInterceptorChain.java index d064c31c03..639c362445 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/core/ContextPayloadInterceptorChain.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/core/ContextPayloadInterceptorChain.java @@ -19,6 +19,7 @@ package org.springframework.security.rsocket.core; import java.util.List; import java.util.ListIterator; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import reactor.util.context.Context; @@ -41,11 +42,11 @@ import org.springframework.security.rsocket.api.PayloadInterceptorChain; */ class ContextPayloadInterceptorChain implements PayloadInterceptorChain { - private final PayloadInterceptor currentInterceptor; + private final @Nullable PayloadInterceptor currentInterceptor; - private final ContextPayloadInterceptorChain next; + private final @Nullable ContextPayloadInterceptorChain next; - private Context context; + private @Nullable Context context; ContextPayloadInterceptorChain(List interceptors) { if (interceptors == null) { @@ -68,18 +69,20 @@ class ContextPayloadInterceptorChain implements PayloadInterceptorChain { return interceptor; } - private ContextPayloadInterceptorChain(PayloadInterceptor currentInterceptor, ContextPayloadInterceptorChain next) { + private ContextPayloadInterceptorChain(@Nullable PayloadInterceptor currentInterceptor, + @Nullable ContextPayloadInterceptorChain next) { this.currentInterceptor = currentInterceptor; this.next = next; } @Override + @SuppressWarnings("NullAway") // Dataflow analysis limitation public Mono next(PayloadExchange exchange) { return Mono.defer(() -> shouldIntercept() ? this.currentInterceptor.intercept(exchange, this.next) : Mono.deferContextual(Mono::just).cast(Context.class).doOnNext((c) -> this.context = c).then()); } - Context getContext() { + @Nullable Context getContext() { if (this.next == null) { return this.context; } diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java index b2493d6ac7..2e46f0963b 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java @@ -28,6 +28,7 @@ import reactor.util.context.Context; import org.springframework.security.rsocket.api.PayloadExchangeType; import org.springframework.security.rsocket.api.PayloadInterceptor; +import org.springframework.util.Assert; import org.springframework.util.MimeType; /** @@ -91,6 +92,7 @@ class PayloadInterceptorRSocket extends RSocketProxy { public Flux requestChannel(Publisher payloads) { return Flux.from(payloads).switchOnFirst((signal, innerFlux) -> { Payload firstPayload = signal.get(); + Assert.notNull(firstPayload, "payload cannot be null"); return intercept(PayloadExchangeType.REQUEST_CHANNEL, firstPayload) .flatMapMany((context) -> innerFlux.index() .concatMap((tuple) -> justOrIntercept(tuple.getT1(), tuple.getT2())) diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java index 77034498b8..fffca5a636 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java @@ -23,10 +23,10 @@ import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import io.rsocket.metadata.WellKnownMimeType; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import reactor.util.context.Context; -import org.springframework.lang.Nullable; import org.springframework.security.rsocket.api.PayloadExchangeType; import org.springframework.security.rsocket.api.PayloadInterceptor; import org.springframework.util.Assert; @@ -44,8 +44,7 @@ class PayloadSocketAcceptor implements SocketAcceptor { private final List interceptors; - @Nullable - private MimeType defaultDataMimeType; + private @Nullable MimeType defaultDataMimeType; private MimeType defaultMetadataMimeType = MimeTypeUtils .parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()); @@ -85,7 +84,7 @@ class PayloadSocketAcceptor implements SocketAcceptor { }); } - private MimeType parseMimeType(String str, MimeType defaultMimeType) { + private @Nullable MimeType parseMimeType(String str, @Nullable MimeType defaultMimeType) { return StringUtils.hasText(str) ? MimeTypeUtils.parseMimeType(str) : defaultMimeType; } diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/core/package-info.java b/rsocket/src/main/java/org/springframework/security/rsocket/core/package-info.java new file mode 100644 index 0000000000..a5ff1f3cb6 --- /dev/null +++ b/rsocket/src/main/java/org/springframework/security/rsocket/core/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * Spring Security RSocket core integration. + */ +@NullMarked +package org.springframework.security.rsocket.core; + +import org.jspecify.annotations.NullMarked; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java index 99ee9eda5a..bbe224742e 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationDecoder.java @@ -18,6 +18,7 @@ package org.springframework.security.rsocket.metadata; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -44,7 +45,7 @@ public class BasicAuthenticationDecoder extends AbstractDecoder decode(Publisher input, ResolvableType elementType, - MimeType mimeType, Map hints) { + @Nullable MimeType mimeType, @Nullable Map hints) { return Flux.from(input).map(DataBuffer::asByteBuffer).map((byteBuffer) -> { byte[] sizeBytes = new byte[4]; byteBuffer.get(sizeBytes); @@ -61,7 +62,7 @@ public class BasicAuthenticationDecoder extends AbstractDecoder decodeToMono(Publisher input, ResolvableType elementType, - MimeType mimeType, Map hints) { + @Nullable MimeType mimeType, @Nullable Map hints) { return Mono.from(input).map(DataBuffer::asByteBuffer).map((byteBuffer) -> { int usernameSize = byteBuffer.getInt(); byte[] usernameBytes = new byte[usernameSize]; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java index 7e6b3aab66..bd98fbe0fa 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BasicAuthenticationEncoder.java @@ -20,6 +20,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -47,14 +48,15 @@ public class BasicAuthenticationEncoder extends AbstractEncoder encode(Publisher inputStream, - DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map hints) { + DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType, + @Nullable Map hints) { return Flux.from(inputStream) .map((credentials) -> encodeValue(credentials, bufferFactory, elementType, mimeType, hints)); } @Override public DataBuffer encodeValue(UsernamePasswordMetadata credentials, DataBufferFactory bufferFactory, - ResolvableType valueType, MimeType mimeType, Map hints) { + ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map hints) { String username = credentials.getUsername(); String password = credentials.getPassword(); byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8); diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java index de330d3a4d..459ef9616d 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/BearerTokenAuthenticationEncoder.java @@ -21,6 +21,7 @@ import java.util.Map; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.metadata.AuthMetadataCodec; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -53,14 +54,15 @@ public class BearerTokenAuthenticationEncoder extends AbstractEncoder encode(Publisher inputStream, - DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map hints) { + DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType, + @Nullable Map hints) { return Flux.from(inputStream) .map((credentials) -> encodeValue(credentials, bufferFactory, elementType, mimeType, hints)); } @Override public DataBuffer encodeValue(BearerTokenMetadata credentials, DataBufferFactory bufferFactory, - ResolvableType valueType, MimeType mimeType, Map hints) { + ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map hints) { String token = credentials.getToken(); NettyDataBufferFactory factory = nettyFactory(bufferFactory); ByteBufAllocator allocator = factory.getByteBufAllocator(); diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java index 1e28c770e9..612ed6e467 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/SimpleAuthenticationEncoder.java @@ -21,6 +21,7 @@ import java.util.Map; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.metadata.AuthMetadataCodec; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -53,14 +54,15 @@ public class SimpleAuthenticationEncoder extends AbstractEncoder encode(Publisher inputStream, - DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map hints) { + DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType, + @Nullable Map hints) { return Flux.from(inputStream) .map((credentials) -> encodeValue(credentials, bufferFactory, elementType, mimeType, hints)); } @Override public DataBuffer encodeValue(UsernamePasswordMetadata credentials, DataBufferFactory bufferFactory, - ResolvableType valueType, MimeType mimeType, Map hints) { + ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map hints) { String username = credentials.getUsername(); String password = credentials.getPassword(); NettyDataBufferFactory factory = nettyFactory(bufferFactory); diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/metadata/package-info.java b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/package-info.java new file mode 100644 index 0000000000..8ebe2ce057 --- /dev/null +++ b/rsocket/src/main/java/org/springframework/security/rsocket/metadata/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * Spring Security RSocket metadata integration. + */ +@NullMarked +package org.springframework.security.rsocket.metadata; + +import org.jspecify.annotations.NullMarked; diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/PayloadExchangeMatcher.java b/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/PayloadExchangeMatcher.java index 3f99ef01da..dde03690d3 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/PayloadExchangeMatcher.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/PayloadExchangeMatcher.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.security.rsocket.api.PayloadExchange; @@ -46,9 +47,9 @@ public interface PayloadExchangeMatcher { private final boolean match; - private final Map variables; + private final @Nullable Map variables; - private MatchResult(boolean match, Map variables) { + private MatchResult(boolean match, @Nullable Map variables) { this.match = match; this.variables = variables; } @@ -61,7 +62,7 @@ public interface PayloadExchangeMatcher { * Gets potential variables and their values * @return */ - public Map getVariables() { + public @Nullable Map getVariables() { return this.variables; } diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/package-info.java b/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/package-info.java new file mode 100644 index 0000000000..fcb0c96092 --- /dev/null +++ b/rsocket/src/main/java/org/springframework/security/rsocket/util/matcher/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * Spring Security RSocket matching APIs. + */ +@NullMarked +package org.springframework.security.rsocket.util.matcher; + +import org.jspecify.annotations.NullMarked;