IntersectAllowLists combines the allow list produced by RBAC expansion with the API key's stored allow list. The result enforces both constraints: any resource must be allowed by the scope *and* the database filter. Wildcards in either list are respected and short-circuit appropriately. Intuition:
(scopeList []AllowListElement, dbList []AllowListElement)
| 172 | // stored. Only scopes that intentionally embed resource filters would trim the |
| 173 | // DB entries. |
| 174 | func IntersectAllowLists(scopeList []AllowListElement, dbList []AllowListElement) []AllowListElement { |
| 175 | // Empty DB list means no additional restriction. |
| 176 | if len(dbList) == 0 { |
| 177 | // Defensive: API keys should always persist a non-empty allow list, but |
| 178 | // we cannot have an empty allow list, thus we fail close. |
| 179 | return nil |
| 180 | } |
| 181 | |
| 182 | // If scope already allows everything, the db list is authoritative. |
| 183 | scopeAll := allowListContainsAll(scopeList) |
| 184 | dbAll := allowListContainsAll(dbList) |
| 185 | |
| 186 | switch { |
| 187 | case scopeAll && dbAll: |
| 188 | return []AllowListElement{AllowListAll()} |
| 189 | case scopeAll: |
| 190 | return dbList |
| 191 | case dbAll: |
| 192 | return scopeList |
| 193 | } |
| 194 | |
| 195 | // Otherwise compute intersection. |
| 196 | resultSet := make(map[string]AllowListElement) |
| 197 | for _, scopeElem := range scopeList { |
| 198 | matching := intersectAllow(scopeElem, dbList) |
| 199 | for _, elem := range matching { |
| 200 | resultSet[elem.String()] = elem |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | if len(resultSet) == 0 { |
| 205 | return []AllowListElement{} |
| 206 | } |
| 207 | |
| 208 | result := make([]AllowListElement, 0, len(resultSet)) |
| 209 | for _, elem := range resultSet { |
| 210 | result = append(result, elem) |
| 211 | } |
| 212 | |
| 213 | slices.SortFunc(result, func(a, b AllowListElement) int { |
| 214 | if a.Type == b.Type { |
| 215 | return strings.Compare(a.ID, b.ID) |
| 216 | } |
| 217 | return strings.Compare(a.Type, b.Type) |
| 218 | }) |
| 219 | |
| 220 | normalized, err := NormalizeAllowList(result) |
| 221 | if err != nil { |
| 222 | return result |
| 223 | } |
| 224 | if normalized == nil { |
| 225 | return []AllowListElement{} |
| 226 | } |
| 227 | return normalized |
| 228 | } |
| 229 | |
| 230 | func allowListContainsAll(elements []AllowListElement) bool { |
| 231 | if len(elements) == 0 { |