diff --git a/apps/console/src/pages/iam/organizations/settings/_components/GoogleWorkspaceConnector.tsx b/apps/console/src/pages/iam/organizations/settings/_components/GoogleWorkspaceConnector.tsx index 08cefcf5b..a6d6c26cd 100644 --- a/apps/console/src/pages/iam/organizations/settings/_components/GoogleWorkspaceConnector.tsx +++ b/apps/console/src/pages/iam/organizations/settings/_components/GoogleWorkspaceConnector.tsx @@ -40,6 +40,7 @@ const googleWorkspaceConnectorFragment = graphql` id bridge { id + type excludedUserNames connector { id @@ -78,7 +79,7 @@ export function GoogleWorkspaceConnector(props: { }) { const { fKey, oauth2Scopes } = props; const data = useFragment(googleWorkspaceConnectorFragment, fKey); - const bridge = data?.bridge; + const bridge = data?.bridge?.type === "GOOGLE_WORKSPACE" ? data.bridge : null; const connector = bridge?.connector; const scimConfigurationId = data?.id; const bridgeId = bridge?.id; diff --git a/apps/console/src/pages/iam/organizations/settings/_components/Microsoft365Connector.tsx b/apps/console/src/pages/iam/organizations/settings/_components/Microsoft365Connector.tsx index 926526d04..ee55aa660 100644 --- a/apps/console/src/pages/iam/organizations/settings/_components/Microsoft365Connector.tsx +++ b/apps/console/src/pages/iam/organizations/settings/_components/Microsoft365Connector.tsx @@ -40,6 +40,7 @@ const microsoft365ConnectorFragment = graphql` id bridge { id + type excludedUserNames connector { id @@ -78,7 +79,7 @@ export function Microsoft365Connector(props: { }) { const { fKey, oauth2Scopes } = props; const data = useFragment(microsoft365ConnectorFragment, fKey); - const bridge = data?.bridge; + const bridge = data?.bridge?.type === "MICROSOFT_365" ? data.bridge : null; const connector = bridge?.connector; const scimConfigurationId = data?.id; const bridgeId = bridge?.id; diff --git a/pkg/coredata/migrations/20260507T150000Z.sql b/pkg/coredata/migrations/20260507T150000Z.sql new file mode 100644 index 000000000..5127fb090 --- /dev/null +++ b/pkg/coredata/migrations/20260507T150000Z.sql @@ -0,0 +1,15 @@ +-- Copyright (c) 2026 Probo Inc . +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +-- PERFORMANCE OF THIS SOFTWARE. + +ALTER TYPE connector_provider ADD VALUE IF NOT EXISTS 'MICROSOFT_365'; diff --git a/pkg/iam/scim/bridge/provider/microsoft365/provider.go b/pkg/iam/scim/bridge/provider/microsoft365/provider.go index 6d952b725..6e26bd73f 100644 --- a/pkg/iam/scim/bridge/provider/microsoft365/provider.go +++ b/pkg/iam/scim/bridge/provider/microsoft365/provider.go @@ -23,6 +23,8 @@ import ( "fmt" "io" "net/http" + "net/url" + "strconv" "strings" scimclient "go.probo.inc/probo/pkg/iam/scim/bridge/client" @@ -47,6 +49,11 @@ const graphPageSize = 999 // API misbehaves. const graphMaxPages = 1000 +// graphMemberFilter restricts /users to home-tenant members and +// excludes B2B guest users invited from external tenants — those guests +// should not be provisioned into the connected organization. +const graphMemberFilter = "userType eq 'Member'" + var _ provider.Provider = (*Provider)(nil) type Provider struct { @@ -102,16 +109,14 @@ type graphUsersResponse struct { } func (p *Provider) ListUsers(ctx context.Context) (scimclient.Users, error) { - url := fmt.Sprintf( - "%s/users?$select=%s&$top=%d", - graphBaseURL, - graphUserSelect, - graphPageSize, - ) + endpoint, err := buildListUsersURL() + if err != nil { + return nil, fmt.Errorf("cannot build list users URL: %w", err) + } var allUsers scimclient.Users for range graphMaxPages { - users, next, err := p.fetchPage(ctx, url) + users, next, err := p.fetchPage(ctx, endpoint) if err != nil { return nil, err } @@ -146,14 +151,29 @@ func (p *Provider) ListUsers(ctx context.Context) (scimclient.Users, error) { if next == "" { return allUsers, nil } - url = next + endpoint = next } return nil, fmt.Errorf("microsoft 365: pagination limit of %d pages reached", graphMaxPages) } -func (p *Provider) fetchPage(ctx context.Context, url string) ([]graphUser, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func buildListUsersURL() (string, error) { + u, err := url.Parse(graphBaseURL + "/users") + if err != nil { + return "", fmt.Errorf("cannot parse graph base URL: %w", err) + } + + q := u.Query() + q.Set("$select", graphUserSelect) + q.Set("$top", strconv.Itoa(graphPageSize)) + q.Set("$filter", graphMemberFilter) + u.RawQuery = q.Encode() + + return u.String(), nil +} + +func (p *Provider) fetchPage(ctx context.Context, endpoint string) ([]graphUser, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, "", fmt.Errorf("cannot create graph users request: %w", err) }