Coder Social home page Coder Social logo

tools4everbv / helloid-conn-prov-target-exchangeonline Goto Github PK

View Code? Open in Web Editor NEW
0.0 7.0 0.0 300 KB

Exchange Online - Target - Permissions

PowerShell 100.00%
exchange exchange-online office365 powershell mailbox shared-mailbox distribution group shared distribution-group

helloid-conn-prov-target-exchangeonline's Introduction

⚠️ Warning
This connector is written and tested for the EXO module v3.1. Please make sure you have installed, at least, this version.
ℹ️ Information
This repository contains the connector and configuration code only. The implementer is responsible to acquire the connection details such as username, password, certificate, etc. You might even need to sign a contract or agreement with the supplier before implementing this connector. Please contact the client's application manager to coordinate the connector requirements.

Versioning

Version Description Date
2.0.0 Use of Access Token to authenticate and no longer use additional PS sessions 2023/06/09
1.0.0 Initial release 2022/03/30

Table of Contents

Requirements

  • Installed and available Microsoft Exchange Online PowerShell V3.1 module. Please see the Microsoft documentation for more information. The download can be found here.
  • Required to run On-Premises since it is not allowed to import a module with the Cloud Agent.
  • An App Registration in Azure AD is required.

Introduction

For this connector we have the option to correlate to and/or update Exchange Online (Office 365) users and/or mailboxes and provision permission(s) to a group and/or shared mailbox.

Only Exchange and Cloud-only groups are supported

If you want to create Exchange Online (Office 365) users and/or mailboxes, please use the built-in Microsoft (Azure) Active Directory target system. Or setup Business Rules to provision an Office 365 license group, Microsoft will automatically provision a mailbox for this user.

Installing the Microsoft Exchange Online PowerShell V3.1 module

Since we use the cmdlets from the Microsoft Exchange Online PowerShell module, it is required this module is installed and available for the service account. Please follow the Microsoft documentation on how to install the module.

Creating the Azure AD App Registration and certificate

The steps below are based on the Microsoft documentation as of the moment of release. The Microsoft documentation should always be leading and is susceptible to change. The steps below might not reflect those changes.

Please note that our steps differ from the current documentation as we use Access Token Based Authentication instead of Certificate Based Authentication

Application Registration

The first step is to register a new Azure Active Directory Application. The application is used to connect to Exchange and to manage permissions.

  • Navigate to App Registrations in Azure, and select “New Registration” (Azure Portal > Azure Active Directory > App Registration > New Application Registration).
  • Next, give the application a name. In this example we are using “ExO PowerShell CBA” as application name.
  • Specify who can use this application (Accounts in this organizational directory only).
  • Specify the Redirect URI. You can enter any url as a redirect URI value. In this example we used http://localhost because it doesn't have to resolve.
  • Click the “Register” button to finally create your new application.

Some key items regarding the application are the Application ID (which is the Client ID), the Directory ID (which is the Tenant ID) and Client Secret.

Configuring App Permissions

The Microsoft Graph documentation provides details on which permission are required for each permission type.

  • To assign your application the right permissions, navigate to Azure Portal > Azure Active Directory > App Registrations.
  • Select the application we created before, and select “API Permissions” or “View API Permissions”.
  • To assign a new permission to your application, click the “Add a permission” button.
  • From the “Request API Permissions” screen click “Office 365 Exchange Online”.

    The Office 365 Exchange Online might not be a selectable API. In thise case, select "APIs my organization uses" and search here for "Office 365 Exchange Online"_

  • For this connector the following permissions are used as Application permissions:
    • Manage Exchange As Application Exchange.ManageAsApp
  • To grant admin consent to our application press the “Grant admin consent for TENANT” button.

Assign Azure AD roles to the application

Azure AD has more than 50 admin roles available. The Exchange Administrator role should provide the required permissions for any task in Exchange Online PowerShell. However, some actions may not be allowed, such as managing other admin accounts, for this the Global Administrator would be required. and Exchange Administrator roles. Please note that the required role may vary based on your configuration.

  • To assign the role(s) to your application, navigate to Azure Portal > Azure Active Directory > Roles and administrators.
  • On the Roles and administrators page that opens, find and select one of the supported roles e.g. “Exchange Administrator” by clicking on the name of the role (not the check box) in the results.
  • On the Assignments page that opens, click the “Add assignments” button.
  • In the Add assignments flyout that opens, find and select the app that we created before.
  • When you're finished, click Add.
  • Back on the Assignments page, verify that the app has been assigned to the role.

For more information about the permissions, please see the Microsoft docs:

Authentication and Authorization

There are multiple ways to authenticate to the Graph API with each has its own pros and cons, in this example we are using the Authorization Code grant type.

  • First we need to get the Client ID, go to the Azure Portal > Azure Active Directory > App Registrations.
  • Select your application and copy the Application (client) ID value.
  • After we have the Client ID we also have to create a Client Secret.
  • From the Azure Portal, go to Azure Active Directory > App Registrations.
  • Select the application we have created before, and select "Certificates and Secrets".
  • Under “Client Secrets” click on the “New Client Secret” button to create a new secret.
  • Provide a logical name for your secret in the Description field, and select the expiration date for your secret.
  • It's IMPORTANT to copy the newly generated client secret, because you cannot see the value anymore after you close the page.
  • At last we need to get the Tenant ID. This can be found in the Azure Portal by going to Azure Active Directory > Overview.

Connection settings

The following settings are required to connect.

Setting Description
Azure AD Organization The name of the organization to connect to and where the Azure AD App Registration exists. Please note: This has to be the .onmicrosoft domain name
Azure AD Tenant ID Id of the Azure tenant
Azure AD App Id The Application (client) ID of the Azure AD App Registration with Exchange Permissions
Azure AD App Secret Secret of the Azure AD App Registration with Exchange Permissions

Getting help

For more information on how to configure a HelloID PowerShell connector, please refer to our documentation pages

If you need help, feel free to ask questions on our forum

HelloID Docs

The official HelloID documentation can be found at: https://docs.helloid.com/

helloid-conn-prov-target-exchangeonline's People

Contributors

evanderiet avatar rschouten97 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

helloid-conn-prov-target-exchangeonline's Issues

The check if the "ExchangeOnlineManagement" module is already imported doesn't work

The check if the "ExchangeOnlineManagement" module is already imported doesn't work.

I recommend changing the code

# If module is imported say that and do nothing
        if (Get-Module -Verbose:$false | Where-Object { $_.Name -eq $ModuleName }) {
            Write-Verbose "Module [$ModuleName] is already imported."
        }
        else {
            # If module is not imported, but available on disk then import
            if (Get-Module -ListAvailable -Verbose:$false | Where-Object { $_.Name -eq $ModuleName }) {
                $module = Import-Module $ModuleName -Cmdlet $commands -Verbose:$false
                Write-Verbose "Imported module [$ModuleName]"
            }
            else {
                # If the module is not imported, not available and not in the online gallery then abort
                throw "Module [$ModuleName] is not available. Please install the module using: Install-Module -Name [$ModuleName] -Force"
            }
        }

To:

# Check if module is already imported or available on disk
        $module = Get-Module -Name $moduleName -ListAvailable -ErrorAction SilentlyContinue -Verbose:$false

        if ($module) {
            Write-Verbose "Module [$moduleName] is already imported."
        } else {
            # Check if module is available in online gallery
            if (Find-Module -Name $moduleName) {
                # Import module with specified commands
                $module = Import-Module $ModuleName -Cmdlet $commands -Verbose:$false
                Write-Verbose "Imported module [$ModuleName]"
            } else {
                # If the module is not imported, not available and not in the online gallery then abort
                throw "Module [$ModuleName] is not available. Please install the module using: Install-Module -Name [$ModuleName] -Force"
            }
        }

Switch from “Get-EXOMailbox” to “Get-EXORecipient”

Currently, the connector uses “Get-EXOMailbox” to query the Shared mailboxes for the permissions.
However, the command “Get-EXORecipient” is at least twice as fast and provides the same data. At least, the same data HelloID requires, it doesn't provide the UserPrincipalName, but this isn't required for usage in HelloID.
With “Get-EXOMailbox” we experience the error "timeout occurred" a lot, sometimes even all the time (depends on the amount of data), with “Get-EXORecipient” the data is available in about 10~15 seconds (this of course may vary based on the amount of data).

The updated script would be something like this:

#####################################################
# HelloID-Conn-Prov-Target-ExchangeOnline-Permissions-SharedMailboxes
#
# Version: 2.0.0
#####################################################
# Initialize default values
$c = $configuration | ConvertFrom-Json

# Set TLS to accept TLS, TLS 1.1 and TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12

# Set debug logging
switch ($($c.isDebug)) {
    $true { $VerbosePreference = "Continue" }
    $false { $VerbosePreference = "SilentlyContinue" }
}
$InformationPreference = "Continue"
$WarningPreference = "Continue"

# Define configuration properties as required
$requiredConfigurationFields = @("AzureADOrganization", "AzureADTenantId", "AzureADAppId", "AzureADAppSecret")

# Used to connect to Exchange Online in an unattended scripting scenario using an App ID and App Secret to create an Access Token.
$AADOrganization = $c.AzureADOrganization
$AADTenantId = $c.AzureADTenantId
$AADAppID = $c.AzureADAppId
$AADAppSecret = $c.AzureADAppSecret

# PowerShell commands to import
$commands = @(
    "Get-User"
    , "Get-EXORecipient"
)

#region functions
function Resolve-HTTPError {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline
        )]
        [object]$ErrorObject
    )
    process {
        $httpErrorObj = [PSCustomObject]@{
            FullyQualifiedErrorId = $ErrorObject.FullyQualifiedErrorId
            MyCommand             = $ErrorObject.InvocationInfo.MyCommand
            RequestUri            = $ErrorObject.TargetObject.RequestUri
            ScriptStackTrace      = $ErrorObject.ScriptStackTrace
            ErrorMessage          = ""
        }
        if ($ErrorObject.Exception.GetType().FullName -eq "Microsoft.PowerShell.Commands.HttpResponseException") {
            $httpErrorObj.ErrorMessage = $ErrorObject.ErrorDetails.Message
        }
        elseif ($ErrorObject.Exception.GetType().FullName -eq "System.Net.WebException") {
            $httpErrorObj.ErrorMessage = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd()
        }
        Write-Output $httpErrorObj
    }
}

function Get-ErrorMessage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline
        )]
        [object]$ErrorObject
    )
    process {
        $errorMessage = [PSCustomObject]@{
            VerboseErrorMessage = $null
            AuditErrorMessage   = $null
        }

        if ( $($ErrorObject.Exception.GetType().FullName -eq "Microsoft.PowerShell.Commands.HttpResponseException") -or $($ErrorObject.Exception.GetType().FullName -eq "System.Net.WebException")) {
            $httpErrorObject = Resolve-HTTPError -Error $ErrorObject

            $errorMessage.VerboseErrorMessage = $httpErrorObject.ErrorMessage

            $errorMessage.AuditErrorMessage = $httpErrorObject.ErrorMessage
        }

        # If error message empty, fall back on $ex.Exception.Message
        if ([String]::IsNullOrEmpty($errorMessage.VerboseErrorMessage)) {
            $errorMessage.VerboseErrorMessage = $ErrorObject.Exception.Message
        }
        if ([String]::IsNullOrEmpty($errorMessage.AuditErrorMessage)) {
            $errorMessage.AuditErrorMessage = $ErrorObject.Exception.Message
        }

        Write-Output $errorMessage
    }
}
#endregion functions

try {
    # Check if required fields are available in configuration object
    $incompleteConfiguration = $false
    foreach ($requiredConfigurationField in $requiredConfigurationFields) {
        if ($requiredConfigurationField -notin $c.PsObject.Properties.Name) {
            $incompleteConfiguration = $true
            Write-Warning "Required configuration object field [$requiredConfigurationField] is missing"
        }
        elseif ([String]::IsNullOrEmpty($c.$requiredConfigurationField)) {
            $incompleteConfiguration = $true
            Write-Warning "Required configuration object field [$requiredConfigurationField] has a null or empty value"
        }
    }

    if ($incompleteConfiguration -eq $true) {
        throw "Configuration object incomplete, cannot continue."
    }

    try {           
        # Import module
        $moduleName = "ExchangeOnlineManagement"

        # If module is imported say that and do nothing
        if (Get-Module -Verbose:$false | Where-Object { $_.Name -eq $ModuleName }) {
            Write-Verbose "Module [$ModuleName] is already imported."
        }
        else {
            # If module is not imported, but available on disk then import
            if (Get-Module -ListAvailable -Verbose:$false | Where-Object { $_.Name -eq $ModuleName }) {
                $module = Import-Module $ModuleName -Cmdlet $commands -Verbose:$false
                Write-Verbose "Imported module [$ModuleName]"
            }
            else {
                # If the module is not imported, not available and not in the online gallery then abort
                throw "Module [$ModuleName] is not available. Please install the module using: Install-Module -Name [$ModuleName] -Force"
            }
        }
    }
    catch {
        $ex = $PSItem
        $errorMessage = Get-ErrorMessage -ErrorObject $ex

        Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
        throw "Error importing module [$ModuleName]. Error Message: $($errorMessage.AuditErrorMessage)"
    }

    # Connect to Exchange
    try {
        # Create access token
        Write-Verbose "Creating Access Token"

        $baseUri = "https://login.microsoftonline.com/"
        $authUri = $baseUri + "$AADTenantId/oauth2/token"
        
        $body = @{
            grant_type    = "client_credentials"
            client_id     = "$AADAppID"
            client_secret = "$AADAppSecret"
            resource      = "https://outlook.office365.com"
        }
        
        $Response = Invoke-RestMethod -Method POST -Uri $authUri -Body $body -ContentType "application/x-www-form-urlencoded" -UseBasicParsing:$true
        $accessToken = $Response.access_token

        # Connect to Exchange Online in an unattended scripting scenario using an access token.
        Write-Verbose "Connecting to Exchange Online"

        $exchangeSessionParams = @{
            Organization     = $AADOrganization
            AppID            = $AADAppID
            AccessToken      = $accessToken
            CommandName      = $commands
            ShowBanner       = $false
            ShowProgress     = $false
            TrackPerformance = $false
            ErrorAction      = "Stop"
        }
        $exchangeSession = Connect-ExchangeOnline @exchangeSessionParams
        
        Write-Information "Successfully connected to Exchange Online"
    }
    catch {
        $ex = $PSItem
        $errorMessage = Get-ErrorMessage -ErrorObject $ex

        Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
        throw "Error connecting to Exchange Online. Error Message: $($errorMessage.AuditErrorMessage)"
    }
    
    # Get Exchange Online Shared Mailboxes
    try {
        Write-Verbose "Querying EXO Shared Mailboxes"
            
        # Only get Exchange Shared Mailboxes (can be changed easily to get all mailboxes)
        $properties = @("Guid", "DisplayName")
        $mailboxes = Get-EXORecipient -RecipientTypeDetails SharedMailbox -Properties $properties -resultSize unlimited


        Write-Information "Successfully queried EXO Shared Mailboxes. Result count: $(($mailboxes | Measure-Object).Count)"
    }
    catch { 
        $ex = $PSItem
        $errorMessage = Get-ErrorMessage -ErrorObject $ex

        Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
        throw "Error querying EXO Shared Mailboxes. Error Message: $($errorMessage.AuditErrorMessage)"
    }
}
finally {
    # Send results
    foreach ($mailbox in $mailboxes) {
        # Shorten DisplayName to max. 100 chars
        $displayName = "Shared Mailbox - $($mailbox.DisplayName)"
        $displayName = $displayName.substring(0, [System.Math]::Min(100, $displayName.Length)) 
        $permission = @{
            DisplayName    = $displayName
            Identification = @{
                Id          = $mailbox.Guid
                Name        = $displayName
                Permissions = @("Full Access", "Send As") # Options:  Full Access,Send As, Send on Behalf
            }
        }

        Write-Output ($permission | ConvertTo-Json -Depth 10) 
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.