diff --git a/cmd/api/src/analysis/azure/azure_integration_test.go b/cmd/api/src/analysis/azure/azure_integration_test.go
index 90f81a735f..3453df27a4 100644
--- a/cmd/api/src/analysis/azure/azure_integration_test.go
+++ b/cmd/api/src/analysis/azure/azure_integration_test.go
@@ -127,7 +127,7 @@ func TestAzureEntityGroupMembership(t *testing.T) {
if groupPaths, err := azureanalysis.FetchEntityGroupMembershipPaths(tx, harness.AZBaseHarness.User); err != nil {
t.Fatal(err)
} else {
- assert.ElementsMatch(t, harness.AZBaseHarness.UserFirstDegreeGroups.IDs(), groupPaths.AllNodes().ContainingNodeKinds(azure.Group).IDs())
+ assert.ElementsMatch(t, harness.AZBaseHarness.UserFirstDegreeGroups.IDs(), groupPaths.AllNodes().ContainingNodeKinds(azure.Group, azure.Group365).IDs())
}
})
}
@@ -554,6 +554,31 @@ func TestGroupEntityDetails(t *testing.T) {
})
}
+func TestGroup365EntityDetails(t *testing.T) {
+ testContext := integration.NewGraphTestContext(t, schema.DefaultGraphSchema())
+ testContext.ReadTransactionTestWithSetup(func(harness *integration.HarnessDetails) error {
+ harness.AZEntityPanelHarness.Setup(testContext)
+ return nil
+
+ }, func(harness integration.HarnessDetails, tx graph.Transaction) {
+
+ groupObjectID, err := harness.AZEntityPanelHarness.Group365.Properties.Get(common.ObjectID.String()).String()
+ require.Nil(t, err)
+
+ assert.NotEqual(t, "", groupObjectID)
+
+ group, err := azureanalysis.Group365EntityDetails(testContext.Graph.Database, groupObjectID, false)
+
+ require.Nil(t, err)
+ assert.Equal(t, harness.AZEntityPanelHarness.Group365.Properties.Get(common.ObjectID.String()).Any(), group.Properties[common.ObjectID.String()])
+ assert.Equal(t, 0, group.InboundObjectControl)
+
+ group, err = azureanalysis.Group365EntityDetails(testContext.Graph.Database, groupObjectID, true)
+ require.Nil(t, err)
+ assert.NotEqual(t, 0, group.InboundObjectControl)
+ })
+}
+
func TestManagementGroupEntityDetails(t *testing.T) {
testContext := integration.NewGraphTestContext(t, schema.DefaultGraphSchema())
testContext.ReadTransactionTestWithSetup(func(harness *integration.HarnessDetails) error {
@@ -807,11 +832,13 @@ func TestFetchInboundEntityObjectControlPaths(t *testing.T) {
paths, err := azureanalysis.FetchInboundEntityObjectControlPaths(tx, harness.AZInboundControlHarness.ControlledAZUser)
require.Nil(t, err)
nodes := paths.AllNodes().IDs()
- require.Equal(t, 8, len(nodes))
+ require.Equal(t, 10, len(nodes))
require.NotContains(t, nodes, harness.AZInboundControlHarness.AZAppA.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.ControlledAZUser.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroupA.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroupB.ID)
+ require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroup365A.ID)
+ require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroup365B.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZServicePrincipalA.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZServicePrincipalB.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZUserA.ID)
@@ -830,11 +857,13 @@ func TestFetchInboundEntityObjectControllers(t *testing.T) {
control, err := azureanalysis.FetchInboundEntityObjectControllers(tx, harness.AZInboundControlHarness.ControlledAZUser, 0, 0)
require.Nil(t, err)
nodes := control.IDs()
- require.Equal(t, 7, len(nodes))
+ require.Equal(t, 9, len(nodes))
require.NotContains(t, nodes, harness.AZInboundControlHarness.ControlledAZUser.ID)
require.NotContains(t, nodes, harness.AZInboundControlHarness.AZAppA.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroupA.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroupB.ID)
+ require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroup365A.ID)
+ require.Contains(t, nodes, harness.AZInboundControlHarness.AZGroup365B.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZServicePrincipalA.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZServicePrincipalB.ID)
require.Contains(t, nodes, harness.AZInboundControlHarness.AZUserA.ID)
diff --git a/cmd/api/src/analysis/azure/queries.go b/cmd/api/src/analysis/azure/queries.go
index 9be76367a6..b3aa3971db 100644
--- a/cmd/api/src/analysis/azure/queries.go
+++ b/cmd/api/src/analysis/azure/queries.go
@@ -99,6 +99,10 @@ func GraphStats(ctx context.Context, db graph.Database) (model.AzureDataQualityS
stat.Groups = int(count)
aggregation.Groups += int(count)
+ case azure.Group365:
+ stat.Groups365 = int(count)
+ aggregation.Groups365 += int(count)
+
case azure.App:
stat.Apps = int(count)
aggregation.Apps += int(count)
diff --git a/cmd/api/src/analysis/azure/queries_test.go b/cmd/api/src/analysis/azure/queries_test.go
index 10f57150a5..8922c1682f 100644
--- a/cmd/api/src/analysis/azure/queries_test.go
+++ b/cmd/api/src/analysis/azure/queries_test.go
@@ -41,6 +41,7 @@ func TestAnalysisAzure_GraphStats(t *testing.T) {
assert.NotZero(t, agg.Tenants)
assert.NotZero(t, agg.Users)
assert.NotZero(t, agg.Groups)
+ assert.NotZero(t, agg.Groups365)
assert.NotZero(t, agg.Apps)
assert.NotZero(t, agg.ServicePrincipals)
assert.NotZero(t, agg.Devices)
diff --git a/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go b/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go
index 7b379adae8..6e8e14edd7 100644
--- a/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go
+++ b/cmd/api/src/api/bloodhoundgraph/bloodhoundgraph.go
@@ -150,6 +150,10 @@ func (s *BloodHoundGraphNode) SetIcon(nType string) {
s.FontIcon = &BloodHoundGraphFontIcon{
Text: "fa-users",
}
+ case "AZGroup365":
+ s.FontIcon = &BloodHoundGraphFontIcon{
+ Text: "fa-users",
+ }
case "AZKeyVault":
s.FontIcon = &BloodHoundGraphFontIcon{
Text: "fa-lock",
@@ -319,6 +323,8 @@ func (s *BloodHoundGraphNode) SetBackground(nType string) {
s.BloodHoundGraphItem.Color = "#17E625"
case "Group":
s.BloodHoundGraphItem.Color = "#DBE617"
+ case "AZGroup365":
+ s.BloodHoundGraphItem.Color = "#34D2EB"
case "Computer":
s.BloodHoundGraphItem.Color = "#E67873"
case "Container":
diff --git a/cmd/api/src/api/v2/azure.go b/cmd/api/src/api/v2/azure.go
index 74f3ec6d37..a6e02cd4b3 100644
--- a/cmd/api/src/api/v2/azure.go
+++ b/cmd/api/src/api/v2/azure.go
@@ -45,6 +45,7 @@ const (
entityTypeBase = "az-base"
entityTypeUsers = "users"
entityTypeGroups = "groups"
+ entityTypeGroups365 = "groups365"
entityTypeTenants = "tenants"
entityTypeManagementGroups = "management-groups"
entityTypeSubscriptions = "subscriptions"
@@ -339,6 +340,9 @@ func GetAZEntityInformation(ctx context.Context, db graph.Database, entityType,
case entityTypeGroups:
return azure.GroupEntityDetails(db, objectID, hydrateCounts)
+ case entityTypeGroups365:
+ return azure.Group365EntityDetails(db, objectID, hydrateCounts)
+
case entityTypeTenants:
return azure.TenantEntityDetails(db, objectID, hydrateCounts)
diff --git a/cmd/api/src/daemons/datapipe/azure_convertors.go b/cmd/api/src/daemons/datapipe/azure_convertors.go
index 0149c6083b..e238cb15c4 100644
--- a/cmd/api/src/daemons/datapipe/azure_convertors.go
+++ b/cmd/api/src/daemons/datapipe/azure_convertors.go
@@ -55,10 +55,16 @@ func getKindConverter(kind enums.Kind) func(json.RawMessage, *ConvertedAzureData
return convertAzureFunctionAppRoleAssignment
case enums.KindAZGroup:
return convertAzureGroup
+ case enums.KindAZGroup365:
+ return convertAzureGroup365
case enums.KindAZGroupMember:
return convertAzureGroupMember
+ case enums.KindAZGroup365Member:
+ return convertAzureGroup365Member
case enums.KindAZGroupOwner:
return convertAzureGroupOwner
+ case enums.KindAZGroup365Owner:
+ return convertAzureGroup365Owner
case enums.KindAZKeyVault:
return convertAzureKeyVault
case enums.KindAZKeyVaultAccessPolicy:
@@ -282,6 +288,24 @@ func convertAzureGroup(raw json.RawMessage, converted *ConvertedAzureData) {
}
}
+func convertAzureGroup365(raw json.RawMessage, converted *ConvertedAzureData) {
+
+ var data models.Group365
+
+ if err := json.Unmarshal(raw, &data); err != nil {
+
+ slog.Error(fmt.Sprintf(SerialError, "azure Microsoft 36 group", err))
+
+ } else {
+
+ converted.NodeProps = append(converted.NodeProps, ein.ConvertAzureGroup365ToNode(data))
+
+ converted.RelProps = append(converted.RelProps, ein.ConvertAzureGroup365ToRel(data))
+
+ }
+
+}
+
func convertAzureGroupMember(raw json.RawMessage, converted *ConvertedAzureData) {
var (
data models.GroupMembers
@@ -294,6 +318,18 @@ func convertAzureGroupMember(raw json.RawMessage, converted *ConvertedAzureData)
}
}
+func convertAzureGroup365Member(raw json.RawMessage, converted *ConvertedAzureData) {
+ var (
+ data models.Group365Members
+ )
+
+ if err := json.Unmarshal(raw, &data); err != nil {
+ slog.Error(fmt.Sprintf(SerialError, "azure Microsoft 365 group members", err))
+ } else {
+ converted.RelProps = append(converted.RelProps, ein.ConvertAzureGroup365MembersToRels(data)...)
+ }
+}
+
func convertAzureGroupOwner(raw json.RawMessage, converted *ConvertedAzureData) {
var (
data models.GroupOwners
@@ -305,6 +341,17 @@ func convertAzureGroupOwner(raw json.RawMessage, converted *ConvertedAzureData)
}
}
+func convertAzureGroup365Owner(raw json.RawMessage, converted *ConvertedAzureData) {
+ var (
+ data models.Group365Owners
+ )
+ if err := json.Unmarshal(raw, &data); err != nil {
+ slog.Error(fmt.Sprintf(SerialError, "azure Microsoft 365 group owners", err))
+ } else {
+ converted.RelProps = append(converted.RelProps, ein.ConvertAzureGroup365OwnerToRels(data)...)
+ }
+}
+
func convertAzureKeyVault(raw json.RawMessage, converted *ConvertedAzureData) {
var data models.KeyVault
if err := json.Unmarshal(raw, &data); err != nil {
diff --git a/cmd/api/src/database/dataquality.go b/cmd/api/src/database/dataquality.go
index d51c1f7bd0..1512d3e816 100644
--- a/cmd/api/src/database/dataquality.go
+++ b/cmd/api/src/database/dataquality.go
@@ -77,6 +77,7 @@ WITH aggregated_quality_stats AS (
DATE_TRUNC('day', created_at) AS created_date,
MAX(users) AS max_users,
MAX(groups) AS max_groups,
+ MAX(groups365) AS max_groups365,
MAX(computers) AS max_computers,
MAX(ous) AS max_ous,
MAX(containers) AS max_containers,
@@ -101,6 +102,7 @@ SELECT
created_date AS created_at,
SUM(max_users) AS users,
SUM(max_groups) AS groups,
+ SUM(max_groups365) AS groups365,
SUM(max_computers) AS computers,
SUM(max_ous) AS ous,
SUM(max_containers) AS containers,
diff --git a/cmd/api/src/database/migration/migrations/v7.4.0.sql b/cmd/api/src/database/migration/migrations/v7.4.0.sql
new file mode 100644
index 0000000000..32ff3cca5e
--- /dev/null
+++ b/cmd/api/src/database/migration/migrations/v7.4.0.sql
@@ -0,0 +1,25 @@
+-- Copyright 2025 Specter Ops, Inc.
+--
+-- Licensed under the Apache License, Version 2.0
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- SPDX-License-Identifier: Apache-2.0
+
+-- Migration to add `Microsoft 365 groups` column to relevant tables
+
+-- Add `groups365` column to `azure_data_quality_aggregations` table
+ALTER TABLE IF EXISTS azure_data_quality_aggregations
+ ADD COLUMN groups365 bigint;
+
+-- Add `groups365` column to `azure_data_quality_stats` table
+ALTER TABLE IF EXISTS azure_data_quality_stats
+ ADD COLUMN groups365 bigint;
\ No newline at end of file
diff --git a/cmd/api/src/migrations/manifest.go b/cmd/api/src/migrations/manifest.go
index 46605c7f49..88bde1e0ca 100644
--- a/cmd/api/src/migrations/manifest.go
+++ b/cmd/api/src/migrations/manifest.go
@@ -155,7 +155,7 @@ func Version_508_Migration(ctx context.Context, db graph.Database) error {
return query.And(
query.Kind(query.Start(), azure.Entity),
// Not all of these node types are being changed, but there's no harm in adding them to the migration
- query.KindIn(query.End(), azure.ManagementGroup, azure.ResourceGroup, azure.Subscription, azure.KeyVault, azure.AutomationAccount, azure.ContainerRegistry, azure.LogicApp, azure.VMScaleSet, azure.WebApp, azure.FunctionApp, azure.ManagedCluster, azure.VM),
+ query.KindIn(query.End(), azure.ManagementGroup, azure.ResourceGroup, azure.Subscription, azure.KeyVault, azure.AutomationAccount, azure.ContainerRegistry, azure.LogicApp, azure.VMScaleSet, azure.WebApp, azure.FunctionApp, azure.ManagedCluster, azure.VM, azure.Group365),
query.Kind(query.Relationship(), azure.Owns),
)
}).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error {
diff --git a/cmd/api/src/model/azurequality.go b/cmd/api/src/model/azurequality.go
index d24d309379..0ac134eb15 100644
--- a/cmd/api/src/model/azurequality.go
+++ b/cmd/api/src/model/azurequality.go
@@ -20,6 +20,7 @@ type AzureStatKinds struct {
Relationships int `json:"relationships"`
Users int `json:"users"`
Groups int `json:"groups"`
+ Groups365 int `json:"groups365"`
Apps int `json:"apps"`
ServicePrincipals int `json:"service_principals"`
Devices int `json:"devices"`
diff --git a/cmd/api/src/test/integration/graph.go b/cmd/api/src/test/integration/graph.go
index 8e65f9962e..b4f230a259 100644
--- a/cmd/api/src/test/integration/graph.go
+++ b/cmd/api/src/test/integration/graph.go
@@ -228,6 +228,17 @@ func (s *GraphTestContext) NewAzureGroup(name, objectID, tenantID string) *graph
}), azure.Entity, azure.Group)
}
+func (s *GraphTestContext) NewAzureGroup365(name, objectID, tenantID string) *graph.Node {
+
+ return s.NewNode(graph.AsProperties(graph.PropertyMap{
+
+ common.Name: name,
+ common.ObjectID: objectID,
+ azure.TenantID: tenantID,
+ azure.IsAssignableToRole: true,
+ }), azure.Entity, azure.Group365)
+}
+
func (s *GraphTestContext) NewAzureVM(name, objectID, tenantID string) *graph.Node {
return s.NewNode(graph.AsProperties(graph.PropertyMap{
common.Name: name,
diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go
index fea0151f22..6a40ca0dfd 100644
--- a/cmd/api/src/test/integration/harnesses.go
+++ b/cmd/api/src/test/integration/harnesses.go
@@ -806,7 +806,7 @@ func (s *AZBaseHarness) Setup(testCtx *GraphTestContext) {
s.ServicePrincipal = testCtx.NewAzureServicePrincipal(HarnessServicePrincipalName, RandomObjectID(testCtx.testCtx), tenantID)
s.Nodes.Add(s.Tenant, s.User, s.Application, s.ServicePrincipal)
s.UserFirstDegreeGroups = graph.NewNodeSet()
- s.NumPaths = 1287
+ s.NumPaths = 1307
// Tie the user to the tenant and vice-versa
// Note: This will cause a full re-traversal of paths outbound from the user object
@@ -816,6 +816,7 @@ func (s *AZBaseHarness) Setup(testCtx *GraphTestContext) {
// Create some MemberOf relationships for the new user
for nestingDepth := numGroups; nestingDepth > 0; nestingDepth-- {
newGroups := s.CreateAzureNestedGroupChain(testCtx, tenantID, nestingDepth)
+ newGroups.AddSet(s.CreateAzureNestedGroup365Chain(testCtx, tenantID, nestingDepth))
s.Nodes.Add(newGroups.Slice()...)
for _, newGroup := range newGroups {
@@ -892,12 +893,38 @@ func (s *AZBaseHarness) CreateAzureNestedGroupChain(testCtx *GraphTestContext, t
return groupNodes
}
+func (s *AZBaseHarness) CreateAzureNestedGroup365Chain(testCtx *GraphTestContext, tenantID string, chainDepth int) graph.NodeSet {
+ var (
+ previousGroup *graph.Node
+ groupNodes = graph.NewNodeSet()
+ )
+
+ for groupIdx := 0; groupIdx < chainDepth; groupIdx++ {
+ var (
+ objectID = RandomObjectID(testCtx.testCtx)
+ newGroup = testCtx.NewAzureGroup365(fmt.Sprintf("AZGroup365_%s", objectID), objectID, tenantID)
+ )
+
+ if previousGroup == nil {
+ testCtx.NewRelationship(s.User, newGroup, azure.MemberOf)
+ s.UserFirstDegreeGroups.Add(newGroup)
+ } else {
+ testCtx.NewRelationship(previousGroup, newGroup, azure.MemberOf)
+ }
+
+ groupNodes.Add(newGroup)
+ previousGroup = newGroup
+ }
+ return groupNodes
+}
+
type AZGroupMembershipHarness struct {
- Tenant *graph.Node
- UserA *graph.Node
- UserB *graph.Node
- UserC *graph.Node
- Group *graph.Node
+ Tenant *graph.Node
+ UserA *graph.Node
+ UserB *graph.Node
+ UserC *graph.Node
+ Group *graph.Node
+ Group365 *graph.Node
}
func (s *AZGroupMembershipHarness) Setup(testCtx *GraphTestContext) {
@@ -907,12 +934,17 @@ func (s *AZGroupMembershipHarness) Setup(testCtx *GraphTestContext) {
s.UserB = testCtx.NewAzureUser("UserB", "UserB", "", RandomObjectID(testCtx.testCtx), "", tenantID, false)
s.UserC = testCtx.NewAzureUser("UserC", "UserC", "", RandomObjectID(testCtx.testCtx), "", tenantID, false)
s.Group = testCtx.NewAzureGroup("Group", RandomObjectID(testCtx.testCtx), tenantID)
+ s.Group365 = testCtx.NewAzureGroup365("Group365", RandomObjectID(testCtx.testCtx), tenantID)
testCtx.NewRelationship(s.Tenant, s.Group, azure.Contains)
+ testCtx.NewRelationship(s.Tenant, s.Group365, azure.Contains)
testCtx.NewRelationship(s.UserA, s.Group, azure.MemberOf)
testCtx.NewRelationship(s.UserB, s.Group, azure.MemberOf)
testCtx.NewRelationship(s.UserC, s.Group, azure.MemberOf)
+ testCtx.NewRelationship(s.UserA, s.Group365, azure.MemberOf)
+ testCtx.NewRelationship(s.UserB, s.Group365, azure.MemberOf)
+ testCtx.NewRelationship(s.UserC, s.Group365, azure.MemberOf)
}
type AZManagementGroupHarness struct {
@@ -941,6 +973,7 @@ type AZEntityPanelHarness struct {
Application *graph.Node
Device *graph.Node
Group *graph.Node
+ Group365 *graph.Node
ManagementGroup *graph.Node
ResourceGroup *graph.Node
KeyVault *graph.Node
@@ -957,6 +990,7 @@ func (s *AZEntityPanelHarness) Setup(testCtx *GraphTestContext) {
s.Application = testCtx.NewAzureApplication("App", RandomObjectID(testCtx.testCtx), tenantID)
s.Device = testCtx.NewAzureDevice("Device", RandomObjectID(testCtx.testCtx), RandomObjectID(testCtx.testCtx), tenantID)
s.Group = testCtx.NewAzureGroup("Group", RandomObjectID(testCtx.testCtx), tenantID)
+ s.Group365 = testCtx.NewAzureGroup("Group365", RandomObjectID(testCtx.testCtx), tenantID)
s.ManagementGroup = testCtx.NewAzureResourceGroup("Mgmt Group", RandomObjectID(testCtx.testCtx), tenantID)
s.ResourceGroup = testCtx.NewAzureResourceGroup("Resource Group", RandomObjectID(testCtx.testCtx), tenantID)
s.KeyVault = testCtx.NewAzureKeyVault("Key Vault", RandomObjectID(testCtx.testCtx), tenantID)
@@ -978,6 +1012,7 @@ func (s *AZEntityPanelHarness) Setup(testCtx *GraphTestContext) {
testCtx.NewRelationship(s.User, s.Group, azure.Owns)
testCtx.NewRelationship(s.User, s.ResourceGroup, azure.Owns)
testCtx.NewRelationship(s.User, s.ManagementGroup, azure.Owner)
+ testCtx.NewRelationship(s.User, s.Group365, azure.Owns)
// Key Vault
testCtx.NewRelationship(s.User, s.KeyVault, azure.Owns)
@@ -1209,6 +1244,8 @@ type AZInboundControlHarness struct {
AZAppA *graph.Node
AZGroupA *graph.Node
AZGroupB *graph.Node
+ AZGroup365A *graph.Node
+ AZGroup365B *graph.Node
AZUserA *graph.Node
AZUserB *graph.Node
AZServicePrincipalA *graph.Node
@@ -1222,20 +1259,27 @@ func (s *AZInboundControlHarness) Setup(testCtx *GraphTestContext) {
s.AZAppA = testCtx.NewAzureApplication("AZAppA", RandomObjectID(testCtx.testCtx), tenantID)
s.AZGroupA = testCtx.NewAzureGroup("AZGroupA", RandomObjectID(testCtx.testCtx), tenantID)
s.AZGroupB = testCtx.NewAzureGroup("AZGroupB", RandomObjectID(testCtx.testCtx), tenantID)
+ s.AZGroup365A = testCtx.NewAzureGroup365("AZGroup365A", RandomObjectID(testCtx.testCtx), tenantID)
+ s.AZGroup365B = testCtx.NewAzureGroup365("AZGroup365B", RandomObjectID(testCtx.testCtx), tenantID)
s.AZUserA = testCtx.NewAzureUser("AZUserA", "AZUserA", "", RandomObjectID(testCtx.testCtx), HarnessUserLicenses, tenantID, HarnessUserMFAEnabled)
s.AZUserB = testCtx.NewAzureUser("AZUserB", "AZUserB", "", RandomObjectID(testCtx.testCtx), HarnessUserLicenses, tenantID, HarnessUserMFAEnabled)
s.AZServicePrincipalA = testCtx.NewAzureServicePrincipal("AZServicePrincipalA", RandomObjectID(testCtx.testCtx), tenantID)
s.AZServicePrincipalB = testCtx.NewAzureServicePrincipal("AZServicePrincipalB", RandomObjectID(testCtx.testCtx), tenantID)
testCtx.NewRelationship(s.AZTenant, s.AZGroupA, azure.Contains)
+ testCtx.NewRelationship(s.AZTenant, s.AZGroup365A, azure.Contains)
testCtx.NewRelationship(s.AZUserA, s.AZGroupA, azure.MemberOf)
testCtx.NewRelationship(s.AZServicePrincipalB, s.AZGroupB, azure.MemberOf)
+ testCtx.NewRelationship(s.AZUserA, s.AZGroup365A, azure.MemberOf)
+ testCtx.NewRelationship(s.AZServicePrincipalB, s.AZGroup365B, azure.MemberOf)
testCtx.NewRelationship(s.AZAppA, s.AZServicePrincipalA, azure.RunsAs)
testCtx.NewRelationship(s.AZGroupA, s.ControlledAZUser, azure.ResetPassword)
testCtx.NewRelationship(s.AZGroupB, s.ControlledAZUser, azure.ResetPassword)
+ testCtx.NewRelationship(s.AZGroup365A, s.ControlledAZUser, azure.ResetPassword)
+ testCtx.NewRelationship(s.AZGroup365B, s.ControlledAZUser, azure.ResetPassword)
testCtx.NewRelationship(s.AZUserB, s.ControlledAZUser, azure.ResetPassword)
testCtx.NewRelationship(s.AZServicePrincipalA, s.ControlledAZUser, azure.ResetPassword)
}
diff --git a/cmd/ui/src/ducks/entityinfo/types.ts b/cmd/ui/src/ducks/entityinfo/types.ts
index 72eb4ad651..5312334a7e 100644
--- a/cmd/ui/src/ducks/entityinfo/types.ts
+++ b/cmd/ui/src/ducks/entityinfo/types.ts
@@ -305,6 +305,11 @@ export interface AZGroupInfo extends AZEntityInfo {
roles: number;
}
+export interface AZGroup365Info extends AZEntityInfo {
+ props: BasicInfo;
+ inbound_object_control: number;
+}
+
export interface AZKeyVaultInfo extends AZEntityInfo {
props: BasicInfo;
Readers: {
diff --git a/cmd/ui/src/ducks/graph/graphutils.ts b/cmd/ui/src/ducks/graph/graphutils.ts
index 1227107075..92d66df5ee 100644
--- a/cmd/ui/src/ducks/graph/graphutils.ts
+++ b/cmd/ui/src/ducks/graph/graphutils.ts
@@ -227,6 +227,7 @@ const ICONS: { [id in GraphNodeTypes]: string } = {
[GraphNodeTypes.Domain]: 'fa-globe',
[GraphNodeTypes.GPO]: 'fa-th-list',
[GraphNodeTypes.Group]: 'fa-users',
+ [GraphNodeTypes.AZGroup365]: 'fa-users',
[GraphNodeTypes.OU]: 'fa-sitemap',
[GraphNodeTypes.User]: 'fa-user',
[GraphNodeTypes.Container]: 'fa-box',
diff --git a/cmd/ui/src/ducks/graph/types.ts b/cmd/ui/src/ducks/graph/types.ts
index 3ee5002d3b..b233eedde4 100644
--- a/cmd/ui/src/ducks/graph/types.ts
+++ b/cmd/ui/src/ducks/graph/types.ts
@@ -22,6 +22,7 @@ export enum GraphNodeTypes {
AZDevice = 'AZDevice',
AZFunctionApp = 'AZFunctionApp',
AZGroup = 'AZGroup',
+ AZGroup365 = 'AZGroup365',
AZKeyVault = 'AZKeyVault',
AZManagementGroup = 'AZManagementGroup',
AZResourceGroup = 'AZResourceGroup',
diff --git a/docs/resources/nodes/az-group365.mdx b/docs/resources/nodes/az-group365.mdx
new file mode 100644
index 0000000000..e3d59590c0
--- /dev/null
+++ b/docs/resources/nodes/az-group365.mdx
@@ -0,0 +1,30 @@
+---
+title: AZGroup365
+---
+
+
+
+## Node properties
+The node supports the properties of the table below.
+
+
+Properties which are blank/null will not be shown in the Entity Panel.
+
+
+| | |
+| --- | --- |
+| **Entity Panel name** | **Description** |
+| Tier Zero / High Value | BloodHound Enterprise: Whether the object is part of Tier Zero of the Microsoft's Active Directory Tier Model, or the Control Plane of Microsoft's Enterprise Access Model.
BloodHound CE: Whether the object is currently marked as High Value. By default any object that belongs to Tier Zero is marked as High Value. |
+| Display Name | The display name for the object. |
+| Object ID | The object's security identifier (SID), a unique identifier in the directory. |
+| Admin Count | Whether the object currently, or possibly ever has belonged to a certain set of highly privileged groups. For Active Directory nodes this is related to the AdminSDHolder object and the SDProp process, read about that [here](https://adsecurity.org/?p=2053). |
+| Created | The time when the object was created in the directory. |
+| Description | The contents of the description field for the object. |
+| Is Role Assignable | Whether the group can be assigned to Azure roles. When set to "True," group members inherit role-based permissions. When set to "False," role assignments are not allowed for the group. |
+| On-Prem Sync Enabled | Whether the object is synchronized to on-premises Active Directory. |
+| Security Enabled | Whether the group is a Security Principal, meaning it can be used to secure objects in Entra ID. |
+| Security Identifier | - |
+| Mail | The mail of the Group |
+| Visibility | Set to "True" if the group can be join by anyone, "False" if not |
+| Tenant ID | Unique identifier for the Azure tenant. |
+
diff --git a/packages/cue/bh/azure/azure.cue b/packages/cue/bh/azure/azure.cue
index f4d82c412b..af8904505c 100644
--- a/packages/cue/bh/azure/azure.cue
+++ b/packages/cue/bh/azure/azure.cue
@@ -254,6 +254,21 @@ TenantID: types.#StringEnum & {
representation: "tenantid"
}
+Visibility: types.#StringEnum & {
+ symbol: "Visibility"
+ schema: "azure"
+ name: "Visibility"
+ representation: "visibility"
+}
+
+M365GroupMail: types.#StringEnum & {
+ symbol: "Mail"
+ schema: "azure"
+ name: "M365 Group Mail"
+ representation: "mail"
+}
+
+
Properties: [
AppOwnerOrganizationID,
AppDescription,
@@ -287,6 +302,8 @@ Properties: [
PublisherDomain,
SignInAudience,
RoleTemplateID,
+ Visibility,
+ M365GroupMail,
]
// Kinds
@@ -332,6 +349,12 @@ Group: types.#Kind & {
representation: "AZGroup"
}
+Group365: types.#Kind & {
+ symbol: "Group365"
+ schema: "azure"
+ representation: "AZGroup365"
+}
+
KeyVault: types.#Kind & {
symbol: "KeyVault"
schema: "azure"
@@ -418,6 +441,7 @@ NodeKinds: [
Device,
FunctionApp,
Group,
+ Group365,
KeyVault,
ManagementGroup,
ResourceGroup,
diff --git a/packages/go/analysis/analysis_test.go b/packages/go/analysis/analysis_test.go
index 2b7ebc8288..17a54f1cd5 100644
--- a/packages/go/analysis/analysis_test.go
+++ b/packages/go/analysis/analysis_test.go
@@ -105,6 +105,7 @@ func TestGetNodeKindDisplayLabel(t *testing.T) {
assert.Equal(ad.Group.String(), analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties(), ad.Entity, ad.Group, ad.LocalGroup)), "should return valid kind other than LocalGroup if one is present")
assert.Equal(ad.LocalGroup.String(), analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties(), ad.Entity, ad.LocalGroup)), "should return LocalGroup if no other valid kinds are present")
assert.Equal(azure.Group.String(), analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties(), azure.Entity, azure.Group)), "should return valid Azure kind when base and kind are present")
+ assert.Equal(azure.Group365.String(), analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties(), azure.Entity, azure.Group365)), "should return valid Azure kind when base and kind are present")
assert.Equal(analysis.NodeKindUnknown, analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties(), unsupportedKind)), "should return Unknown when only an unsupported kind is present")
assert.Equal(ad.Entity.String(), analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties(), ad.Entity, unsupportedKind)), "should return valid kind if one is preseneven if an unsupported kind is also present")
assert.Equal(analysis.NodeKindUnknown, analysis.GetNodeKindDisplayLabel(graph.PrepareNode(graph.NewProperties())), "should return Unknown if no node has no kinds on it")
diff --git a/packages/go/analysis/azure/azure.go b/packages/go/analysis/azure/azure.go
index 35881ed687..13a0d9c8de 100644
--- a/packages/go/analysis/azure/azure.go
+++ b/packages/go/analysis/azure/azure.go
@@ -34,6 +34,7 @@ func GetDescendentKinds(kind graph.Kind) []graph.Kind {
return []graph.Kind{
azure.User,
azure.Group,
+ azure.Group365,
azure.ManagementGroup,
azure.Subscription,
azure.ResourceGroup,
diff --git a/packages/go/analysis/azure/group365.go b/packages/go/analysis/azure/group365.go
new file mode 100644
index 0000000000..5d7e6c803e
--- /dev/null
+++ b/packages/go/analysis/azure/group365.go
@@ -0,0 +1,62 @@
+// Copyright 2025 Specter Ops, Inc.
+//
+// Licensed under the Apache License, Version 2.0
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package azure
+
+import (
+ "context"
+
+ "github.com/specterops/bloodhound/dawgs/graph"
+)
+
+func NewGroup365EntityDetails(node *graph.Node) Group365Details {
+
+ return Group365Details{
+ Node: FromGraphNode(node),
+ }
+}
+
+func Group365EntityDetails(db graph.Database, objectID string, hydrateCounts bool) (Group365Details, error) {
+
+ var details Group365Details
+ return details, db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
+
+ if node, err := FetchEntityByObjectID(tx, objectID); err != nil {
+ return err
+
+ } else {
+ details = NewGroup365EntityDetails(node)
+
+ if hydrateCounts {
+ details, err = PopulateGroup365EntityDetailsCounts(tx, node, details)
+ }
+
+ return err
+ }
+ })
+}
+
+func PopulateGroup365EntityDetailsCounts(tx graph.Transaction, node *graph.Node, details Group365Details) (Group365Details, error) {
+
+ if inboundObjectControl, err := FetchInboundEntityObjectControllers(tx, node, 0, 0); err != nil {
+ return details, err
+
+ } else {
+ details.InboundObjectControl = inboundObjectControl.Len()
+ }
+
+ return details, nil
+}
diff --git a/packages/go/analysis/azure/model.go b/packages/go/analysis/azure/model.go
index 5a18517dce..96364d45a2 100644
--- a/packages/go/analysis/azure/model.go
+++ b/packages/go/analysis/azure/model.go
@@ -116,6 +116,11 @@ type GroupDetails struct {
InboundObjectControl int `json:"inbound_object_control"`
}
+type Group365Details struct {
+ Node
+ InboundObjectControl int `json:"inbound_object_control"`
+}
+
type TenantDetails struct {
Node
diff --git a/packages/go/ein/azure.go b/packages/go/ein/azure.go
index 18250274bc..b1d3113e47 100644
--- a/packages/go/ein/azure.go
+++ b/packages/go/ein/azure.go
@@ -479,6 +479,82 @@ func ConvertAzureGroupMembersToRels(data models.GroupMembers) []IngestibleRelati
return relationships
}
+func ConvertAzureGroup365ToNode(data models.Group365) IngestibleNode {
+
+ return IngestibleNode{
+ ObjectID: strings.ToUpper(data.Id),
+ PropertyMap: map[string]any{
+ common.Name.String(): strings.ToUpper(fmt.Sprintf("%s@%s", data.DisplayName, data.TenantName)),
+ common.WhenCreated.String(): ParseISO8601(data.CreatedDateTime),
+ common.Description.String(): data.Description,
+ common.DisplayName.String(): data.DisplayName,
+ azure.IsAssignableToRole.String(): data.IsAssignableToRole,
+ azure.OnPremID.String(): data.OnPremisesSecurityIdentifier,
+ azure.OnPremSyncEnabled.String(): data.OnPremisesSyncEnabled,
+ azure.SecurityEnabled.String(): data.SecurityEnabled,
+ azure.SecurityIdentifier.String(): data.SecurityIdentifier,
+ azure.Visibility.String(): data.Visibility,
+ azure.TenantID.String(): strings.ToUpper(data.TenantId),
+ azure.Mail.String(): data.Mail,
+ },
+ Label: azure.Group365,
+ }
+}
+
+func ConvertAzureGroup365ToRel(data models.Group365) IngestibleRelationship {
+
+ return NewIngestibleRelationship(
+ IngestibleSource{
+ Source: strings.ToUpper(data.TenantId),
+ SourceType: azure.Tenant,
+ },
+
+ IngestibleTarget{
+ TargetType: azure.Group365,
+ Target: strings.ToUpper(data.Id),
+ },
+
+ IngestibleRel{
+ RelProps: map[string]any{},
+ RelType: azure.Contains,
+ },
+ )
+
+}
+
+func ConvertAzureGroup365MembersToRels(data models.Group365Members) []IngestibleRelationship {
+ relationships := make([]IngestibleRelationship, 0)
+
+ for _, raw := range data.Members {
+ var (
+ member azure2.DirectoryObject
+ )
+ if err := json.Unmarshal(raw.Member, &member); err != nil {
+ slog.Error(fmt.Sprintf(SerialError, "azure Microsoft 365 group member", err))
+ } else if memberType, err := ExtractTypeFromDirectoryObject(member); errors.Is(err, ErrInvalidType) {
+ slog.Warn(fmt.Sprintf(ExtractError, err))
+ } else if err != nil {
+ slog.Error(fmt.Sprintf(ExtractError, err))
+ } else {
+ relationships = append(relationships, NewIngestibleRelationship(
+ IngestibleSource{
+ Source: strings.ToUpper(member.Id),
+ SourceType: memberType,
+ },
+ IngestibleTarget{
+ TargetType: azure.Group365,
+ Target: strings.ToUpper(data.GroupId),
+ },
+ IngestibleRel{
+ RelProps: map[string]any{},
+ RelType: azure.MemberOf,
+ },
+ ))
+ }
+ }
+ return relationships
+}
+
func ConvertAzureGroupOwnerToRels(data models.GroupOwners) []IngestibleRelationship {
relationships := make([]IngestibleRelationship, 0)
@@ -513,6 +589,40 @@ func ConvertAzureGroupOwnerToRels(data models.GroupOwners) []IngestibleRelations
return relationships
}
+func ConvertAzureGroup365OwnerToRels(data models.Group365Owners) []IngestibleRelationship {
+ relationships := make([]IngestibleRelationship, 0)
+
+ for _, raw := range data.Owners {
+ var (
+ owner azure2.DirectoryObject
+ )
+ if err := json.Unmarshal(raw.Owner, &owner); err != nil {
+ slog.Error(fmt.Sprintf(SerialError, "azure Microsoft 365 group owner", err))
+ } else if ownerType, err := ExtractTypeFromDirectoryObject(owner); errors.Is(err, ErrInvalidType) {
+ slog.Warn(fmt.Sprintf(ExtractError, err))
+ } else if err != nil {
+ slog.Error(fmt.Sprintf(ExtractError, err))
+ } else {
+ relationships = append(relationships, NewIngestibleRelationship(
+ IngestibleSource{
+ Source: strings.ToUpper(owner.Id),
+ SourceType: ownerType,
+ },
+ IngestibleTarget{
+ TargetType: azure.Group365,
+ Target: strings.ToUpper(data.GroupId),
+ },
+ IngestibleRel{
+ RelProps: map[string]any{},
+ RelType: azure.Owns,
+ },
+ ))
+ }
+ }
+
+ return relationships
+}
+
func ConvertAzureKeyVault(data models.KeyVault) (IngestibleNode, IngestibleRelationship) {
return IngestibleNode{
ObjectID: strings.ToUpper(data.Id),
diff --git a/packages/go/ein/incoming_models.go b/packages/go/ein/incoming_models.go
index 2fa883504f..7ad8668caa 100644
--- a/packages/go/ein/incoming_models.go
+++ b/packages/go/ein/incoming_models.go
@@ -210,6 +210,11 @@ type Group struct {
Members []TypedPrincipal
}
+type Group365 struct {
+ IngestBase
+ Members []TypedPrincipal
+}
+
type User struct {
IngestBase
AllowedToDelegate []TypedPrincipal
diff --git a/packages/go/graphschema/azure/azure.go b/packages/go/graphschema/azure/azure.go
index c0e8651a4b..5cac4b439e 100644
--- a/packages/go/graphschema/azure/azure.go
+++ b/packages/go/graphschema/azure/azure.go
@@ -32,6 +32,7 @@ var (
Device = graph.StringKind("AZDevice")
FunctionApp = graph.StringKind("AZFunctionApp")
Group = graph.StringKind("AZGroup")
+ Group365 = graph.StringKind("AZGroup365")
KeyVault = graph.StringKind("AZKeyVault")
ManagementGroup = graph.StringKind("AZManagementGroup")
ResourceGroup = graph.StringKind("AZResourceGroup")
@@ -129,10 +130,12 @@ const (
PublisherDomain Property = "publisherdomain"
SignInAudience Property = "signinaudience"
RoleTemplateID Property = "templateid"
+ Visibility Property = "visibility"
+ Mail Property = "mail"
)
func AllProperties() []Property {
- return []Property{AppOwnerOrganizationID, AppDescription, AppDisplayName, ServicePrincipalType, UserType, TenantID, ServicePrincipalID, ServicePrincipalNames, OperatingSystemVersion, TrustType, IsBuiltIn, AppID, AppRoleID, DeviceID, NodeResourceGroupID, OnPremID, OnPremSyncEnabled, SecurityEnabled, SecurityIdentifier, EnableRBACAuthorization, Scope, Offer, MFAEnabled, License, Licenses, LoginURL, MFAEnforced, UserPrincipalName, IsAssignableToRole, PublisherDomain, SignInAudience, RoleTemplateID}
+ return []Property{AppOwnerOrganizationID, AppDescription, AppDisplayName, ServicePrincipalType, UserType, TenantID, ServicePrincipalID, ServicePrincipalNames, OperatingSystemVersion, TrustType, IsBuiltIn, AppID, AppRoleID, DeviceID, NodeResourceGroupID, OnPremID, OnPremSyncEnabled, SecurityEnabled, SecurityIdentifier, EnableRBACAuthorization, Scope, Offer, MFAEnabled, License, Licenses, LoginURL, MFAEnforced, UserPrincipalName, IsAssignableToRole, PublisherDomain, SignInAudience, RoleTemplateID, Visibility, Mail}
}
func ParseProperty(source string) (Property, error) {
switch source {
@@ -200,6 +203,10 @@ func ParseProperty(source string) (Property, error) {
return SignInAudience, nil
case "templateid":
return RoleTemplateID, nil
+ case "visibility":
+ return Visibility, nil
+ case "mail":
+ return Mail, nil
default:
return "", errors.New("Invalid enumeration value: " + source)
}
@@ -270,6 +277,10 @@ func (s Property) String() string {
return string(SignInAudience)
case RoleTemplateID:
return string(RoleTemplateID)
+ case Visibility:
+ return string(Visibility)
+ case Mail:
+ return string(Mail)
default:
return "Invalid enumeration case: " + string(s)
}
@@ -340,6 +351,10 @@ func (s Property) Name() string {
return "Sign In Audience"
case RoleTemplateID:
return "Role Template ID"
+ case Visibility:
+ return "Visibility"
+ case Mail:
+ return "M365 Group Mail"
default:
return "Invalid enumeration case: " + string(s)
}
@@ -371,5 +386,5 @@ func PathfindingRelationships() []graph.Kind {
return []graph.Kind{AvereContributor, Contributor, GetCertificates, GetKeys, GetSecrets, HasRole, MemberOf, Owner, RunsAs, VMContributor, AutomationContributor, KeyVaultContributor, VMAdminLogin, AddMembers, AddSecret, ExecuteCommand, GlobalAdmin, PrivilegedAuthAdmin, Grant, GrantSelf, PrivilegedRoleAdmin, ResetPassword, UserAccessAdministrator, Owns, CloudAppAdmin, AppAdmin, AddOwner, ManagedIdentity, AKSContributor, NodeResourceGroup, WebsiteContributor, LogicAppContributor, AZMGAddMember, AZMGAddOwner, AZMGAddSecret, AZMGGrantAppRoles, AZMGGrantRole, SyncedToADUser, Contains}
}
func NodeKinds() []graph.Kind {
- return []graph.Kind{Entity, VMScaleSet, App, Role, Device, FunctionApp, Group, KeyVault, ManagementGroup, ResourceGroup, ServicePrincipal, Subscription, Tenant, User, VM, ManagedCluster, ContainerRegistry, WebApp, LogicApp, AutomationAccount}
+ return []graph.Kind{Entity, VMScaleSet, App, Role, Device, FunctionApp, Group, Group365, KeyVault, ManagementGroup, ResourceGroup, ServicePrincipal, Subscription, Tenant, User, VM, ManagedCluster, ContainerRegistry, WebApp, LogicApp, AutomationAccount}
}
diff --git a/packages/go/schemagen/go.sum b/packages/go/schemagen/go.sum
index 76b7e82947..bf503cb08f 100644
--- a/packages/go/schemagen/go.sum
+++ b/packages/go/schemagen/go.sum
@@ -1,14 +1,21 @@
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY=
+cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg=
cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0=
+cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0=
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
+github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
github.com/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk=
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
+github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
+github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -23,19 +30,33 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/protocolbuffers/txtpbfmt v0.0.0-20240823084532-8e6b51fa9bef h1:ej+64jiny5VETZTqcc1GFVAPEtaSk6U1D0kKC2MS5Yc=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20240823084532-8e6b51fa9bef/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
+golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts
index 7f72d67ab1..836d5a6ee8 100644
--- a/packages/javascript/bh-shared-ui/src/graphSchema.ts
+++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts
@@ -753,6 +753,7 @@ export enum AzureNodeKind {
Device = 'AZDevice',
FunctionApp = 'AZFunctionApp',
Group = 'AZGroup',
+ Group365 = 'AZGroup365',
KeyVault = 'AZKeyVault',
ManagementGroup = 'AZManagementGroup',
ResourceGroup = 'AZResourceGroup',
@@ -783,6 +784,8 @@ export function AzureNodeKindToDisplay(value: AzureNodeKind): string | undefined
return 'FunctionApp';
case AzureNodeKind.Group:
return 'Group';
+ case AzureNodeKind.Group365:
+ return 'Group365';
case AzureNodeKind.KeyVault:
return 'KeyVault';
case AzureNodeKind.ManagementGroup:
@@ -996,6 +999,8 @@ export enum AzureKindProperties {
PublisherDomain = 'publisherdomain',
SignInAudience = 'signinaudience',
RoleTemplateID = 'templateid',
+ Visibility = 'visibility',
+ Mail = 'mail',
}
export function AzureKindPropertiesToDisplay(value: AzureKindProperties): string | undefined {
switch (value) {
@@ -1063,6 +1068,10 @@ export function AzureKindPropertiesToDisplay(value: AzureKindProperties): string
return 'Sign In Audience';
case AzureKindProperties.RoleTemplateID:
return 'Role Template ID';
+ case AzureKindProperties.Visibility:
+ return 'Visibility';
+ case AzureKindProperties.Mail:
+ return 'M365 Group Mail';
default:
return undefined;
}
@@ -1179,4 +1188,4 @@ export function CommonKindPropertiesToDisplay(value: CommonKindProperties): stri
default:
return undefined;
}
-}
+}
\ No newline at end of file
diff --git a/packages/javascript/bh-shared-ui/src/utils/content.ts b/packages/javascript/bh-shared-ui/src/utils/content.ts
index 693883054a..44be6182bd 100644
--- a/packages/javascript/bh-shared-ui/src/utils/content.ts
+++ b/packages/javascript/bh-shared-ui/src/utils/content.ts
@@ -59,6 +59,8 @@ export const entityInformationEndpoints: Record
apiClient.getAZEntityInfoV2('groups', id, undefined, false, undefined, undefined, undefined, options),
+ [AzureNodeKind.Group365]: (id: string, options?: RequestOptions) =>
+ apiClient.getAZEntityInfoV2('groups365', id, undefined, false, undefined, undefined, undefined, options),
[AzureNodeKind.KeyVault]: (id: string, options?: RequestOptions) =>
apiClient.getAZEntityInfoV2('key-vaults', id, undefined, false, undefined, undefined, undefined, options),
[AzureNodeKind.ManagementGroup]: (id: string, options?: RequestOptions) =>
@@ -227,6 +229,33 @@ export const allSections: Partial EntityInfo
queryType: 'azgroup-outbound_object_control',
},
],
+ [AzureNodeKind.Group365]: (id: string) => [
+ {
+ id,
+ label: 'Members',
+ queryType: 'azgroup365-members',
+ },
+ {
+ id,
+ label: 'Member Of',
+ queryType: 'azgroup365-member_of',
+ },
+ {
+ id,
+ label: 'Roles',
+ queryType: 'azgroup365-roles',
+ },
+ {
+ id,
+ label: 'Inbound Object Control',
+ queryType: 'azgroup365-inbound_object_control',
+ },
+ {
+ id,
+ label: 'Outbound Object Control',
+ queryType: 'azgroup365-outbound_object_control',
+ },
+ ],
[AzureNodeKind.KeyVault]: (id: string) => [
{
id,
@@ -1102,6 +1131,33 @@ export const entityRelationshipEndpoints = {
signal: controller.signal,
})
.then((res) => res.data),
+ 'azgroup365-members': ({ id, counts, skip, limit, type }) =>
+ apiClient
+ .getAZEntityInfoV2('groups365', id, 'group-members', counts, skip, limit, type, { signal: controller.signal })
+ .then((res : any) => res.data),
+ 'azgroup365-member_of': ({ id, counts, skip, limit, type }) =>
+ apiClient
+ .getAZEntityInfoV2('groups365', id, 'group-membership', counts, skip, limit, type, {
+ signal: controller.signal,
+ })
+ .then((res : any) => res.data),
+ 'azgroup365-roles': ({ id, counts, skip, limit, type }) =>
+ apiClient
+ .getAZEntityInfoV2('groups365', id, 'roles', counts, skip, limit, type, { signal: controller.signal })
+ .then((res : any) => res.data),
+
+ 'azgroup365-inbound_object_control': ({ id, counts, skip, limit, type }) =>
+ apiClient
+ .getAZEntityInfoV2('groups365', id, 'inbound-control', counts, skip, limit, type, {
+ signal: controller.signal,
+ })
+ .then((res : any) => res.data),
+ 'azgroup365-outbound_object_control': ({ id, counts, skip, limit, type }) =>
+ apiClient
+ .getAZEntityInfoV2('groups365', id, 'outbound-control', counts, skip, limit, type, {
+ signal: controller.signal,
+ })
+ .then((res: any) => res.data),
'azkeyvault-key_readers': ({ id, counts, skip, limit, type }) =>
apiClient
.getAZEntityInfoV2('key-vaults', id, 'key-readers', counts, skip, limit, type, {
diff --git a/packages/javascript/bh-shared-ui/src/utils/icons.ts b/packages/javascript/bh-shared-ui/src/utils/icons.ts
index 546a80d399..4b3653a413 100644
--- a/packages/javascript/bh-shared-ui/src/utils/icons.ts
+++ b/packages/javascript/bh-shared-ui/src/utils/icons.ts
@@ -151,6 +151,11 @@ export const NODE_ICON: IconDictionary = {
color: '#F57C9B',
},
+ [AzureNodeKind.Group365]: {
+ icon: faUsers,
+ color: '#34D2EB',
+ },
+
[AzureNodeKind.Tenant]: {
icon: faCloud,
color: '#54F2F2',
diff --git a/packages/javascript/bh-shared-ui/src/views/DataQuality/TenantInfo.tsx b/packages/javascript/bh-shared-ui/src/views/DataQuality/TenantInfo.tsx
index e1a3fa6d5e..ced463aef5 100644
--- a/packages/javascript/bh-shared-ui/src/views/DataQuality/TenantInfo.tsx
+++ b/packages/javascript/bh-shared-ui/src/views/DataQuality/TenantInfo.tsx
@@ -39,6 +39,7 @@ const useStyles = makeStyles((theme) => ({
export const TenantMap = {
users: { displayText: 'Users', kind: AzureNodeKind.User },
groups: { displayText: 'Groups', kind: AzureNodeKind.Group },
+ groups365: { displayText: 'Microsoft 365', kind: AzureNodeKind.Group365 },
apps: { displayText: 'Apps', kind: AzureNodeKind.App },
service_principals: {
displayText: 'Service Principals',
diff --git a/packages/javascript/js-client-library/package-lock.json b/packages/javascript/js-client-library/package-lock.json
new file mode 100644
index 0000000000..916445095b
--- /dev/null
+++ b/packages/javascript/js-client-library/package-lock.json
@@ -0,0 +1,1977 @@
+{
+ "name": "js-client-library",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "js-client-library",
+ "dependencies": {
+ "axios": "^1.8.2"
+ },
+ "devDependencies": {
+ "@rollup/plugin-typescript": "^11.1.6",
+ "@typescript-eslint/eslint-plugin": "^7.3.0",
+ "@typescript-eslint/parser": "^7.3.0",
+ "eslint": "^8.57.0",
+ "eslint-config-prettier": "^9.1.0",
+ "prettier": "^3.2.5",
+ "prettier-plugin-organize-imports": "^4.1.0",
+ "rollup": "^4.13.2",
+ "rollup-plugin-delete": "^2.0.0",
+ "typescript": "^5.1.6"
+ }
+ },
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.10.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.14",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rollup/plugin-typescript": {
+ "version": "11.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.14.0||^3.0.0||^4.0.0",
+ "tslib": "*",
+ "typescript": ">=3.7.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ },
+ "tslib": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+ "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.13.2",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.13.2",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.13.2",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.13.2",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.13.2",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.13.2",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.13.2",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.13.2",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/glob": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.11.29",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.5.1",
+ "@typescript-eslint/scope-manager": "7.3.0",
+ "@typescript-eslint/type-utils": "7.3.0",
+ "@typescript-eslint/utils": "7.3.0",
+ "@typescript-eslint/visitor-keys": "7.3.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.3.0",
+ "@typescript-eslint/types": "7.3.0",
+ "@typescript-eslint/typescript-estree": "7.3.0",
+ "@typescript-eslint/visitor-keys": "7.3.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.3.0",
+ "@typescript-eslint/visitor-keys": "7.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "7.3.0",
+ "@typescript-eslint/utils": "7.3.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "7.3.0",
+ "@typescript-eslint/visitor-keys": "7.3.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "9.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
+ "version": "11.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+ "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "7.3.0",
+ "@typescript-eslint/types": "7.3.0",
+ "@typescript-eslint/typescript-estree": "7.3.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.3.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/acorn": {
+ "version": "8.11.3",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.8.2",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/del": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "globby": "^10.0.1",
+ "graceful-fs": "^4.2.2",
+ "is-glob": "^4.0.1",
+ "is-path-cwd": "^2.2.0",
+ "is-path-inside": "^3.0.1",
+ "p-map": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "9.1.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.6",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.20.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "10.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-cwd": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.2.5",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-organize-imports": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "prettier": ">=2.0",
+ "typescript": ">=2.9",
+ "vue-tsc": "^2.1.0"
+ },
+ "peerDependenciesMeta": {
+ "vue-tsc": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.12.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.13.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.13.2",
+ "@rollup/rollup-android-arm64": "4.13.2",
+ "@rollup/rollup-darwin-arm64": "4.13.2",
+ "@rollup/rollup-darwin-x64": "4.13.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.13.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.13.2",
+ "@rollup/rollup-linux-arm64-musl": "4.13.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.13.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.13.2",
+ "@rollup/rollup-linux-x64-gnu": "4.13.2",
+ "@rollup/rollup-linux-x64-musl": "4.13.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.13.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.13.2",
+ "@rollup/rollup-win32-x64-msvc": "4.13.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup-plugin-delete": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "del": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.6.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.1.6",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}