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; 011 012import jakarta.annotation.Nonnull; 013 014import java.util.Arrays; 015import java.util.Optional; 016import java.util.Set; 017import java.util.stream.Collectors; 018 019import static org.apache.commons.lang3.StringUtils.defaultString; 020 021public class ScopeValidation { 022 023 /** 024 * Returns true if {@literal theClientAllowedScopes} contains {@literal theRequestedScope} or if 025 * {@literal theClientAllowedScopes} contains a scope that is more broad, such as <code>patient/*.read</code> 026 * and {@literal theRequestedScope} contains a narrower version such as <code>patient/Observation.read</code>. 027 */ 028 public static boolean isScopeAllowedDirectlyOrIndirectly( 029 Set<String> theClientAllowedScopes, String theRequestedScope) { 030 boolean isAllowable = false; 031 if (theClientAllowedScopes.contains(theRequestedScope)) { 032 // try simple match first 033 isAllowable = true; 034 } else { 035 Optional<SmartClinicalScope> maybeParsesScope = SmartClinicalScope.fromScopeString(theRequestedScope); 036 if (maybeParsesScope.isPresent()) { 037 // if this is a clinical scope (e.g. patient/*.*), then do some detailed checks. 038 SmartClinicalScope parsedRequestScope = maybeParsesScope.get(); 039 isAllowable = theClientAllowedScopes.stream() 040 .map(SmartClinicalScope::fromScopeString) 041 .flatMap(Optional::stream) 042 .anyMatch(parsedRequestScope::isImpliedBy); 043 } 044 } 045 return isAllowable; 046 } 047 048 @Nonnull 049 public static Set<String> scopesFromString(String scopeString) { 050 String[] split = defaultString(scopeString).split(" +"); 051 return Arrays.stream(split).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); 052 } 053}