Param( [Parameter(Mandatory=$true)][String]$oktaToken, #provided by Okta: https://developer.okta.com/docs/api/getting_started/getting_a_token.html [Parameter(Mandatory=$true)][String]$oktaAPIBaseURL, #provided by Okta, e.g.: "https://yourcompany.okta.com" [Parameter(Mandatory=$true)][String]$o365login, [Parameter(Mandatory=$true)][String]$o365password, [Switch]$readOnly ) ####CONFIG #O365 - OKTA ROLE MAPPING $roleMapping = @( @{"O365RoleName" = "Admins";"OktaRoleName" = "Office 365 Administrators"}; #role 1 @{"O365RoleName" = "Helpdesk";"OktaRoleName" = "Office 365 Helpdesk"}; #role 2 @{"O365RoleName" = "Migration Management";"OktaRoleName" = "Office 365 Migration Team"}; #role 3 ) function validateExOConnection{ Param( [Parameter(Mandatory=$true)]$o365login, [Parameter(Mandatory=$true)]$o365password, [switch]$retry ) if($script:Session -eq $Null -or $script:Session.State -ne "Opened"){ #There is no session, or it has gone stale write-verbose "Exchange Online connection not available or has gone stale, attempting to connect" $failed = $False try{ $secpasswd = ConvertTo-SecureString $o365password -AsPlainText -Force $Credentials = New-Object System.Management.Automation.PSCredential ($o365login, $secpasswd) $a = New-PSSessionOption $a.IdleTimeout = 432000000000 $script:Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credentials -Authentication Basic -AllowRedirection -SessionOption $a $res = Import-PSSession $Session -AllowClobber -DisableNameChecking -WarningAction SilentlyContinue write-verbose "Connected to Exchange Online" return $Null }catch{ $failed = $True write-verbose "Failed to connect to Exchange Online! $($Error[0])" } if($failed -and !$retry){ validateExoConnection -o365login $o365login -o365password $o365password -retry return $Null } if($failed -and $retry){ Throw "Failed to connect to Exchange Online twice! Aborting $($Error[0])" } } } function Invoke-PagedMethod { Param( $url, $authHeader ) try{ $response = Invoke-WebRequest $url -Method GET -Headers $authHeader -ErrorAction Stop $links = @{} if ($response.Headers.Link) { foreach ($header in $response.Headers.Link.split(",")) { if ($header -match '<(.*)>; rel="(.*)"') { $links[$matches[2]] = $matches[1] } } } @{objects = ConvertFrom-Json $response.content; nextUrl = $links.next} }catch{ Throw $_ } } function buildOktaAuthHeader{ Param( [Parameter(Mandatory=$true)]$oktaToken ) $authHeader = @{ 'Content-Type'='application/json' 'Accept'='application/json' 'Authorization'= 'SSWS '+$oktaToken } return $authHeader } function retrieveAllOktaGroups{ Param( [Parameter(Mandatory=$true)]$oktaToken, [Parameter(Mandatory=$true)]$oktaAPIBaseURL ) $authHeader = buildOktaAuthHeader -oktaToken $oktaToken $groups = @() $res = @{"nextURL"="$oktaAPIBaseURL/api/v1/groups?limit=200&filter=type eq `"OKTA_GROUP`""} while($true){ try{ if($res.nextURL -eq $Null){ write-verbose "finished retireveAllOktaUsers function" break; } $res = Invoke-PagedMethod –Url $res.nextURL -authHeader $authHeader -Method GET if($res.objects){ $groups += $res.objects } write-verbose "retrieved $($groups.Count) groups..." }catch{ Throw "failed to retrieve groups from Okta: $_" } } return $groups } function retrieveOktaGroupMembers{ Param( [Parameter(Mandatory=$true)]$oktaToken, [Parameter(Mandatory=$true)]$groupHref ) $authHeader = buildOktaAuthHeader -oktaToken $oktaToken $members = @() $res = @{"nextURL"="$($groupHref)?limit=200"} while($true){ try{ if($res.nextURL -eq $Null){ write-verbose "finished paged function" break; } $res = Invoke-PagedMethod –Url $res.nextURL -authHeader $authHeader -Method GET if($res.objects){ $members += $res.objects } write-verbose "retrieved $($members.Count) members..." }catch{ Throw "failed to retrieve members from Okta: $_" } } return ,$members } function retrieveOktaGroupsAndMembers{ Param( [Parameter(Mandatory=$true)]$oktaToken, [Parameter(Mandatory=$true)]$groupNameFilter, [Parameter(Mandatory=$true)]$oktaAPIBaseURL ) $groupsAndMembers = @() foreach($group in (retrieveAllOktaGroups -oktaToken $oktaToken -oktaAPIBaseURL $oktaAPIBaseURL)){ #filter Oxfam Role Groups ONLY if(($groupNameFilter -and $group.profile.name -eq $groupNameFilter) -or !$groupNameFilter){ $groupMembers = retrieveOktaGroupMembers -oktaToken $oktaToken -groupHref $group._links.users.href if($groupMembers){ Write-Verbose "Adding $($groupMembers.Count) members to group" $group | Add-Member -MemberType NoteProperty -Name "Members" -Value $groupMembers } $groupsAndMembers+=$group } } return $groupsAndMembers } function equalizeMembersFromOktaToExchange{ Param( [Parameter(Mandatory=$true)]$oktaMembers, [Parameter(Mandatory=$true)]$exchangeMembers, [Parameter(Mandatory=$true)]$exchangeGroupIdentifier ) write-verbose "START: $exchangeGroupIdentifier" #check if all Okta members are in the Exchange Group foreach($oktaMember in $oktaMembers){ if($exchangeMembers.PrimarySmtpAddress -contains $oktaMember.profile.login -or $exchangeMembers.WindowsLiveID -contains $oktaMember.profile.login){ write-verbose "found $($oktaMember.profile.login) in exchange role group, nothing to do here" }else{ write-verbose "$($oktaMember.profile.login) not found in exchange role group, will attempt to add" try{ if(!$readOnly) {Add-RoleGroupMember -Identity $exchangeGroupIdentifier -Member $oktaMember.profile.login -Confirm:$False -BypassSecurityGroupManagerCheck:$True -ErrorAction Stop} write-verbose "SUCCESS" }catch{ write-error "Failed to add member to role group: $_" continue } } } #check if all Exchange members are in the Okta Group foreach($exchangeMember in $exchangeMembers){ if($oktaMembers.profile.login -contains $exchangeMember.PrimarySmtpAddress -or $oktaMembers.profile.login -contains $exchangeMember.WindowsLiveID){ write-verbose "found exchange role member $($exchangeMember.displayName) in okta role group, nothing to do here" }else{ write-verbose "$($exchangeMember.displayName) not found in okta role group, adding to removal list" try{ if(!$readOnly) {Remove-RoleGroupMember -Identity $exchangeGroupIdentifier -Confirm:$False -BypassSecurityGroupManagerCheck:$True -Member $exchangeMember.displayName -ErrorAction Stop} write-verbose "SUCCESS" }catch{ write-error "Failed to remove member from role group: $_" continue } } } write-verbose "DONE: $exchangeGroupIdentifier" } #log in to ExO try{ validateExOConnection -o365login $o365login -o365password $o365password }catch{ Write-Error "Failed to connect to Exchange Online: $($Error[0])" Exit } foreach ($role in $roleMapping){ #skip any roles that are not properly defined if(!$role.O365RoleName -or !$role.OktaRoleName){ continue } #fetch the Okta role (group) members $oktaGroup = retrieveOktaGroupsAndMembers -oktaToken $oktaToken -groupNameFilter $role.OktaRoleName -oktaAPIBaseURL $oktaAPIBaseURL #fetch the Exchange Role members $exchangeRoleMembers = Get-RoleGroupMember -Identity $role.O365RoleName -ResultSize Unlimited -ErrorAction Stop #equalize group members equalizeMembersFromOktaToExchange -exchangeGroupIdentifier $role.O365RoleName -oktaMembers $oktaGroup.Members -exchangeMembers $exchangeRoleMembers }