Show reconnect when access-review connectors need new OAuth scopes
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
@@ -69,6 +69,58 @@ func FormatScopeString(scopes []string) string {
|
||||
return strings.Join(sorted, " ")
|
||||
}
|
||||
|
||||
// microsoftGraphScopePrefix is stripped when comparing scopes so that
|
||||
// Microsoft's short permission names (User.Read.All) match the full
|
||||
// resource URIs we request (https://graph.microsoft.com/User.Read.All).
|
||||
const microsoftGraphScopePrefix = "https://graph.microsoft.com/"
|
||||
|
||||
// canonicalizeScope normalizes a scope string for equality checks.
|
||||
func canonicalizeScope(scope string) string {
|
||||
return strings.TrimPrefix(scope, microsoftGraphScopePrefix)
|
||||
}
|
||||
|
||||
// MissingScopes returns the sorted list of scopes present in required but
|
||||
// absent from granted. Empty strings in either input are ignored. Scope
|
||||
// comparison is canonicalized so Microsoft Graph short names and full
|
||||
// resource URIs are treated as equivalent. The result uses the required
|
||||
// scope strings as provided and never aliases any input.
|
||||
func MissingScopes(required, granted []string) []string {
|
||||
grantedSet := make(map[string]struct{}, len(granted))
|
||||
for _, s := range granted {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
grantedSet[canonicalizeScope(s)] = struct{}{}
|
||||
}
|
||||
|
||||
missing := make([]string, 0)
|
||||
seenMissing := make(map[string]struct{})
|
||||
|
||||
for _, s := range required {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
key := canonicalizeScope(s)
|
||||
if _, ok := grantedSet[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := seenMissing[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seenMissing[key] = struct{}{}
|
||||
|
||||
missing = append(missing, s)
|
||||
}
|
||||
|
||||
sort.Strings(missing)
|
||||
|
||||
return missing
|
||||
}
|
||||
|
||||
// UnionScopes returns the sorted, deduplicated union of the given scope
|
||||
// slices. Empty strings and empty slices are handled gracefully. The
|
||||
// result is a fresh slice and never aliases any input.
|
||||
|
||||
@@ -53,6 +53,36 @@ func TestParseScopeString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingScopes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
required []string
|
||||
granted []string
|
||||
want []string
|
||||
}{
|
||||
{"both empty", nil, nil, []string{}},
|
||||
{"none missing", []string{"a", "b"}, []string{"b", "a"}, []string{}},
|
||||
{"some missing", []string{"a", "b", "c"}, []string{"a"}, []string{"b", "c"}},
|
||||
{"all missing", []string{"a", "b"}, nil, []string{"a", "b"}},
|
||||
{"drops empty strings", []string{"a", ""}, []string{""}, []string{"a"}},
|
||||
{"sorted output", []string{"z", "a"}, nil, []string{"a", "z"}},
|
||||
{
|
||||
"microsoft graph short vs full uri",
|
||||
[]string{"https://graph.microsoft.com/User.Read.All", "https://graph.microsoft.com/AuditLog.Read.All"},
|
||||
[]string{"User.Read.All"},
|
||||
[]string{"https://graph.microsoft.com/AuditLog.Read.All"},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, c.want, MissingScopes(c.required, c.granted))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnionScopes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user