Skip to content

Commit

Permalink
FINERACT-2021: refactor the note GET end-points to eliminate manual s…
Browse files Browse the repository at this point in the history
…erialization with GSON.

- Implemented a new custom module for the note API.
- Used Spring Web-MVC annotations instead of JAX-RS.
- Dispensed the redundant query parameters like "fields, prettyPrint ..etc"
- Implemented a new `CommonWebConfiguration` for establishing the security configurations (not complete yet).
  • Loading branch information
Zeyad2003 committed Jun 21, 2024
1 parent 1c53fe2 commit 937fd43
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 0 deletions.
26 changes: 26 additions & 0 deletions custom/typesafe/common/configuration/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/

description = 'The new Type-Safe API layer (v3): Common Configuration'

group = 'org.apache.fineract.typesafe.common'

archivesBaseName = 'fineract-custom-common-configuration'

apply from: 'dependencies.gradle'
26 changes: 26 additions & 0 deletions custom/typesafe/common/configuration/dependencies.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/

dependencies {
implementation(project(':fineract-core'))
implementation(project(':fineract-provider'))
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.boot:spring-boot-starter-security')
compileOnly('org.springframework.boot:spring-boot-autoconfigure')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/
package org.apache.fineract.typesafe.common.configuration;

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter;
import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter;
import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreHelper;
import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter;
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.MDCWrapper;
import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.apache.fineract.notification.service.UserNotificationService;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.context.SecurityContextHolderFilter;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class CommonWebConfiguration {

private final FineractProperties fineractProperties;

private final ServerProperties serverProperties;

private final BasicAuthenticationEntryPoint basicAuthenticationEntryPoint;

private final AuthenticationManager authenticationManagerBean;

private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;

private final ConfigurationDomainService configurationDomainService;

private final CacheWritePlatformService cacheWritePlatformService;

private final UserNotificationService userNotificationService;

private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;

private final BusinessDateReadPlatformService businessDateReadPlatformService;

private final MDCWrapper mdcWrapper;

private final FineractRequestContextHolder fineractRequestContextHolder;

private final IdempotencyStoreHelper idempotencyStoreHelper;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.securityMatcher(antMatcher("/v3/**")).authorizeHttpRequests((auth) -> {
auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/v3/**")).permitAll().requestMatchers(antMatcher("/v3/**"))
.fullyAuthenticated();
}).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint))
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class)
.addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class)
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class)
.addFilterAfter(fineractInstanceModeApiFilter(), CorrelationHeaderFilter.class)
.addFilterAfter(idempotencyStoreFilter(), FineractInstanceModeApiFilter.class);

if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/v3/**")).requiresSecure());
}

return http.build();
}

public RequestResponseFilter requestResponseFilter() {
return new RequestResponseFilter();
}

public FineractInstanceModeApiFilter fineractInstanceModeApiFilter() {
return new FineractInstanceModeApiFilter(fineractProperties);
}

public IdempotencyStoreFilter idempotencyStoreFilter() {
return new IdempotencyStoreFilter(fineractRequestContextHolder, idempotencyStoreHelper, fineractProperties);
}

public CorrelationHeaderFilter correlationHeaderFilter() {
return new CorrelationHeaderFilter(fineractProperties, mdcWrapper);
}

public TenantAwareBasicAuthenticationFilter tenantAwareBasicAuthenticationFilter() {
TenantAwareBasicAuthenticationFilter filter = new TenantAwareBasicAuthenticationFilter(authenticationManagerBean,
basicAuthenticationEntryPoint, toApiJsonSerializer, configurationDomainService, cacheWritePlatformService,
userNotificationService, basicAuthTenantDetailsService, businessDateReadPlatformService);
filter.setRequestMatcher(antMatcher("/v3/**"));
return filter;
}
}
26 changes: 26 additions & 0 deletions custom/typesafe/note/api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/

description = 'The new Type-Safe API layer (v3): Note Api'

group = 'org.apache.fineract.typesafe.note'

archivesBaseName = 'fineract-custom-note-api'

apply from: 'dependencies.gradle'
26 changes: 26 additions & 0 deletions custom/typesafe/note/api/dependencies.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/

dependencies {
implementation(project(':fineract-core'))
implementation(project(':fineract-provider'))
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.boot:spring-boot-starter')
implementation('io.swagger.core.v3:swagger-annotations-jakarta')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
*/

package org.apache.fineract.typesafe.note.api;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON_VALUE;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Collection;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.note.api.NotesApiResourceSwagger;
import org.apache.fineract.portfolio.note.data.NoteData;
import org.apache.fineract.portfolio.note.domain.NoteType;
import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException;
import org.apache.fineract.portfolio.note.service.NoteReadPlatformService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/v3/{resourceType}/{resourceId}/notes", consumes = { APPLICATION_JSON_VALUE }, produces = { APPLICATION_JSON_VALUE,
APPLICATION_PROBLEM_JSON_VALUE })
@Tag(name = "Notes", description = "Notes API allows to enter notes for supported resources.")
@RequiredArgsConstructor
public class NoteApiController {

private final NoteReadPlatformService noteReadService;

@GetMapping
@Operation(summary = "Retrieve a Resource's description blabla", description = """
Retrieves a Resource's Notes
Note: Notes are returned in descending createOn order.
Example Requests:
clients/2/notes
groups/2/notes""")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = NotesApiResourceSwagger.GetResourceTypeResourceIdNotesResponse.class)))) })
public Collection<NoteData> retrieveNotesByResource(@PathVariable("resourceType") final String resourceType,
@PathVariable("resourceId") final Long resourceId) {

final NoteType noteType = NoteType.fromApiUrl(resourceType);

if (noteType == null) {
throw new NoteResourceNotSupportedException(resourceType);
}

// TODO: define this in a centralized security configuration
// this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType,
// resourceId).entityName());

final Integer noteTypeId = noteType.getValue();

return this.noteReadService.retrieveNotesByResource(resourceId, noteTypeId);
}

@GetMapping("/{noteId}")
@Operation(summary = "Retrieve a Resource Note", description = """
Retrieves a Resource Note
Example Requests:
clients/1/notes/76
groups/1/notes/20
clients/1/notes/76
groups/1/notes/20""")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = NotesApiResourceSwagger.GetResourceTypeResourceIdNotesNoteIdResponse.class))) })
public NoteData retrieveNote(@PathVariable("resourceType") final String resourceType, @PathVariable("resourceId") final Long resourceId,
@PathVariable("noteId") final Long noteId) {

final NoteType noteType = NoteType.fromApiUrl(resourceType);

if (noteType == null) {
throw new NoteResourceNotSupportedException(resourceType);
}

// TODO: define this in a centralized security configuration
// this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType,
// resourceId).entityName());

return this.noteReadService.retrieveNote(noteId, resourceId, noteType.getValue());
}
}

0 comments on commit 937fd43

Please sign in to comment.