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}