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