001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2024 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.api.security.permission; 011 012import ca.cdr.api.log.Logs; 013import ca.cdr.api.model.json.GrantedAuthorityJson; 014import ca.uhn.fhir.context.FhirContext; 015import jakarta.annotation.Nonnull; 016import jakarta.annotation.Nullable; 017import org.apache.commons.lang3.StringUtils; 018import org.apache.commons.lang3.Validate; 019 020import java.util.Optional; 021 022/** 023 * Parser for PermissionEnum arguments 024 */ 025public final class PermissionArgumentParser { 026 /** 027 * Singleton for thread-safe non-fhir parser 028 */ 029 static final PermissionArgumentParser NO_FHIR_CONTEXT_PARSER = 030 new PermissionArgumentParser(IdBuilder.withoutFhirContext()); 031 032 private final IdBuilder myIdBuilder; 033 034 PermissionArgumentParser(IdBuilder theIdBuilder) { 035 myIdBuilder = theIdBuilder; 036 } 037 038 @Nonnull 039 public static PermissionArgumentParser withFhirContext(@Nonnull FhirContext theFhirContext) { 040 return new PermissionArgumentParser(IdBuilder.fromFhirContext(theFhirContext)); 041 } 042 043 @Nonnull 044 public static PermissionArgumentParser withoutFhirContext() { 045 return NO_FHIR_CONTEXT_PARSER; 046 } 047 048 /** 049 * Parse any authority generically. 050 * Parses the GrantedAuthorityJson argument via the declared format of the PermissionEnum. 051 * Use {@link ca.cdr.api.security.permission.PermissionArgumentFormat.IPermissionComponent} type checks 052 * to extract information. 053 * 054 * @param theGrantedAuthority the authority (permission and argument) to parse 055 * @return the parsed value, if the format was valid 056 */ 057 public Optional<PermissionArgumentValue> parse(@Nonnull GrantedAuthorityJson theGrantedAuthority) { 058 return parseForValue(theGrantedAuthority, PermissionArgumentValue.class); 059 } 060 061 /** 062 * Parse the argument string from the GrantedAuthorityJson into the declared argument value type. 063 * It is an error to request the wrong type. Use {@link #parse(GrantedAuthorityJson)} to parse 064 * for any permission. 065 * 066 * @param theGrantedAuthority the permission and argument to parse 067 * @param theArgumentValueType the concrete parsed value type or a component interface 068 * @param <T> the type or type component to be parsed 069 * @return the parsed data, if the declared argument format agrees with theArgumentValueType 070 * @throws IllegalArgumentException if the declared GA type does not match theArgumentValueType 071 */ 072 @Nonnull 073 public <T extends PermissionArgumentValue> Optional<T> parseForValue( 074 @Nonnull GrantedAuthorityJson theGrantedAuthority, @Nonnull Class<T> theArgumentValueType) { 075 PermissionArgumentFormat<?> permissionArgumentFormat = 076 theGrantedAuthority.getPermission().getFormat(); 077 Validate.isAssignableFrom( 078 theArgumentValueType, 079 permissionArgumentFormat.getType(), 080 "Cannot assign the permission format %s for %s to a %s", 081 permissionArgumentFormat.getType().getSimpleName(), 082 theGrantedAuthority.getPermission(), 083 theArgumentValueType.getSimpleName()); 084 085 Optional<? extends PermissionArgumentFormat.IPermissionComponent> parsed = 086 parse(theGrantedAuthority.getArgument(), permissionArgumentFormat); 087 088 if (parsed.isEmpty()) { 089 if (theGrantedAuthority.getPermission().isRequiresArgument()) { 090 Logs.getSecurityTroubleshootingLog() 091 .warn( 092 "Ignoring invalid authority - {} does not match declared format: {}", 093 theGrantedAuthority, 094 permissionArgumentFormat.getType().getSimpleName()); 095 } 096 return Optional.empty(); 097 } else { 098 Validate.isInstanceOf(theArgumentValueType, parsed.get()); 099 //noinspection unchecked - we just checked it above 100 return (Optional<T>) parsed; 101 } 102 } 103 104 /** 105 * Parse a string against a defined format. 106 * @param thePermissionArgument the permission argument to parse 107 * @param theFormat the permission argument format 108 * @return The value-type for the format, or empty if the string doesn't match the format. 109 * @param <T> The concrete argument value type (e.g. {@link TypeInCompartmentWithOptionalFilter}), for the format. 110 */ 111 public <T extends PermissionArgumentValue> Optional<T> parse( 112 @Nullable String thePermissionArgument, @Nonnull PermissionArgumentFormat<T> theFormat) { 113 return theFormat.parse(thePermissionArgument, myIdBuilder); 114 } 115 116 /** 117 * Goofy check looking for the presence of filter expressions in an argument. 118 * Faster than a full parse. 119 * @param theGrantedAuthorityJson the authority to check 120 * @return true if the permission argument type can have a filter AND the filter is present 121 */ 122 public static boolean hasFilter(GrantedAuthorityJson theGrantedAuthorityJson) { 123 return PermissionArgumentFormat.IOptionalFilterRestriction.class.isAssignableFrom( 124 theGrantedAuthorityJson.getPermission().getFormat().getType()) 125 && StringUtils.isNotEmpty(theGrantedAuthorityJson.getArgument()) 126 && theGrantedAuthorityJson.getArgument().indexOf(PermissionArgumentFormat.FILTER_DELIMITER) != -1; 127 } 128}