Office 365 Out of Office SaaS Connector

This connector is now hosted on SailPoint’s Colab. You can download and discuss this connector here: Outlook Out of Office Connector

Hey everyone, I tried to play a bit with the new SaaS Connectivity Framework and created this “Outlook Out of Office” connector. It leverages Azure Graph API to get the Out of Office Status directly from the Exchange Online Mailboxes. You can then import that status as an identity attribute and trigger workflows based on that (to configure a work reassignment for instance).

You can find the connector (source and zip file) here : GitHub - olivier-detilleux-sp/outlook-out-of-office: SaaS Connector to get Outlook out of Office Status

Please share your feedbacks if any.

7 Likes

Perfect timing with long weekend approaching :-p, will check it out.

1 Like

What a good idea, will check this out!

1 Like

This is Great. Thanks @olivier_detilleux :confetti_ball:

Hey Oliver, this looks awesome, I was trying to play around with it, and I did successfully load the connector however it doesn’t appear in the source list for a connector option. I assume i’m missing a step in the process. I loaded outlook-out-of-office.zip into the tenant via cli, got no errors, is there something special you have to do for custom connectors?

disregard, I was looking for the wrong name. :slight_smile:

1 Like

Hi Me again, I was able to get the connector running, test connection is successful, but then trying to aggregate I get this error after a few minutes. Our mailboxes are hosted in azure so i’m a little stumped on next troubleshooting steps, any ideas would be much appreciated! Running the query without a filter returns a similar error. I’m thinking maybe i need a better filter or its a limitation of our azure configuration, we do run in a hybrid model here and the mailboxes start in onprem and are synced to o365 if that potentially matters.

java.lang.RuntimeException - java.lang.IllegalStateException: [ConnectorError] The mailbox is either inactive, soft-deleted, or is hosted on-premise. (requestId: 4f3f22890a654c4694b7ac391955e3b7) - java.lang.RuntimeException: java.lang.IllegalStateException: [ConnectorError] The mailbox is either inactive, soft-deleted, or is hosted on-premise. (requestId: 4f3f22890a654c4694b7ac391955e3b7) at com.sailpoint.mantis.qpoc.message.AccountAggregation.iterateResourceObjects_aroundBody8(AccountAggregation.java:650) at com.sailpoint.mantis.qpoc.message.AccountAggregation$AjcClosure9.run(AccountAggregation.java:1) at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167) at com.sailpoint.tracing.otel.TracedAspect.lambda$traceExecution$0(TracedAspect.java:30) at com.sailpoint.tracing.otel.GlobalTracer.trace(GlobalTracer.java:158) at com.sailpoint.tracing.otel.GlobalTracer.trace(GlobalTracer.java:137) at com.sailpoint.tracing.otel.GlobalTracer.trace(GlobalTracer.java:125) at com.sailpoint.tracing.otel.TracedAspect.traceExecution(TracedAspect.java:32) at com.sailpoint.mantis.qpoc.message.AccountAggregation.iterateResourceObjects(AccountAggregation.java:583) at com.sailpoint.mantis.qpoc.message.AccountAggregation.handleMessage_aroundBody0(AccountAggregation.java:346) at com.sailpoint.mantis.qpoc.message.AccountAggregation$AjcClosure1.run(AccountAggregation.java:1) at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167) at com.sailpoint.atlas.metrics.MessageMetricsAspect.meterMessageTimeAndExceptions(MessageMetricsAspect.java:68) at com.sailpoint.mantis.qpoc.message.AccountAggregation.handleMessage(AccountAggregation.java:331) at com.sailpoint.atlas.messaging.server.TypeMessageHandler.handleMessage(TypeMessageHandler.java:87) at com.sailpoint.mantis.qpoc.utility.QpocMessageHandler.handleMessage_aroundBody0(QpocMessageHandler.java:60) at com.sailpoint.mantis.qpoc.utility.QpocMessageHandler$AjcClosure1.run(QpocMessageHandler.java:1) at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167) at com.sailpoint.atlas.metrics.MessageMetricsAspect.meterMessageTimeAndExceptions(MessageMetricsAspect.java:68) at com.sailpoint.mantis.qpoc.utility.QpocMessageHandler.handleMessage(QpocMessageHandler.java:52) at com.sailpoint.mantis.platform.message.ObjectConfigMessageHandler.handleMessage(ObjectConfigMessageHandler.java:33) at com.sailpoint.atlas.tracing.plugin.otel.TraceMessageHandler.handleMessage(TraceMessageHandler.java:57) at com.sailpoint.atlas.message.DynamicMessageHandler$ChainedMessageHandlerAdapter.handleMessage(DynamicMessageHandler.java:46) at com.sailpoint.atlas.tracing.plugin.TracingMessageHandler.handleMessage(TracingMessageHandler.java:88) at com.sailpoint.atlas.message.DynamicMessageHandler$ChainedMessageHandlerAdapter.handleMessage(DynamicMessageHandler.java:46) at com.sailpoint.atlas.usage.plugin.UsageMessageHandler.handleMessage(UsageMessageHandler.java:36) at com.sailpoint.atlas.message.DynamicMessageHandler$ChainedMessageHandlerAdapter.handleMessage(DynamicMessageHandler.java:46) at com.sailpoint.atlas.message.DynamicMessageHandler.handleMessage(DynamicMessageHandler.java:36) at com.sailpoint.mantis.platform.message.SailPointContextMessageHandler.handleMessage(SailPointContextMessageHandler.java:55) at com.sailpoint.atlas.message.FailureNotificationHandler.handleMessage(FailureNotificationHandler.java:55) at com.sailpoint.atlas.message.RequestContextMessageHandler.handleMessage(RequestContextMessageHandler.java:72) at com.sailpoint.mantis.platform.message.ExceptionMessageHandler.handleMessage(ExceptionMessageHandler.java:49) at com.sailpoint.atlas.messaging.server.MessageProcessor.handleJobMessage(MessageProcessor.java:196) at com.sailpoint.atlas.messaging.server.MessageProcessor.handleMessage(MessageProcessor.java:129) at com.sailpoint.atlas.messaging.server.MessageProcessor.lambda$null$0(MessageProcessor.java:99) at com.sailpoint.atlas.messaging.server.MessageProcessor.withOrgLogging(MessageProcessor.java:171) at com.sailpoint.atlas.messaging.server.MessageProcessor.lambda$asyncHandleMessage$1(MessageProcessor.java:99) at com.sailpoint.atlas.messaging.server.impl.SourceRunnableImpl.run(SourceRunnableImpl.java:77) at com.sailpoint.atlas.messaging.server.impl.BufferedSourceQueue$IncrementingSourceRunnable.run(BufferedSourceQueue.java:181) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:829) Caused by: java.lang.IllegalStateException: [ConnectorError] The mailbox is either inactive, soft-deleted, or is hosted on-premise. (requestId: 4f3f22890a654c4694b7ac391955e3b7) at com.sailpoint.connector.cloud.spconnect.SpConnectProxy$1.nextResponse_aroundBody0(SpConnectProxy.java:306) at com.sailpoint.connector.cloud.spconnect.SpConnectProxy$1$AjcClosure1.run(SpConnectProxy.java:1) at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167) at com.sailpoint.tracing.otel.TracedAspect.lambda$traceExecution$0(TracedAspect.java:30) at com.sailpoint.tracing.otel.GlobalTracer.trace(GlobalTracer.java:158) at com.sailpoint.tracing.otel.GlobalTracer.trace(GlobalTracer.java:137) at com.sailpoint.tracing.otel.GlobalTracer.trace(GlobalTracer.java:125) at com.sailpoint.tracing.otel.TracedAspect.traceExecution(TracedAspect.java:32) at com.sailpoint.connector.cloud.spconnect.SpConnectProxy$1.nextResponse(SpConnectProxy.java:302) at sailpoint.connector.cloud.CloudConnector$CloudBridgeIterator.buildDataBlockIterator(CloudConnector.java:1110) at sailpoint.connector.cloud.CloudConnector$CloudBridgeIterator.checkForMoreData(CloudConnector.java:1098) at sailpoint.connector.cloud.CloudConnector$CloudBridgeIterator.hasNext(CloudConnector.java:1024) at sailpoint.connector.ConnectorProxy$CustomizingIterator.peek(ConnectorProxy.java:738) at sailpoint.connector.ConnectorProxy$CustomizingIterator.hasNext(ConnectorProxy.java:765) at com.sailpoint.mantis.qpoc.message.AccountAggregation.iterateResourceObjects_aroundBody8(AccountAggregation.java:618) ... 43 more

I’ve been fiddling with this and I believe I figured out my problem. Sharing here for anyone else who wants to utilize something like this in the future…

The connector works great so props to Oliver on making and providing this solution. the problems I was encountering were environment specific, the error I pasted above was happening in the getMailboxSettings() being made for each account returned in getAllAccounts(), some of the accounts in list were not graphAPI enabled, figuring out what ones exactly was a bit of a difficult task, so I opted to just alter the connector a bit.

what I had to do to get this to work was modify the getAllAccounts() function to add a header and count parameter to enable some of the more complicated query use in MS Graph - see link. I added ConsistencyLevel header so that I could filter the response a little more finely to limit my calls to just our active employee base.

    async getAllAccounts(): Promise<Map<string, string>[]> {
        let results: Map<string, string>[] = []

        let response: PageCollection = await this.client
            .api('/users')
            .header ('ConsistencyLevel', 'eventual')
            .top(100)
            .count(true)
            .select([
                'id',
                'userPrincipalName',
                'mail',
                'displayName',
                'givenName',
                'surname',
                'lastPasswordChangeDateTime',
            ])
            .filter(this.filter)
            .get()

but I was still getting errors even after I got the query correct, so what I eventually came up with was just to wrap the getMailboxSettings() in a try-catch block as ultimately i did not care about the random mailboxes that were not api enabled for this purpose, so just ignore them and move on.

async getMailboxSettings(identity: string): Promise<Map<string, string>> {
        let result: Map<string, string> = new Map()
        try{
            await this.client
                .api('/users/' + identity + '/mailboxSettings/automaticRepliesSetting')
                .top(1)
                //.select(['status', 'scheduledStartDateTime', 'scheduledEndDateTime'])
                .get()
                .then((mailboxSettings) => {
                    result.set('automaticRepliesSetting', mailboxSettings.status)
                    result.set('scheduledEndDateTime', mailboxSettings.scheduledEndDateTime.dateTime)
                    result.set('scheduledStartDateTime', mailboxSettings.scheduledStartDateTime.dateTime)

                    if (mailboxSettings.status == 'alwaysEnabled') {
                        result.set('automaticRepliesStatus', 'enabled')
                    }

                    if (mailboxSettings.status == 'scheduled') {
                        const start = new Date(mailboxSettings.scheduledStartDateTime.dateTime)
                        const end = new Date(mailboxSettings.scheduledEndDateTime.dateTime)
                        const now = new Date()
                        if (start < now && end > now) {
                            result.set('automaticRepliesStatus', 'enabled')
                        } else {
                            result.set('automaticRepliesStatus', 'disabled')
                        }
                    }

                    if (mailboxSettings.status == 'disabled') {
                        result.set('automaticRepliesStatus', 'disabled')
                    }
                })

            } catch(error){
                console.error('Error fetching mailbox settings:', error)
            }
        return result
       
    } 

repacked, and reuploaded the connector and ran an aggregation and I have the account population i needed, with the data required! next step, workflows to set work reassignment for out of office :stuck_out_tongue:
image

Thanks a lot for you feedback! I will update m’y code accordingly.
Regarding the workflow, I already published an article about it : Workflow to manage Work Reassignments based on Identity Attribute Change - #2 by bostelmann
Let me know if this what you are looking for

That’s definitely the idea! thanks for the link - will look into it.