UNCLASSIFIED

package mil.tron.commonapi.appgateway;
import java.util.ArrayList;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import mil.tron.commonapi.ApplicationProperties;
import mil.tron.commonapi.service.utility.ResolvePathFromRequest;
public class GatewayCacheResolver implements CacheResolver {
@Autowired
private CacheManager cacheManager;
@Autowired
private ApplicationProperties prefixProperties;
public GatewayCacheResolver() {}
public GatewayCacheResolver(
CacheManager cacheManager,
ApplicationProperties prefixProperties
) {
this.cacheManager = cacheManager;
this.prefixProperties = prefixProperties;
}
@Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<Cache> result = new ArrayList<Cache>();
for(Object arg : context.getArgs()) {
if(arg instanceof HttpServletRequest) {
String path = ResolvePathFromRequest.resolve((HttpServletRequest) arg, prefixProperties.getCombinedPrefixes());
int appSourceAndEndpointSeparator = path.indexOf("/");
// If the trimmed path starts with "/", something didn't parse correctly.
// We should put that in the default cache
if(appSourceAndEndpointSeparator > 0) {
String appSourcePath = path.substring(0, appSourceAndEndpointSeparator);
result.add(cacheManager.getCache(appSourcePath));
break;
}
}
}
if(result.size() == 0) {
result.add(cacheManager.getCache("default"));
}
return result;
}
}
package mil.tron.commonapi.appgateway;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
public class GatewayKeyGenerator implements KeyGenerator {
// Build the key to be in the format classname_methodname_/uri/path/_requestmethod_requestParam1_value1_requestParam2_value2
// e.g. AppGatewayController_handleGetRequests_/api/v1/app/arms-gateway-local/aircrew-svc/flyer-7-30-60-90_GET_fromDate_2020-12-11:000000_toDate_2020-12-15:000000_inverse_1_harmCode_ABCD_systemId_1_recStatus_N
@Override
public Object generate(Object target, Method method, Object... params) {
List<String> keyParts = new ArrayList<String>();
keyParts.add(target.getClass().getSimpleName());
keyParts.add(method.getName());
if(params.length > 0 && params[0] instanceof ContentCachingRequestWrapper) {
ContentCachingRequestWrapper ccrw = (ContentCachingRequestWrapper) params[0];
keyParts.add(ccrw.getRequestURI());
keyParts.add(ccrw.getMethod().toString());
Map<String, String[]> paramsMap = ccrw.getParameterMap();
for(Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
keyParts.add(entry.getKey());
keyParts.addAll(Arrays.asList(entry.getValue()));
}
} else {
keyParts.add(StringUtils.arrayToDelimitedString(params, "_"));
}
return String.join("_", keyParts);
}
}
package mil.tron.commonapi.appgateway;
import org.apache.camel.CamelContext;
import org.apache.camel.Predicate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.support.builder.ExpressionBuilder;
import org.apache.camel.support.builder.PredicateBuilder;
public class GatewayRoute extends RouteBuilder {
private String appSourcePath;
public GatewayRoute(CamelContext camelContext, String appSourcePath) {
super(camelContext);
this.appSourcePath = appSourcePath;
}
@Override
public void configure() throws Exception {
Predicate throttleEnabled = header("is-throttle-enabled").isEqualTo(true);
Predicate throttleRateLimit = header("throttle-rate-limit").isGreaterThanOrEqualTo(0);
from(AppGatewayRouteBuilder.generateAppSourceRouteUri(appSourcePath))
.id(AppGatewayRouteBuilder.generateAppSourceRouteId(appSourcePath))
.streamCaching()
.choice()
.when(PredicateBuilder.and(throttleEnabled, throttleRateLimit))
.throttle(ExpressionBuilder.headerExpression("throttle-rate-limit")).timePeriodMillis(60000).rejectExecution(true)
.toD("${header.request-url}")
.endChoice()
.otherwise()
.toD("${header.request-url}")
.end();
}
}
\ No newline at end of file
package mil.tron.commonapi.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 mil.tron.commonapi.annotation.response.WrappedEnvelopeResponse;
import mil.tron.commonapi.annotation.security.PreAuthorizeDashboardAdmin;
import mil.tron.commonapi.dto.appclient.AppClientUserDetailsDto;
import mil.tron.commonapi.dto.appclient.AppClientUserDto;
import mil.tron.commonapi.dto.appclient.AppClientUserDtoResponseWrapped;
import mil.tron.commonapi.dto.PrivilegeDto;
import mil.tron.commonapi.dto.PrivilegeDtoResponseWrapper;
import mil.tron.commonapi.exception.ExceptionResponse;
import mil.tron.commonapi.exception.InvalidAppSourcePermissions;
import mil.tron.commonapi.service.AppClientUserService;
import mil.tron.commonapi.service.PrivilegeService;
import org.assertj.core.util.Lists;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@RestController
public class AppClientController {
private AppClientUserService appClientService;
private PrivilegeService privilegeService;
private static final String DASHBOARD_ADMIN = "DASHBOARD_ADMIN";
private static final String APP_CLIENT_DEVELOPER_PRIV = "APP_CLIENT_DEVELOPER";
private static final String INVALID_PERMS = "Invalid User Permissions";
public AppClientController(AppClientUserService appClientService, PrivilegeService privilegeService) {
this.appClientService = appClientService;
this.privilegeService = privilegeService;
}
private boolean getUserIsDashBoardAdmin() {
return !SecurityContextHolder.getContext().getAuthentication()
.getAuthorities()
.stream()
.filter(item -> item.getAuthority().equals(DASHBOARD_ADMIN))
.collect(Collectors.toList())
.isEmpty();
}
private void checkUserIsDashBoardAdminOrAppClientDeveloper(UUID appId) {
if (!getUserIsDashBoardAdmin()) {
// user wasn't a dashboard admin, so see if they're a APP_CLIENT_DEVELOPER for given appId...
validateAppClientDeveloperAccessForUser(appId);
}
}
/**
* Private helper to authorize a user to an app client's info identified by the app client's UUID
* @param appId the appid of the app client
*/
private void validateAppClientDeveloperAccessForUser(UUID appId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userEmail = authentication.getCredentials().toString(); // get the JWT email string
if (!appClientService.userIsAppClientDeveloperForApp(appId, userEmail)) {
throw new InvalidAppSourcePermissions(INVALID_PERMS);
}
}
/**
* Wrapper for the checkUserIsDashBoardAdminOrAppClientDeveloper but traps
* its exceptions and just returns false
* @param appId UUID of the app source
* @return true if user is Dashboard Admin or App Client Developer for given appId
*/
private boolean userIsDashBoardAdminOrAppClientDeveloper(UUID appId) {
boolean result = true;
try {
this.checkUserIsDashBoardAdminOrAppClientDeveloper(appId);
}
catch (InvalidAppSourcePermissions ex) {
result = false;
}
return result;
}
/**
* @deprecated No longer valid T166. See {@link #getAppClientUsersWrapped()} for new usage.
* @return
*/
@Operation(summary = "Retrieves all application client user information",
description = "Retrieves application client user information. Requires Dashboard Admin access or App Client Developer.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AppClientUserDto.class))))
})
@Deprecated(since = "v2")
@GetMapping({"${api-prefix.v1}/app-client"})
public ResponseEntity<Object> getAppClientUsers() {
List<AppClientUserDto> dtos = Lists.newArrayList(this.appClientService
.getAppClientUsers())
.stream()
.filter(source -> userIsDashBoardAdminOrAppClientDeveloper(source.getId()))
.collect(Collectors.toList());
return new ResponseEntity<>(dtos, HttpStatus.OK);
}
@Operation(summary = "Retrieves all application client user information",
description = "Retrieves application client user information. Requires Dashboard Admin access or App Client Developer.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppClientUserDtoResponseWrapped.class)))
})
@WrappedEnvelopeResponse
@GetMapping({"${api-prefix.v2}/app-client"})
public ResponseEntity<Object> getAppClientUsersWrapped() {
List<AppClientUserDto> dtos = Lists.newArrayList(this.appClientService
.getAppClientUsers())
.stream()
.filter(source -> userIsDashBoardAdminOrAppClientDeveloper(source.getId()))
.collect(Collectors.toList());
return new ResponseEntity<>(dtos, HttpStatus.OK);
}
@Operation(summary = "Get an App Client's Information",
description = "Get an App Client by its UUID. Requires DASHBOARD_ADMIN or be an App Client Developer of that UUID.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppClientUserDetailsDto.class))),
@ApiResponse(responseCode = "403",
description = "Requester isn't a DASHBOARD_ADMIN or an App Client Developer of this App Client",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "404",
description = "Resource with that ID doesn't exist",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "400",
description = "Bad request",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@GetMapping({"${api-prefix.v1}/app-client/{id}", "${api-prefix.v2}/app-client/{id}"})
public ResponseEntity<Object> getAppClientRecord(@PathVariable UUID id) {
checkUserIsDashBoardAdminOrAppClientDeveloper(id);
return new ResponseEntity<>(appClientService.getAppClient(id), HttpStatus.OK);
}
@Operation(summary = "Adds an App Client",
description = "Adds a App Client User. Requires DASHBOARD_ADMIN access.")
@ApiResponses(value = {
@ApiResponse(responseCode = "201",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppClientUserDto.class))),
@ApiResponse(responseCode = "409",
description = "Resource already exists with the name provided",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "400",
description = "Bad request",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@PreAuthorizeDashboardAdmin
@PostMapping({"${api-prefix.v1}/app-client", "${api-prefix.v2}/app-client"})
public ResponseEntity<AppClientUserDto> createAppClientUser(
@Parameter(description = "App Client to create", required = true) @Valid @RequestBody AppClientUserDto appClient) {
return new ResponseEntity<>(appClientService.createAppClientUser(appClient), HttpStatus.CREATED);
}
@Operation(summary = "Updates an existing Application Client",
description = "Updates an existing Application Client. Requires DASHBOARD_ADMIN access to change any attribute," +
"or be APP_CLIENT_DEVELOPER role for app client of given UUID to be able to manage change App Client Developers - " +
"any of fields changed as APP_CLIENT_DEVELOPER will not be changed.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppClientUserDto.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "400",
description = "Bad request",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@PutMapping({"${api-prefix.v1}/app-client/{id}", "${api-prefix.v2}/app-client/{id}"})
public ResponseEntity<AppClientUserDto> updateAppClient(
@Parameter(description = "App Client ID to update", required = true) @PathVariable("id") UUID appClientId,
@Parameter(description = "Updated app client record", required = true) @Valid @RequestBody AppClientUserDto appClient) {
checkUserIsDashBoardAdminOrAppClientDeveloper(appClientId);
if (getUserIsDashBoardAdmin()) {
return new ResponseEntity<>(appClientService
.updateAppClientUser(appClientId, appClient), HttpStatus.OK);
}
else {
// they must be APP_CLIENT_DEVELOPER of this app then... so just let them modify certain fields
return new ResponseEntity<>(appClientService
.updateAppClientDeveloperItems(appClientId, appClient), HttpStatus.OK);
}
}
@Operation(summary = "Deletes an App Client",
description = "Deletes an existing App Client. Requires DASHBOARD_ADMIN access.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppClientUserDto.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@PreAuthorizeDashboardAdmin
@DeleteMapping({"${api-prefix.v1}/app-client/{id}", "${api-prefix.v2}/app-client/{id}"})
public ResponseEntity<Object> deleteAppClient(
@Parameter(description = "App Client ID to delete", required = true) @PathVariable("id") UUID id) {
return new ResponseEntity<>(appClientService.deleteAppClientUser(id), HttpStatus.OK);
}
/**
* @deprecated No longer valid T166. See {@link #getClientTypePrivsWrapped()} for new usage.
* @return
*/
@Operation(summary = "Gets all available privileges available for an app-client",
description = "Gets all the app client privileges so that privilege names can be mapped to their IDs. " +
"Must be a DASHBOARD_ADMIN or APP_CLIENT_DEVELOPER")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Operation Successful",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = PrivilegeDto.class))))
})
@Deprecated(since = "v2")
@GetMapping({"${api-prefix.v1}/app-client/privs"})
@PreAuthorize("hasAuthority('DASHBOARD_ADMIN') || hasAuthority('APP_CLIENT_DEVELOPER')")
public ResponseEntity<Object> getClientTypePrivs() {
List<PrivilegeDto> scratchPrivs = Lists.newArrayList(privilegeService.getPrivileges())
.stream()
.filter(item -> item.getName().startsWith(APP_CLIENT_DEVELOPER_PRIV)
|| item.getName().toLowerCase().startsWith("person")
|| item.getName().toLowerCase().startsWith("organization")
|| item.getName().startsWith("WRITE")
|| item.getName().startsWith("READ"))
.collect(Collectors.toList());
return new ResponseEntity<>(scratchPrivs, HttpStatus.OK);
}
@Operation(summary = "Gets all available privileges available for an app-client",
description = "Gets all the app client privileges so that privilege names can be mapped to their IDs. " +
"Must be a DASHBOARD_ADMIN or APP_CLIENT_DEVELOPER")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Operation Successful",
content = @Content(schema = @Schema(implementation = PrivilegeDtoResponseWrapper.class)))
})
@WrappedEnvelopeResponse
@GetMapping({"${api-prefix.v2}/app-client/privs"})
@PreAuthorize("hasAuthority('DASHBOARD_ADMIN') || hasAuthority('APP_CLIENT_DEVELOPER')")
public ResponseEntity<Object> getClientTypePrivsWrapped() {
List<PrivilegeDto> scratchPrivs = Lists.newArrayList(privilegeService.getPrivileges())
.stream()
.filter(item -> item.getName().startsWith(APP_CLIENT_DEVELOPER_PRIV)
|| item.getName().toLowerCase().startsWith("person")
|| item.getName().toLowerCase().startsWith("organization")
|| item.getName().startsWith("WRITE")
|| item.getName().startsWith("READ"))
.collect(Collectors.toList());
return new ResponseEntity<>(scratchPrivs, HttpStatus.OK);
}
}
package mil.tron.commonapi.controller;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.server.ResponseStatusException;
import mil.tron.commonapi.annotation.security.PreAuthorizeGateway;
import mil.tron.commonapi.exception.InvalidAppSourcePermissions;
import mil.tron.commonapi.service.AppGatewayService;
@Controller
public class AppGatewayController {
private AppGatewayService appGatewayService;
@Autowired
AppGatewayController(AppGatewayService appGatewayService) {
this.appGatewayService = appGatewayService;
}
@Cacheable(keyGenerator = "gatewayKeyGenerator", cacheResolver = "gatewayCacheResolver")
@PreAuthorizeGateway
public ResponseEntity<byte[]> handleCachedRequests(HttpServletRequest requestObject, HttpServletResponse responseObject, @PathVariable Map<String, String> vars) //NOSONAR
throws ResponseStatusException,
IOException,
InvalidAppSourcePermissions {
HttpHeaders headers = new HttpHeaders();
byte[] response = this.appGatewayService.sendRequestToAppSource(requestObject);
if (response != null ) {
headers.setContentLength(response.length);
return new ResponseEntity<>(response, headers, HttpStatus.OK);
}
byte[] emptyArray = new byte[0];
return new ResponseEntity<>(emptyArray, headers, HttpStatus.NO_CONTENT);
}
@PreAuthorizeGateway
public ResponseEntity<byte[]> handleRequests(HttpServletRequest requestObject, HttpServletResponse responseObject, @PathVariable Map<String, String> vars) //NOSONAR
throws ResponseStatusException,
IOException,
InvalidAppSourcePermissions {
HttpHeaders headers = new HttpHeaders();
byte[] response = this.appGatewayService.sendRequestToAppSource(requestObject);
if (response != null ) {
headers.setContentLength(response.length);
return new ResponseEntity<>(response, headers, HttpStatus.OK);
}
byte[] emptyArray = new byte[0];
return new ResponseEntity<>(emptyArray, headers, HttpStatus.NO_CONTENT);
}
}
\ No newline at end of file
package mil.tron.commonapi.controller;
import java.util.Date;
import java.util.UUID;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
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.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 mil.tron.commonapi.dto.metrics.AppClientCountMetricDto;
import mil.tron.commonapi.dto.metrics.AppEndpointCountMetricDto;
import mil.tron.commonapi.dto.metrics.AppSourceCountMetricDto;
import mil.tron.commonapi.dto.metrics.AppSourceMetricDto;
import mil.tron.commonapi.dto.metrics.EndpointMetricDto;
import mil.tron.commonapi.exception.BadRequestException;
import mil.tron.commonapi.exception.ExceptionResponse;
import mil.tron.commonapi.service.MetricService;
@RestController
@RequestMapping({"${api-prefix.v1}/metrics", "${api-prefix.v2}/metrics"})
@PreAuthorize("hasAuthority('DASHBOARD_ADMIN') or @appSourceService.userIsAdminForAppSource(#id, principal.username)")
public class MetricsController {
private MetricService metricService;
private final String dateMessage = "Start date must be before End Date";
@Value("${api-prefix.v1}")
private String apiVersion;
@Autowired
MetricsController(MetricService metricService) {
this.metricService = metricService;
}
@Operation(summary = "Retrieves all stored metrics values for given endpoint", description = "Retrieves all stored metric values for given endpoint")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = EndpointMetricDto.class))),
@ApiResponse(responseCode = "400",
description = "Bad Rquest (Start date and end date are both required. Start date must be before end date)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege or must be an Admin of the endpoint's App Source)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@PreAuthorize("hasAuthority('DASHBOARD_ADMIN') or @appSourceService.userIsAdminForAppSourceByEndpoint(#id, principal.username)")
@GetMapping("/endpoint/{id}")
public ResponseEntity<EndpointMetricDto> getAllMetricsForEndpoint (
@Parameter(description = "Endpoint Id to search with", required = true) @PathVariable("id") UUID id,
@Parameter(description = "Earliest date to include", required = true) @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Valid Date startDate,
@Parameter(description = "Latest date to include", required = true) @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Valid Date endDate
) {
if(startDate.compareTo(endDate) > -1) {
throw new BadRequestException(dateMessage);
}
return new ResponseEntity<>(metricService.getAllMetricsForEndpointDto(id, startDate, endDate), HttpStatus.OK);
}
@Operation(summary = "Retrieves all stored metrics values for given app source", description = "Retrieves all stored metric values for given app source")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppSourceMetricDto.class))),
@ApiResponse(responseCode = "400",
description = "Bad Rquest (Start date and end date are both required. Start date must be before end date)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege or must be an Admin of the App Source)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@GetMapping("/appsource/{id}")
public ResponseEntity<AppSourceMetricDto> getAllMetricsForAppSource (
@Parameter(description = "App Source Id to search with", required = true) @PathVariable("id") UUID id,
@Parameter(description = "Earliest date to include", required = true) @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
@Parameter(description = "Latest date to include", required = true) @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate
) {
if(startDate.compareTo(endDate) > -1) {
throw new BadRequestException(dateMessage);
}
return new ResponseEntity<>(metricService.getMetricsForAppSource(id, startDate, endDate), HttpStatus.OK);
}
@Operation(summary = "Retrieves sum of stored metric values for given app source", description = "Retrieves sum of stored metric values for given app source for each endppoint and for each app client")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppSourceCountMetricDto.class))),
@ApiResponse(responseCode = "400",
description = "Bad Rquest (Start date and end date are both required. Start date must be before end date)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege or must be an Admin of the App Source)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@GetMapping("/count/{id}")
public ResponseEntity<AppSourceCountMetricDto> getCountOfMetricsForAppSource (
@Parameter(description = "App Source Id to search with", required = true) @PathVariable("id") UUID id,
@Parameter(description = "Earliest date to include", required = true) @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
@Parameter(description = "Latest date to include", required = true) @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate
) {
if(startDate.compareTo(endDate) > -1) {
throw new BadRequestException(dateMessage);
}
return new ResponseEntity<>(metricService.getCountOfMetricsForAppSource(id, startDate, endDate), HttpStatus.OK);
}
@Operation(summary = "Retrieves sum of stored metric values for given endpoint path on given app source", description = "Retrieves sum of stored metric values for given endpoint path on given app source for each app client")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppEndpointCountMetricDto.class))),
@ApiResponse(responseCode = "400",
description = "Bad Rquest (Start date, end date, and path are all required. Start date must be before end date)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege or must be an Admin of the App Source)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@GetMapping("/count/{id}/endpoint")
public ResponseEntity<AppEndpointCountMetricDto> getCountOfMetricsForEndpoint (
@Parameter(description = "App Source Id to search with", required = true) @PathVariable("id") UUID id,
@Parameter(description = "Endpoint Path to search with", required = true) @RequestParam("path") String path,
@Parameter(description = "Endpoint Request Method Type", required = true) @RequestParam("method") RequestMethod method,
@Parameter(description = "Earliest date to include", required = true) @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
@Parameter(description = "Latest date to include", required = true) @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate
) {
if(startDate.compareTo(endDate) > -1) {
throw new BadRequestException(dateMessage);
}
return new ResponseEntity<>(metricService.getCountOfMetricsForEndpoint(id, path, method, startDate, endDate), HttpStatus.OK);
}
@Operation(summary = "Retrieves sum of stored metric values for given app client name on given app source", description = "Retrieves sum of stored metric values for given app client name on given app source for each endpoint")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = AppClientCountMetricDto.class))),
@ApiResponse(responseCode = "400",
description = "Bad Rquest (Start date, end date, and name are all required. Start date must be before end date)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege or must be an Admin of the App Source)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "404",
description = "Resource not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@GetMapping("/count/{id}/appclient")
public ResponseEntity<AppClientCountMetricDto> getCountOfMetricsForAppClient (
@Parameter(description = "App Source Id to search with", required = true) @PathVariable("id") UUID id,
@Parameter(description = "App Client Name to search with", required = true) @RequestParam("name") String name,
@Parameter(description = "Earliest date to include", required = true) @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
@Parameter(description = "Latest date to include", required = true) @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate
) {
if(startDate.compareTo(endDate) > -1) {
throw new BadRequestException(dateMessage);
}
return new ResponseEntity<>(metricService.getCountOfMetricsForAppClient(id, name, startDate, endDate), HttpStatus.OK);
}
}
package mil.tron.commonapi.controller.advice; package mil.tron.commonapi.controller.advice;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import mil.tron.commonapi.entity.Organization;
import mil.tron.commonapi.entity.Person;
import mil.tron.commonapi.exception.efa.IllegalModificationBaseException;
import mil.tron.commonapi.exception.efa.IllegalOrganizationModification;
import mil.tron.commonapi.exception.efa.IllegalPersonModification;
import mil.tron.commonapi.service.OrganizationService; import mil.tron.commonapi.service.OrganizationService;
import mil.tron.commonapi.service.PersonService; import mil.tron.commonapi.service.PersonService;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice @ControllerAdvice
public class ExceptionHandlerAdvice { public class ExceptionHandlerAdvice {
...@@ -24,38 +13,5 @@ public class ExceptionHandlerAdvice { ...@@ -24,38 +13,5 @@ public class ExceptionHandlerAdvice {
this.orgService = orgService; this.orgService = orgService;
this.personService = personService; this.personService = personService;
} }
/**
* Handles EFA related exceptions for the event in which fields were attempted
* to be modified without the proper privileges.
*
* @param ex the EFA exception
* @param request the request
* @return appropriate {@link ResponseEntity} including status code 203, Warning header
*/
@ExceptionHandler(value = { IllegalOrganizationModification.class, IllegalPersonModification.class })
protected ResponseEntity<Object> handleIllegalEntityModification(RuntimeException ex, WebRequest request) {
IllegalModificationBaseException efaException = (IllegalModificationBaseException)ex;
HttpHeaders headers = new HttpHeaders();
headers.set("Warning", "214 - Denied Entity Fields: " + String.join(",", efaException.getDeniedFields()));
Object data;
switch (efaException.getEfaType()) {
case ORGANIZATION:
data = orgService.convertToDto((Organization)efaException.getData());
break;
case PERSON:
data = personService.convertToDto((Person)efaException.getData(), null);
break;
default:
throw new UnsupportedOperationException(String.format("%s is not a supported EFA Type for exception handling", efaException.getEfaType()));
}
return ResponseEntity
.status(HttpStatus.NON_AUTHORITATIVE_INFORMATION)
.headers(headers)
.body(data);
}
} }
package mil.tron.commonapi.controller.kpi;
import java.util.Date;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 mil.tron.commonapi.annotation.response.WrappedEnvelopeResponse;
import mil.tron.commonapi.annotation.security.PreAuthorizeDashboardAdmin;
import mil.tron.commonapi.dto.kpi.KpiSummaryDto;
import mil.tron.commonapi.dto.kpi.KpiSummaryDtoResponseWrapper;
import mil.tron.commonapi.exception.ExceptionResponse;
import mil.tron.commonapi.service.kpi.KpiService;
@RestController
@RequestMapping({"${api-prefix.v2}/kpi"})
@PreAuthorizeDashboardAdmin
public class KpiController {
private KpiService kpiService;
KpiController(KpiService kpiService) {
this.kpiService = kpiService;
}
@Operation(summary = "Retrieves all KPI information", description = "Retrieves all KPI information between two dates.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = KpiSummaryDto.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "400",
description = "Bad Request. Possible reasons include: \n\n"
+ "Start Date required.\n\n"
+ "Start date must be before or equal to End Date.\n\n"
+ "Start date cannot be in the future (there would be no data).",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@GetMapping("/summary")
public ResponseEntity<KpiSummaryDto> getKpiSummary (
@Parameter(description = "Earliest date to include in UTC.",
schema = @Schema(type="string", format = "date", example = "yyyy-MM-dd"))
@RequestParam(required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date startDate,
@Parameter(description = "Latest date to include in UTC. Will default to the current date if not provided.",
schema = @Schema(type="string", format = "date", example = "yyyy-MM-dd"))
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date endDate
) {
return new ResponseEntity<>(kpiService.aggregateKpis(startDate, endDate), HttpStatus.OK);
}
@Operation(summary = "Retrieves previously recorded KPIs.",
description = "Retrieves previously recorded KPIs. The KPIs will be reported in weekly increments. "
+ "Monday is the start of the week and Sunday is the end of the week.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = KpiSummaryDtoResponseWrapper.class))),
@ApiResponse(responseCode = "403",
description = "Forbidden (Requires DASHBOARD_ADMIN privilege)",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
@ApiResponse(responseCode = "400",
description = "Bad Request. Possible reasons include: \n\n"
+ "Start Date required.\n\n"
+ "Start date must be before or equal to End Date.\n\n"
+ "Start date cannot be set to within the current week or in the future (there would be no data).",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@WrappedEnvelopeResponse
@GetMapping("/series")
public ResponseEntity<List<KpiSummaryDto>> getKpiSeries (
@Parameter(description = "Earliest date to include in UTC.",
schema = @Schema(type="string", format = "date", example = "yyyy-MM-dd"))
@RequestParam(required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date startDate,
@Parameter(description = "Latest date to include in UTC. Will default to the previous week from today if not provided.",
schema = @Schema(type="string", format = "date", example = "yyyy-MM-dd"))
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date endDate
) {
return new ResponseEntity<>(kpiService.getKpisRangeOnStartDateBetween(startDate, endDate), HttpStatus.OK);
}
}
package mil.tron.commonapi.controller.pubsub;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 mil.tron.commonapi.annotation.pubsub.PreAuthorizeAnyAppClientOrDeveloper;
import mil.tron.commonapi.annotation.pubsub.PreAuthorizeSubscriptionCreation;
import mil.tron.commonapi.annotation.pubsub.PreAuthorizeSubscriptionOwner;
import mil.tron.commonapi.annotation.response.WrappedEnvelopeResponse;
import mil.tron.commonapi.annotation.security.PreAuthorizeDashboardAdmin;
import mil.tron.commonapi.dto.EventInfoDto;
import mil.tron.commonapi.dto.EventInfoDtoResponseWrapper;
import mil.tron.commonapi.dto.pubsub.PubSubLedgerEntryDto;
import mil.tron.commonapi.dto.pubsub.PubSubLedgerEntryDtoResponseWrapper;
import mil.tron.commonapi.dto.pubsub.SubscriberDto;
import mil.tron.commonapi.dto.pubsub.SubscriberDtoResponseWrapper;
import mil.tron.commonapi.exception.BadRequestException;
import mil.tron.commonapi.exception.ExceptionResponse;
import mil.tron.commonapi.pubsub.EventManagerService;
import mil.tron.commonapi.service.AppClientUserService;
import mil.tron.commonapi.service.pubsub.SubscriberService;
import mil.tron.commonapi.service.utility.IstioHeaderUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Allows RESTful creation and management of event subscriptions
*/
@RestController
public class SubscriberController {
@Autowired
private SubscriberService subService;
@Autowired
private AppClientUserService appClientUserService;
@Autowired
private EventManagerService eventManagerService;
/**
* @deprecated No longer valid T166. See {@link #getAllSubscriptionsWrapped(Authentication authentication)} for new usage.
* @return
*/
@Operation(summary = "Retrieves all registered subscriptions", description = "Retrieves all subscriptions")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = SubscriberDto.class))))
})
@Deprecated(since = "v2")
@PreAuthorizeDashboardAdmin
@GetMapping({"${api-prefix.v1}/subscriptions"})
public ResponseEntity<Object> getAllSubscriptions() {
return new ResponseEntity<>(subService.getAllSubscriptions(), HttpStatus.OK);
}
@Operation(summary = "Retrieves all registered subscriptions", description = "Retrieves all subscriptions")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = SubscriberDtoResponseWrapper.class)))
})
@WrappedEnvelopeResponse
@GetMapping({"${api-prefix.v2}/subscriptions"})
public ResponseEntity<Object> getAllSubscriptionsWrapped(Authentication authentication) {
return new ResponseEntity<>(Lists.newArrayList(subService.getAllSubscriptions())
.stream()
.filter(item -> authentication // user is the owning app client itself
.getName()
.equalsIgnoreCase(IstioHeaderUtils.extractSubscriberNamespace(item.getSubscriberAddress()))
|| authentication // or user is a DASHBOARD_ADMIN
.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList())
.contains("DASHBOARD_ADMIN")
|| appClientUserService // or user is the developer for said application subscription
.userIsAppClientDeveloperForAppSubscription(item.getId(), authentication.getName()))
.peek(item -> item.setSecret("")) // sanitize the secret from going outbound
.collect(Collectors.toList()), HttpStatus.OK);
}
//
@Operation(summary = "Adds/updates a subscription", description = "Adds a new subscription, or updates an existing subscription")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = SubscriberDto.class)))
})
@PreAuthorizeSubscriptionCreation
@PostMapping({"${api-prefix.v1}/subscriptions", "${api-prefix.v2}/subscriptions"})
public ResponseEntity<SubscriberDto> createSubscription(@Valid @RequestBody SubscriberDto subscriber) {
return new ResponseEntity<>(subService.upsertSubscription(subscriber), HttpStatus.OK);
}
//
@Operation(summary = "Retrieves a registered subscription", description = "Retrieve a subscription by its UUID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = SubscriberDto.class))),
@ApiResponse(responseCode = "404",
description = "Record not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
})
@PreAuthorizeSubscriptionOwner
@GetMapping({"${api-prefix.v1}/subscriptions/{id}", "${api-prefix.v2}/subscriptions/{id}"})
public ResponseEntity<SubscriberDto> getSubscription(@PathVariable UUID id) {
SubscriberDto item = subService.getSubscriberById(id);
item.setSecret(""); // sanitize secret from going outbound
return new ResponseEntity<>(subService.getSubscriberById(id), HttpStatus.OK);
}
//
@Operation(summary = "Deletes a subscription", description = "Deletes a subscription by its UUID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = SubscriberDto.class))),
@ApiResponse(responseCode = "404",
description = "Record not found",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))),
})
@PreAuthorizeSubscriptionOwner
@DeleteMapping({"${api-prefix.v1}/subscriptions/{id}", "${api-prefix.v2}/subscriptions/{id}"})
public ResponseEntity<Object> cancelSubscription(@PathVariable UUID id) {
subService.cancelSubscription(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
/**
* @deprecated No longer valid T166. See {@link #getEventSinceDateWrapped(String)} for new usage.
* @param sinceDate
* @return
*/
@Operation(summary = "Retrieves all ledger entries from specified date/time regardless of event type",
description = "Date/time needs to be in zulu time with format yyyy-MM-ddTHH:mm:ss")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = PubSubLedgerEntryDto.class)))),
@ApiResponse(responseCode = "400",
description = "Bad Request - malformed date/time",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ExceptionResponse.class))))
})
@Deprecated(since = "v2")
@GetMapping({"${api-prefix.v1}/subscriptions/events/replay"})
public ResponseEntity<Object> getEventSinceDate(
@RequestParam(name="sinceDateTime", required = false) String sinceDate) {
try {
// manual string to date conversion to force user's time zone to UTC so no conversion takes place
// as opposed to relying on @DateTimeFormat...
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = fmt.parse(sinceDate);
return new ResponseEntity<>(eventManagerService.getMessagesSinceDateTime(date), HttpStatus.OK);
} catch (ParseException ex) {
throw new BadRequestException("Could not convert given date stamp to a valid date/time - check format is yyyy-MM-ddTHH:mm:ss");
}
}
@Operation(summary = "Retrieves all ledger entries from specified date/time regardless of event type",
description = "Date/time needs to be in zulu time with format yyyy-MM-ddTHH:mm:ss")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = PubSubLedgerEntryDtoResponseWrapper.class))),
@ApiResponse(responseCode = "400",
description = "Bad Request - malformed date/time",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)))
})
@WrappedEnvelopeResponse
@PreAuthorizeAnyAppClientOrDeveloper
@GetMapping({"${api-prefix.v2}/subscriptions/events/replay"})
public ResponseEntity<Object> getEventSinceDateWrapped(
@RequestParam(name="sinceDateTime", required = false) String sinceDate) {
try {
// manual string to date conversion to force user's time zone to UTC so no conversion takes place
// as opposed to relying on @DateTimeFormat...
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = fmt.parse(sinceDate);
return new ResponseEntity<>(eventManagerService.getMessagesSinceDateTime(date), HttpStatus.OK);
} catch (ParseException ex) {
throw new BadRequestException("Could not convert given date stamp to a valid date/time - check format is yyyy-MM-ddTHH:mm:ss");
}
}
/**
* @deprecated No longer valid T166. See {@link #getEventsSinceCountAndTypeWrapped(List)} for new usage.
* @param events
* @return
*/
@Operation(summary = "Retrieves all ledger entries from specified event count(s) and event types(s)",
description = "Simply provide a list of type EventInfoDto containing the event types and the LAST event count received for that event. " +
"The returned list will contain, as its start point, the point in time at which the oldest of those event types(s)/event count(s) " +
"occurred at - the remainder of that list will be event entries containing only events specified in the request body. Note the event count(s) " +
"provided should be equal to the actual count received from Common. This endpoint will know to return events from that count + 1.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = PubSubLedgerEntryDto.class)))),
@ApiResponse(responseCode = "400",
description = "Bad Request - malformed date/time",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ExceptionResponse.class))))
})
@Deprecated(since = "v2")
@PostMapping({"${api-prefix.v1}/subscriptions/events/replay-events"})
public ResponseEntity<Object> getEventsSinceCountAndType(
@Parameter(description = "List of events and counts to rewind to and playback", required = true) @Valid @RequestBody List<EventInfoDto> events) {
return new ResponseEntity<>(eventManagerService.getMessagesSinceEventCountByType(events), HttpStatus.OK);
}
@Operation(summary = "Retrieves all ledger entries from specified event count(s) and event types(s)",
description = "Simply provide a list of type EventInfoDto containing the event types and the LAST event count received for that event. " +
"The returned list will contain, as its start point, the point in time at which the oldest of those event types(s)/event count(s) " +
"occurred at - the remainder of that list will be event entries containing only events specified in the request body. Note the event count(s) " +
"provided should be equal to the actual count received from Common. This endpoint will know to return events from that count + 1.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = PubSubLedgerEntryDtoResponseWrapper.class))),
@ApiResponse(responseCode = "400",
description = "Bad Request - malformed date/time",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ExceptionResponse.class))))
})
@WrappedEnvelopeResponse
@PreAuthorizeAnyAppClientOrDeveloper
@PostMapping({"${api-prefix.v2}/subscriptions/events/replay-events"})
public ResponseEntity<Object> getEventsSinceCountAndTypeWrapped(
@Parameter(description = "List of events and counts to rewind to and playback", required = true) @Valid @RequestBody List<EventInfoDto> events) {
return new ResponseEntity<>(eventManagerService.getMessagesSinceEventCountByType(events), HttpStatus.OK);
}
/**
* @deprecated No longer valid T166. See {@link #getLatestCountsWrapped()} for new usage.
* @return
*/
@Operation(summary = "Retrieves most current counts for each event type", description = "Retrieves latest counts for each event type in a key-value pair object")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = EventInfoDto.class))))})
@Deprecated(since = "v2")
@GetMapping({"${api-prefix.v1}/subscriptions/events/latest"})
public ResponseEntity<Object> getLatestCounts() {
return new ResponseEntity<>(eventManagerService.getEventTypeCounts(), HttpStatus.OK);
}
@Operation(summary = "Retrieves most current counts for each event type", description = "Retrieves latest counts for each event type in a key-value pair object")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Successful operation",
content = @Content(schema = @Schema(implementation = EventInfoDtoResponseWrapper.class)))})
@WrappedEnvelopeResponse
@PreAuthorizeAnyAppClientOrDeveloper
@GetMapping({"${api-prefix.v2}/subscriptions/events/latest"})
public ResponseEntity<Object> getLatestCountsWrapped() {
return new ResponseEntity<>(eventManagerService.getEventTypeCounts(), HttpStatus.OK);
}
}
package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.util.UUID;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@EqualsAndHashCode
public class AppClientUserPrivDto {
@Getter
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private UUID id;
@NotNull
private UUID appClientUser;
@JsonInclude(Include.NON_NULL)
private String appClientUserName;
@NotNull
private UUID appEndpoint;
@JsonInclude(Include.NON_NULL)
private String privilege;
}
package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.*;
import mil.tron.commonapi.entity.pubsub.events.EventType;
/**
* DTO for holding the event count for a given pub sub message
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
public class EventInfoDto {
@Getter
@Setter
private EventType eventType;
@Getter
@Setter
private Long eventCount;
}
package mil.tron.commonapi.dto;
import java.util.List;
import mil.tron.commonapi.dto.response.WrappedResponse;
public class EventInfoDtoResponseWrapper extends WrappedResponse<List<EventInfoDto>> {
}
package mil.tron.commonapi.dto; package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
...@@ -14,12 +11,13 @@ import mil.tron.commonapi.entity.Organization; ...@@ -14,12 +11,13 @@ import mil.tron.commonapi.entity.Organization;
import mil.tron.commonapi.entity.Person; import mil.tron.commonapi.entity.Person;
import mil.tron.commonapi.entity.branches.Branch; import mil.tron.commonapi.entity.branches.Branch;
import mil.tron.commonapi.entity.orgtypes.Unit; import mil.tron.commonapi.entity.orgtypes.Unit;
import mil.tron.commonapi.validations.NullOrNotBlankValidation;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/** /**
* Mirror Organization entity but only shows UUIDs for nested Persons/Orgs * Mirror Organization entity but only shows UUIDs for nested Persons/Orgs
...@@ -180,32 +178,4 @@ public class OrganizationDto { ...@@ -180,32 +178,4 @@ public class OrganizationDto {
} }
} }
@JsonIgnore
private Map<String, String> meta;
@JsonAnyGetter
public Map<String, String> getMeta() {
return meta;
}
@JsonAnySetter
public OrganizationDto setMetaProperty(String property, String value) {
if (meta == null) {
meta = new HashMap<>();
}
meta.put(property, value);
return this;
}
@JsonIgnore
public String getMetaProperty(String property) {
return meta != null ? meta.get(property) : null;
}
public OrganizationDto removeMetaProperty(String property) {
if (meta != null) {
meta.remove(property);
}
return this;
}
} }
package mil.tron.commonapi.dto; package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
...@@ -14,8 +16,6 @@ import mil.tron.commonapi.validations.ValidPhoneNumber; ...@@ -14,8 +16,6 @@ import mil.tron.commonapi.validations.ValidPhoneNumber;
import javax.validation.constraints.Email; import javax.validation.constraints.Email;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
...@@ -228,37 +228,8 @@ public class PersonDto { ...@@ -228,37 +228,8 @@ public class PersonDto {
@JsonSetter(PersonDto.ORG_LEADERSHIPS_FIELD) @JsonSetter(PersonDto.ORG_LEADERSHIPS_FIELD)
public void setOrgLeaderships(Set<UUID> orgLeaderships) {} //NOSONAR public void setOrgLeaderships(Set<UUID> orgLeaderships) {} //NOSONAR
@JsonIgnore
private Map<String, String> meta;
@JsonAnyGetter
public Map<String, String> getMeta() {
return meta;
}
@JsonIgnore @JsonIgnore
public String getFullName() { public String getFullName() {
return String.format("%s %s", firstName, lastName); return String.format("%s %s", firstName, lastName);
} }
@JsonIgnore
public String getMetaProperty(String property) {
return meta != null ? meta.get(property) : null;
}
@JsonAnySetter
public PersonDto setMetaProperty(String property, String value) {
if (meta == null) {
meta = new HashMap<>();
}
meta.put(property, value);
return this;
}
public PersonDto removeMetaProperty(String property) {
if (meta != null) {
meta.remove(property);
}
return this;
}
} }
package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import mil.tron.commonapi.entity.scratch.ScratchStorageAppUserPriv;
import mil.tron.commonapi.entity.scratch.ScratchStorageUser;
import org.modelmapper.ModelMapper;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.*;
/**
* Represents a registered scratch space app that includes:
* - its UUID
* - its app name
* - its users and their privileges, represented as a collection of:
* {
* userId,
* userEmail,
* privs: [
* {
* userPrivPairId,
* priv: {
* id,
* name
* }
* }
* ]
* }
*
* The 'userPrivPairId' is the UUID of the particular ScratchStorageAppUserPriv entry
* in the database (ScratchStorageAppUserPrivRepository to be specific). You can delete
* the record of that UUID and effectively remove that Privilege of that user from that application.
*
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScratchStorageAppRegistryDto {
/**
* Private inner class that will represent a ScratchUserPriv entity
* with its userPrivePairId field representing the particular ScratchUserPriv UUID in the db
* so that a user/priv for a given app can easily be manipulated
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PrivilegeIdPair {
@Getter
@Setter
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private UUID userPrivPairId;
@Getter
@Setter
private PrivilegeDto priv;
}
/**
* Private inner class that represents a set of ScratchUserPriv's but reduced
* by user's email for better representation to the client UI (see setter method below for reducer method)
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class UserWithPrivs {
@Getter
@Setter
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private UUID userId;
@Getter
@Setter
private String emailAddress;
@Getter
@Setter
private List<PrivilegeIdPair> privs;
}
/**
* The registered scratch space app's unique ID
*/
@Getter
@Setter
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private UUID id;
/**
* The String name of the app
*/
@Getter
@Setter
@NotBlank
@NotNull
private String appName;
@Getter
@Setter
private boolean appHasImplicitRead = false;
@Getter
@Setter
private boolean aclMode = false;
@Getter
private List<UserWithPrivs> userPrivs;
@JsonSetter("userPrivs")
public void setJsonUserPrivs(List<UserWithPrivs> privs) {
this.userPrivs = privs;
}
/**
* Sets the userAppPrivs field by taking a Set of ScratchStorageAppUserPriv pairs
* and reducing them by user email address into a set of UserWithPrivs types
* @param privs
*/
public void setUserPrivs(List<ScratchStorageAppUserPriv> privs) {
ModelMapper mapper = new ModelMapper();
Map<ScratchStorageUser, Set<PrivilegeIdPair>> privHash = new HashMap<>();
// build out a map keyed by ScratchUser with their list of privileges
for (ScratchStorageAppUserPriv priv : privs) {
if (privHash.containsKey(priv.getUser())) {
Set<PrivilegeIdPair> privList = privHash.get(priv.getUser());
privList.add(PrivilegeIdPair.builder()
.userPrivPairId(priv.getId())
.priv(mapper.map(priv.getPrivilege(), PrivilegeDto.class))
.build());
privHash.put(priv.getUser(), privList);
}
else {
Set<PrivilegeIdPair> newPrivList = new HashSet<>();
newPrivList.add(PrivilegeIdPair.builder()
.userPrivPairId(priv.getId())
.priv(mapper.map(priv.getPrivilege(), PrivilegeDto.class))
.build());
privHash.put(priv.getUser(), newPrivList);
}
}
// reduce the map to a UserWithPriv Set
Set<Map.Entry<ScratchStorageUser, Set<PrivilegeIdPair>>> privSet = privHash.entrySet();
Set<UserWithPrivs> userPrivsSet = new HashSet<>();
for (Map.Entry<ScratchStorageUser, Set<PrivilegeIdPair>> pair : privSet) {
userPrivsSet.add(UserWithPrivs
.builder()
.userId(pair.getKey().getId())
.emailAddress(pair.getKey().getEmail())
.privs(Lists.newArrayList(pair.getValue()))
.build());
}
this.userPrivs = Lists.newArrayList(userPrivsSet);
}
}
package mil.tron.commonapi.dto;
import java.util.List;
import mil.tron.commonapi.dto.response.WrappedResponse;
public class ScratchStorageAppRegistryDtoResponseWrapper extends WrappedResponse<List<ScratchStorageAppRegistryDto>> {
}
package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.UUID;
/**
* Used to set/define priv (by its Privilege id) to assign to a user (by user email)
* which will then be bound to a scratch space application entry
* in the ScratchStorageAppRegistry table
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScratchStorageAppUserPrivDto {
@Getter
@Setter
@Builder.Default
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private UUID id = UUID.randomUUID();
@Getter
@NotBlank
@NotNull
@Email(message="Malformed email address")
private String email;
public void setEmail(String email) {
this.email = email.trim().toLowerCase();
}
@Getter
@Setter
@NotNull
private Long privilegeId;
}
package mil.tron.commonapi.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.UUID;
/**
* Representation of a scratch storage key/value pair
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScratchStorageEntryDto {
@JsonIgnore
@Getter
@Setter
@Builder.Default
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private UUID id = UUID.randomUUID();
@Getter
@Setter
private String value;
@Getter
@Setter
@NotNull
@NotBlank
private String key;
@Getter
@Setter
@NotNull
private UUID appId;
}