Implement support for user consent #4
@ -3,6 +3,8 @@ package se.su.dsv.oauth2;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import se.su.dsv.oauth2.admin.repository.ClientRepository;
|
||||
import se.su.dsv.oauth2.admin.repository.ClientRow;
|
||||
|
||||
@ -15,6 +17,11 @@ import java.util.Optional;
|
||||
@Configuration
|
||||
@Profile("embedded")
|
||||
public class EmbeddedConfiguration {
|
||||
@Bean
|
||||
public OAuth2AuthorizationService authorizationService() {
|
||||
return new InMemoryOAuth2AuthorizationService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientRepository clientRepository() {
|
||||
ArrayList<ClientRow> clients = new ArrayList<>();
|
||||
|
||||
@ -2,14 +2,21 @@ package se.su.dsv.oauth2.web.oauth2;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@ -22,13 +29,16 @@ public class ConsentController {
|
||||
|
||||
private final AuthorizationServerSettings authorizationServerSettings;
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
|
||||
public ConsentController(
|
||||
final AuthorizationServerSettings authorizationServerSettings,
|
||||
final RegisteredClientRepository registeredClientRepository)
|
||||
final RegisteredClientRepository registeredClientRepository,
|
||||
final OAuth2AuthorizationService authorizationService)
|
||||
{
|
||||
this.authorizationServerSettings = authorizationServerSettings;
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
@GetMapping("/oauth2/consent")
|
||||
@ -61,6 +71,31 @@ public class ConsentController {
|
||||
return "consent";
|
||||
}
|
||||
|
||||
@PostMapping("/oauth2/consent")
|
||||
public String denyConsent(@RequestParam("state") String state) {
|
||||
OAuth2Authorization authorization = authorizationService.findByToken(
|
||||
state,
|
||||
new OAuth2TokenType(OAuth2ParameterNames.STATE));
|
||||
if (authorization == null) {
|
||||
return "redirect:/";
|
||||
}
|
||||
else {
|
||||
String registeredClientId = authorization.getRegisteredClientId();
|
||||
RegisteredClient registeredClient = registeredClientRepository.findById(registeredClientId);
|
||||
authorizationService.remove(authorization);
|
||||
if (registeredClient == null) {
|
||||
return "redirect:/";
|
||||
}
|
||||
String redirectUri = registeredClient.getRedirectUris()
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ErrorResponseException(HttpStatus.BAD_REQUEST));
|
||||
return "redirect:" + redirectUri +
|
||||
"?error=" + OAuth2ErrorCodes.ACCESS_DENIED +
|
||||
"&state=" + state;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClientDomain(final RegisteredClient client) {
|
||||
return client.getRedirectUris()
|
||||
.stream()
|
||||
@ -89,4 +124,9 @@ public class ConsentController {
|
||||
public Authentication authentication(Authentication authentication) {
|
||||
return authentication;
|
||||
}
|
||||
|
||||
@ModelAttribute("csrfToken")
|
||||
public CsrfToken csrfToken(CsrfToken csrfToken) {
|
||||
return csrfToken;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
@import org.springframework.security.core.Authentication
|
||||
@import org.springframework.security.web.csrf.CsrfToken
|
||||
@import java.util.Set
|
||||
|
||||
@param String clientId
|
||||
@ -8,6 +9,7 @@
|
||||
@param String state
|
||||
@param Authentication currentUser
|
||||
@param Set<String> scopes
|
||||
@param CsrfToken csrfToken
|
||||
|
||||
@template.base(title = "Consent", content = @`
|
||||
<h1>Consent</h1>
|
||||
@ -23,6 +25,7 @@
|
||||
<form method="post" action="${authorizationUrl}">
|
||||
<input type="hidden" name="client_id" value="${clientId}">
|
||||
<input type="hidden" name="state" value="${state}">
|
||||
<input type="hidden" name="${csrfToken.getParameterName()}" value="${csrfToken.getToken()}">
|
||||
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">
|
||||
|
||||
@ -23,7 +23,9 @@ import java.util.Set;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
@ -99,6 +101,41 @@ public class ConsentFlowTest extends AbstractMetadataTest {
|
||||
.andExpect(content().string(not(containsString("openid"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deny_consent_redirects_back_to_client_with_access_denied_error() throws Exception {
|
||||
String principal = "some-other-end-user";
|
||||
MvcResult consentResponse = attemptAuthorizationWithConsentResponse(principal)
|
||||
.andReturn();
|
||||
|
||||
String consentURL = consentResponse.getRequest()
|
||||
.getRequestURL()
|
||||
.append("?")
|
||||
.append(consentResponse.getRequest().getQueryString())
|
||||
.toString();
|
||||
|
||||
UriComponents consentUri = parseUrl(consentURL);
|
||||
|
||||
String clientId = consentUri.getQueryParams().getFirst("client_id");
|
||||
String state = consentUri.getQueryParams().getFirst("state");
|
||||
|
||||
MvcResult denyResult = mockMvc.perform(post("/oauth2/consent")
|
||||
.with(remoteUser(principal))
|
||||
.with(csrf())
|
||||
.formField("client_id", clientId)
|
||||
.formField("state", state))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
UriComponents redirectedUri = parseUrl(denyResult.getResponse().getRedirectedUrl());
|
||||
|
||||
UriComponents clientRedirectUri = parseUrl(TestConfig.REDIRECT_URI);
|
||||
|
||||
assertEquals(clientRedirectUri.getHost(), redirectedUri.getHost());
|
||||
assertEquals(clientRedirectUri.getScheme(), redirectedUri.getScheme());
|
||||
assertEquals(clientRedirectUri.getPath(), redirectedUri.getPath());
|
||||
assertEquals("access_denied", redirectedUri.getQueryParams().getFirst("error"));
|
||||
}
|
||||
|
||||
private ResultActions attemptAuthorizationWithConsentResponse(String principal) throws Exception {
|
||||
Set<String> scopes = Set.of();
|
||||
return attemptAuthorizationWithConsentResponseUsingScopes(principal, scopes);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user