Better error handling, especially during developer authorization
This commit is contained in:
parent
c421125eb4
commit
857d59d391
src
main
test/java/se/su/dsv/oauth2
@ -9,11 +9,14 @@ import jakarta.servlet.http.HttpFilter;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
|
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
|
||||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||||
@ -72,7 +75,11 @@ public class CustomAuthorizationEndpointFilter extends HttpFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loggedInUser.getAuthorities().contains(new Entitlement(developerEntitlement))) {
|
if (loggedInUser.getAuthorities().contains(new Entitlement(developerEntitlement))) {
|
||||||
proceedWithDeveloperAuthorization(request, response, loggedInUser);
|
try {
|
||||||
|
proceedWithDeveloperAuthorization(request, response, loggedInUser);
|
||||||
|
} catch (OAuth2AuthorizationCodeRequestAuthenticationException exception) {
|
||||||
|
sendAuthorizationError(request, response, exception);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
@ -193,4 +200,40 @@ public class CustomAuthorizationEndpointFilter extends HttpFilter {
|
|||||||
String redirectUri = uriBuilder.build(true).toUriString();
|
String redirectUri = uriBuilder.build(true).toUriString();
|
||||||
this.redirectStrategy.sendRedirect(request, response, redirectUri);
|
this.redirectStrategy.sendRedirect(request, response, redirectUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendAuthorizationError(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
OAuth2AuthorizationCodeRequestAuthenticationException authorizationCodeRequestAuthenticationException)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
OAuth2Error error = authorizationCodeRequestAuthenticationException.getError();
|
||||||
|
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authorizationCodeRequestAuthenticationException
|
||||||
|
.getAuthorizationCodeRequestAuthentication();
|
||||||
|
|
||||||
|
if (authorizationCodeRequestAuthentication == null
|
||||||
|
|| !StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
|
||||||
|
response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||||
|
.fromUriString(authorizationCodeRequestAuthentication.getRedirectUri())
|
||||||
|
.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
|
||||||
|
if (StringUtils.hasText(error.getDescription())) {
|
||||||
|
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION,
|
||||||
|
UriUtils.encode(error.getDescription(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(error.getUri())) {
|
||||||
|
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_URI,
|
||||||
|
UriUtils.encode(error.getUri(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(authorizationCodeRequestAuthentication.getState())) {
|
||||||
|
uriBuilder.queryParam(OAuth2ParameterNames.STATE,
|
||||||
|
UriUtils.encode(authorizationCodeRequestAuthentication.getState(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
// build(true) -> Components are explicitly encoded
|
||||||
|
String redirectUri = uriBuilder.build(true).toUriString();
|
||||||
|
this.redirectStrategy.sendRedirect(request, response, redirectUri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
src/main/resources/templates/error.jte
Normal file
8
src/main/resources/templates/error.jte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@param Integer status
|
||||||
|
@param String error
|
||||||
|
@param String message
|
||||||
|
|
||||||
|
@template.base(title = message, content = @`
|
||||||
|
<h1>${status} ${error}</h1>
|
||||||
|
<p>${message}</p>
|
||||||
|
`)
|
@ -6,8 +6,14 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
import static se.su.dsv.oauth2.ShibbolethRequestProcessor.remoteUser;
|
import static se.su.dsv.oauth2.ShibbolethRequestProcessor.remoteUser;
|
||||||
|
import static se.su.dsv.oauth2.TestRegisteredClientConfiguration.CLIENT_ID;
|
||||||
|
import static se.su.dsv.oauth2.TestRegisteredClientConfiguration.REDIRECT_URI;
|
||||||
|
|
||||||
@SpringBootTest(
|
@SpringBootTest(
|
||||||
classes = TestRegisteredClientConfiguration.class,
|
classes = TestRegisteredClientConfiguration.class,
|
||||||
@ -155,4 +161,67 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest {
|
|||||||
assertTrue(claims.getStringListClaim("entitlements").contains(customEntitlement),
|
assertTrue(claims.getStringListClaim("entitlements").contains(customEntitlement),
|
||||||
"Does not contain custom entitlement");
|
"Does not contain custom entitlement");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void correctly_handles_missing_response_type_parameter() throws Exception {
|
||||||
|
mockMvc.perform(post(getAuthorizationEndpoint())
|
||||||
|
.with(remoteUser("developer")
|
||||||
|
.entitlement(DEVELOPER_ENTITLEMENT))
|
||||||
|
.queryParam("client_id", CLIENT_ID)
|
||||||
|
.queryParam("redirect_uri", REDIRECT_URI)
|
||||||
|
.formField("principal", "developer"))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(status().reason(containsString("response_type")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void redirects_back_to_client_if_there_are_errors_but_valid_redirect_uri() throws Exception {
|
||||||
|
mockMvc.perform(post(getAuthorizationEndpoint())
|
||||||
|
.with(remoteUser("developer")
|
||||||
|
.entitlement(DEVELOPER_ENTITLEMENT))
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("client_id", CLIENT_ID)
|
||||||
|
.queryParam("redirect_uri", REDIRECT_URI)
|
||||||
|
.queryParam("scope", "invalid")
|
||||||
|
.formField("principal", "developer"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(result -> {
|
||||||
|
String redirectedUrl = result.getResponse().getRedirectedUrl();
|
||||||
|
assertThat(redirectedUrl, containsString("error=invalid_scope"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void does_not_redirect_with_invalid_client_id() throws Exception {
|
||||||
|
mockMvc.perform(post(getAuthorizationEndpoint())
|
||||||
|
.with(remoteUser("developer")
|
||||||
|
.entitlement(DEVELOPER_ENTITLEMENT))
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("client_id", "invalid-client-id")
|
||||||
|
.queryParam("redirect_uri", REDIRECT_URI)
|
||||||
|
.formField("principal", "developer"))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(status().reason(containsString("client_id")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maintains_state_during_error_redirect() throws Exception {
|
||||||
|
String state = "state123";
|
||||||
|
|
||||||
|
mockMvc.perform(post(getAuthorizationEndpoint())
|
||||||
|
.with(remoteUser("developer")
|
||||||
|
.entitlement(DEVELOPER_ENTITLEMENT))
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("client_id", CLIENT_ID)
|
||||||
|
.queryParam("redirect_uri", REDIRECT_URI)
|
||||||
|
.queryParam("state", state)
|
||||||
|
.queryParam("scope", "invalid")
|
||||||
|
.formField("principal", "developer"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(result -> {
|
||||||
|
String redirectedUrl = result.getResponse().getRedirectedUrl();
|
||||||
|
assertThat(redirectedUrl, containsString("error=invalid_scope"));
|
||||||
|
assertThat(redirectedUrl, containsString("state=" + state));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user