diff --git a/cas/spring-security-cas.gradle b/cas/spring-security-cas.gradle index fd4a614fa5..a475013057 100644 --- a/cas/spring-security-cas.gradle +++ b/cas/spring-security-cas.gradle @@ -1,3 +1,7 @@ +plugins { + id 'security-nullability' +} + apply plugin: 'io.spring.convention.spring-module' dependencies { diff --git a/cas/src/main/java/org/springframework/security/cas/ServiceProperties.java b/cas/src/main/java/org/springframework/security/cas/ServiceProperties.java index caf03dd62a..450142e8ef 100644 --- a/cas/src/main/java/org/springframework/security/cas/ServiceProperties.java +++ b/cas/src/main/java/org/springframework/security/cas/ServiceProperties.java @@ -16,6 +16,8 @@ package org.springframework.security.cas; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; @@ -34,7 +36,7 @@ public class ServiceProperties implements InitializingBean { public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service"; - private String service; + private @Nullable String service; private boolean authenticateAllArtifacts; @@ -62,7 +64,7 @@ public class ServiceProperties implements InitializingBean { * * @return the URL of the service the user is authenticating to */ - public final String getService() { + public final @Nullable String getService() { return this.service; } diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java index 0779a509a6..9955b11e71 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java @@ -21,6 +21,8 @@ import org.apache.commons.logging.LogFactory; import org.apereo.cas.client.validation.Assertion; import org.apereo.cas.client.validation.TicketValidationException; import org.apereo.cas.client.validation.TicketValidator; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; @@ -62,6 +64,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class); + @SuppressWarnings("NullAway.Init") private AuthenticationUserDetailsService authenticationUserDetailsService; private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); @@ -70,11 +73,13 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache(); + @SuppressWarnings("NullAway.Init") private String key; + @SuppressWarnings("NullAway.Init") private TicketValidator ticketValidator; - private ServiceProperties serviceProperties; + private @Nullable ServiceProperties serviceProperties; private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); @@ -89,7 +94,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { if (!supports(authentication.getClass())) { return null; } @@ -129,11 +134,14 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException { try { - Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), - getServiceUrl(authentication)); + Object credentials = authentication.getCredentials(); + if (credentials == null) { + throw new BadCredentialsException("Authentication.getCredentials() cannot be null"); + } + Assertion assertion = this.ticketValidator.validate(credentials.toString(), getServiceUrl(authentication)); UserDetails userDetails = loadUserByAssertion(assertion); this.userDetailsChecker.check(userDetails); - return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), + return new CasAuthenticationToken(this.key, userDetails, credentials, this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion); } catch (TicketValidationException ex) { @@ -149,7 +157,8 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia * @param authentication * @return */ - private String getServiceUrl(Authentication authentication) { + @NullUnmarked + private @Nullable String getServiceUrl(Authentication authentication) { String serviceUrl; if (authentication.getDetails() instanceof ServiceAuthenticationDetails) { return ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl(); @@ -215,7 +224,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia return this.statelessTicketCache; } - protected TicketValidator getTicketValidator() { + protected @Nullable TicketValidator getTicketValidator() { return this.ticketValidator; } diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java index c0ca75e2c7..ac77f48c5b 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java @@ -19,6 +19,8 @@ package org.springframework.security.cas.authentication; import java.io.Serial; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; @@ -41,7 +43,7 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT private final String identifier; - private Object credentials; + private @Nullable Object credentials; /** * This constructor can be safely used by any code that wishes to create a @@ -86,7 +88,7 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT } @Override - public Object getCredentials() { + public @Nullable Object getCredentials() { return this.credentials; } diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/NullStatelessTicketCache.java b/cas/src/main/java/org/springframework/security/cas/authentication/NullStatelessTicketCache.java index 4284161a39..6a28d77af2 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/NullStatelessTicketCache.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/NullStatelessTicketCache.java @@ -16,6 +16,8 @@ package org.springframework.security.cas.authentication; +import org.jspecify.annotations.Nullable; + /** * Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful * in instances where storing of tickets for stateless session management is not required. @@ -33,7 +35,7 @@ public final class NullStatelessTicketCache implements StatelessTicketCache { * @return null since we are not storing any tickets. */ @Override - public CasAuthenticationToken getByTicketId(final String serviceTicket) { + public @Nullable CasAuthenticationToken getByTicketId(final String serviceTicket) { return null; } diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/SpringCacheBasedTicketCache.java b/cas/src/main/java/org/springframework/security/cas/authentication/SpringCacheBasedTicketCache.java index bd5030b785..ae6e5e7279 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/SpringCacheBasedTicketCache.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/SpringCacheBasedTicketCache.java @@ -18,6 +18,7 @@ package org.springframework.security.cas.authentication; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.cache.Cache; import org.springframework.core.log.LogMessage; @@ -42,7 +43,7 @@ public class SpringCacheBasedTicketCache implements StatelessTicketCache { } @Override - public CasAuthenticationToken getByTicketId(final String serviceTicket) { + public @Nullable CasAuthenticationToken getByTicketId(final String serviceTicket) { final Cache.ValueWrapper element = (serviceTicket != null) ? this.cache.get(serviceTicket) : null; logger.debug(LogMessage.of(() -> "Cache hit: " + (element != null) + "; service ticket: " + serviceTicket)); return (element != null) ? (CasAuthenticationToken) element.get() : null; diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/StatelessTicketCache.java b/cas/src/main/java/org/springframework/security/cas/authentication/StatelessTicketCache.java index 74df6bb9df..46b4ba3e15 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/StatelessTicketCache.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/StatelessTicketCache.java @@ -16,6 +16,8 @@ package org.springframework.security.cas.authentication; +import org.jspecify.annotations.Nullable; + /** * Caches CAS service tickets and CAS proxy tickets for stateless connections. * @@ -69,7 +71,7 @@ public interface StatelessTicketCache { *

* @return the fully populated authentication token */ - CasAuthenticationToken getByTicketId(String serviceTicket); + @Nullable CasAuthenticationToken getByTicketId(String serviceTicket); /** * Adds the specified CasAuthenticationToken to the cache. diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/package-info.java b/cas/src/main/java/org/springframework/security/cas/authentication/package-info.java index bebdce4140..4cfab548f9 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/package-info.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/package-info.java @@ -18,4 +18,7 @@ * An {@code AuthenticationProvider} that can process CAS service tickets and proxy * tickets. */ +@NullMarked package org.springframework.security.cas.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/package-info.java b/cas/src/main/java/org/springframework/security/cas/jackson2/package-info.java new file mode 100644 index 0000000000..dae1c1502d --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/jackson2/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. + */ + +/** + * Jackson support for CAS. + */ +@NullMarked +package org.springframework.security.cas.jackson2; + +import org.jspecify.annotations.NullMarked; diff --git a/cas/src/main/java/org/springframework/security/cas/package-info.java b/cas/src/main/java/org/springframework/security/cas/package-info.java index a3ce420968..90592aa897 100644 --- a/cas/src/main/java/org/springframework/security/cas/package-info.java +++ b/cas/src/main/java/org/springframework/security/cas/package-info.java @@ -18,4 +18,7 @@ * Spring Security support for Apereo's Central Authentication Service * (CAS). */ +@NullMarked package org.springframework.security.cas; + +import org.jspecify.annotations.NullMarked; diff --git a/cas/src/main/java/org/springframework/security/cas/userdetails/package-info.java b/cas/src/main/java/org/springframework/security/cas/userdetails/package-info.java new file mode 100644 index 0000000000..24681a528a --- /dev/null +++ b/cas/src/main/java/org/springframework/security/cas/userdetails/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. + */ + +/** + * {@link org.springframework.security.core.userdetails.UserDetails} abstractions for CAS. + */ +@NullMarked +package org.springframework.security.cas.userdetails; + +import org.jspecify.annotations.NullMarked; diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java index 539ab57ccd..59aaff4474 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java +++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationEntryPoint.java @@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apereo.cas.client.util.CommonUtils; import org.apereo.cas.client.util.WebUtils; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.cas.ServiceProperties; @@ -47,9 +48,10 @@ import org.springframework.util.Assert; */ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { + @SuppressWarnings("NullAway.Init") private ServiceProperties serviceProperties; - private String loginUrl; + private @Nullable String loginUrl; /** * Determines whether the Service URL should include the session id for the specific @@ -117,7 +119,7 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In * https://www.mycompany.com/cas/login. * @return the enterprise-wide CAS login URL */ - public final String getLoginUrl() { + public final @Nullable String getLoginUrl() { return this.loginUrl; } diff --git a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java index ddc01f51ca..06623db874 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java +++ b/cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java @@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpSession; import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage; import org.apereo.cas.client.util.WebUtils; import org.apereo.cas.client.validation.TicketValidator; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AuthenticationDetailsSource; @@ -190,12 +191,12 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil /** * The last portion of the receptor url, i.e. /proxy/receptor */ - private RequestMatcher proxyReceptorMatcher; + private @Nullable RequestMatcher proxyReceptorMatcher; /** * The backing storage to store ProxyGrantingTicket requests. */ - private ProxyGrantingTicketStorage proxyGrantingTicketStorage; + private @Nullable ProxyGrantingTicketStorage proxyGrantingTicketStorage; private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER; @@ -244,7 +245,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil } @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + public @Nullable Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException { // if the request is a proxy request process it and return null to indicate the // request has been processed @@ -422,6 +423,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil * @param request * @return */ + @SuppressWarnings("NullAway") // Dataflow analysis limitation private boolean proxyReceptorRequest(HttpServletRequest request) { final boolean result = proxyReceptorConfigured() && this.proxyReceptorMatcher.matches(request); this.logger.debug(LogMessage.format("proxyReceptorRequest = %s", result)); diff --git a/cas/src/main/java/org/springframework/security/cas/web/authentication/DefaultServiceAuthenticationDetails.java b/cas/src/main/java/org/springframework/security/cas/web/authentication/DefaultServiceAuthenticationDetails.java index bb5fe0d793..cbfe9825cc 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/authentication/DefaultServiceAuthenticationDetails.java +++ b/cas/src/main/java/org/springframework/security/cas/web/authentication/DefaultServiceAuthenticationDetails.java @@ -21,6 +21,7 @@ import java.net.URL; import java.util.regex.Pattern; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.cas.authentication.ServiceAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetails; @@ -49,8 +50,8 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails * string from containing the artifact name and value. This can be created using * {@link #createArtifactPattern(String)}. */ - DefaultServiceAuthenticationDetails(String casService, HttpServletRequest request, Pattern artifactPattern) - throws MalformedURLException { + DefaultServiceAuthenticationDetails(@Nullable String casService, HttpServletRequest request, + Pattern artifactPattern) throws MalformedURLException { super(request); URL casServiceUrl = new URL(casService); int port = getServicePort(casServiceUrl); @@ -104,7 +105,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails * @return the query String minus the artifactParameterName and the corresponding * value. */ - private String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) { + private @Nullable String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) { final String query = request.getQueryString(); if (query == null) { return null; diff --git a/cas/src/main/java/org/springframework/security/cas/web/authentication/package-info.java b/cas/src/main/java/org/springframework/security/cas/web/authentication/package-info.java index 5b415a1424..ae1670e08b 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/authentication/package-info.java +++ b/cas/src/main/java/org/springframework/security/cas/web/authentication/package-info.java @@ -18,4 +18,7 @@ * Authentication processing mechanisms which respond to the submission of authentication * credentials using CAS. */ +@NullMarked package org.springframework.security.cas.web.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/cas/src/main/java/org/springframework/security/cas/web/package-info.java b/cas/src/main/java/org/springframework/security/cas/web/package-info.java index df1f9edff2..b89ed4d96b 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/package-info.java +++ b/cas/src/main/java/org/springframework/security/cas/web/package-info.java @@ -17,4 +17,7 @@ /** * Authenticates standard web browser users via CAS. */ +@NullMarked package org.springframework.security.cas.web; + +import org.jspecify.annotations.NullMarked; diff --git a/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java b/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java index bc759f5f7a..436b9a5682 100644 --- a/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java +++ b/core/src/main/java/org/springframework/security/authentication/BadCredentialsException.java @@ -18,6 +18,8 @@ package org.springframework.security.authentication; import java.io.Serial; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.AuthenticationException; /** @@ -35,7 +37,7 @@ public class BadCredentialsException extends AuthenticationException { * Constructs a BadCredentialsException with the specified message. * @param msg the detail message */ - public BadCredentialsException(String msg) { + public BadCredentialsException(@Nullable String msg) { super(msg); } @@ -45,7 +47,7 @@ public class BadCredentialsException extends AuthenticationException { * @param msg the detail message * @param cause root cause */ - public BadCredentialsException(String msg, Throwable cause) { + public BadCredentialsException(@Nullable String msg, Throwable cause) { super(msg, cause); } diff --git a/core/src/main/java/org/springframework/security/core/AuthenticationException.java b/core/src/main/java/org/springframework/security/core/AuthenticationException.java index 37fea816f1..e6e2d0662d 100644 --- a/core/src/main/java/org/springframework/security/core/AuthenticationException.java +++ b/core/src/main/java/org/springframework/security/core/AuthenticationException.java @@ -50,7 +50,7 @@ public abstract class AuthenticationException extends RuntimeException { * root cause. * @param msg the detail message */ - public AuthenticationException(String msg) { + public AuthenticationException(@Nullable String msg) { super(msg); }