001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.server.interceptor.binary; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.api.Hook; 025import ca.uhn.fhir.interceptor.api.Interceptor; 026import ca.uhn.fhir.interceptor.api.Pointcut; 027import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; 028import ca.uhn.fhir.rest.api.server.RequestDetails; 029import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 030import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; 031import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationConstants; 032import ca.uhn.fhir.util.FhirTerser; 033import org.apache.commons.lang3.Validate; 034import org.hl7.fhir.instance.model.api.IBaseBinary; 035import org.hl7.fhir.instance.model.api.IBaseResource; 036 037import static org.apache.commons.lang3.StringUtils.isNotBlank; 038 039/** 040 * This security interceptor checks any Binary resources that are being exposed to 041 * a user and can forbid the user from accessing them based on the security context 042 * found in <code>Binary.securityContext.identifier</code>. 043 * <p> 044 * This interceptor is intended to be subclassed. The default implementation if it 045 * is not subclassed will reject any access to a Binary resource unless the 046 * request is a system request (using {@link SystemRequestDetails} or the Binary 047 * resource has no value in <code>Binary.securityContext.identifier</code>. 048 * </p> 049 * <p> 050 * Override {@link #securityContextIdentifierAllowed(String, String, RequestDetails)} in order 051 * to allow the user to access specific context values. 052 * </p> 053 * 054 * @since 6.8.0 055 */ 056@SuppressWarnings("unused") 057@Interceptor(order = AuthorizationConstants.ORDER_BINARY_SECURITY_INTERCEPTOR) 058public class BinarySecurityContextInterceptor { 059 060 private final FhirContext myFhirContext; 061 062 /** 063 * Constructor 064 * 065 * @param theFhirContext The FHIR context 066 */ 067 public BinarySecurityContextInterceptor(FhirContext theFhirContext) { 068 Validate.notNull(theFhirContext, "theFhirContext must not be null"); 069 myFhirContext = theFhirContext; 070 } 071 072 /** 073 * Interceptor hook method. Do not call this method directly. 074 */ 075 @Hook(Pointcut.STORAGE_PRESHOW_RESOURCES) 076 public void preShowResources(IPreResourceShowDetails theShowDetails, RequestDetails theRequestDetails) { 077 for (IBaseResource nextResource : theShowDetails.getAllResources()) { 078 if (nextResource instanceof IBaseBinary) { 079 applyAccessControl((IBaseBinary) nextResource, theRequestDetails); 080 } 081 } 082 } 083 084 /** 085 * Interceptor hook method. Do not call this method directly. 086 */ 087 @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED) 088 public void preShowResources( 089 IBaseResource theOldValue, IBaseResource theNewValue, RequestDetails theRequestDetails) { 090 if (theOldValue instanceof IBaseBinary) { 091 applyAccessControl((IBaseBinary) theOldValue, theRequestDetails); 092 } 093 } 094 095 /** 096 * This method applies security to a given Binary resource. It is not typically 097 * overridden but you could override it if you wanted to completely replace the 098 * security logic in this interceptor. 099 * 100 * @param theBinary The Binary resource being checked 101 * @param theRequestDetails The request details associated with this request 102 */ 103 protected void applyAccessControl(IBaseBinary theBinary, RequestDetails theRequestDetails) { 104 FhirTerser terser = myFhirContext.newTerser(); 105 String securityContextSystem = 106 terser.getSinglePrimitiveValueOrNull(theBinary, "securityContext.identifier.system"); 107 String securityContextValue = 108 terser.getSinglePrimitiveValueOrNull(theBinary, "securityContext.identifier.value"); 109 110 if (isNotBlank(securityContextSystem) || isNotBlank(securityContextValue)) { 111 applyAccessControl(theBinary, securityContextSystem, securityContextValue, theRequestDetails); 112 } 113 } 114 115 /** 116 * This method applies access controls to a Binary resource containing the 117 * given identifier system and value in the Binary.securityContext element. 118 * 119 * @param theBinary The binary resource 120 * @param theSecurityContextSystem The identifier system 121 * @param theSecurityContextValue The identifier value 122 * @param theRequestDetails The request details 123 */ 124 protected void applyAccessControl( 125 IBaseBinary theBinary, 126 String theSecurityContextSystem, 127 String theSecurityContextValue, 128 RequestDetails theRequestDetails) { 129 if (theRequestDetails instanceof SystemRequestDetails) { 130 return; 131 } 132 if (securityContextIdentifierAllowed(theSecurityContextSystem, theSecurityContextValue, theRequestDetails)) { 133 return; 134 } 135 136 handleForbidden(theBinary); 137 } 138 139 /** 140 * Handles a non-permitted operation. This method throws a {@link ForbiddenOperationException} 141 * but you could override it to change that behaviour. 142 */ 143 protected void handleForbidden(IBaseBinary theBinary) { 144 throw new ForbiddenOperationException(Msg.code(2369) + "Security context not permitted"); 145 } 146 147 /** 148 * Determines whether the current user has access to the given security 149 * context identifier. This method is intended to be overridden, the default 150 * implementation simply always returns <code>false</code>. 151 * 152 * @param theSecurityContextSystem The <code>Binary.securityContext.identifier.system</code> value 153 * @param theSecurityContextValue The <code>Binary.securityContext.identifier.value</code> value 154 * @param theRequestDetails The request details associated with this request 155 * @return Returns <code>true</code> if the request should be permitted, and <code>false</code> otherwise 156 */ 157 protected boolean securityContextIdentifierAllowed( 158 String theSecurityContextSystem, String theSecurityContextValue, RequestDetails theRequestDetails) { 159 return false; 160 } 161}