azuread

package
v1.85.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: BSD-3-Clause Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	User           string = "User"
	Group          string = "Group"
	GroupMember    string = "GroupMember"
	Application    string = "Application"
	Device         string = "Device"
	Role           string = "Role"
	RoleMember     string = "RoleMember"
	RoleAssignment string = "RoleAssignment"

	// Use a combination of $top and $skip to paginate the response for these two PIM entities.
	RoleAssignmentScheduleRequest  string = "RoleAssignmentScheduleRequest"
	GroupAssignmentScheduleRequest string = "GroupAssignmentScheduleRequest"
)

Variables

View Source
var (
	// ValidEntityExternalIDs is a set of valid external IDs of entities that can be queried.
	ValidEntityExternalIDs = map[string]EntityInfo{
		User:  {},
		Group: {},
		GroupMember: {
			// contains filtered or unexported fields
		},
		Application: {},
		Device:      {},
		Role:        {},
		RoleMember: {
			// contains filtered or unexported fields
		},
		RoleAssignment:                 {},
		RoleAssignmentScheduleRequest:  {},
		GroupAssignmentScheduleRequest: {},
	}
)

Functions

func ConstructEndpoint

func ConstructEndpoint(request *Request) (string, *framework.Error)

ConstructEndpoint constructs and returns the endpoint to query the datasource.

func ExtractImplicitFilters

func ExtractImplicitFilters(advancedFilters AdvancedFilters) map[string][]EntityFilter

ExtractImplicitFilters extracts implicit User/Group filters from the GroupMember's advanced filters. A GroupMember advanced filter may look like:

"GroupMember": [

{
  "scopeEntity": "Group",
  "scopeEntityFilter": "id in ('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')", // Canada Group
  "members": [
	{
	  "memberEntity": "User",
	  "memberEntityFilter": "department eq 'engineering'"
	}
  ]
},
{
  "scopeEntity": "Group",
  "scopeEntityFilter": "id in ('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')", // United States Group
  "members": [
	{
	  "memberEntity": "User",
	  "memberEntityFilter": "department eq 'engineering'"
	},
	{
	  "memberEntity": "User",
	  "memberEntityFilter": "department eq 'product'"
	},
	{
	  "memberEntity": "Group",
	  "memberEntityFilter": "startswith(displayName, 'California')"
	}
  ]
},

] The above filter means: 1) Sync all GroupMember user nodes from the Canada group where the department is engineering. 2) Sync all GroupMember user nodes from the United States group where the department is engineering or product. 3) Sync all GroupMember group nodes from the United States group where the group name starts with California. This implies we should only sync: 1) User nodes from the Canada group where the department is engineering. 2) User nodes from the United States group where the department is engineering or product. 3) Group nodes from the United States group where the group name starts with California. 4) The Canada and United States groups themselves. Thus, this function will extract the following implicit filters (only User filters are shown for brevity) "User": [

{
  "scopeEntity": "Group",
  "scopeEntityFilter": "id in ('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')", // Canada Group
  "members": [
	{
	  "memberEntity": "User",
	  "memberEntityFilter": "department eq 'engineering'"
	}
  ]
},
{
  "scopeEntity": "Group",
  "scopeEntityFilter": "id in ('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')", // United States Group
  "members": [
	{
	  "memberEntity": "User",
	  "memberEntityFilter": "department eq 'engineering'"
	},
	{
	  "memberEntity": "User",
	  "memberEntityFilter": "department eq 'product'"
	},
  ]
},

].

func IsAdvancedQuery added in v1.53.1

func IsAdvancedQuery(request *Request, endpoint string) bool

nolint: lll IsAdvancedQuery determines if the request requires advanced query capabilities based on Microsoft Graph advanced query documentation. If any of the conditions are met, it returns true and a header "ConsistencyLevel: eventual" should be added to the request. See: https://learn.microsoft.com/en-us/graph/aad-advanced-queries?tabs=http#microsoft-entra-id-directory-objects-that-support-advanced-query-capabilities for more information. The ODATA logical operator NOT also needs the ConsistencyLevel header. See: https://learn.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0&tabs=http The function checks for the following conditions: 1. If the request already has UseAdvancedFilters set to true, it returns true. 2. If the endpoint contains $count, $search, or $orderby query parameters. 3. If the endpoint contains $filter with advanced operators like endsWith, contains, startsWith. 4. If the endpoint contains $filter with 'ne' or 'not' operators as whole words. 5. If the endpoint contains $filter with 'NOT' operator as a whole word.

func MarshalAdvancedFilterCursor

func MarshalAdvancedFilterCursor(cursor *AdvancedFilterCursor) (string, *framework.Error)

MarshalAdvancedFilterCursor marshals the struct and b64 encodes it.

func NewAdapter

func NewAdapter(client Client) framework.Adapter[Config]

NewAdapter instantiates a new Adapter.

func ParseResponse

func ParseResponse(body []byte) (objects []map[string]any, nextLink *string, err *framework.Error)

Types

type Adapter

type Adapter struct {
	AzureADClient Client
}

Adapter implements the framework.Adapter interface to query pages of objects from datasources.

func (*Adapter) GetPage

func (a *Adapter) GetPage(ctx context.Context, request *framework.Request[Config]) framework.Response

GetPage is called by SGNL's ingestion service to query a page of objects from a datasource.

func (*Adapter) RequestPageFromDatasource

func (a *Adapter) RequestPageFromDatasource(
	ctx context.Context, request *framework.Request[Config],
) framework.Response

RequestPageFromDatasource requests a page of objects from a datasource.

func (*Adapter) ValidateGetPageRequest

func (a *Adapter) ValidateGetPageRequest(ctx context.Context, request *framework.Request[Config]) *framework.Error

ValidateGetPageRequest validates the fields of the GetPage Request.

type AdvancedFilterCursor

type AdvancedFilterCursor struct {
	EntityFilterIndex int                                 `json:"entityFilterIndex"`
	MemberFilterIndex int                                 `json:"memberFilterIndex"`
	Cursor            *pagination.CompositeCursor[string] `json:"cursor"`
}

AdvancedFilterCursor is a simple wrapper around the existing composite cursor. The existing composite cursor helps paginate through G groups and fetch U users.

When using advanced filters for an entityExternalID (e.g. `groupMembers“),

  1. Iterate each scopedEntity (i-th index) (e.g. a `group`) and get its filter, a scopeFilter
  2. For that scopedEntity, iterate each memberEntity (j-th index) (e.g. `users`), and get its filter, a memberFilter
  3. For every (scopedEntity, memberEntity) pair, 3.1 the scopeFilter would filter down the /groups response to a subset of G groups 3.2 the memberFilter would filter down the /members response to a subset of U users for each group 3.3 Call GetPage with these filters 3.3 The pagination within GetPage is captured by the existing composite cursor 3.4 If composite cursor is not nil, it means there are more pages for the current (scopedEntity, memberEntity) pair. 3.5 Move to the next (scopedEntity, memberEntity) pair if the composite cursor is nil.
  4. Continue calling GetPage until all (scopedEntity, memberEntity) pairs are processed

This is how the advanced filter cursor wraps the existing composite cursor.

func UnmarshalAdvancedFilterCursor

func UnmarshalAdvancedFilterCursor(cursor string) (*AdvancedFilterCursor, *framework.Error)

UnmarshalAdvancedFilterCursor decodes the b64 encoded string and unmarshals it.

type AdvancedFilters

type AdvancedFilters struct {
	// ScopedObjects is a map of entityExternalID to a list of EntityFilter configurations.
	ScopedObjects map[string][]EntityFilter `json:"getObjectsByScope,omitempty"`
}

nolint: godot,lll Example of AdvancedFilters:

"advancedFilters": {
	"getObjectsByScope": {
		// The externalId is a key
		// The value is a list of filter configurations
		"GroupMember": [
			{
				// To obtain a subset of groupMembers, we scope the entity `Group` and apply a scope-filter.
				// Observe that scopeEntityFilter is extensible because
				// - native AAD filter syntax and not limited to uniqueIds
				// - allows applying same user-filter/group-filter to several groups matching the scope-filter
				"scopeEntity": "Group",
				"scopeEntityFilter": "id in ('7df6bf7d-7b09-4399-9aed-0e345d1ea7b2', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', '84168a05-7f94-46f5-8d2f-302f6c2ca638')",

				// Fetch both users and groups belonging to the groups matching the scope-filter
				// The set of users and groups are further filtered down using memberEntityFilter
				"members": [
					{
						// All Users belonging to group matching scope-filters will be further filtered down using memberEntityFilter
						"memberEntity": "User",
						"memberEntityFilter": "department eq 'Architecture and Authentication'"
					},
					{
						// All Groups belonging to group matching scope-filters will NOT be further filtered down as memberEntityFilter is absent
						"memberEntity": "Group"
					}
				]
			},
			{
				"scopeEntity": "Group",
				"scopeEntityFilter": "id eq '94e98d95-8e04-47ee-b2d4-5a1bd96bf0a9'",
				// Only fetch users belonging to the groups matching the scope-filter
				"members": [
					{
						"memberEntity": "User"
					}
				]
			},
			{
				"scopeEntity": "Group",
				"scopeEntityFilter": "id eq '821d9845-3482-41c8-9a0e-d93f4d576731'",

				// Fetch both users and groups belonging to the groups matching the scope-filter
				"members": [
					{
						"memberEntity": "User"
					},
					{
						"memberEntity": "Group"
					}
				]
			}
		],
		// This ingests only those group entity nodes starting with `Test` or `SGNL`
		"Group": [
			{
				"scopeEntity": "Group",
				"scopeEntityFilter": "startswith(displayName, 'Test')"
			},
			{
				"scopeEntity": "Group",
				"scopeEntityFilter": "startswith(displayName, 'SGNL')"
			}
		]
	}
}

type Client

type Client interface {
	GetPage(ctx context.Context, request *Request) (*Response, *framework.Error)
}

Client is a client that allows querying the datasource which contains JSON objects.

func NewClient

func NewClient(client *http.Client) Client

NewClient returns a Client to query the datasource.

type Config

type Config struct {
	// Common configuration
	*config.CommonConfig

	// APIVersion is the version of the Microsoft Graph API to use.
	APIVersion string `json:"apiVersion,omitempty"`

	// Filters contains a map of filters for each entity associated with this
	// datasource. The key is the entity's external_name, and the value is the filter string.
	Filters map[string]string `json:"filters,omitempty"`

	// ApplyFiltersToMembers determines whether filters applied to parent objects should be applied when querying
	// member objects. For example, if true, Group filters be applied when pulling GroupMembers.
	ApplyFiltersToMembers bool `json:"applyFiltersToMembers,omitempty"`

	// Optional advanced filters to apply to the request.
	// See advanced_filters.go for more information.
	AdvancedFilters *AdvancedFilters `json:"advancedFilters,omitempty"`
}

Config is the configuration passed in each GetPage calls to the adapter. Adapter configuration example: nolint: godot

{
    "requestTimeoutSeconds": 10,
    "localTimeZoneOffset": 43200,
    "apiVersion": "v1.0",
    "filters": {
        "users": "displayName ne null"
    },
    "applyFiltersToMembers": true
}

func (*Config) Validate

func (c *Config) Validate(_ context.Context) error

ValidateConfig validates that a Config received in a GetPage call is valid.

type Datasource

type Datasource struct {
	Client *http.Client
}

Datasource directly implements a Client interface to allow querying an external datasource.

func (*Datasource) GetPage

func (d *Datasource) GetPage(ctx context.Context, request *Request) (*Response, *framework.Error)

GetPage fetches a page of data, accumulating members from multiple parent groups if needed to satisfy page size.

type DatasourceResponse

type DatasourceResponse struct {
	Values   []map[string]any `json:"value"`
	NextLink *string          `json:"@odata.nextLink"`
}

Graph API response format example: User Response: https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http#examples Paging: https://learn.microsoft.com/en-us/graph/paging?tabs=http

type EntityFilter

type EntityFilter struct {
	ScopeEntity       string         `json:"scopeEntity"`
	ScopeEntityFilter string         `json:"scopeEntityFilter"`
	Members           []MemberFilter `json:"members"`
}

type EntityInfo

type EntityInfo struct {
	// contains filtered or unexported fields
}

type MemberFilter

type MemberFilter struct {
	MemberEntity       string `json:"memberEntity"`
	MemberEntityFilter string `json:"memberEntityFilter,omitempty"`
}

type Request

type Request struct {
	// BaseURL is the Base URL of the datasource to query.
	BaseURL string

	// Token is the Bearer API token to authenticate a request.
	Token string

	// PageSize is the maximum number of objects to return from the entity.
	PageSize int64

	// EntityExternalID is the external ID of the entity.
	// The external ID should match the API's resource name.
	EntityExternalID string

	// Cursor identifies the first object of the page to return, as returned by
	// the last request for the entity.
	// nil in the request for the first page.
	Cursor *pagination.CompositeCursor[string]

	// Skip is the number of objects to skip from the beginning of the entity.
	// This is applicable to PIM entities like RoleAssignmentScheduleRequest and GroupAssignmentScheduleRequest.
	Skip int64

	// Filter contains the optional filter to apply to the current request.
	Filter *string

	// ParentFilter contains the optional filter to apply when retrieving parent objects for a member entity.
	// This will only be set if the current entity has a parent and ApplyFiltersToMembers is enabled.
	ParentFilter *string

	// Attributes contains the list of attributes to request along with the current request.
	Attributes []*framework.AttributeConfig

	// APIVersion the API version to use.
	APIVersion string

	// RequestTimeoutSeconds is the timeout duration for requests made to datasources.
	// This should be set to the number of seconds to wait before timing out.
	RequestTimeoutSeconds int

	// UseAdvancedFilters is a flag that indicates whether advanced filters should be used.
	// Set $count=true in the query to enable advanced filters.
	// See https://learn.microsoft.com/en-us/graph/aad-advanced-queries for more information.
	UseAdvancedFilters bool

	// AdvancedFilterMemberExternalID is the external ID of the member entity to apply advanced filters to.
	AdvancedFilterMemberExternalID *string
}

Request is a request to the datasource.

type Response

type Response struct {
	// StatusCode is an HTTP status code.
	StatusCode int

	// RetryAfterHeader is the Retry-After response HTTP header, if set.
	RetryAfterHeader string

	// Objects is the list of
	// May be empty.
	Objects []map[string]any

	// NextCursor is the cursor that identifies the first object of the next page.
	// nil if this is the last page in this full sync.
	NextCursor *pagination.CompositeCursor[string]
}

Response is a response returned by the datasource.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL