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;
013import org.apache.commons.lang3.builder.EqualsBuilder;
014import org.apache.commons.lang3.builder.HashCodeBuilder;
015
016import java.util.EnumSet;
017import java.util.Set;
018
019import static ca.cdr.api.security.ScopeConstants.SMART_SCOPE_READ_PERMISSION;
020import static ca.cdr.api.security.ScopeConstants.SMART_SCOPE_WRITE_PERMISSION;
021import static ca.cdr.api.security.ScopeConstants.SUPPORTED_RESOURCE_OPERATIONS;
022
023public class SmartPermissions {
024
025        @Nonnull
026        private final EnumSet<SmartV2Permission> myV2Permissions;
027
028        @Nonnull
029        private final String myPermissions;
030
031        SmartPermissions(@Nonnull EnumSet<SmartV2Permission> theV2Permissions, @Nonnull String thePermisionString) {
032                myV2Permissions = theV2Permissions;
033                myPermissions = thePermisionString;
034        }
035
036        @Nonnull
037        public static SmartPermissions parse(String thePermisionString) {
038                EnumSet<SmartV2Permission> v2PermissionSet;
039                boolean isV1 = isValidSmartV1Permission(thePermisionString);
040                boolean isV2 = isValidSmartV2Permission(thePermisionString);
041                if (isV1) {
042                        v2PermissionSet = v1ToV2PermissionSet(thePermisionString);
043                } else if (isV2) {
044                        v2PermissionSet = v2toV2PermissionSet(thePermisionString);
045                } else {
046                        throw new IllegalArgumentException("Invalid permission: " + thePermisionString);
047                }
048                return new SmartPermissions(v2PermissionSet, thePermisionString);
049        }
050
051        @Nonnull
052        private static EnumSet<SmartV2Permission> v1ToV2PermissionSet(String thePerms) {
053                EnumSet<SmartV2Permission> v2Perms = EnumSet.noneOf(SmartV2Permission.class);
054
055                switch (thePerms) {
056                        case "*" -> {
057                                v2Perms.addAll(EnumSet.allOf(SmartV2Permission.class));
058                        }
059                        case SMART_SCOPE_READ_PERMISSION -> {
060                                v2Perms.add(SmartV2Permission.READ);
061                                v2Perms.add(SmartV2Permission.SEARCH);
062                        }
063                        case SMART_SCOPE_WRITE_PERMISSION -> {
064                                v2Perms.add(SmartV2Permission.CREATE);
065                                v2Perms.add(SmartV2Permission.UPDATE);
066                                v2Perms.add(SmartV2Permission.DELETE);
067                        }
068                        default -> throw new IllegalStateException("Unexpected value: " + thePerms);
069                }
070                return v2Perms;
071        }
072
073        @Nonnull
074        private static EnumSet<SmartV2Permission> v2toV2PermissionSet(String thePerms) {
075                EnumSet<SmartV2Permission> v2Perms = EnumSet.noneOf(SmartV2Permission.class);
076
077                for (int i = 0; i < thePerms.length(); i++) {
078                        SmartV2Permission perm = SmartV2Permission.fromCharCode(thePerms.substring(i, i + 1));
079                        v2Perms.add(perm);
080                }
081                return v2Perms;
082        }
083
084        @Override
085        public boolean equals(Object theO) {
086                if (this == theO) return true;
087
088                if (!(theO instanceof SmartPermissions that)) return false;
089
090                return new EqualsBuilder()
091                                .append(myV2Permissions, that.myV2Permissions)
092                                .append(myPermissions, that.myPermissions)
093                                .isEquals();
094        }
095
096        @Override
097        public int hashCode() {
098                return new HashCodeBuilder(17, 37)
099                                .append(myV2Permissions)
100                                .append(myPermissions)
101                                .toHashCode();
102        }
103
104        /**
105         * Are these valid v1 permissions (i.e. read, write, or *),
106         * or valid v2 permissions (e.g. cruds).
107         */
108        static boolean isValidPermission(String thePermission) {
109                return isValidSmartV1Permission(thePermission) || isValidSmartV2Permission(thePermission);
110        }
111
112        private static boolean isValidSmartV1Permission(String thePermission) {
113                return SUPPORTED_RESOURCE_OPERATIONS.contains(thePermission) || "*".equals(thePermission);
114        }
115
116        /** See the spec - https://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html#scopes-for-requesting-fhir-resources */
117        static boolean isValidSmartV2Permission(String thePermissions) {
118                return thePermissions.matches("c?r?u?d?s?");
119        }
120
121        @Nonnull
122        public String getPermissionString() {
123                return myPermissions;
124        }
125
126        @Nonnull
127        public Set<SmartV2Permission> getV2Permissions() {
128                return EnumSet.copyOf(myV2Permissions);
129        }
130
131        public boolean hasV2Permission(SmartV2Permission theSmartV2Permission) {
132                return myV2Permissions.contains(theSmartV2Permission);
133        }
134
135        public enum SmartV2Permission {
136                CREATE,
137                READ,
138                UPDATE,
139                DELETE,
140                SEARCH;
141
142                public String getCharCode() {
143                        return switch (this) {
144                                case CREATE -> "c";
145                                case READ -> "r";
146                                case UPDATE -> "u";
147                                case DELETE -> "d";
148                                case SEARCH -> "s";
149                        };
150                }
151
152                public static SmartV2Permission fromCharCode(String charCode) {
153                        return switch (charCode) {
154                                case "c" -> CREATE;
155                                case "r" -> READ;
156                                case "u" -> UPDATE;
157                                case "d" -> DELETE;
158                                case "s" -> SEARCH;
159                                default -> throw new IllegalArgumentException("Unknown char code: " + charCode);
160                        };
161                }
162        }
163}