Add Microsoft 365 SCIM bridge and access review driver
Microsoft 365's native SCIM endpoint is unreliable, so mirror the Google Workspace bridge over Microsoft Graph: a new MICROSOFT_365 OAuth2 connector, a SCIM bridge provider listing /v1.0/users with $select pagination, and an access review driver that derives admin status from /directoryRoles members. Refactor the bridge runner to share OAuth2 plumbing across providers and surface the new bridge type, scopes, UI card, and bootstrap env wiring. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -50,6 +50,7 @@ var providerDisplayNames = map[coredata.ConnectorProvider]string{
|
||||
coredata.ConnectorProviderGitHub: "GitHub",
|
||||
coredata.ConnectorProviderIntercom: "Intercom",
|
||||
coredata.ConnectorProviderResend: "Resend",
|
||||
coredata.ConnectorProviderMicrosoft365: "Microsoft 365",
|
||||
}
|
||||
|
||||
// ProviderDisplayName returns the human-readable label for a connector provider.
|
||||
@@ -627,3 +628,67 @@ func (r *notionNameResolver) ResolveInstanceName(ctx context.Context) (string, e
|
||||
|
||||
return resp.Bot.WorkspaceName, nil
|
||||
}
|
||||
|
||||
// microsoft365NameResolver resolves the Microsoft 365 tenant display name
|
||||
// via the Microsoft Graph organization endpoint.
|
||||
type microsoft365NameResolver struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewMicrosoft365NameResolver(httpClient *http.Client) NameResolver {
|
||||
return µsoft365NameResolver{httpClient: httpClient}
|
||||
}
|
||||
|
||||
func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
"https://graph.microsoft.com/v1.0/organization?$select=displayName,verifiedDomains",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot create microsoft 365 organization request: %w", err)
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
httpResp, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot execute microsoft 365 organization request: %w", err)
|
||||
}
|
||||
defer func() { _ = httpResp.Body.Close() }()
|
||||
|
||||
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("cannot fetch microsoft 365 organization: unexpected status %d", httpResp.StatusCode)
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
Value []struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
VerifiedDomains []struct {
|
||||
Name string `json:"name"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
} `json:"verifiedDomains"`
|
||||
} `json:"value"`
|
||||
}
|
||||
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
|
||||
return "", fmt.Errorf("cannot decode microsoft 365 organization response: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.Value) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
org := resp.Value[0]
|
||||
if org.DisplayName != "" {
|
||||
return org.DisplayName, nil
|
||||
}
|
||||
for _, d := range org.VerifiedDomains {
|
||||
if d.IsDefault {
|
||||
return d.Name, nil
|
||||
}
|
||||
}
|
||||
if len(org.VerifiedDomains) > 0 {
|
||||
return org.VerifiedDomains[0].Name, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user