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.model.enm.PermissionEnum; 013import ca.cdr.api.model.json.GrantedAuthorityJson; 014import jakarta.annotation.Nonnull; 015import jakarta.annotation.Nullable; 016import org.apache.commons.lang3.Validate; 017import org.hl7.fhir.instance.model.api.IIdType; 018 019import java.util.Collection; 020import java.util.Optional; 021import java.util.regex.Pattern; 022 023/** 024 * Public interface for the various permission argument formats. 025 * @param <F> The type this format parses/formats 026 */ 027public interface PermissionArgumentFormat<F extends PermissionArgumentValue> { 028 029 char FILTER_DELIMITER = '?'; 030 char TYPE_COMPARTMENT_DELIMITER = ':'; 031 String RESOURCE_TYPE_LIST_DELIMITER = " "; 032 String RESOURCE_TYPE_PATTERN = "[A-Z][a-zA-Z\\d]*"; 033 /** 034 * format for resource ids - used for compartments. 035 * The spec says [a-zA-Z0-9.\-]{1,36}, but hybrid might do any crazy thing, 036 * so we only verify we don't have any of our permission-format chars. 037 * @see ca.uhn.fhir.model.primitive.IdDt */ 038 Pattern ID_FORMAT = Pattern.compile(RESOURCE_TYPE_PATTERN + "/[^" + FILTER_DELIMITER + TYPE_COMPARTMENT_DELIMITER 039 + RESOURCE_TYPE_LIST_DELIMITER + "]+"); 040 041 /** 042 * Typesafe authority builder. 043 * Validates the argument format against the declared format for thePermissionEnum 044 * @param thePermissionEnum the permission 045 * @param theArgumentValue the concrete parsed argument type - use {@link NoArgument#INSTANCE} for no-arg. 046 */ 047 static GrantedAuthorityJson buildAuthority( 048 @Nonnull PermissionEnum thePermissionEnum, @Nonnull PermissionArgumentValue theArgumentValue) { 049 String formattedArg = thePermissionEnum.getFormat().formatIfValid(theArgumentValue); 050 return new GrantedAuthorityJson(thePermissionEnum, formattedArg); 051 } 052 053 /** 054 * Parse a permission argument string. 055 * @param theArgumentString The string to parse. null is treated as empty. 056 * @param theIdBuilder factory for parsing IIdType types 057 * @return the parsed arg value if the input matches this format 058 */ 059 @Nonnull 060 Optional<F> parse(@Nullable String theArgumentString, @Nonnull IdBuilder theIdBuilder); 061 062 @Nonnull 063 default <T extends PermissionArgumentValue> String formatIfValid(@Nonnull T theArgument) { 064 Validate.isInstanceOf(this.getType(), theArgument); 065 //noinspection unchecked 066 return format((F) theArgument); 067 } 068 069 /** 070 * @return the type of the value bean we parse 071 */ 072 Class<F> getType(); 073 074 /** 075 * format the value back into a string for persistence. 076 * @param theArgument the arg value bean 077 * @return the formatted string - empty will be the empty string, not null 078 */ 079 @Nonnull 080 String format(@Nonnull F theArgument); 081 082 /* -------------- */ 083 /* format components */ 084 /* -------------- */ 085 /** Empty marker for facets of permission arguments */ 086 interface IPermissionComponent {} 087 088 /** The permission is scoped to only apply to a type */ 089 interface IResourceTypeRestriction extends IPermissionComponent { 090 /** A resource type - e.g. Patient */ 091 String getResourceType(); 092 } 093 094 /** the permission is scoped to a compartment */ 095 interface ICompartmentRestriction extends IPermissionComponent { 096 /** Get compartment owner */ 097 IIdType getOwner(); 098 099 /** The type of the compartment - i.e. the type of the compartment owner. E.g Patient, Device, etc. */ 100 default String getCompartmentType() { 101 return getOwner().getResourceType(); 102 } 103 } 104 105 interface ICompartmentsRestriction extends IPermissionComponent { 106 /** Get compartment owners */ 107 Collection<IIdType> getOwners(); 108 } 109 110 /** The permission is scoped to a single instance */ 111 interface IInstanceRestriction extends IPermissionComponent, IResourceTypeRestriction { 112 /** The scope instance */ 113 IIdType getInstanceId(); 114 115 /** The type of the instance */ 116 @Override 117 default String getResourceType() { 118 return getInstanceId().getResourceType(); 119 } 120 } 121 122 /** The permission has an optional FHIR filter */ 123 interface IOptionalFilterRestriction extends IPermissionComponent { 124 /** The fhir query - optional */ 125 Optional<String> getFilter(); 126 /** Is this permission scoped by a fhir query? */ 127 default boolean hasFilter() { 128 return getFilter().isPresent(); 129 } 130 } 131 132 interface ICodeInValueSetRestriction extends IPermissionComponent { 133 /** A resource type - e.g., Patient */ 134 String getResourceType(); 135 136 /** a search parameter */ 137 String getSearchParamName(); 138 139 /** a URL identifying a value set */ 140 String getValueSetUrl(); 141 } 142 143 interface IOperationRestriction extends IPermissionComponent { 144 /** the name of an operation */ 145 String getOperationName(); 146 } 147 148 interface IExportResourceTypesRestriction extends IPermissionComponent { 149 /** indicates that the user has permission to export all types */ 150 boolean canExportAllResourceTypes(); 151 152 /** the names of the types that the user has permission to export */ 153 Collection<String> getResourceTypes(); 154 } 155 156 interface ICompartmentWithExportResourceTypesRestriction extends IPermissionComponent, ICompartmentRestriction { 157 158 /** the names of the types that the user has permission to export */ 159 Collection<String> getResourceTypes(); 160 } 161 162 interface ICompartmentsWithExportResourceTypesRestriction 163 extends IPermissionComponent, ICompartmentsRestriction, IExportResourceTypesRestriction {} 164 165 interface INodeAndModuleIdRestriction extends IPermissionComponent { 166 /** the nodeId that the user has permission for */ 167 String getNodeId(); 168 /** the moduleId that the user has permission for */ 169 String getModuleId(); 170 } 171}