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}