Skip to content

Commit

Permalink
fix: vic-2340 refactoring Authority
Browse files Browse the repository at this point in the history
  • Loading branch information
idriss.naji committed Jan 20, 2023
1 parent 001a24f commit d917ddc
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package de.caritas.cob.consultingtypeservice.api.auth;

import static java.util.Collections.emptyList;

import com.google.common.collect.Lists;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum Authority {
TENANT_ADMIN(UserRole.TENANT_ADMIN,
Lists.newArrayList(
AuthorityValue.PATCH_APPLICATION_SETTINGS,
AuthorityValue.CREATE_CONSULTING_TYPE)),
TOPIC_ADMIN(UserRole.TOPIC_ADMIN,
Lists.newArrayList(
AuthorityValue.CREATE_TOPIC,
AuthorityValue.UPDATE_TOPIC,
AuthorityValue.GET_ALL_TOPICS_WITH_TRANSLATION,
AuthorityValue.GET_TOPICS_TRANSLATION_BY_ID));

private final UserRole userRole;
private final List<String> grantedAuthorities;

public static List<String> getAuthoritiesByUserRole(UserRole userRole) {
Optional<Authority> authorityByUserRole =
Stream.of(values()).filter(authority -> authority.userRole.equals(userRole)).findFirst();

return authorityByUserRole.isPresent()
? authorityByUserRole.get().getGrantedAuthorities()
: emptyList();
}

public static class AuthorityValue {

private AuthorityValue() {
}


public static final String PREFIX = "AUTHORIZATION_";
public static final String PATCH_APPLICATION_SETTINGS = PREFIX + "PATCH_APPLICATION_SETTINGS";
public static final String CREATE_CONSULTING_TYPE = PREFIX + "CREATE_CONSULTING_TYPE";
public static final String CREATE_TOPIC = PREFIX + "CREATE_TOPIC";
public static final String UPDATE_TOPIC = PREFIX + "UPDATE_TOPIC";
public static final String GET_ALL_TOPICS_WITH_TRANSLATION =
PREFIX + "GET_ALL_TOPICS_WITH_TRANSLATION";
public static final String GET_TOPICS_TRANSLATION_BY_ID =
PREFIX + "GET_TOPICS_TRANSLATION_BY_ID";


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package de.caritas.cob.consultingtypeservice.api.auth;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.stereotype.Component;

/**
* Own implementation of the Spring GrantedAuthoritiesMapper.
*/
@Component
public class RoleAuthorizationAuthorityMapper implements GrantedAuthoritiesMapper {

@Override
public Collection<? extends GrantedAuthority> mapAuthorities(
Collection<? extends GrantedAuthority> authorities) {
Set<String> roleNames =
authorities.stream()
.map(GrantedAuthority::getAuthority)
.map(String::toLowerCase)
.collect(Collectors.toSet());

return mapAuthorities(roleNames);
}

private Set<GrantedAuthority> mapAuthorities(Set<String> roleNames) {
return roleNames.parallelStream()
.map(UserRole::getRoleByValue)
.filter(Optional::isPresent)
.map(Optional::get)
.map(Authority::getAuthoritiesByUserRole)
.flatMap(Collection::parallelStream)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import de.caritas.cob.consultingtypeservice.generated.api.controller.SettingsApi;
import de.caritas.cob.consultingtypeservice.generated.api.controller.SettingsadminApi;
import io.swagger.annotations.Api;
import java.util.Optional;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -15,8 +16,6 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.NativeWebRequest;

import java.util.Optional;

/**
* Controller for consulting type API requests.
*/
Expand Down Expand Up @@ -46,7 +45,7 @@ public ResponseEntity<ApplicationSettingsDTO> getApplicationSettings() {
}

@Override
@PreAuthorize("hasAuthority('tenant-admin')")
@PreAuthorize("hasAuthority('AUTHORIZATION_PATCH_APPLICATION_SETTINGS')")
public ResponseEntity<ApplicationSettingsDTO> patchApplicationSettings(ApplicationSettingsPatchDTO settingsPatchDTO) {
applicationSettingsServiceFacade.patchApplicationSettings(settingsPatchDTO);
var settings = applicationSettingsServiceFacade.getApplicationSettings();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public ResponseEntity<List<ConsultingTypeGroupResponseDTO>> getConsultingTypeGro
}

@Override
@PreAuthorize("hasAuthority('tenant-admin')")
@PreAuthorize("hasAuthority('AUTHORIZATION_CREATE_CONSULTING_TYPE')")
public ResponseEntity<FullConsultingTypeResponseDTO> createConsultingType(
final ConsultingTypeDTO consultingTypeDTO) {
return ResponseEntity.ok(consultingTypeService.createConsultingType(consultingTypeDTO));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class TopicAdminController implements TopicadminApi {
private AuthenticatedUser authenticatedUser;

@Override
@PreAuthorize("hasAuthority('topic-admin')")
@PreAuthorize("hasAuthority('AUTHORIZATION_CREATE_TOPIC')")
public ResponseEntity<TopicMultilingualDTO> createTopic(
@Valid final TopicMultilingualDTO topicMultilingualDTO) {
log.info("Creating topic by user {} ", authenticatedUser.getUsername());
Expand All @@ -38,7 +38,7 @@ public ResponseEntity<TopicMultilingualDTO> createTopic(
}

@Override
@PreAuthorize("hasAuthority('topic-admin')")
@PreAuthorize("hasAuthority('AUTHORIZATION_UPDATE_TOPIC')")
public ResponseEntity<TopicMultilingualDTO> updateTopic(
final Long id, @Valid final TopicMultilingualDTO topicMultilingualDTO) {
log.info("Updating topic with id {} by user {} ", id, authenticatedUser.getUsername());
Expand All @@ -48,7 +48,7 @@ public ResponseEntity<TopicMultilingualDTO> updateTopic(
}

@Override
@PreAuthorize("hasAuthority('topic-admin')")
@PreAuthorize("hasAuthority('AUTHORIZATION_GET_ALL_TOPICS_WITH_TRANSLATION')")
public ResponseEntity<List<TopicMultilingualDTO>> getAllTopicsWithTranslation() {
final var topics = topicServiceFacade.getAllTopicsMultilingual();
return !CollectionUtils.isEmpty(topics)
Expand All @@ -57,7 +57,7 @@ public ResponseEntity<List<TopicMultilingualDTO>> getAllTopicsWithTranslation()
}

@Override
@PreAuthorize("hasAuthority('topic-admin')")
@PreAuthorize("hasAuthority('AUTHORIZATION_GET_TOPICS_TRANSLATION_BY_ID')")
public ResponseEntity<TopicMultilingualDTO> getTopicWithTranslationById(final Long id) {
return new ResponseEntity<>(topicServiceFacade.getTopicMultilingualById(id), HttpStatus.OK);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.caritas.cob.consultingtypeservice.config;

import de.caritas.cob.consultingtypeservice.api.auth.RoleAuthorizationAuthorityMapper;
import de.caritas.cob.consultingtypeservice.filter.HttpTenantFilter;
import de.caritas.cob.consultingtypeservice.filter.StatelessCsrfFilter;
import javax.annotation.Nullable;
Expand All @@ -16,7 +17,6 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
Expand Down Expand Up @@ -117,9 +117,16 @@ protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
}

@Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) {
public void configureGlobal(final AuthenticationManagerBuilder auth,
RoleAuthorizationAuthorityMapper authorityMapper) {
final KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(authorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}

protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
var provider = new KeycloakAuthenticationProvider();
provider.setGrantedAuthoritiesMapper(new RoleAuthorizationAuthorityMapper());
return provider;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package de.caritas.cob.consultingtypeservice.api.controller;


import static de.caritas.cob.consultingtypeservice.api.auth.UserRole.TENANT_ADMIN;
import static de.caritas.cob.consultingtypeservice.api.auth.UserRole.TOPIC_ADMIN;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.http.MediaType.APPLICATION_JSON;
Expand All @@ -19,6 +21,8 @@
import de.caritas.cob.consultingtypeservice.api.repository.ApplicationSettingsRepository;
import de.caritas.cob.consultingtypeservice.api.tenant.TenantContext;
import de.caritas.cob.consultingtypeservice.api.util.JsonConverter;
import java.util.Map;
import javax.servlet.http.Cookie;
import org.assertj.core.util.Lists;
import org.assertj.core.util.Maps;
import org.assertj.core.util.Sets;
Expand All @@ -41,9 +45,6 @@
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.http.Cookie;
import java.util.Map;

@SpringBootTest(classes = ConsultingTypeServiceApplication.class)
@TestPropertySource(properties = "spring.profiles.active=testing")
@TestPropertySource(properties = "feature.multitenancy.with.single.domain.enabled=true")
Expand Down Expand Up @@ -104,7 +105,8 @@ void patchApplicationSettings_Should_ReturnUpdatedApplicationSettings_When_Patch
throws Exception {
// given
giveApplicationSettingEntityWithDynamicReleaseToggles();
final Authentication authentication = givenMockAuthentication(UserRole.TENANT_ADMIN);
AuthenticationMockBuilder builder = new AuthenticationMockBuilder();
Authentication authentication = builder.withUserRole(TENANT_ADMIN.getValue()).build();
ApplicationSettingsPatchDTO patchDTO = new ApplicationSettingsPatchDTO();
patchDTO.setLegalContentChangesBySingleTenantAdminsAllowed(
new ApplicationSettingsDTOMultitenancyWithSingleDomainEnabled().value(false)
Expand All @@ -120,31 +122,33 @@ void patchApplicationSettings_Should_ReturnUpdatedApplicationSettings_When_Patch
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON)
.content(jsonRequest)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.multitenancyWithSingleDomainEnabled.value").value(true))
.andExpect(jsonPath("$.multitenancyWithSingleDomainEnabled.readOnly").value(true))
.andExpect(jsonPath("$.multitenancyEnabled.value").value(false))
.andExpect(jsonPath("$.multitenancyEnabled.readOnly").value(true))
.andExpect(jsonPath("$.useTenantService.value").value(true))
.andExpect(jsonPath("$.useTenantService.readOnly").value(false))
.andExpect(jsonPath("$.enableWalkthrough.value").value(false))
.andExpect(jsonPath("$.enableWalkthrough.readOnly").value(false))
.andExpect(jsonPath("$.disableVideoAppointments.value").value(true))
.andExpect(jsonPath("$.disableVideoAppointments.readOnly").value(false))
.andExpect(jsonPath("$.mainTenantSubdomainForSingleDomainMultitenancy.value").value("app2"))
.andExpect(jsonPath("$.mainTenantSubdomainForSingleDomainMultitenancy.readOnly").value(false))
.andExpect(jsonPath("$.budibaseAuthClientId.value").value("budibaseAuthClientId"))
.andExpect(jsonPath("$.budibaseAuthClientId.readOnly").value(false))
.andExpect(jsonPath("$.calcomUrl.value").value("calcomUrl"))
.andExpect(jsonPath("$.calcomUrl.readOnly").value(false))
.andExpect(jsonPath("$.budibaseUrl.value").value("budibaseUrl"))
.andExpect(jsonPath("$.budibaseUrl.readOnly").value(false))
.andExpect(jsonPath("$.calendarAppUrl.value").value("calendarAppUrl"))
.andExpect(jsonPath("$.calendarAppUrl.readOnly").value(false))
.andExpect(jsonPath("$.useOverviewPage.value").value(false))
.andExpect(jsonPath("$.useOverviewPage.readOnly").value(false))
.andExpect(jsonPath("$.legalContentChangesBySingleTenantAdminsAllowed.value").value(false))
.andExpect(jsonPath("$.legalContentChangesBySingleTenantAdminsAllowed.readOnly").value(false))
.andExpect(status().isOk())
.andExpect(jsonPath("$.multitenancyWithSingleDomainEnabled.value").value(true))
.andExpect(jsonPath("$.multitenancyWithSingleDomainEnabled.readOnly").value(true))
.andExpect(jsonPath("$.multitenancyEnabled.value").value(false))
.andExpect(jsonPath("$.multitenancyEnabled.readOnly").value(true))
.andExpect(jsonPath("$.useTenantService.value").value(true))
.andExpect(jsonPath("$.useTenantService.readOnly").value(false))
.andExpect(jsonPath("$.enableWalkthrough.value").value(false))
.andExpect(jsonPath("$.enableWalkthrough.readOnly").value(false))
.andExpect(jsonPath("$.disableVideoAppointments.value").value(true))
.andExpect(jsonPath("$.disableVideoAppointments.readOnly").value(false))
.andExpect(jsonPath("$.mainTenantSubdomainForSingleDomainMultitenancy.value").value("app2"))
.andExpect(
jsonPath("$.mainTenantSubdomainForSingleDomainMultitenancy.readOnly").value(false))
.andExpect(jsonPath("$.budibaseAuthClientId.value").value("budibaseAuthClientId"))
.andExpect(jsonPath("$.budibaseAuthClientId.readOnly").value(false))
.andExpect(jsonPath("$.calcomUrl.value").value("calcomUrl"))
.andExpect(jsonPath("$.calcomUrl.readOnly").value(false))
.andExpect(jsonPath("$.budibaseUrl.value").value("budibaseUrl"))
.andExpect(jsonPath("$.budibaseUrl.readOnly").value(false))
.andExpect(jsonPath("$.calendarAppUrl.value").value("calendarAppUrl"))
.andExpect(jsonPath("$.calendarAppUrl.readOnly").value(false))
.andExpect(jsonPath("$.useOverviewPage.value").value(false))
.andExpect(jsonPath("$.useOverviewPage.readOnly").value(false))
.andExpect(jsonPath("$.legalContentChangesBySingleTenantAdminsAllowed.value").value(false))
.andExpect(
jsonPath("$.legalContentChangesBySingleTenantAdminsAllowed.readOnly").value(false))
.andExpect(jsonPath("$.releaseToggles.featureToggleTenantCreationEnabled").value(true));

// clean up
Expand Down Expand Up @@ -173,18 +177,21 @@ private void resetSettingsToPreviousState(Authentication authentication) throws
@Test
void patchApplicationSettings_Should_ReturnForbidden_When_UserNameDoesNotHavePermissionToPatchSettings()
throws Exception {
final Authentication authentication = givenMockAuthentication(UserRole.TOPIC_ADMIN);
AuthenticationMockBuilder builder = new AuthenticationMockBuilder();

ApplicationSettingsPatchDTO patchDTO = new ApplicationSettingsPatchDTO();
patchDTO.setLegalContentChangesBySingleTenantAdminsAllowed(new ApplicationSettingsDTOMultitenancyWithSingleDomainEnabled().value(false).readOnly(true));
patchDTO.setLegalContentChangesBySingleTenantAdminsAllowed(
new ApplicationSettingsDTOMultitenancyWithSingleDomainEnabled().value(false)
.readOnly(true));
String jsonRequest = JsonConverter.convertToJson(patchDTO);
mockMvc.perform(patch("/settingsadmin")
.with(authentication(authentication))
.header("csrfHeader", "csrfToken")
.cookie(new Cookie("csrfCookie", "csrfToken"))
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON)
.content(jsonRequest)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
.with(authentication(builder.withUserRole(TOPIC_ADMIN.getValue()).build()))
.header("csrfHeader", "csrfToken")
.cookie(new Cookie("csrfCookie", "csrfToken"))
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON)
.content(jsonRequest)
.contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}

private void giveApplicationSettingEntityWithDynamicReleaseToggles() {
Expand All @@ -195,17 +202,15 @@ private void giveApplicationSettingEntityWithDynamicReleaseToggles() {
}

private Authentication givenMockAuthentication(final UserRole authority) {
final var securityContext = mock(
RefreshableKeycloakSecurityContext.class);
final var securityContext = mock(RefreshableKeycloakSecurityContext.class);
when(securityContext.getTokenString()).thenReturn("tokenString");
final var token = mock(AccessToken.class, Mockito.RETURNS_DEEP_STUBS);
when(securityContext.getToken()).thenReturn(token);
givenOtherClaimsAreDefinedForToken(token);
final KeycloakAccount mockAccount = new SimpleKeycloakAccount(() -> "user", Sets.newHashSet(),
securityContext);
final Authentication authentication = new KeycloakAuthenticationToken(mockAccount, true,
Lists.newArrayList((GrantedAuthority) () -> authority.getValue()));
return authentication;
securityContext);
return new KeycloakAuthenticationToken(mockAccount, true,
Lists.newArrayList((GrantedAuthority) authority::getValue));
}

private void givenOtherClaimsAreDefinedForToken(final AccessToken token) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.caritas.cob.consultingtypeservice.api.controller;

import com.google.common.collect.Lists;
import de.caritas.cob.consultingtypeservice.api.auth.RoleAuthorizationAuthorityMapper;
import java.util.Collection;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
Expand All @@ -10,24 +11,20 @@

public class AuthenticationMockBuilder {

private String authority;
private String userRole;
private String tenantId;

AuthenticationMockBuilder withAuthority(String authority) {
this.authority = authority;
return this;
}

AuthenticationMockBuilder withTenantIdAttribute(String tenantId) {
this.tenantId = tenantId;
AuthenticationMockBuilder withUserRole(String userRole) {
this.userRole = userRole;
return this;
}

public Authentication build() {
return new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Lists.newArrayList((GrantedAuthority) () -> authority);
return new RoleAuthorizationAuthorityMapper()
.mapAuthorities(Lists.newArrayList(() -> userRole));
}

@Override
Expand Down
Loading

0 comments on commit d917ddc

Please sign in to comment.