Quantcast
Channel: Outlook dev blog

Office 365 APIs and Python Part 2: Contacts API

$
0
0

This post is the second part of a series of posts chronicling my (mis)adventures learning Python and Django, and using them to integrate with Office 365. This extends the sample app from Part 1 to add CRUD operations with the Contacts API.

When I left off in Part 1, I had a working Python/Django app that was able to retrieve an OAuth2 access token from Azure AD. Now it's time to take that token and do something interesting with it! In this post, I tackle calling the Contacts API.

Fixing the view

The index view from Part 1 just showed the access token in base64 format. That's not what we want, so we need to do a bit of work on the index view. The end goal is to show something along these lines:

 

Instead of just passing the access token, we want to pass a list of contacts. So the first thing was to define a DisplayContact class (in models.py) to represent the bits of the contacts that we're going to work with:

class DisplayContact:

given_name = ''

last_name = ''

mobile_phone = ''

email1_address = ''

email1_name = ''

email2_address = ''

email2_name = ''

email3_address = ''

email3_name = ''

id = ''

I also added a function (load_json) to that class to initialize itself from the JSON returned from the Contacts API, and another function (get_json) to generate the JSON that the Contacts API expects when creating or updating a contact.

Now the app needs to actually get that JSON. In o365service.py, I added a function called get_contacts. The version on GitHub has a lot of logging added, but here's a stripped down version without logging, so you can see how straightforward this really is:

def get_contacts(contact_endpoint, token, parameters):

headers = { 'Authorization' : 'Bearer {0}'.format(token),

'Accept' : 'application/json' }

get_contacts = '{0}/Me/Contacts'.format(contact_endpoint)

if (not parameters is None):

get_contacts = '{0}{1}'.format(get_contacts, parameters)

r = requests.get(get_contacts, headers = headers)

if (r.status_code == requests.codes.unauthorized):

return None

return r.json()

You may have noticed the "parameters" parameter to the function, and maybe you're wondering what that's for. That is an optional parameter used to specify OData query parameters. When I call this function from the index view in views.py, I actually pass "?$select=GivenName,Surname,MobilePhone1,EmailAddresses". If you compare with the fields in the DisplayContact class, you'll notice that they match up. By using the $select parameter, I'm asking the server only to send me the fields I care about, and reducing the size of the response payload.

Doing more than just read

So now the app can get all the contacts from the server and display them. Time to move on to the other operations: Create, Update, and Delete.

Delete

To implement delete, I created a "delete" action in views.py that takes the contact ID as a parameter. I then created a delete_contact function in o365service.py which is very similar to the get_contacts function. Using the Django template language I added a Delete button to each contact entry in the table, with a link to the "delete" action that includes the contact's ID as a parameter.

Edit

To implement edit, I followed a similar pattern. I created an "update" action in views.py, and an update_contact function in o365service.py. I also added an Edit button to each contact in the table. The one difference was that for edit, we need to actually display a new UI to show all the fields and allow the user to change them. For that, I added an "edit" view in views.py and a get_contact_by_id function in o365service.py. This view calls the get_contact_by_id method to load the contact, then renders the details.html template, which is basically an HTML form. The submit action on the form calls the "update" action.

Create

Create follows a similar pattern as Edit. There's a view called "new", and an action called "create", both in views.py. The "new" view renders the same template (details.html) as the "edit" view. Through the magic of the Django template language, the submit action for the form changes to the "create" action when the template is rendered with a null DisplayContact. The "create" action calls the create_contact function in o365service.py.

Modifying the EmailAddresses property

I found some interesting "quirks" when working with the EmailAddresses property. If you've looked at a JSON response, you may have noticed that the EmailAddresses property is an array of 3 Microsoft.OutlookServices.EmailAddress objects. These correspond to the "Email", "Email 2", and "Email 3" entries on Outlook Web App, in order. Empty entries will come back as "null" in the response. When you're writing to this property, the null entries matter!

When you're updating the property, you'll want to be careful to achieve the desired result. For example, if there currently exists just one entry, and you want to add a second entry, you need to update the property with the original entry, followed by the new entry, followed by null. If you just send the new entry, it will overwrite the old one!

However, when you're creating a new contact, you cannot include null entries. Doing so will result in a 500 HTTP error. This means you can't create a contact with "Email" blank and "Email 2" filled in, if that's your goal. You can however update the contact to move "Email" to "Email 2", if you really want to.

Get the code

The updated code is posted on GitHub. As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT).


Office 365 APIs and Python Part 3: Mail and Calendar API

$
0
0

This post is the third part of a series of posts chronicling my (mis)adventures learning Python and Django, and using them to integrate with Office 365. This extends the sample app from Part 2 to add CRUD operations with the Mail and Calendar APIs.

I had so much fun making part 1 and part 2 of this sample, I figured why not take it farther and implement a bit of all three of the Exchange-based APIs? In this post, I take on the Mail API and the Calendar API.

Paving the way

My plan was to extend the existing o365service module to include Mail and Calendar functions. But before I started, I decided to try to streamline the existing code a bit. To that end, I added a make_api_call function to the module that handles setting headers and sending the HTTP request. I updated all of the API-specific function like create_contact to use this function.

Being a good citizen

Since all of my API calls are now going through the make_api_call method, I figured this was a good time to add client instrumentation. Now each request's id is written to the debug log. If this was a production application and I needed help from Microsoft to figure out why calls were failing, this forensic data could help expedite a resolution, so it's definitely a recommended practice.

Enable Fiddler capture

Another small change I made while consolidating the HTTP code was to add a global flag to turn off SSL certificate validation. While developing part 1 and 2, I was never able to get Fiddler captures of the calls made by my app. With Fiddler running, I would always get SSL certificate validation errors. It turns out that the requests module will fail requests when the SSL certificate isn't valid. Of course that's a good thing, but it makes it difficult to use Fiddler. No problem though! The requests module has the ability to suppress SSL certificate validation on a per-request basis. If you want to use Fiddler, set the verifySSL variable at the top of o365service.py to False.

Adding the APIs

Adding the Mail and Calendar APIs was fairly easy since I had the Contacts API functions as a starting point. Copying those methods and making a few tweaks was really all it took. The function create_message isn't very different than create_contact. You gotta love REST. I won't go into detail here, the relevant changes are all in o365service.py.

Invoking the new APIs

If you look at the 1.2 update on GitHub, you may notice that there are no new views in the app. That's right! I didn't change the UI of the app to use these new APIs. Instead, I wrote tests. Django has a neat testing framework. I created the MailApiTests and CalendarApiTests classes, and threw in a ContactsApiTests class for good measure. What I like about the way Django testing works is that I can easily invoke all tests with one command, or I can be selective. For example, I can run all of the Mail API tests with this command:

python manage.py test contacts.test.MailApiTests

Or, I can just invoke the test_delete_message test with this command:

python manage.py test contacts.test.MailApiTests.test_delete_message

You have to manually copy a valid access token into test.py to get these tests to run. If you use the app up to the point where http://hostname/contacts will display a list of contacts, you can browse to http://hostname/admin, login as your superuser, and copy the access token from your Office365Connection record. Keep in access tokens expire after an hour.

Expanding coverage

Some of you may have noticed that the o365service module doesn't cover everything. For example, you can work with contacts, but not with contact folders. Messages are there, but not mail folders. Or more subtly, you can get messages from the Inbox, but not from other folders. So what if you want to do more? The make_api_call method can help here. You can add another method to the o365service module, or you can call make_api_call directly! As an example of this, I use it in the test_send_draft_message test to get the messages from the Drafts folder.

Moving on

This will likely be my last post in the Python series. I've had a blast playing with Python and Django, and they seem like a natural platform for consuming the Office 365 APIs. As fun as it's been, it's time for me to move on to another language. What will it be? Stay tuned to find out!*

*Probably Ruby. However, I'd love to hear suggestions from the community. Is there a language or platform you're dying to see the Office 365 APIs run on? Drop a note in the comments.

Get the code

The updated code is posted on GitHub. As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT).

Building Daemon or Service Apps with Office 365 Mail, Calendar, and Contacts APIs (OAuth2 client credential flow)

$
0
0

Device and Web Server applications usually require a user to sign-on and consent to allow the application to access and work with their information in Office 365. The underlying protocol to gain access is based on the OAuth2 authorization code grant flow. I described this a while ago in one of my earlier blogs Using OAuth2 to access Calendar, Contact and Mail API in Office 365 Exchange Online. As part of this OAuth2 flow, the application gets an access token/refresh token pair that represents a delegation given to the application by a specific individual user for a set of permissions. Essentially before the application can access data for a user, it has to get an access token/refresh token for each user, and to get those the user has to sign-on to the application at least once.

 

There are however a category of applications where this is not desirable or possible. These applications usually run in the background as a daemon app or service and need access without the user having to sign-on. OAuth2 provides a different flow for these types of applications, called the client credential grant flow. You can read more about this flow in the AAD Authentication Protocol documentation here. We're happy to announce that Office 365 now supports this flow to gain access to the Office 365 Calendar, Contacts and Mail APIs.

 

When using this flow, the application presents its client credentials to the OAuth2 token issuing endpoint, and in return gets an access token that represents the application itself without any user information. This is sometime also called an "App-Only" token. There is no need for the application to get a refresh token. When the access token expires, it simply goes back to the OAuth2 token issuing endpoint to get a new one. Also, since there is no user information in the token, the app must specify the user within the API call when using this "App-Only" token.

 

Note: Access token lifetime is currently 60 minutes. This applies to all access tokens in Office 365, regardless which OAuth2 flow is used.

 

 

Defining permissions

 

The first step to configure your application to use "App-Only" tokens is to define what permissions your application needs. Just like permissions for authorization code grant flow apps, these permissions are defined with the application registration in the Microsoft Azure Management Portal for Azure Active Directory (AAD). While the permissions for the authorization code grant flow are called "Delegated Permissions" (because a user delegates those permission to the app), the permissions for the client credential flow are called "Application permissions" (because those permissions are directly assigned to the application).

 

Below is a screen shot of the AAD Application Registration permissions section:

Machine generated alternative text:
permissions to other applications 
Delegated Permissions: 1 
Delegated Permissions: O 
Windows Azure Active Directory 
Office 365 Exchange Online 
Add application 
Application Permissions: 1 
Application Permissions: 3

 

Note: When creating the application, the application must be created as a "Web Application and/or Web API" within the AAD application management portal.

 

 

Granting consent and app authentication strength

 

Now that application permissions are defined within the application registration, the application can ask for consent to be available in another Office 365 organization. Application Permissions must be consented by a tenant administrator (global administrator) of an Office 365 organization. This is because these applications are quite powerful in terms of what data they can access in the Office 365 organization. For example, a service application with the Mail.Read permission that acquires access tokens via client credential flow can read mail in any mailbox in the Office 365 organization.

 

Because of the broad access these kinds of apps enjoy, there is an additional requirement for the app to successfully obtain an access token. Instead of using a client ID and client secret, the app must use an X.509 certificate with a public/private key pair. Usage of simple symmetric keys are not allowed, and while the application could get an access token using a symmetric key, the API will return an access denied error with such an access token. Self-issued X.509 certificates are accepted, but the application must register the public X.509 certificate in AAD with the application definition. The app then maintains the private key and uses it to assert its identity to the token issuing endpoint.

 

The application implements the consent flow in a similar way as the authorization code grant flow by sending a request to the OAuth2 authorize endpoint. However when the authorize endpoint redirects back with an authorization code to the application, the code can be happily ignored and thrown away. For getting tokens only the client credentials are necessary. Most applications build an experience such as "Sign-up my Organization" to accomplish the initial one-time consent.

 

The key take away of this section is that one-time consent is part of a daemon app configuration.  Once the consent is given, the service or daemon app can get access tokens and start calling the Office 365 Rest APIs.

 

 

Revoking consent

 

Consent to service applications can be revoked just like for other applications that are installed by a tenant administrator of the Office 365 organization. The administrator can either go to the AAD Azure Management Portal, find the application in the application view, select and delete it, or alternatively the administrator can use Azure AD PowerShell to remove the app via the "Remove-MSOLServicePrincipal" cmdlet.

 

 

Acquiring an access token with client credential flow

 

There is one important aspect when asking for app-only tokens: a tenant specific token issuing endpoint must be used. In other words, the "common" OAuth2 token issuing endpoint https://login.windows.net/common/oauth2/token cannot be used in this flow and will return an error.

 

The application can get the tenant specific endpoint quite easily. During the consent, when the authorize endpoint is hit and a code is delivered in the redirect to the application, an ID token can be requested together with the code. This ID token contains the tenant Id as "tid" claim-type. As the ID token is a JSON Web Token it is fairly simple to parse.

 

Getting an Initial ID token with Consent

 

Below is an example of how to trigger consent using an OpenID Connect Hybrid Flow (see OpenID spec here) request. This request will provide your application with the consent flow and redirect back with the code and ID token in a post request. Your application can ignore the code and get the ID token from the received form data (see OpenID spec on form post response mode)

 

GET https://login.windows.net/common/oauth2/authorize?state=e82ea723-7112-472c-94d4-6e66c0ca52b6&response_type=code+id_token&scope=openid&nonce=c328d2df-43d1-4e4d-a884-7cfb492beadc&client_id=0308CDD9-874D-4F87-85E0-A0DA7E05F999&redirect_uri=https:%2f%2flocalhost:44304%2fHome%2f&resource=https:%2f%2fgraph.ppe.windows.net%2f&prompt=admin_consent&response_mode=form_post HTTP/1.1

 

In ASP.Net you could simply retrieve the ID token via Request.Form["id_token"] on your redirect page.  You can find the tenantId in the "tid" claim within the JWT id_token.

 

 

 

In the example below I used Azure Active Directory Client library (ADAL) to acquire an "app-only" access token via client credential flow. My application maintains the private key in a well-protected directory on my web server as a PFX file. However it is better to maintain key material in a more secure storage such as the Windows certificate storage of the computer account.

 

Code snippet: Getting an "App-Only" token

 

           // need to address the tenant specific authority/token issuing endpoint

          //  https://login.windows.net/<tenant-id>/oauth2/authorize

          //  retrieved tenantID from ID token for the app during consent flow (authorize flow)

    string authority = appConfig.AuthorizationUri.Replace("common", tenantId);

               AuthenticationContextauthenticationContext = new AuthenticationContext(

                   authority,

                   false);

 

               string certfile = Server.MapPath(appConfig.ClientCertificatePfx);

 

                X509Certificate2 cert = new X509Certificate2(

                    certfile,

                    appConfig.ClientCertificatePfxPassword, // password for the cert file containing private key

                    X509KeyStorageFlags.MachineKeySet);

 

                ClientAssertionCertificatecac = new ClientAssertionCertificate(

                    appConfig.ClientId, cert);

                   

                varauthenticationResult = await authenticationContext.AcquireTokenAsync(

                    resource,   // always https://outlook.office365.com for Mail, Calendar, Contacts API

                    cac);

 

                return authenticationResult.AccessToken;

 

 

Note: The complete sample of a web app using client credential flow is available in GitHub on

·        https://github.com/mattleib/o365api-as-apponly-webapp

 

 

 

Configuring a X.509 public cert for your application

 

One last challenge is how to actually configure a X.509 public certificate with the application definition. Unfortunately X.509 certificates are not exposed as a UI element in the AAD application management portal. The applications public certificates need to be managed through the manifest.

 

Step 0: (If you do not have an X.509 certificate already) Create a self-issued certificate

You can easily generate a self-issued certificate with the makecert.exe tool.

 

1.      From the command line, run: makecert -r -pe -n "CN=MyCompanyNameMyAppName Cert" -b 12/15/2014 -e 12/15/2016 -ss my -len 2048

2.      Open the Certificates MMC snap-in and connect to your user account. Find the new certificate in the Personal folder and export it to a base64-encoded CER file.

 

Note: Make sure the key length is at least 2048 when generating the X.509 certificate. Shorter key length are not accepted as valid keys.

 

 

Step 1:  Get the base64 encoded cert value and thumbprint from a .cer X509 public cert file using PowerShell

 

Note: The instructions below show using Windows PowerShell to get properties of a x.509 certificate. Other platforms provide similar tools to retrieve properties of certificates.

 

$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2

$cer.Import("mycer.cer")

$bin = $cer.GetRawCertData()

$base64Value = [System.Convert]::ToBase64String($bin)

 

$bin = $cer.GetCertHash()

$base64Thumbprint = [System.Convert]::ToBase64String($bin)

 

$keyid = [System.Guid]::NewGuid().ToString()

 

Store the values for $base64Thumbprint, $base64Value and $keyid, to be used in the next step.



Step 2:  Upload cert through the manifest file

 

3.      Log in to the Azure Management Portal (https://manage.windowsazure.com)

4.      Go to the AAD snap-in and there navigate to the application that you want to configure with an X.509 certificate

5.      Download the application manifest file through the Azure Management Portal

Machine generated alternative text:
VIEW 
Manifest 
MANAGE 
MANIFEST 
UPLOAD Lca

 

6.      Replace the empty KeyCredentials”: [], property with the following JSON.  NOTE:  The KeyCredentials complex type is documented here:  http://msdn.microsoft.com/en-us/library/azure/dn151681.aspx

 

  "keyCredentials": [

    {

      "customKeyIdentifier": "$base64Thumbprint_from_above",

      "keyId": "$keyid_from_above",

      "type": "AsymmetricX509Cert",

      "usage": "Verify",

      "value":  "$base64Value_from_above"

     }

   ],

 

e.g.

 

  "keyCredentials": [

    {

      "customKeyIdentifier": "ieF43L8nkyw/PEHjWvj+PkWebXk=",

      "keyId": "2d6d849e-3e9e-46cd-b5ed-0f9e30d078cc",

      "type": "AsymmetricX509Cert",

      "usage": "Verify",

      "value": "MIICWjCCAgSgAwIBA***omitted for brevity***qoD4dmgJqZmXDfFyQ"

    }

  ],

 

7.      Save the change to the application manifest file.

8.      Upload the edited application manifest file through the Azure Management Portal.

9.      Optional:  Download the manifest again, and see your X.509 cert is present on the application.

 

Note:  KeyCredentials is a collection, so it’s totally possible to upload multiple X.509 certificates for rollover scenarios, or delete certs for compromise scenarios.

 

 

Must-Do Application Developing practices calling Mail, Calendar and Contacts API

 

Unrelated to OAuth2, there are three HTTP request headers you should always include when making requests to the Office365 APIs. This is the "User-Agent" the "client-request-id" and "Date". For User-Agent you can follow RFC 2616 which basically describes it as {ProductName}/{ProductVersion} [{comment}]. For "client-request-id" your app should create a new GUID for each request. When the request fails "return-client-request-id" returns the GUID that was submitted as "client-request-id" with the request. It is highly recommended that you persist the failed request together with client-request-id and all HTTP response headers in some application log. The "Date" request header signals the date and time that the message was sent. It should follow the "HTTP-date" format as defined by RFC 2616)}. If you ever need help troubleshooting your application with the Office 365 APIs, this will be pave the route to a fast and successful resolution of the problem.

 

 

Summary

 

To sum up:

·        With the availability of the OAuth2 client credential flow it is now possible to build daemon or service apps in addition to device and web server applications. 

·        The installation or bootstrapping of those service apps is very similar to web server apps and requires an administrator of an Office 365 organization to consent.

·        Unlike web server apps, which can use symmetric keys, a service app must use X.509 certificates as authentication strength to acquire access tokens and successfully call the Mail, Calendar and Contacts APIs.

·        Service apps have the requested access level to all mailboxes in the Office 365 organization the administrator has consented to.

·        Service apps can be revoked by an administrator of the Office 365 organization in the same way like web server apps.

 

As always, thank you for reading and feedback, questions, etc. are very much appreciated!

 

Please use Stack Overflow for questions, and be sure to tag your post with "office365".

 

Matthias Leibmann

 

 

References

OAuth2 client credential flow: http://msdn.microsoft.com/en-us/library/azure/dn645543.aspx

OAuth2 client libraries: http://msdn.microsoft.com/en-us/library/azure/dn151135.aspx

AAD on GitHubhttps://github.com/AzureADSamples

OAuth Sandbox: https://oauthplay.azurewebsites.net/

API Sandbox: http://apisandbox.msdn.microsoft.com/

Platform Overview: http://msdn.microsoft.com/en-us/office/office365/howto/platform-development-overview

Apps for Office: http://msdn.microsoft.com/en-us/library/office/fp161507(v=office.15).aspx

Office on Github: https://github.com/OfficeDev

Sample Web App to this Bloghttps://github.com/mattleib/o365api-as-apponly-webapp

 

 

Client Credentials Flow for Mail API in Python

$
0
0

Matthias announced support for the Client Credentials flow in the Mail, Calendar, and Contacts APIs a couple of weeks ago, and since then, we’ve had a lot of questions about implementing it. Matthias published a great sample using .NET and ADAL, but many of you have asked for details on implementing this on other platforms, especially those that don’t have an ADAL library. I enjoyed working with Python so much, I figured I’d try implementing this in Python.

Note: I’m going to go into gory details here. If you just want to get to the code, you can find it on GitHub.

Details, Details

Matthias’ post covers the steps to request consent and access tokens. For the most part this was straightforward. However, I quickly ran into an issue. What information do I need to include in my token request? ADAL makes it look simple. I know I need the private key from the certificate that I uploaded to my app’s manifest, but what do I do with it? Well, ADAL is open source, so I could go dig through their source, but luckily, Vittorio posted the details on his blog. Matthias also updated his sample to include an alternative method of requesting the token that bypasses ADAL.

So the POST body isn’t that much different than in the authorization code grant flow. As expected, the grant_type is different. The big difference is that instead of a client_secret, we have two new fields: client_assertion_type and client_assertion. The client_assertion_type is always "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", so that just leaves the real challenge: client_assertion.

Building the Assertion

Vittorio’s blog provides the details. We just need to create a JWT with the appropriate fields. Here’s the breakdown:

Header

  • alg: Always set to "RS256".
  • x5t: Thumbprint of the app’s cert. This is the same value that is present in the customKeyIdentifier field of the keyCredentials parameter in your app’s manifest file in Azure AD.

Payload

  • sub: Your app’s client ID.
  • iss: Your app’s client ID.
  • jti: A random GUID.
  • exp: A UNIX epoch time value that specifies when this assertion expires. Recommended to be Now + 10 minutes.
  • nbf: A UNIX epoch time value that specifies the start time of this assertion’s validity period. Recommended to be Now – 5 minutes.
  • aud: The URL to the tenant-specific token endpoint. Note: The common endpoint "https://login.windows.net/common/oauth2/token" will NOT work.

Sample Assertion

So let’s say you have an app with a client ID of bcdb5ae8-1ab8-47c3-b0b2-6756048e2ddc, and the consenting admin’s tenant ID (from the ID token you received during the consent process) is 5e0699a2-7e10-4d08-8ebb-4f7d7406ad09. Your assertion might look like:

{
"alg": "RS256",
"x5t": "W-Kt0b4B7RkK34cdXVR91e_nf9x"
},
{
"sub": "bcdb5ae8-1ab8-47c3-b0b2-6756048e2ddc",
"iss": "bcdb5ae8-1ab8-47c3-b0b2-6756048e2ddc",
"jti": "9ef089d9-b116-40d8-bbe2-57a756e32ea9",
"exp": 1423168488,
"nbf": 1423167888,
"aud": "https://login.windows.net/5e0699a2-7e10-4d08-8ebb-4f7d7406ad09/oauth2/token"
}

This is very easy to do in Python using dictionaries.

Encoding and Signing

Now that you have the assertion, we need to encode it and sign it before it’s ready to use in the client_assertion claim.

Encoding

To encode the assertion, take each part separately (the header and the payload), and base64-encode it. You should then make it url-safe, and remove any trailing '=' characters. Finally, you join the two values together with a '.' character. Using the sample assertion above, that gives us:

eyJhbGciOiAiUlMyNTYiLCAieDV0IjogIlctS3QwYjRCN1JrSzM0Y2RYVlI5MWVfbmY5eCJ9.eyJhdWQiOiAiaHR0cHM6Ly9sb2dpbi53aW5kb3dzLm5ldC81ZTA2OTlhMi03ZTEwLTRkMDgtOGViYi00ZjdkNzQwNmFkMDkvb2F1dGgyL3Rva2VuIiwgImp0aSI6ICI5ZWYwODlkOS1iMTE2LTQwZDgtYmJlMi01N2E3NTZlMzJlYTkiLCAiZXhwIjogMTQyMzE2ODQ4OCwgImlzcyI6ICJiY2RiNWFlOC0xYWI4LTQ3YzMtYjBiMi02NzU2MDQ4ZTJkZGMiLCAic3ViIjogImJjZGI1YWU4LTFhYjgtNDdjMy1iMGIyLTY3NTYwNDhlMmRkYyIsICJuYmYiOiAxNDIzMTY3ODg4fQ 

In Python, this involves using the built-in JSON library to dump the dictionaries to a string serialization, encode that into a UTF-8 byte array, and base64-encode it with the urlsafe_b64encode function.

Signing

With our encoded value in hand, you now need to sign it. Using the same certificate that you used when registering the app in Azure AD, create an RSA-256 signature. You then base64-encode the signature, and append it to the existing value, again using the '.' character as a separator. With our sample data, that results in something like:

eyJhbGciOiAiUlMyNTYiLCAieDV0IjogIlctS3QwYjRCN1JrSzM0Y2RYVlI5MWVfbmY5eCJ9.eyJhdWQiOiAiaHR0cHM6Ly9sb2dpbi53aW5kb3dzLm5ldC81ZTA2OTlhMi03ZTEwLTRkMDgtOGViYi00ZjdkNzQwNmFkMDkvb2F1dGgyL3Rva2VuIiwgImp0aSI6ICI5ZWYwODlkOS1iMTE2LTQwZDgtYmJlMi01N2E3NTZlMzJlYTkiLCAiZXhwIjogMTQyMzE2ODQ4OCwgImlzcyI6ICJiY2RiNWFlOC0xYWI4LTQ3YzMtYjBiMi02NzU2MDQ4ZTJkZGMiLCAic3ViIjogImJjZGI1YWU4LTFhYjgtNDdjMy1iMGIyLTY3NTYwNDhlMmRkYyIsICJuYmYiOiAxNDIzMTY3ODg4fQ.BjLYFjloB4XojO5YELKeXc7m_-uhEHsDZ2MKjYHY4UX5fJYSLxVpNsn6uv_ICEhEcmn5wqoh6H7x2qgCUyf-jFto56GK7ygGqf1thGAVpDMzWF3fYsvZ7g3G-x_xPxOSul2EehXse8TqDhM1fhYHD2wpTeRGCSgZtuQwJiXy4R-ysnV9YFhzu0OhBxSwjfwA7XZaqPgvZNNm1BI-L9KQ-yAIrhBkv1LnVJ3k6oNAdxRbCmzLGdEm-Dq4EYZEVXqgdPn5wo98xtETDQ6ee6VH3xjyxpoyxHAfw3BO9Ve27EdVNiQLCEwOhsuRjQvmuzqKa3IUzuVkIzudO3RYQL3qfw 

Side Note: You can parse these tokens using any JWT parser, like http://jwt.calebb.net/.

Signing in Python was a little trickier. I found the Python-RSA library, which worked for me. I had to use OpenSSL to extract the private key from my certificate into an RSA-formatted PEM file in order to use this library.

Seeing it in Action

The sample app on GitHub implements all of this logic (see clientcredhelper.py) and uses the resulting access token to call the Mail API to list messages in the Inbox. Once you login with an admin account and grant permission, you can then view the 10 most recent messages in the Inbox of any user in your organization by entering their email address in the User Email field and clicking Set User. As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT).

Ruby on Rails and Office 365 Tutorial

$
0
0

I published a quick tutorial on writing your first Office 365 API app using Ruby on Rails. The tutorial takes you from creating a new Rails project to listing the contents of the user's Inbox using the Mail API. I'd love to get feedback on the tutorial. What do you think of the format? Does it go into enough detail, or too much? For you Ruby experts out there, did I leave any crucial information out?

Let me know what you think in the comments or on Twitter (@JasonJohMSFT).

Announcing C# Support in the API Sandbox for Office 365 APIs

$
0
0

Today we bring you a guest post from Xiaoying Guo, a Senior Program Manager in our Cloud Platform Tools group. Enjoy!

We're pleased to announce that C# is now supported in the API Sandbox for Office 365 APIs! After our initial release with JavaScript in October, the team has been hard at work to deliver a C# language experience as well. I'd love to take this chance to tell you more about API Sandbox: what it is, and how it can help you to get started with the Office 365 APIs.

Figure 1: The home page of API Sandbox

Just one click to try the code with Office 365 APIs

Last October, the Office Team announced the general availability of the Office 365 APIs, including Mail, Calendar, Contacts, and Files. These robust, REST-based APIs enable all developers to integrate their custom experiences with the information in Office 365, regardless of platform or device preferences. To make your programming more productive, we provide multiple client libraries and SDKs for apps that run on different platforms using different programming languages. In order to build an app that uses the Office 365 APIs, you would need to sign up for an Office 365 tenant, register your app in Azure AD, and perhaps install the tools to build the apps. However, if you simply want to explore Office 365 APIs, no sign-up is required when you use API Sandbox. This is our goal for API Sandbox: a quick getting-started experience to try Office 365 APIs without any sign-up or environment setup. No matter whether you simply want to know what these APIs are capable of, or you want to learn more about how to use them by tinkering with code, the only thing you need to do is to open your favorite web browser using your favorite OS, then launch API Sandbox at http://apisandbox.msdn.com.

As its name implies, API Sandbox can run your code in a "sandbox" environment (i.e. a sample Office 365 tenant) we set up for you. By simply clicking the "Run" button above the code snippets, you will see right away the result printed in the "Console" panel underneath the code snippets.

Of course, if you have already have an Office 365 account, and want to try APIs against your real data, API Sandbox also enables an option for you to sign in your own Office 365 tenant for the API calls.

Figure 2: Select "My Office 365" to call the APIs using your own Office 365 tenant

Interactive learning experience for Office 365 APIs

API Sandbox provides an interactive code editing experience for you to learn Office 365 APIs. The code editor of API Sandbox supports syntax highlighting and IntelliSense in both C# and JavaScript, which makes it easy for you to discover the APIs using client libraries.

Figure 3: Get the IntelliSense for outlookClient object

No idea where to start? No problem! You can find multiple code samples in API Sandbox to start with. Those code samples cover both of the scenarios of using Office 365 client libraries and the REST APIs.

Figure 4: Expand "Code Samples" to get sample code snippets for client libraries and REST

API Sandbox is also integrated with the Office 365 API documentation to provide an interactive experience for you to learn the APIs faster. In the Office 365 REST API documentation, for all
GET requests, you can click the "Try" button in the sample request, and see the live response returned from the sample tenant.

Figure 5: Interactive Office 365 REST API reference at http://api.msdn.com

Similarly, in the client library documentation, the "Open" button under the code snippets will open the selected snippet in API Sandbox, so that you can modify and run it.

Figure 6: Interactive Office 365 client library documentation

Note that API Sandbox has implemented the authentication and the creation of Office 365 client objects, so that you can focus on learning the APIs that can get you information from Office 365 mail, calendars, contacts, and files.

Looking ahead

Currently, API Sandbox is still in Preview. In the current release of the sandbox we support GET requests for Office 365 APIs, and allow you to write code with C# and JavaScript. We are working on more features that can help you to ramp up on the new APIs, including better integration with documentation, clearer guidance for you to export the code snippets into your projects, support for more Microsoft cloud services, etc. We are looking to enhance support over time, and want to deliver the features that are important and useful for you. If you have any thoughts on what you want to see in API Sandbox, please send us your feedback through the API Sandbox. We are looking forward to hearing from you!

Figure 7: Send us feedback!

Happy learning!

Xiaoying Guo

Sr. Program Manager, Microsoft Cloud Platform Tools

 

 

Using the Calendar API in PHP

$
0
0

In the same spirit as my Python experiment, I decided to give PHP a try and see how far I could get with the Office 365 APIs. The same disclaimers apply: I am a total PHP neophyte. I'm certain that my code is probably non-optimal, but hey, it's a learning experience!

The idea

I had this idea for a simple app that could make use of the Calendar API. The app is a list of upcoming shows for a local community theater's Shakespearean festival. Visitors to their site can connect their Office 365 account and then add events directly to their calendar for the shows that they are attending.

The setup

There are a lot of options for PHP development, on multiple platforms. I expect this sample to run on any of them (please let me know if it doesn't!). In my case, I decided to install IIS 8 on my laptop and install PHP via the Web Platform Installer. If you're developing on a Windows machine, this is super easy. In just a few short minutes I was up and running. I didn't have to install anything else, the installer included all of the libraries I needed.

The execution

I started by creating a home page (home.php) that would show a table of upcoming shows. To make it a little dynamic, I created a class to generate the list based on the current date, and to randomize the location and whether or not a voucher was required (more on this later). To keep things constant throughout a session, I save that list in the $_SESSION global so I can reuse it. I ended up with a page that looks like this:

Now to make the "Connect My Calendar" button do something.

OAuth

I decided to create a static class to implement all of the Office 365-related functionality. I created Office365Service.php and created the Office365Service class. The first thing we need it to do is "connect" the user's calendar. Under the covers what that really means is having the user logon and provide consent to the app, then retrieving an access token. If you're familiar with OAuth2, then this is pretty straightforward. Essentially, we need to implement the authorization code grant flow against Azure AD.

To start that process, the "Connect My Calendar" button will send the user right to the authorization code request link. I added the getLoginUrl function to build this link based on my client ID:

 private static $authority = "https://login.windows.net";
private static $authorizeUrl = '/common/oauth2/authorize?client_id=%1$s&redirect_uri=%2$s&response_type=code';

// Builds a login URL based on the client ID and redirect URI
public static function getLoginUrl($redirectUri) {
  $loginUrl = self::$authority.sprintf(self::$authorizeUrl, ClientReg::$clientId,
    urlencode($redirectUri));
  error_log("Generated login URL: ".$loginUrl);
  return $loginUrl;
}

I created authorize.php to serve as the redirect page, which is where Azure sends the authorization code response. All this file needs to do is extract the code parameter from the request, and use that to issue a token request.

 $session_state = $_GET['session_state'];

...

// Use the code supplied by Azure to request an access token.
$tokens = Office365Service::getTokenFromAuthCode($code, $redirectUri);

So that's the next function I added to Office365Service:

 // Sends a request to the token endpoint to exchange an auth code
// for an access token.
public static function getTokenFromAuthCode($authCode, $redirectUri) {
  // Build the form data to post to the OAuth2 token endpoint
  $token_request_data = array(
    "grant_type" => "authorization_code",
    "code" => $authCode,
    "redirect_uri" => $redirectUri,
    "resource" => "https://outlook.office365.com/",
    "client_id" => ClientReg::$clientId,
    "client_secret" => ClientReg::$clientSecret
  );

  // Calling http_build_query is important to get the data
  // formatted as Azure expects.
  $token_request_body = http_build_query($token_request_data);

  $curl = curl_init(self::$authority.self::$tokenUrl);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_POSTFIELDS, $token_request_body);

  if (self::$enableFiddler) {
    // ENABLE FIDDLER TRACE
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
    // SET PROXY TO FIDDLER PROXY
    curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8888");
  }

  $response = curl_exec($curl);

  $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  if (self::isFailure($httpCode)) {
    return array('errorNumber' => $httpCode,
                 'error' => 'Token request returned HTTP error '.$httpCode);
  }

  // Check error
  $curl_errno = curl_errno($curl);
  $curl_err = curl_error($curl);
  if ($curl_errno) {
    $msg = $curl_errno.": ".$curl_err;
    return array('errorNumber' => $curl_errno,
                 'error' => $msg);
  }

  curl_close($curl);

  // The response is a JSON payload, so decode it into
  // an array.
  $json_vals = json_decode($response, true);
  return $json_vals;
}

As you can see, I used curl for issuing requests. I found it very well suited for the job. I could easily build the request body as a standard array, encode it with http_build_query, and send it. Handling the response was easy too, with the built-in JSON functionjson_decode. That puts the response into an easy to manage array.

Now, back in authorize.php, I can save the tokens into the $_SESSION global and redirect back to the home page:

 // Save the access token and refresh token to the session.
$_SESSION['accessToken'] = $tokens['access_token'];
$_SESSION['refreshToken'] = $tokens['refresh_token'];
// Parse the id token returned in the response to get the user name.
$_SESSION['userName'] = Office365Service::getUserName($tokens['id_token']);

// Redirect back to the homepage.
$homePage = "http".(($_SERVER["HTTPS"] == "on") ? "s://" : "://")
                  .$_SERVER["HTTP_HOST"]."/php-calendar/home.php";
header("Location: ".$homePage);

Notice that I also get the user's name from the ID token. This is just so I can display the logged on user's name in my app. Check the getUserName function in Office365Service.php if you're interested to see how that's done.

Calendar API

Now that the user can login, my homepage looks a little different:

Notice that the buttons now say "Add to Calendar", and the user's name appears in the top right along with a "logout" link. The logout link is very simple, it just goes to:

 https://login.windows.net/common/oauth2/logout?post_logout_redirect_uri=<URL to post-logout page>

The value you set in the post_logout_redirect_uri is where Azure will send the browser after logging the user out. In my case, I created logout.php, which removes the data I stored in the $_SESSION global and then redirects to the home page.

CalendarView

Now on to what we came to see, the Calendar API. Clicking on the "Add to Calendar" takes us to the addToCalendar.php page:

So the first use of the Calendar API is the table of events on the right-hand side. To get this data, I created the getEventsForData function in Office365Service.php:

 // Uses the Calendar API's CalendarView to get all events
// on a specific day. CalendarView handles expansion of recurring items.
public static function getEventsForDate($access_token, $date) {
  // Set the start of our view window to midnight of the specified day.
  $windowStart = $date->setTime(0,0,0);
  $windowStartUrl = self::encodeDateTime($windowStart);

  // Add one day to the window start time to get the window end.
  $windowEnd = $windowStart->add(new DateInterval("P1D"));
  $windowEndUrl = self::encodeDateTime($windowEnd);

  // Build the API request URL
  $calendarViewUrl = self::$outlookApiUrl."/Me/CalendarView?"
                    ."startDateTime=".$windowStartUrl
                    ."&endDateTime=".$windowEndUrl
                    ."&\$select=Subject,Start,End" // Limit the data returned
                    ."&\$orderby=Start"; // Sort the results by the start time.

  return self::makeApiCall($access_token, "GET", $calendarViewUrl);
}

This function uses the CalendarView API to get the list of events on a specific day. The advantage of using CalendarView is that when responding to a CalendarView request, the server handles expanding recurring meetings to figure out if a recurring meeting has an instance that falls in the specified time window. The instance will be included in the results like a normal appointment!

You may have noticed that the function ends by calling another function, makeApiCall. Even though it's a detour from the Calendar API, let's take a quick look at that function, because it shows some things that apply to all of the Office 365 REST APIs.

 // Make an API call.
public static function makeApiCall($access_token, $method, $url, $payload = NULL) {
  // Generate the list of headers to always send.
  $headers = array(
    "User-Agent: php-calendar/1.0", // Sending a User-Agent header is a best practice.
    "Authorization: Bearer ".$access_token, // Always need our auth token!
    "Accept: application/json", // Always accept JSON response.
    "client-request-id: ".self::makeGuid(), // Stamp each request with a new GUID.
    "return-client-request-id: true"// The server will send request-id in response.
  );

  $curl = curl_init($url);

  if (self::$enableFiddler) {
    // ENABLE FIDDLER TRACE
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
    // SET PROXY TO FIDDLER PROXY
    curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8888");
  }

  switch(strtoupper($method)) {
    case "GET":
      // Nothing to do, GET is the default and needs no
      // extra headers.
      break;
    case "POST":
      // Add a Content-Type header (IMPORTANT!)
      $headers[] = "Content-Type: application/json";
      curl_setopt($curl, CURLOPT_POST, true);
      curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
      break;
    case "PATCH":
      // Add a Content-Type header (IMPORTANT!)
      $headers[] = "Content-Type: application/json";
      curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PATCH");
      curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
      break;
    case "DELETE":
      curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
      break;
    default:
      error_log("INVALID METHOD: ".$method);
      exit;
  }

  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  $response = curl_exec($curl);

  $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

  if (self::isFailure($httpCode)) {
    return array('errorNumber' => $httpCode,
                 'error' => 'Request returned HTTP error '.$httpCode);
  }

  $curl_errno = curl_errno($curl);
  $curl_err = curl_error($curl);

  if ($curl_errno) {
    $msg = $curl_errno.": ".$curl_err;
    curl_close($curl);
    return array('errorNumber' => $curl_errno,
                 'error' => $msg);
  }
  else {
    curl_close($curl);
    return json_decode($response, true);
  }
}

The first thing I want to bring attention to is the set of headers sent with every request. Matthias and I have both talked about this before, but it bears repeating. While Authorization and Accept are self-explanatory, the others are related to what we refer to as client instrumentation. Doing this should be considered a "must-have" when using the REST APIs. It can be invaluable if you run into errors.

The second thing is subtle but important. For POST and PATCH requests, you MUST set the Content-Type header to application/json. If you don't, you'll get an ErrorInvalidRequest error with the message "Cannot read the request body."

Creating Events

Ok, detour over, back to the Calendar API! The second use of the API is to create the event when the user clicks on the "Add to my calendar" button on the addToCalendar.php page. Clicking that button takes the user to doAdd.php, which does the actual adding. For this, I added the addEventToCalendar function to Office365Service.php:

 // Use the Calendar API to add an event to the default calendar.
public static function addEventToCalendar($access_token, $subject, $location,
    $startTime, $endTime, $attendeeString) {
  // Create a static body.
  $htmlBody = "<html><body>Added by php-calendar app.</body></html>";

  // Generate the JSON payload
  $event = array(
    "Subject" => $subject,
    "Location" => array("DisplayName" => $location),
    "Start" => self::encodeDateTime($startTime),
    "End" => self::encodeDateTime($endTime),
    "Body" => array("ContentType" => "HTML", "Content" => $htmlBody)
  );

  if (!is_null($attendeeString) && strlen($attendeeString) > 0) {
    $attendeeAddresses = array_filter(explode(';', $attendeeString));

    $attendees = array();
    foreach($attendeeAddresses as $address) {
      error_log("Adding ".$address);

      $attendee = array(
        "EmailAddress" => array ("Address" => $address),
        "Type" => "Required"
      );

      $attendees[] = $attendee;
    }

    $event["Attendees"] = $attendees;
  }

  $eventPayload = json_encode($event);

  $createEventUrl = self::$outlookApiUrl."/Me/Events";

  $response = self::makeApiCall($access_token, "POST", $createEventUrl, $eventPayload);

  // If the call succeeded, the response should be a JSON representation of the
  // new event. Try getting the Id property and return it.
  if ($response['Id']) {
    return $response['Id'];
  }

  else {
    return $response;
  }
}

Notice how easy PHP makes it to build the JSON payloads. All I need do is create an array and use json_encode to generate the payload. Very nice! Again this uses makeApiCall to send the request. We also don't need to worry about sending meeting invites. By adding attendees, the server takes care of that for us!

Adding an attachment

Remember before I said we'd get to the "voucher required" thing later? The voucher was really an excuse to add an attachment. If you add an event that requires a voucher to your calendar, the app will add the voucher as an attachment on the event. To do this, I added the addAttachmentToEvent function:

 // Use the Calendar API to add an attachment to an event.
public static function addAttachmentToEvent($access_token, $eventId, $attachmentData) {
  // Generate the JSON payload
  $attachment = array(
    "@odata.type" => "#Microsoft.OutlookServices.FileAttachment",
    "Name" => "voucher.txt",
    "ContentBytes" => base64_encode($attachmentData)
  );

  $attachmentPayload = json_encode($attachment);
  error_log("ATTACHMENT PAYLOAD: ".$attachmentPayload);

  $createAttachmentUrl = self::$outlookApiUrl."/Me/Events/".$eventId."/Attachments";

  return self::makeApiCall($access_token, "POST", $createAttachmentUrl,
    $attachmentPayload);
}

The value of $attachmentData is the binary contents of the file to attach. In this case, it's a simple text file.

At this point you might be wondering: "Why not just include the attachment as part of the event when you created it? Wouldn't that be more efficient?" Well yes, it would, but it doesn't work! In order to add an attachment, you have to create the event first then POST to the event's Attachments collection.

The end result

If I stick with the premise of this being an experiment, then I have to conclude that PHP is a great language for calling the Office 365 REST APIs. Using just the built in libraries I was able to do everything I needed to do in a straightforward way. If PHP is your language of choice, you should have no trouble integrating with Office 365.

The sample app is available on GitHub. As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT).

Customizing the read experience with contextual mail apps for Outlook

$
0
0

It is common for messages and meeting requests to include key pieces of information users need to access additional information about.  Let’s say you receive an email from a co-worker suggesting a meeting be scheduled for 2pm tomorrow. Rather than having to remember the details, it would be convenient to click on the text suggesting the meeting and schedule it rather than switching over to the calendar.

In the past, read apps supported special activation rules which controlled when an app would appear on a message.  The downside was these contextually activated apps would appear in the app bar and a user may not even notice the app has activated. We decided to improve this experience and are excited to announce a new feature, Contextual Apps, in the Apps for Outlook platform. This means for information such as addresses and phone numbers the app can now be launched directly from the text that activated the app. 

How can I see a contextual app?

Contextual apps are already available to Office 365 users and will be coming to Outlook in the future. There are two pre-installed apps that will show you contextual apps, Suggested Meetings and Bing Maps. The Bing Maps app will activate on addresses that are surfaced in a message or a meeting request you have received.  The text that activated the app is highlighted, blue is the default color, and underlined with a dotted line.

Once the highlighted text is clicked on a card appears near the text and opens the app. Below is a screen of Bing Maps to show how contextual apps will appear when launched.

How do I write a contextual app?

If you are interested in building a contextual app the good news is it is simply a read app that utilizes ItemHasKnownEntity and/or ItemHasRegularExpressionMatch activation rules in the manifest. Contextual apps can activate on known entities such as addresses and phone numbers or on a regular expression you define. Once the activation rules are defined the app will automatically be shown on the relevant text, compose apps cannot be contextual. This means if you have an app that is currently activating using the rules mentioned above your app should automatically make use of this new feature.  If it doesn’t light up you may be hitting one of the special cases where we are unable to highlight. For more information please take a look at our documentation.


Office 365 APIs and Node.js

$
0
0

When we launched the Office 365 APIs, the Visual Studio folks released some very helpful client-side implementations of these APIs, making it much easier to get started. One of these implementations, the Microsoft Office 365 APIs Client Libraries for Cordova Applications, got a lot of attention from JavaScript developers. However, there was some confusion about the library and what it was designed to do. As the name suggests, this library was created to be used in applications built on the Cordova platform, and more specifically, for Multi-Device Hybrid App projects in Visual Studio. Developers who tried using it in a web app soon ran into issues. It just wouldn't work.

With all of the interest in using this library from a web app, when I was asked if we could modify the library to make it work in Node.js, I said "why not?"

Finding the problem

As it turns out, the big stumbling block is that the library uses the AJAX XMLHttpRequest object to send API requests. AJAX is all client-side, and not available to server-side platforms like Node.js. So it makes sense that it doesn't work in web apps.

The solution

Luckily, someone clever already provided a solution for this. The XMLHttpRequest for node.js module sounds like exactly what we need! If we can use this, we can avoid having to change the Cordova library much at all.

With that out of the way, the question left is how to load this non-Node module. As before, someone clever has already found the solution to that. With this trick in my toolbox, I decided to write a Node module to wrap the Cordova libraries (to be specific, just the Mail, Calendar, and Contacts APIs).

The node-outlook module

The code for the module was incredibly simple. All I needed to do was create an index.js file and put the following in it:

 var fs = require("fs");
var path = require("path");
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

var currentDirectory = path.dirname(fs.realpathSync(__filename));

exchangefile = fs.readFileSync(path.join(currentDirectory,
  './exchange-lib/exchange.js'), 'utf8');
eval(exchangefile);
utilityfile = fs.readFileSync(path.join(currentDirectory,
  './exchange-lib/utility.js'), 'utf8');
eval(utilityfile);

exports.Microsoft = Microsoft;

The exchange.js and utility.js files were copied straight from the Cordova library NuGet package. Pretty simple right? Well, it's even simpler now! I published node-outlook on the NPM registry, so you can save yourself the hassle and install via NPM. Or if you prefer, get the code on GitHub.

Using node-outlook

For a full sample that uses this library, check out node-mail. I'll cover the basic requirements here.

In order to use the library, you need to implement a callback function that returns an OAuth2 access token. The OutlookServices.Client class takes this callback as a parameter to its constructor. Whenever the client class makes a call to the Office 365 service, it calls this function to get the token. How you implement the callback will depend on how you're storing and managing tokens. As an example, let's look at how node-mail does it.

 function getAccessToken(resource, session) {
  console.log("getAccessToken called for " + session);
  var deferred = new outlook.Microsoft.Utility.Deferred();
  if (session.token.expired()) {
    session.token.refresh(function(error, result) {
      if (error) {
        console.log("Refresh token error: ", error.message);
      }
      session.token = result;
      console.log("NEW ACCESS TOKEN: ", session.token.token.access_token);
      deferred.resolve(session.token.token.access_token);
    });
  }
  else {
    // Return the token right away
    console.log("EXISTING ACCESS TOKEN: ", session.token.token.access_token);
    deferred.resolve(session.token.token.access_token);
  }
  return deferred;
}

function getAccessTokenFn(resource, session) {
  return function() {
    return getAccessToken(resource, session);
  }
}

The node-mail app caches tokens in the session. The getAccessToken function returns the cached token if it's not expired. Otherwise, it refreshes the token and returns the new token. The getAccessTokenFn function exists to wrap getAccessToken as a function with no parameters, which is what the OutlookServices.Client class expects.

With authorization taken care of, using the library works just like it does in a Cordova app. For example, you could get messages in the inbox with the following code:

 var outlookClient = new outlook.Microsoft.OutlookServices.Client(
  'https://outlook.office365.com/api/v1.0',
  authHelper.getAccessTokenFn('https://outlook.office365.com/', session));

outlookClient.me.messages.getMessages()
.orderBy('DateTimeReceived desc').fetchAll(10).then(function (result) {
  result.forEach(function (message) {
    console.log(message.subject);
  });
}, function (error) {
  console.log(error);
});

For more examples of using the library, see:

As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT). Feel free to report issues or submit pull requests on GitHub!

Announcing Outlook Mail Apps UserVoice page

$
0
0

 

We at Microsoft put our users’ satisfaction first. It is even more important for us in the Outlook Mail Apps team as we have two set of users, developers and consumers. To make sure that every single one of our users are able to communicate their feedback to us we are opening a UserVoice page at http://appsforoutlook.uservoice.com.  Our UserVoice page helps us to ensure every developer’s voice is heard.

In addition to our UserVoice page, we are also monitoring the “office365-apps” tag on StackOverFlow to help you out in developing your apps.

Over the past few months we have enhanced our API set more and added Contextual Mail Apps to Outlook. We have more exciting features in the pipeline and your feedback is going to help us deliver the best experience with the best timing. Please provide us your feedback and wishes.

Ruby gem for Mail, Calendar, and Contacts APIs

$
0
0

Today I published a new Ruby on Rails sample, the Office 365 VCF Import/Export Sample. It's basically a simple tool that allows a user to export contacts from her Office 365 mailbox to vCard files, or import vCard files into her contacts.

But wait, there's more! Instead of implementing the Contacts API in the sample, I created a separate Ruby gem and implemented a portion of the Mail, Calendar, and Contacts APIs. That gem is published on rubygems.org for you to install and use in your projects.

vCard Import/Export

The look and feel of the sample app owes a LOT to the great work done by Michael Hartl in his Ruby on Rails Tutorial.

The sample app basically uses the Contacts API in two ways: to create new contacts and to get existing contacts.

Getting the contacts list

When the user signs in, the app presents a list of existing contacts, sorted alphabetically by display name. The list is paged, showing 30 contacts at a time. Here's what that looks like using the ruby_outlook gem:

 # Maximum 30 results per page.
view_size = 30
# Set the page from the query parameter.
page = params[:page].nil? ? 1 : params[:page].to_i
# Only retrieve display name.
fields = [
"DisplayName"
]
# Sort by display name
sort = { :sort_field => 'DisplayName', :sort_order => 'ASC' }

# Call the ruby_outlook gem
wrapped_contacts = outlook_client.get_contacts user.access_token,
view_size, page, fields, sort

The wrapped_contacts variable is a JSON hash of the returned contacts.

Getting details on a single contact

When the user clicks the "Export" button for a contact, the app gets all fields for that single contact, using its Id. Here's how that's done with the ruby_outlook gem:

 outlook_client = RubyOutlook::Client.new

# Call the ruby_outlook gem to get the contact from its ID
contact = outlook_client.get_contact_by_id current_user.access_token, contact_id

The contact variable is a JSON hash of all the fields of the contact. That is used to build a vCard stream, which the user can download.

Creating a new contact

When the user clicks the "Import" button on the main page, they are given the option to enter vCard data manually, or to open a local vCard file for import.

When the user clicks "Import" on this page, the vCard data is transformed into a JSON hash conforming to the Contact entity defined by the Contact API. It then uploads that to the server via the ruby_outlook gem:

 outlook_client = RubyOutlook::Client.new
response = outlook_client.create_contact current_user.access_token, contact

The contact variable contains the JSON hash of the new contact, and the response variable contains the JSON hash that's returned by the server after creation.

More on the ruby_outlook gem

The gem doesn't just implement the Contacts API. It implements the following:

  • Contacts API: CRUD, including a "get by ID" function.
  • Calendar API: CRUD, including a "get by ID" function and a calendar view function.
  • Mail API: CRUD, including a "get by ID" function. Also implements a send mail function.

You may have noticed that there's a lot here that isn't implemented. For the stuff that isn't there, I created a make_api_call function that allows you to call any part of the API that you need. If you look at the source for the gem, you'll notice that all of the implemented functions (like get_contacts for example), use the make_api_call to do the actual work. Following their example, you can implement any other API call you want. Let's take a look at an example.

 # method (string): The HTTP method to use for the API call. 
# Must be 'GET', 'POST', 'PATCH', or 'DELETE'
# url (string): The URL to use for the API call. Must not contain
# the host. For example: '/api/v1.0/me/messages'
# token (string): access token
# params (hash) a Ruby hash containing any query parameters needed for the API call
# payload (hash): a JSON hash representing the API call's payload. Only used
# for POST or PATCH.
def make_api_call(method, url, token, params = nil, payload = nil)

Using the gem to create a folder

The gem currently has no functions for working with mail folders. However, you can use the make_api_all function to do the work. The details on creating a folder are documented here. Using that information, you can do the following to create a subfolder in the Inbox:

 outlook_client = RubyOutlook::Client.new

create_folder_url = '/api/v1.0/me/folders/inbox/childfolders'
new_folder_payload = { 'DisplayName' => 'New Subfolder' }

create_result = outlook_client.make_api_call('POST', create_folder_url,
    token, nil, new_folder_payload)

Using similar methods you should be able to call anything that the APIs support.

Go download the gem, the sample, or both! As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT). Feel free to report issues or submit pull requests on GitHub!

Links

Populate your Office 365 Developer Tenant with sample data

$
0
0

An Office 365 Developer Tenant is a great resource for developing with the Office 365 REST APIs. It gives you a place to register your app, and a user to test against. However, you have to manually add data to your user’s mailbox or OneDrive. Today we’re excited to announce a sample Windows Store App project that will populate data for Office 365 API services such as mail, calendar, contact and files. Not only do you automate the process of getting test data into your developer tenant, but you get some sample code for creating items!

The app works by reading new contacts, mails and calendar events from an XML file and adding them to the logged in Office 365 user's account. For files, you browse and select one or more files from your computer and the app uploads them for you. Existing data on the Office 365 tenant account will not be affected by this app.

You can use this app to quickly populate data into your Office 365 developer tenant so you can get started building Office 365 apps by interacting with the populated data. Let’s take a look at how it works.

Get the sample

Before you start, make sure that you have the needed tools. You’re going to need:

The sample is hosted on GitHub here: https://github.com/OfficeDev/O365API-PopulateSampleData. Download or clone the repository and open it up in Visual Studio. The README contains all of the details on configuring the project to run on your machine. Go ahead and follow Step 1 and Step 2.

Customize the data

Step 2 in the README introduce you to the XML files that contain the sample data for mail, calendar, and contacts. They contain a few sample entries to get you started, but you can add as many more as you like. One important detail that’s easy to miss is that for mail, the app adds mails by sending them as you. So to have them show up in your Inbox, make sure that you add your email address to the ToRecipients entries.

You’ll browse for files in the app itself, so there’s no XML file for files. For convenience you may want to generate some test files in a single directory.

Run the sample App

Press F5, to run the app. This is how the application looks:

Now you can click the buttons to add data to your user. The first one you choose will prompt you to login.

Add Files to OneDrive

When you click “Add MyFiles”, you’ll get a browse window to select files. Once you select the files you want and click Open, the app will upload them to the user’s OneDrive.

Add Mail, Events, or Contacts

Adding mail, events, or contacts is as easy as clicking the corresponding button. The app will parse the corresponding XML file and create new items for each entry. After it’s done, it will show a list of the current items of the appropriate type.

Next steps

Besides populating data, the app can also update or delete data. The README on GitHub has instructions for updating the XML files for update or delete.

We hope that you find this app useful! Please report issues on GitHub.

Check out dev.outlook.com!

$
0
0

As you can probably tell from announcements we made at BUILD this week like add-ins for Outlook coming to Outlook.com, new features in our REST APIs like web hooks and search, and new ribbon extensibility in the next version of Outlook; we are serious about investing in Outlook, Outlook.com, the Office 365 Outlook Service, and Exchange to build a powerful unified platform that reaches hundreds of millions of users using web standards like REST, OAuth, JSON, HTML, and JavaScript. 

In addition to all of these investments we wanted to make it even easier for developers to build add-ins and applications that integrate with Outlook and Exchange by creating a single site for the Outlook platform with tutorials, references, support and feedback forums, SDK’s, and helpful tools.  That site is http://dev.outlook.com– we encourage you to check it out and give us your feedback.

We are going to be iterating on the content quickly over the upcoming weeks, adding getting started guides, code samples, and useful information on the rollout of the extensibility platform to Outlook.com.  So bookmark it and check back often to see what we have added. 

http://dev.outlook.com is an Outlook centric-view on Office extensibility.  If you are interested in the overall Office platform make sure to check out http://dev.office.com where you will find information on the entire Office platform.

Making App Registration Easier

$
0
0

I know I've been a little quieter than usual recently. No new blog posts, no new samples...you'd think I was taking a break. In reality, I've been heads-down working on the https://dev.outlook.com portal that we recently launched. If you haven't checked it out, you should! There's step-by-step tutorials for the Outlook REST APIs for .NET, Node.js, and Ruby, and there's more in the works. The tutorials that require you to register your app manually even have an inline form so you can get a client ID and secret directly from the tutorial page.

That leads me to today's news. I'm happy to announce that we've expanded that app registration tool into a stand-alone page: https://dev.outlook.com/appregistration. Now you can register your apps that use the Mail, Calendar, or Contacts APIs with a simple tool. The best part is, all you need is an Office 365 account. No Azure subscription required!

A caveat: at this time, the tool only supports delegated permissions, so you cannot use this too to register apps that will use the client credential auth flow.

If you're developing or planning to develop with the Outlook APIs, please give this tool a try, especially if you've had problems with registering via the Azure management portal. If you do try the tool, please send feedback! We're still working out the feedback channel for the site, so in the meantime, leave it in the comments below, or send it to me on Twitter: @JasonJohMSFT.

Office 365 API Walkthrough for Windows Store App

$
0
0

With Office 365 API Tools, you can integrate Office 365 services - including Mail, Calendar, Contacts, Files and other services - with your applications. Office 365 API Tools take care of registering your application to consume Office 365 APIs and adding necessary NuGet libraries to your project. Before you get started, you will need an Office 365 Developer Tenant. If you don’t have an Office 365 Developer tenant yet, but are interested in the Office 365 APIs, the API Sandbox offers you a quick in-browser C#/JavaScript code editing experience to try the APIs with no sign-up needed.

Note: To use Office 365 API Tools with Visual Studio 2015 RC, you will need Microsoft Visual Studio Enterprise 2015 RC with Microsoft Office Developer Tools. Choose the Custom option when running the Visual Studio installer and check the option to install Microsoft Office Development Tools. 

If you are using Visual Studio 2013, then you will need the latest version of Microsoft Office Developer Tools for Visual Studio 2013.

In the following walkthrough, we will build a simple Windows Store app and integrate Office 365 APIs to let users sign in, view files stored on their OneDrive accounts and their contacts. Let’s start by creating a new Windows 8 store app. For this demo, I named my app O365SampleApp.

1. Configure Office 365 API

We will use Office 365 API Tools to configure the app to use Office 365 APIs. To do so, right-click on the project node in the solution explorer and select Add -> Connected Service… Select Office 365 APIs from the list of providers and click Configure.

Click on the Register your app link and enter the credentials for your Office 365 Developer tenant account. Note: This step registers your app on Azure Active Directory associated with your Office 365 tenant.

Now you will be presented with the page to set permissions for various Office 365 services. Set the following permissions and click the Add button once all the permissions are set:

  1. For the Contacts service, set the Read your contacts permission.
  2. For the My Files service, set the Read your files permission.

The above steps will add some NuGet libraries, and also enable Read your contacts and Read your files permissions for the app that was registered on Azure Active Directory. A complete list of libraries that were added can be seen in the Visual Studio output window.

2. Access Office 365 API by using .NET Client Library

To access Office 365 APIs using the .NET client library, first acquire an access token. Then get the Outlook Services client for fetching Contacts and SharePoint Client for fetching MyFiles. We can then send asynchronous queries to interact with contacts/files data.

2.1 Get Access Token

In this walkthrough, we will use Azure Active Directory Authentication Libraries (ADAL) to authenticate users and obtain an access token for securing API calls.

Add Microsoft.IdentityModel.Clients.ActiveDirectory to your project through the NuGet Package Manager.

Next, add a new class to the solution; let’s call it AuthenticationHelper.cs and replace the content of this file with:

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using Windows.Storage;
using Windows.UI.Popups;

namespace O365SampleApp
{
class AuthenticationHelper
{
public static readonly string DiscoveryServiceResourceId = "https://api.office.com/discovery/";
const string AuthorityFormat = "https://login.microsoftonline.com/{0}/";
static readonly Uri DiscoveryServiceEndpointUri = new Uri("https://api.office.com/discovery/v1.0/me/");
static readonly string ClientId = App.Current.Resources["ida:ClientID"].ToString();
static ApplicationDataContainer AppSettings = ApplicationData.Current.LocalSettings;
static string _authority = String.Empty;
static string _lastTenantId = "common";
const string _lastTenantIdKey = "LastAuthority";
static AuthenticationContext authContext = null;

public static Uri AppRedirectURI
{
get
{
return WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
}
}

public static string LastTenantId
{
get
{
if (AppSettings.Values.ContainsKey(_lastTenantIdKey) && AppSettings.Values[_lastTenantIdKey] != null)
{
return AppSettings.Values[_lastTenantIdKey].ToString();
}
else
{
return _lastTenantId;
}
}
set
{
_lastTenantId = value;
AppSettings.Values[_lastTenantIdKey] = _lastTenantId;
}
}

public static string Authority
{
get
{
_authority = String.Format(AuthorityFormat, LastTenantId);
return _authority;
}
}

public static async Task<AuthenticationResult> GetAccessToken(string serviceResourceId)
{
AuthenticationResult authResult = null;
if (authContext == null)
{
authContext = new AuthenticationContext(Authority);

#region To enable Windows Integrated Authentication (if you deploying your app in a corporate network)
//// To enable Windows Integrated Authentication, in Package.appxmanifest, in the Capabilities tab, enable:
//// * Enterprise Authentication
//// * Private Networks (Client & Server)
//// * Shared User Certificates
//// Plus add (uncomment) the following line of code:
//authContext.UseCorporateNetwork = true;
#endregion

authResult = await authContext.AcquireTokenAsync(serviceResourceId, ClientId, AppRedirectURI);
}
else
{
authResult = await authContext.AcquireTokenSilentAsync(serviceResourceId, ClientId);
}
LastTenantId = authResult.TenantId;
if (authResult.Status != AuthenticationStatus.Success)
{
LastTenantId = authResult.TenantId;
if (authResult.Error == "authentication_canceled")
{
// The user cancelled the sign-in, no need to display a message.
}
else
{
MessageDialog dialog = new MessageDialog(string.Format("If the error continues, please contact your administrator.\n\nError: {0}\n\n Error Description:\n\n{1}", authResult.Error, authResult.ErrorDescription), "Sorry, an error occurred while signing you in.");
await dialog.ShowAsync();
}
}
return authResult;
}

public static void SignOut()
{
authContext = new AuthenticationContext(Authority);
authContext.TokenCache.Clear();
authContext = null;
ApplicationData.Current.LocalSettings.Values["TenantId"] = null;
ApplicationData.Current.LocalSettings.Values["LastAuthority"] = null;
}
}
}

 

2.2 Get Outlook Services Client and SharePoint Client

An OutlookServiceClient is required to access Calendar, Contacts and Mail APIs. While, a SharePointClient is required to access Files APIs.

Add a new class to the solution; let’s call it Office365Helper.cs and replace the content of this file with:

using Microsoft.Office365.Discovery;
using Microsoft.Office365.OutlookServices;
using Microsoft.Office365.SharePoint.CoreServices;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace O365SampleApp
{
class Office365Helper
{
private static IDictionary<string, CapabilityDiscoveryResult> AppCapabilities = null;

private static async Task getAppCapabilities()
{
DiscoveryClient discoveryClient = new DiscoveryClient(
async () =>
{
var authResult = await AuthenticationHelper.GetAccessToken(AuthenticationHelper.DiscoveryServiceResourceId);
return authResult.AccessToken;
}
);
AppCapabilities = await discoveryClient.DiscoverCapabilitiesAsync();
}

public async static Task<OutlookServicesClient> CreateOutlookClientAsync(string capability)
{
if (AppCapabilities == null)
{
await getAppCapabilities();
}
var myCapability = AppCapabilities
.Where(s => s.Key == capability)
.Select(p => new { Key = p.Key, ServiceResourceId = p.Value.ServiceResourceId, ServiceEndPointUri = p.Value.ServiceEndpointUri })
.FirstOrDefault();
OutlookServicesClient outlookClient = new OutlookServicesClient(myCapability.ServiceEndPointUri,
async () =>
{
var authResult = await AuthenticationHelper.GetAccessToken(myCapability.ServiceResourceId);
return authResult.AccessToken;
});
return outlookClient;
}

public async static Task<SharePointClient> CreateSharePointClientAsync(string capability)
{
if (AppCapabilities == null)
{
await getAppCapabilities();
}
var myCapability = AppCapabilities
.Where(s => s.Key == capability)
.Select(p => new { Key = p.Key, ServiceResourceId = p.Value.ServiceResourceId, ServiceEndPointUri = p.Value.ServiceEndpointUri })
.FirstOrDefault();
SharePointClient spClient = new SharePointClient(myCapability.ServiceEndPointUri,
async () =>
{
var authResult = await AuthenticationHelper.GetAccessToken(myCapability.ServiceResourceId);
return authResult.AccessToken;
});
return spClient;
}
}
}

 

2.3 Interact with Contact and Files Data

By sending async queries to OutlookServiceClient and SharePointClient, we can interact with Contact and Files data respectively.

Add the following getContacts() and getMyFiles() methods to Office365Helper class in Office365Helper.cs file.

public static async Task<List<IContact>> getContacts(OutlookServicesClient contactsClient)
{
List<IContact> myContacts = new List<IContact>();
var contactsResult = await contactsClient.Me.Contacts.OrderBy(c => c.DisplayName).ExecuteAsync();
do
{
var contacts = contactsResult.CurrentPage;
foreach (var contact in contacts)
{
myContacts.Add(contact);
}
contactsResult = await contactsResult.GetNextPageAsync();
} while (contactsResult != null);
return myContacts;
}

public static async Task<List<Microsoft.Office365.SharePoint.FileServices.IItem>> getMyFiles(SharePointClient spClient)
{
List<Microsoft.Office365.SharePoint.FileServices.IItem> myFiles = new List<Microsoft.Office365.SharePoint.FileServices.IItem>();
var myFileResult = await spClient.Files.ExecuteAsync();
do
{
var files = myFileResult.CurrentPage;
foreach (var file in files)
{
myFiles.Add(file);
}
myFileResult = await myFileResult.GetNextPageAsync();
} while (myFileResult != null);
return myFiles;
}

 

3. Update MainPage.xaml to Include GetContacts and GetMyFiles Buttons

Now we will add 3 buttons to our MainPage to trigger the getContacts, getMyFiles and SignOut calls. To do so, open MainPage.xaml from solution explorer and add the following 3 buttons:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button x:Name="BtnGetContacts" Content="Get Contacts" HorizontalAlignment="Left" Margin="58,110,0,0" VerticalAlignment="Top" RenderTransformOrigin="-1.737,-2.446" Click="BtnGetContacts_Click"/>
<Button x:Name="BtnGetMyFiles" Content="Get My Files" HorizontalAlignment="Left" Margin="58,167,0,0" VerticalAlignment="Top" RenderTransformOrigin="-1.737,-2.446" Click="BtnGetMyFiles_Click"/>
<Button x:Name="BtnSignOut" Content="Sign Out" HorizontalAlignment="Left" Margin="58,225,0,0" VerticalAlignment="Top" RenderTransformOrigin="-1.737,-2.446" Click="BtnSignOut_Click"/>
</Grid>

Let’s now create button click event handlers. Open MainPage.xaml.cs file and add these methods within the MainPage class.

private async void BtnGetContacts_Click(object sender, RoutedEventArgs e)
{
var contactsClient = await Office365Helper.CreateOutlookClientAsync("Contacts");
var contacts = await Office365Helper.getContacts(contactsClient);

string contactsString = "";
foreach (var contact in contacts)
{
contactsString += contact.DisplayName + Environment.NewLine;
}
MessageDialog mg = new MessageDialog(contactsString, "Contacts");
await mg.ShowAsync();
}

private async void BtnGetMyFiles_Click(object sender, RoutedEventArgs e)
{
var spClient = await Office365Helper.CreateSharePointClientAsync("MyFiles");
var myFiles = await Office365Helper.getMyFiles(spClient);

string myFilesString = "";
foreach (var myFile in myFiles)
{
myFilesString += myFile.Type + " - " + myFile.Name + Environment.NewLine;
}
MessageDialog mg = new MessageDialog(myFilesString, "My Files");
await mg.ShowAsync();
}

private void BtnSignOut_Click(object sender, RoutedEventArgs e)
{
AuthenticationHelper.SignOut();
}

 

Note: Add using Windows.UI.Popups; to MainPage.xaml.cs so that we can show Contacts and Files results on a MessageDialog.

Our app is now ready to fetch a list of Contacts and MyFiles. You can now press F5 and click on “Get Contacts” button to see a list of your contacts or “Get My Files” button to see the files stored on OneDrive.

Next Steps

Apart from just interacting with Contacts and Files APIs, you can also interact with Mail, Calendar and other services. A complete list can be found on Office 365 API reference. For any service that you want your app to interact with, remember to set the right permissions when using the Office 365 APIs connected service wizard to configure Office 365 APIs. You can also explore the Office 365 APIs using the API Sandbox. Various sample projects for almost every project type can be found on Office Developer GitHub


Announcing Exchange ActiveSync v16

$
0
0

One advantage of having your mailbox in Office 365 is that we usually deploy our innovations there first. We’re making enhancements to Exchange ActiveSync (EAS) in Office 365 that will soon be available to Exchange ActiveSync applications. Update: Exchange ActiveSync v16 will also be included in on-premise installations of Exchange Server 2016.

Updated EAS, which we will call version 16, contains three new major capabilities:

  1. The major focus is enhanced calendar reliability brought about through a reworking of the calendar workflow between server and client. This is a major benefit but ideally will be largely unnoticed by end-users. Success here will be the absence of those hard-to-diagnose calendar related problems sometimes seen when the server and client are not produced by the same company.
  2. Calendar attachments. Previously calendar items synced with EAS could not include any attachments such as agendas, decks or spreadsheets. In version 16 that is now possible.
  3. Syncing the drafts folder was previously not supported. We’ve added that support in version 16. Now you can start an email on your EAS device and continue editing that draft back at your desktop by retrieving the mail from the drafts folder. Or, write your draft at your computer, then make the final tweaks and send it from your phone while you're on the go.

Customers with their mailbox in Office 365 can take advantage of these new features when their favorite EAS client is updated. Update: We are happy to share that iOS 9, announced June 9 at Apple’s Worldwide Developer Conference, will support the calendar features of version 16 when it is released this fall.

As an end user, how can you tell if your mailbox is enabled for EAS version 16? The simplest way is to run an EAS Autodiscover test at https://testconnectivity.microsoft.com and look for version 16 listed as one of the supported protocol versions in the test results. Note that you need a valid Office 365 email address and password in order to run that test, and the results are specific to that mailbox only (not the entire tenant).

For partners who are building Exchange ActiveSync applications, we will have updated protocol documentation posted on MSDN by the end of the month. Partners holding a current EAS patent license and an active developer support agreement can contact their Technical Account Manager (TAM) now for more information prior to the documentation being posted.

Note to developers: Exchange ActiveSync is different from the Office 365 Outlook REST APIs. Exchange ActiveSync is highly optimized for mobile clients connecting directly to Office 365/Exchange Online, and requires a licensing fee to use. The Outlook REST APIs are standards-based REST+JSON+OAuth Mail, Calendar, and Contact APIs that have no licensing fee, and are great for both mobile device-to-Office 365/Exchange Online, and service-to-Office 365/Exchange Online applications. We recommend that developers explore and evaluate the Outlook REST APIs for their applications.

Unified Outlook APIs for Office 365 & Outlook.com are here!

$
0
0

We are excited to announce Outlook REST APIs for mail, calendar, and contacts now support both Office 365 & Outlook.com! This single REST API endpoint https://outlook.office.com/api combined with a new app registration portal and v2.0 app model announced by the Azure Active Directory team, makes it very easy to build a single app that can be used by both Office 365 and Outlook.com users.

As previously announced, we are upgrading Outlook.com users to a new Office 365-based infrastructure. As users are upgraded, apps can use Outlook REST APIs to access these users’ mailboxes. This new platform will enable developers to reach 400 million monthly active Outlook.com users, in addition to tens of millions of Office 365 users. If your app utilizes both REST APIs and our add-in platform, you are all set as we also support Outlook Add-ins for Office 365 and Outlook.com users.

You will find more information in an overview of the Unified Outlook Service API and getting started instructions for multiple platforms at https://dev.outlook.com. Please note that the new OAuth endpoint is in preview and you can check here for details on what is and isn’t supported during Preview.

Please go ahead and try our REST APIs out and let us know your feedback by posting to UserVoice. The new API endpoint and app registration portal are a direct result of the feedback from our developer community. We look forward to seeing many awesome apps being built using the unified Outlook API endpoint to delight Office 365 & Outlook.com users!

Introducing add-in commands

$
0
0

Today we bring you the first in a two-part series of guest blog posts from Andrew Salamatov, a Senior Program Manager with our Outlook team. You can find the second part here. Enjoy!

Back at Build and Ignite, we announced a brand new feature, that we’re very excited about – Outlook add-ins (formerly called apps for Outlook) can now place buttons on the ribbon in Outlook! This simultaneously simplifies interacting with add-ins, as well as makes their presence more visible and engaging.

A new message window in Outlook showing multiple add-in buttons on the ribbon.

In a two-part series, we wanted to share our thinking behind why we invested in commands and how they work, as well as walk through a sample add-in with a command. This is the first post in the series.

What are add-in commands?

The idea behind commands came from your feedback! Users are demanding a fast, simple way to get to the actions they need most. Meanwhile, developers have been trying to make their add-ins stand out and feel native. As we thought about the various scenarios that were being built on the Outlook add-in platform, we arrived at three guiding principles.

First, add-ins must look and feel native to each Outlook client. Mobile Outlook is very different from desktop Outlook and Outlook.com. Yet users flow from one to the other throughout the day and expect the power of 3rd party add-ins to support them, wherever they are. On top of this, the clients differ in their UI and user interaction patterns, due to form factor limitations and use cases. The same UI entry point and experience will not work across all these clients. Instead, our platform must enable developers to integrate in a way that meets user expectation on each form factor.

Second, the platform must scale to support many add-ins. Whether a user wants to use 1 add-in or multiple, the entry points must scale. And this must be completely abstracted away from add-ins, so developers don’t have to focus on handling interoperability with other add-ins.

Third, the platform must be write once, run everywhere. It would be very expensive for developers to implement an add-in for desktop Outlook, then re-write it for Outlook.com and mobile. Add-ins would take years to develop and update with new functionality. Instead, the platform should provide developers with generic concepts and APIs, which may result in different UI or positioning across the different environments, but will behave the same way across all Outlook clients.

Applying these principles to scenarios where the user has to take an action on a message or appointment, it became obvious that the best experience would be to let add-ins place commands for users where users expect them. In desktop Outlook, this is of course the ribbon.

Command types

In this first release of commands, we support 3 different types:

A command which executes a function

A compose message window in Outlook with an add-in command button that executes a function, and a success message in the infobar.

This type of command is best suited for tasks which don’t require extra input beyond clicking a button. For example, "save to CRM", "create a new issue in service issue database from this email", "insert tracking pixel", "archive message", etc. It is important to communicate to the user whether or not this action was successful. This can be done via an infobar message, which is described in more detail in part 2 of the blog post.

A command which launches a task pane

The Yelp add-in in Outlook, which is an example of a command that opens a task pane.

 

The Evernote add-in, which is an example of a command that opens a task pane.

The task pane command is best used when the user needs to enter extra input or interact with UI. The task pane gives your app a place inside Outlook to display your own HTML/JavaScript-based UI.

A command which is a dropdown and shows a list of commands of either of the other two types

The Quick Insert command from the EmojiO sample, which is an example of a command that opens a drop-down menu.

The dropdown command is best suited for when a user needs to make a choice from a set of options. For example, "remind me if no one replies to this email in 1, 2, 3, … days", "assign classification", "archive for X days or months", "quick-order drinks, appetizers or heavy lunch for meeting". This command has an advantage over the task pane, in that the options appear natively and instantly to the user, and the user doesn’t need to wait for the task pane to load, or even move the mouse away. This should be used over a task pane anywhere, where the choice is simple and no additional input is necessary.

Now that you’ve had a taste for commands, stay tuned for our next post, which dives into how to build them!

-Andrew

Building add-ins with commands

$
0
0

Today we bring you the second in a two-part series of guest blog posts from Andrew Salamatov, a Senior Program Manager with our Outlook team. You can find the first part here. Enjoy!

Because of their advantages, we’re encouraging all developers building add-ins to use commands. In fact, soon we’ll start requiring add-ins submitted to the Office Store to use them for scenarios where it makes sense. In this post, I wanted to share the easiest way to get started building an add-in with commands.

To begin, we’ll need an Outlook 2016 client connected to an Office 365 account, a developer preview Outlook.com account, or an on-premises Exchange email account. As we announced back in April, this feature is new to Outlook 2016, which is shipping very soon (and other Outlook clients like mobile and web to follow soon!). For now, you can get your hands on an Outlook 2016 Preview client here. As for the email account, the easiest thing to do is to either use your existing Office 365 account or create one here.

Once you’ve installed the client, let’s get started building the add-in. In this blog post, I’ll show how to build an add-in with a simple button in a message compose form, which will translate selected text into Russian. The user flow will be very simple – user types in some text, clicks the button, and the text is replaced with its translation. As you know, add-ins consist of an XML manifest and html/javascript code. Let’s look at the manifest first.

The manifest

A quick aside on the manifest...

To support add-in commands, we had to introduce some changes to the existing manifest. To declare commands, developers use the existing 1.1 manifest, but must add a new section at the end called VersionOverrides. We introduced this section for backwards compatibility. Older clients, which do not support add-in commands will ignore this and activate the add-in according to the definition above this element. Newer clients will parse and use this section to run the add-in, ignoring activation rules above VersionOverrides. Because we still have users on older bits, your add-in must work the older way as well.

The concept behind VersionOverrides is that elements defined inside it override ones defined above. You can refer to this list for all the elements that can be overridden.

You’ll notice that we’ve changed the Host element to now contain extension points. Extension points are a new concept – the set of all extension points in a form factor define all the different ways that the add-in will integrate in that form factor. In the future, we will add new extension points as well as support for more form factors.

Ok, back to the translation add-in. The key part of the manifest is the Host element:

<Hosts>
  <Host xsi:type="MailHost">

    <DesktopFormFactor>
      <FunctionFile resid="functionFile" />

      <ExtensionPoint xsi:type="MessageComposeCommandSurface">
        <OfficeTab id="TabDefault">
          <Group id="translateGroup">
            <Label resid="groupLabel" />
            <Tooltip resid="groupTooltip" />

            <Control xsi:type="Button" id="translateButton">
              <Label resid="translateButtonLabel" />
              <Tooltip resid="translateButtonTooltip" />
              <Supertip>
                <Title resid="translateSuperTipTitle" />
                <Description resid="translateSuperTipDescription" />
              </Supertip>
              <Icon>
                <bt:Image size="16" resid="icon1_16x16" />
                <bt:Image size="32" resid="icon1_32x32" />
                <bt:Image size="80" resid="icon1_80x80" />
              </Icon>
              <Action xsi:type="ExecuteFunction">
                <FunctionName>translate</FunctionName>
              </Action>
            </Control>
          </Group>
        </OfficeTab>
      </ExtensionPoint>

    </DesktopFormFactor>
  </Host>
</Hosts>

You’ll notice here that we’ve declared an extension point MessageComposeCommandSurface, and in it we have defined controls. This generic name creates an abstraction, so developers have a single way to declare a button on a primary command surface and have it show up across Outlook desktop, Outlook.com and Outlook Web App (support for the latter two coming very soon). Take a look here to see which other extension points are supported currently.

The MessageComposeCommandSurface is made up of OfficeTab elements. These correspond to ribbon tabs in Outlook. Developers can declare one group on the home tab and/or one custom tab with up to ten groups (and six buttons per group). For the translate add-in, we’ll utilize the default Outlook tab (if you wish to create a custom tab, use the CustomTab element instead).

Next, take a look at the Control element. It’s a button, whose action is to execute a function. Most of the elements in it should be self-explanatory, but take a look here for complete documentation. What’s interesting about this button is its action. Rather than showing a task pane or a dropdown, Outlook will execute the specified function in a browser instance. Of course, that JavaScript function will have access to the entire Office.js API as well as all other JavaScript APIs. So, for example, if it needs to launch a pop-up browser window, it can do so using window.open(). This function must be defined in the HTML file specified by the FunctionFile element at the top of VersionOverrides (see full manifest in our Git repo linked at the bottom).

Finally, you’ll notice several resid attributes. We’ve made a slight change how strings are specified in the manifest with the VersionOverrides update. Since for some buttons, developers will want to reuse the same string, which might have many locale overrides, all strings and URLs are now defined once in separate section, and referenced via a resource id, or resid. This section is at the end of the manifest:

<Resources>
  <bt:Images>
    <bt:Image id="icon1_16x16" DefaultValue="https://ssl.microsofttranslator.com/static/222083/img/trans.png"/>
    <bt:Image id="icon1_32x32" DefaultValue="https://ssl.microsofttranslator.com/static/222083/img/trans.png"/>
    <bt:Image id="icon1_80x80" DefaultValue="https://ssl.microsofttranslator.com/static/222083/img/trans.png"/>
  </bt:Images>
  <bt:Urls>
    <bt:Url id="functionFile" DefaultValue="~remoteAppUrl/AppCompose/FunctionFile/Home.html"/>
  </bt:Urls>
  <bt:ShortStrings>
    <bt:String id="groupLabel" DefaultValue="Translator"/>
    <bt:String id="translateButtonLabel" DefaultValue="Translate"/>
    <bt:String id="translateSuperTipTitle" DefaultValue="Click this to translate text from English to Russian."/>
  </bt:ShortStrings>
  <bt:LongStrings>
    <bt:String id="groupTooltip" DefaultValue="Translate actions"/>
    <bt:String id="translateButtonTooltip" DefaultValue="Translates text from English to Russian."/>
    <bt:String id="translateSuperTipDescription" DefaultValue="Translates text from English to Russian."/>
  </bt:LongStrings>
</Resources>

The business logic

Now that we’ve written the manifest, we just need to hook up the logic to the button. Above, we defined the function which should be invoked when the button is clicked, and the file where the function is specified. As you’ll see in the complete sample solution, I broke out the logic into a separate JavaScript file, which is referenced by the HTML file. The main reason for this is to demonstrate that you can still have entirely separate js files, if you like. In the JavaScript file, you can see Office.initialize and the function translate. Office.initialize gets invoked as usual first. You can do any authentication that you need here. Once that returns, the specified function in the manifest will be invoked. In our case, that’s translate.

Important aside: The type of button we defined doesn’t show any UI natively, once pressed. This means that the user won’t know by default whether the click was successful, still processing or resulted in an error. The good news is that developers can use notification message APIs to convey progress and ultimately success or failure.

There are three types of notification messages:

  • Progress indicator
  • Informational message
  • Error

Developers should always show a success or error message, as otherwise users may not know if the operation fully succeeded, or just partially.

Informational messages can be permanent, meaning every time the user clicks on this message/appointment the message will be there (unless the user dismisses it), or temporary, meaning that it will be shown only once to the user. An example of a permanent message would be "This email is now tracked in CRM." While a temporary example would be "Translation succeeded". The latter is not important for the user to see if he or she ever comes back to this message in the future.

Error messages are always displayed to the user just once, and developers don’t control this.

When it comes to progress indicators, it’s up to the developer whether or not to show one. If the function will almost always execute very quickly and return a result, showing a progress indicator for a few milliseconds will result in a "flash" to the user, which they weren’t able to see anyway. So in these cases it doesn’t make sense to show a progress indicator. On the other hand, if the button will make a call to a web service, which is known to take a bit, then a progress indicator should definitely be shown.

Note that if for some reason it takes us more than 500 ms to launch the function (maybe it’s taking us a while to load the HTML/JS), we’ll display a default progress indicator anyway. If the developer then invokes a method to add a new notification message of any type, the current indicator will just be replaced.

There are APIs to replace messages, so that the UI is as smooth as possible. For all the details, go here.

Back to the translate function, which is where all of our logic happens (you’ll notice an event parameter passed – see documentation here and here). In this case, the service we’re using is pretty fast, so we won’t be adding a progress indicator. Instead, we’ll do the translation, and then set a success message (if we did show a progress indicator, we would call replace, instead of add here). The last step is to call event.completed(). This is critical, because it lets Outlook know that your function is done. Otherwise, there will be awkward UI that the user will see. The entire method looks like this:

 function translate(event) {
  Office.context.mailbox.item.getSelectedDataAsync("text", function (ar) {
    var requestUrl = generateRequestUrl(ar.value.data);

    $.ajax({
      url: requestUrl,
      jsonp: "callback",
      dataType: "jsonp",
      success: function (response) {
        var translatedText = response.text;
        var textToWrite = "";

        for (var i = 0; i < translatedText.length; i++)
          textToWrite += translatedText[i] + "<br/>";

        Office.context.mailbox.item.setSelectedDataAsync(textToWrite, { coercionType: "html" }, function (asyncResult) {
          Office.context.mailbox.item.notificationMessages.addAsync("success", {
            type: "informationalMessage",
            icon: "icon1_16x16",
            message: "Translated successfully",
            persistent: false
          });

          event.completed();
        });
      }
    });
  });
}

And that’s it! Now we can publish our code to a web server, install the manifest into a mailbox and try out the add-in. As always, check out Outlook Dev Center for more info. You can get help by posting a question on Stack Overflow, or on our MSDN forum.

You can find the full source code to the project on GitHub.

Enjoy!

-Andrew

Deprecating the REST API preview endpoint

$
0
0

As many of you know, the Outlook REST APIs moved from preview to general availability in October 2014. As part of this transition, we are shutting down the old preview endpoint https://outlook.office365.com/ews/odata on October 15th, 2015. You can continue to use the https://outlook.office.com/api/beta endpoint to check out our latest and greatest APIs. We require all apps & services using the https://outlook.office365.com/ews/odata endpoint to move to https://outlook.office.com/api/v1.0 endpoint by October 15th, 2015. This will also enable the apps to benefit from API enhancements being added continuously to https://outlook.office.com/api/beta and https://outlook.office.com/api/v1.0.

Where do I go to find more about it?

If you want to know more about what features are supported by the https://outlook.office.com/api/ endpoint, go to https://dev.outlook.com/, where you will find getting started materials and API references. Please check https://dev.outlook.com/ for any future updates. You can also post your questions on Stack Overflow with outlook-restapi tag.

Message Body property will filter unsafe HTML by default

$
0
0

As part of the Office 365 APIs, the Mail API lets you read, create, and send messages and manage folders in a user's mailbox in Office 365 or Exchange Online.

We are making a change in what is returned by default in the MessageBody property.

By default, we will strip any potentially unsafe HTML content from the Body of the Message or Post entity if the ContentType is HTML.

Here is an example of potentially unsafe HTML in the message body and below it you can see the filtered body.

Unfiltered HTML body

 "Body": { 
  "ContentType": "HTML",
  "Content": "<html><body><b>Bold</b><script>alert('Alert!');</script></body></html>"
}

Filtered HTML body

 "Body": {
  "ContentType": "HTML",
  "Content": "<html><body><b>Bold</b></body></html>"
}

If you require the un-filtered content, you can continue to get it by providing the following HTTP request header.

 Prefer: outlook.allow-unsafe-html

By default, if the Prefer header is not present, the API will return filtered HTML. The API will only return the unfiltered (and potentially unsafe) HTML if the header is present and set to outlook.allow-unsafe-html.

This change is being rolled out in our production service and will be widely deployed over the next few weeks.

If you have any questions please reach out to us on Stack Overflow using the outlook-restapi tag.

Outlook REST API changes to beta endpoint

$
0
0

We are making a few changes to the Office 365 REST APIs beta endpoint. The changes are being rolled out in pre-prod and production systems and will be widely deployed over the next few weeks.

User ID changed

The User entity's Id property will start returning a new format: {AAD User Id}@{AAD Tenant id} instead of the user's SMTP address.

 {
  ...
  @odata.id: "https://outlook.office365.com/api/beta/Users('9607a528-7230-43d2-bb04-b2e576eafba5@2dc87bd3-88ff-4b99-b7ba-618c973e93ea')",
  Id: "9607a528-7230-43d2-bb04-b2e576eafba5@2dc87bd3-88ff-4b99-b7ba-618c973e93ea"
  EmailAddress: "rohitag@contoso.com",
  DisplayName: "Rohit Nagarmal",
  Alias: "rohitag",
  ...
}

Note that we are also adding the EmailAddress property to User that contains the SMTP address that used to be in Id.

The request to find a user will continue to allow specifying an SMTP in addition to the new id format. So both of these requests are valid:

 GET https://outlook.office365.com/api/beta/users/rohitag@contoso.com

GET https://outlook.office365.com/api/beta/users/9607a528-7230-43d2-bb04-b2e576eafba5@2dc87bd3-88ff-4b99-b7ba-618c973e93ea

With this change, we have made the API more consistent with our Groups API where the ID always returned in the {AAD User Id}@{AAD Tenant id} format.

IsContactPhoto property removed

We have removed the IsContactPhoto property from the FileAttachment entity.

Hopefully this was something that you were not using anyways, but if you had your clients deserialize the FileAttachment object and were reading this property, you will have to update the code to remove references to it.

Name changes

We are making changes to entity names and property names to avoid naming conflict and confusion when we expose the Outlook entities in the Office 365 Unified API.

  • Folder entity renamed to MailFolder
  • Folders property on User entity renamed to MailFolders
  • DatetimeLastModfied renamed to LastModifiedDateTime
  • DateTimeCreated renamed to CreatedDateTime
  • DateTimeReceived renamed to ReceivedDateTime
  • DateTimeSent renamed to SentDateTime
  • DateTimeLastDelivered renamed to LastDeliveredDateTime

Please stay tuned, we are also making updates related to timezones and reminders in calendar events. We will have a separate post on these changes very soon.

Thanks,
Rohit


Outlook REST API changes to beta endpoint - Part II

$
0
0

I wanted to update you all on some more breaking changes that we are making to the Outlook REST API beta endpoint. These changes will be widely deployed over the next few weeks.

Reminders for events

The property Reminder will be removed from the Event entity, and two new properties ReminderMinutesBeforeStart and IsReminderOn will be added to the Event entity.

Example:

 {
  "ReminderMinutesBeforeStart": "15",
  "IsReminderOn": "true"
}

A new function ReminderView will also be added. This will return a list of reminders within the start and end times specified.

Example:

 .../api/Beta/me/ReminderView(startDateTime='2015-10-08T16:56:00',endDateTime='2015-10-12T00:56:00') 

In addition to this, two new actions Snooze and Dismiss will be enabled. These actions can be used to snooze a reminder or dismiss a reminder after it has been fired.

Example:

Snooze:

 .../api/Beta/me/Events('Id')/SnoozeReminder 

{
  "NewReminderTime": {
    "DateTime":"2015-10-13T08:30:00",
    "TimeZone":"Pacific Standard Time"
  }
}

Dismiss:

 .../api/Beta/me/Events('ID')/DismissReminder 

Timezone

Based on feedback we received on the existing timezone implementation, Start and End properties for Event entities will now be a complex type of:

 {
  DateTime
  Timezone
}

Timezone is mandatory for all create requests. It is also mandatory for update requests where start and end times are changed.

Example:

 "Start": {
  DateTime: "2015-09-25T16:00:00",
  TimeZone: "UTC"
}

Properties StartTimezone and EndTimezone will be removed from the Event entity and properties OriginalStartTimezone and OriginalEndTimezone will be added to the Event entity. OriginalStartTimezone and OriginalEndTimezone are meant to reflect what timezone was set when the event was created or updated.

In addition to this, a new preference header outlook.timezone will be supported for all GET requests. This header can be used to get the events in a timezone that is different from the one it was created in.

Example:

 Prefer: outlook.timezone = "Pacific Standard Time" 

Since a new type DateTime is being introduced, all "times" that are in any event related API will support the new DateTime format.

Examples:

Sort

 .../api/beta/me/Events?$orderby=Start/DateTime 

Filter

 //With outlook.timezone header as PST, time will be taken as PST 
.../api/beta/me/Events?$filter=Start/DateTime eq '2015-09-25T09:00:00'

//With no outlook.timezone header, time will be assumed as UTC
.../api/beta/me/Events?$filter=Start/DateTime eq '2015-09-25T16:00:00'

CalendarView/Instances

 .../api/beta/me/CalendarView?startDateTime=2015-09-25T16:00:00&endDateTime=2015-09-25T17:00:20
.../api/beta/me/CalendarView?startDateTime=2015-09-25T16:00:00-08:00&endDateTime=2015-09-25T17:00:20-08:00

Photos

We are renaming the UserPhoto property on User, GroupPhoto property on Group and ContactPhoto property on Contact to just Photo.

More extensive documentation on the new functionality is coming soon in our API reference documentation page. Please let us know if you have any questions, and visit http://dev.outlook.com for the latest news and updates.

Thanks,

Shreedevi

Outlook REST API changes to beta endpoint - Part III

$
0
0

Today we bring you a guest post from Abdelkader Bahgat, a Senior Program Manager with the Outlook team. Enjoy!

I wanted to update you all on some more breaking changes that we are making to the Outlook REST API beta endpoint. These changes impact Notifications and Webhooks. They are going to be deployed worldwide in phases over the next few weeks.

Since this deployment is going in phases, you may see up to two breaking changes for some of the APIs. In this blog, I’ll provide the final state of every change as well as its corresponding interim state (if applicable) during the phased rollout.

There are two type of changes; schema changes and functional changes. Refer to the notification reference documentation for the current schema and functional behavior. Below are the details of each one.

Schema changes

Schema changes apply to both subscription requests and notification payloads as follows. Also they apply to both https://outlook.office.com/api/beta and https://outlook.office365.com/api/beta endpoints.

Subscription request

The following subscription properties were updated as part of the breaking change. The table below shows the current property name, interim name and final name.

CurrentInterimFinal
ResourceURLresourceResource
CallbackURLnotificationURLNotificationURL
ClientStatecontextClientState
ExpirationTimesubscriptionExpirationDateTimeSubscriptionExpirationDateTime
ChangeTypechangeTypeChangeType

Subscription sample

Final version (changes highlighted)

 {
  @odata.type:"#Microsoft.OutlookServices.PushSubscription",
  Resource: "https://outlook.office.com/api/beta/me/folders('Inbox')/messages",
  NotificationURL: "https://webhook.azurewebsites.net/api/send/rest_notify",
  ChangeType: "Created, Updated, Deleted",
  SubscriptionExpirationDateTime: "2015-10-24T18:40:00.0Z",
  ClientState: "Client information"
}

Interim version (changes highlighted)

 {   @odata.type:"#Microsoft.OutlookServices.PushSubscription",
  resource: "https://outlook.office.com/api/beta/me/folders('Inbox')/messages",
  notificationURL: "https://webhook.azurewebsites.net/api/send/rest_notify",
  changeType: "Created, Updated, Deleted",
  subscriptionExpirationDateTime: "2015-10-24T18:40:00.0Z",
  context: "Client information"
}

Notification payload

The following property was added to the notification body.

InterimFinal
resourceResource

The following notification properties were updated as part of the breaking change. The table below shows the current property name, interim name and final name.

Header

Current nameInterim nameFinal name
x-ClientStatex-contextClientState

Body

Current nameInterim nameFinal name
EntityresourceDataResourceData
SubscriptionExpirationTimesubscriptionExpirationDateTimeSubscriptionExpirationDateTime
SubscriptionId*subscriptionIdSubscriptionId
SequenceNumber*sequenceNumberSequenceNumber
ChangeType*changeTypeChangeType

* indicates lowerCamelCase type change in the interim version

Notification sample

Final version (changes highlighted)

ClientState: Client information
OData-Version: 4.0

{
  "value": [
    {
      "@odata.type": "#Microsoft.OutlookServices.Notification",
      "Id": null,
      "SubscriptionId": "Y3BENzI2MEMtODNDOS00OTgwLUI4MzYtRkU0RkJFQ0QwNDA0XzI0MjlFMDM3LTIyODAtNDI5QS05RTI5LTQ2ODJEMjUxNDFENg==",
      "SubscriptionExpirationDateTime": "2015-10-24T18:40:00Z",
      "SequenceNumber": 1,
      "ChangeType": "Created",
      "Resource": "https://outlook.office.com/api/beta/Users('user@contoso.com')/Messages('AQMkADI0MjllMDM3LTIyADgwLTQyOWEtOWUyOS00NjgyZDI1MTQxZDYARgAAA8rN0_5DrtJIjeiZuFmXzhwHAMS1HuBHgjpCsMTk7B3DDOIAAAIBDAAAAMS1HuBHgjpCsMTk7B3DDOIAAAIFeQAAAA==')",
      "ResourceData": {
        "@odata.type": "#Microsoft.OutlookServices.Message",
        "@odata.id": "https://outlook.office.com/api/beta/Users('user@contoso.com')/Messages('AQMkADI0MjllMDM3LTIyADgwLTQyOWEtOWUyOS00NjgyZDI1MTQxZDYARgAAA8rN0_5DrtJIjeiZuFmXzhwHAMS1HuBHgjpCsMTk7B3DDOIAAAIBDAAAAMS1HuBHgjpCsMTk7B3DDOIAAAIFeQAAAA==')",
        "@odata.etag": "W/\"CQAAABYAAADEtR7gR4I6QrDE5OwdwwziAAAAAAT1\"",
        "Id": "AQMkADI0MjllMDM3LTIyADgwLTQyOWEtOWUyOS00NjgyZDI1MTQxZDYARgAAA8rN0_5DrtJIjeiZuFmXzhwHAMS1HuBHgjpCsMTk7B3DDOIAAAIBDAAAAMS1HuBHgjpCsMTk7B3DDOIAAAIFeQAAAA=="
      }
    }
  ]
}

Interim version (changes highlighted)

x-context: Client information
OData-Version: 4.0

{
  "value": [
    {
      "@odata.type": "#Microsoft.OutlookServices.Notification",
      "Id": null,
      "subscriptionId": "Y3BENzI2MEMtODNDOS00OTgwLUI4MzYtRkU0RkJFQ0QwNDA0XzI0MjlFMDM3LTIyODAtNDI5QS05RTI5LTQ2ODJEMjUxNDFENg==",
      "subscriptionExpirationDateTime": "2015-10-24T18:40:00Z",
      "sequenceNumber": 1,
      "changeType": "Created",
      "resource": "https://outlook.office.com/api/beta/Users('user@contoso.com')/Messages('AQMkADI0MjllMDM3LTIyADgwLTQyOWEtOWUyOS00NjgyZDI1MTQxZDYARgAAA8rN0_5DrtJIjeiZuFmXzhwHAMS1HuBHgjpCsMTk7B3DDOIAAAIBDAAAAMS1HuBHgjpCsMTk7B3DDOIAAAIFeQAAAA==')",
      "resourceData": {
        "@odata.type": "#Microsoft.OutlookServices.Message",
        "@odata.id": "https://outlook.office.com/api/beta/Users('user@contoso.com')/Messages('AQMkADI0MjllMDM3LTIyADgwLTQyOWEtOWUyOS00NjgyZDI1MTQxZDYARgAAA8rN0_5DrtJIjeiZuFmXzhwHAMS1HuBHgjpCsMTk7B3DDOIAAAIBDAAAAMS1HuBHgjpCsMTk7B3DDOIAAAIFeQAAAA==')",
        "@odata.etag": "W/\"CQAAABYAAADEtR7gR4I6QrDE5OwdwwziAAAAAAT1\"",
        "Id": "AQMkADI0MjllMDM3LTIyADgwLTQyOWEtOWUyOS00NjgyZDI1MTQxZDYARgAAA8rN0_5DrtJIjeiZuFmXzhwHAMS1HuBHgjpCsMTk7B3DDOIAAAIBDAAAAMS1HuBHgjpCsMTk7B3DDOIAAAIFeQAAAA=="
      }
    }
  ]
}

Functional changes

There are four breaking functional changes in Beta. These changes are:

  1. Changes in Renew subscription API
  2. Addition of Notification URL validation
  3. Retiring Acknowledgement notification
  4. No ClientState in Subscription query response

Not all changes apply to the interim. Here is the functional change applicability matrix (final versus interim):

Functional changeInterimFinal
Changes in Renew subscription APIYesYes
Addition of Notification URL validationNoYes
Retiring Acknowledgement notificationNoYes
No ClientState in Subscription query responseYesYes

Changes in Renew subscription API

The renew subscription mechanism changed from POST to PATCH with payload like the following.

Final version

 {
  @odata.type:"#Microsoft.OutlookServices.PushSubscription",
  SubscriptionExpirationDateTime: "2015-10-24T20:00:00.0Z"
}

Interim version

 {
    @odata.type:"#Microsoft.OutlookServices.PushSubscription",
    subscriptionExpirationDateTime: "2015-10-24T20:00:00.0Z"
}

Addition of Notification URL validation

Outlook REST APIs added validation for notification (callback) URL as part of creating a new subscription. Validation occurs as follows:

  1. Outlook service post the following to webhook service:

     POST https://<notificationUrl>?validationtoken=<TokenDefinedByService>

    ClientState: <Data sent in ClientState value in subscription request (if any)>
  2. Webhooks service must provide a 200 response with the validationtoken value in its body as type plain/text within 5 seconds. The validation token is a random string that should be discarded by the webhook after providing it in the response.

Retiring Acknowledgement notification

The notification URL validation process is replacing the acknowledgement notification.

No ClientState in Subscription query response

The ClientState (or context in interim state) property is not going to be sent back when a client queries for a specific subscription.

More extensive documentation on the new functionality is coming soon in our API reference documentation page. Please let us know if you have any questions, and visit http://dev.outlook.com for the latest news and updates.

Outlook REST API changes to beta endpoint - Part IV

$
0
0

I wanted to give an update on some more breaking changes that we are making to the Outlook REST API beta endpoint. These changes will be widely deployed over the next few weeks.

Scope of collections

We are making it consistent across the Outlook REST APIs to return all items of a given type from its top level collections. For example ../me/messages already returns all the messages of the user across all his mail folders, not just Inbox.

Similarly, we are making a change to the ../contacts, ../contactfolders and ../mailfolders endpoint.

  1. /Contacts endpoint will start returning all the contacts in the signed-in user’s mailbox. Prior to this change you would only see contacts from the default contacts folder of the user.
  2. /ContactFolders will return all ContactFolders in the signed-in user’s mailbox, irrespective of how deeply they are nested.
  3. /MailFolders will also start returning all MailFolders in the signed-in user’s mailbox. Prior to this change /MailFolders would return folders only from the root folder of the user.

Change to recurrence range

In the RecurrenceRange complex type (which is used in recurring meetings), we are changing the type for the StartDate and EndDate properties from DateTime to Date. There is also a new property in the Recurrence complex type, RecurrenceTimeZone, which reflects the timezone for StartDate and EndDate properties.

Old Recurrence

Recurrence: { 
Pattern: {
Type: "Daily",
Interval: 1,
Month: 0,
DayOfMonth:0,
FirstDayOfWeek: "Sunday",
Index: "First"
},
Range: {
Type: "EndDate",
StartDate: "2015-11-09T00:00:00Z",
EndDate: "2015-12-09T00:00:00Z",
NumberOfOccurrences: 0
}
}

New Recurrence

Recurrence: { 
Pattern: {
Type: "Daily",
Interval: 1,
Month: 0,
DayOfMonth:0,
FirstDayOfWeek: "Sunday",
Index: "First"
},
RecurrenceTimeZone: “Pacific Standard Time”,
Range: {
Type: "EndDate",
StartDate: "2015-11-09",
EndDate: "2015-12-09",
NumberOfOccurrences: 0
}
}

More extensive documentation on the new functionality is coming soon in our API reference documentation page. Please let us know if you have any questions, and visit http://dev.outlook.com for the latest news and updates.





Latest Images