Fix Microsoft 365 SCIM bridge connection and scope
Add the missing MICROSOFT_365 value to the connector_provider enum so the connector loader stops failing with SQLSTATE 22P02. Scope each Identity Provider card to its own SCIMBridge type so connecting Microsoft 365 no longer marks Google Workspace as connected (and vice versa). Filter Microsoft Graph /users to userType eq 'Member' so the bridge only syncs home-tenant members and skips B2B guest accounts that were polluting the synced People list. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -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$key>(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;
|
||||
|
||||
@@ -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$key>(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;
|
||||
|
||||
15
pkg/coredata/migrations/20260507T150000Z.sql
Normal file
15
pkg/coredata/migrations/20260507T150000Z.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
--
|
||||
-- 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';
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user