Skip to content

canvizsherm/EDUGraphAPI

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EDUGraphAPI - Office 365 Education Code Sample#

What is EDUGraphAPI?##

EDUGraphAPI is a sample that demonstrates:

EDUGraphAPI is based on ASP.NET MVC. ASP.NET Identity is used in this project.

How To: Configure your Development Environment

Download and install the following tools to run, build and/or develop this application locally.

GitHub Authorization

  1. Generate Token

    • Open https://github.com/settings/tokens in your web browser.
    • Sign into your GitHub account where you forked this repository.
    • Click Generate Token
    • Enter a value in the Token description text box
    • Select all the check boxes

    • Click Generate token
    • Copy the token
  2. Add the GitHub Token to Azure in the Azure Resource Explorer

    • Click PUT

Create a key to use the Bing Maps

  1. Open https://www.bingmapsportal.com/ in your web browser and sign in.

  2. Click My account -> My keys.

  3. Create a Basic key, select Public website as the application type.

  4. Copy the Key and save it.

    Note: The key is used in a subsequent step.

Create an Application in you AAD

  1. Sign into the traditional azure portal: https://manage.windowsazure.com.

  2. Open the AAD where you plan to create the application.

  3. Click ADD on the bottom bar.

  4. Click Add an application my organization is developing.

  5. Input a Name, and select WEB APPLICATION AND/OR WEB API.

  6. Click âž”.

  7. Enter the following values:

    Note: A domain from your tenant must be used here, since this is a multi-tenant application.

  8. Click the âś“.

  9. Click CONFIGURE.

  10. Enable APPLICATION IS MULTI-TENANT.

  1. Configure the following permissions to other applications.
Application Permissions Delegated Permissions
Windows Azure Active Directory Read and write directory data Sign in and read user profile
Read and write directory data
Microsoft Graph None Read all groups
Read directory data
Access directory as the signed in user
Sign user in
  1. In the keys section, click the dropdown list and select a duration, then click Save.

  1. Copy aside the Client ID and the key value.

Deploy the Azure Components

  1. Check to ensure that the build is passing VSTS Build

  2. Fork this repository to your GitHub account

  3. Click the Deploy to Azure Button

    Deploy to Azure

  4. Fill in the values in the deployment page and select the I agree to the terms and conditions stated above checkbox.

    • Resource group: we suggest you to Create a new group.

    • Site Name: please input a name. Like EDUGraphAPICanviz or EDUGraphAPI993.

      Note: If the name you input is taken, you will get some validation errors:

      Click it you will get more details, like storage account is already in other resource group/subscription.

      In this case, please use another name.

    • Sql Administrator Login Password: please DO use a strong password.

    • Source Code Repository URL: replace with the repository name of your fork.

    • Source Code Manual Integration: choose false, since you are deploying from your own fork.

    • Client Id: use the Client Id of the AAD Application your created earlier.

    • Client Secret: use the Key value of the AAD Application your created earlier.

    • Bing Map Key: use the key of Bing Map you got earlier.

    • Check I agree to the terms and conditions stated above.

  5. Click Purchase.

Add REPLY URL to the AAD Application

  1. After the deployment, open the resource group:

  2. Click the web app.

    Copy the URL aside and change the schema to https. This is the replay URL and will be used in next step.

  3. Navigate to the AAD application in the traditional azure portal, then click the Configure tab.

    Add the reply URL:

  4. Click SAVE.

Documentation

Introduction

EDUGraphAPI.Web

This web project is based on an ASP.NET MVC application with the Individual User Accounts selected.

The following files were created by the MVC template, and we only change them a little bit.

  1. /App_Start/Startup.Auth.Identity.cs (The original name is Startup.Auth.cs)
  2. /Controllers/AccountController.cs

This sample project uses ASP.NET Identity and Owin. The two technologies make different kinds of authentication coexist easily. Please get familiar them first.

Below are the important class files used in this web project:

File Description
/App_Start/Startup.Auth.AAD.cs Integrates with Azure Active Directory authentication
/Controllers/AdminController.cs Contains the administrative actions
/Controllers/LinkController.cs Contains the actions to link user accounts
/Controllers/SchoolsController.cs Contains the actions to show school data

EDUGraphAPI.SyncData

This is the WebJob used to sync user data.

EDUGraphAPI.Common

The class library project is used both the EDUGraphAPI.Web and EDUGraphAPI.Common.

The table below shows the folders in the project:

Folder Description
/Data Contains ApplicationDbContext and entity classes
/DataSync Contains the UserSyncSerextensionsvice class which is used by the EDUGraphAPI.SyncData WebJob
/DifferentialQuery Contains the DifferentialQueryService class which is used to send differential query and parse the result.
/Extensions Contains lots of extension methods which simplify coding the make code easy to read
/Utils Contains the wide used class AuthenticationHelper.cs

Microsoft.Education

This project encapsulates the Schools REST API. The core class in this project is EducationServiceClient.

Multi-tenant App

This sample is a multi-tenant App. In the AAD, we enabled the option:

Users from any Azure Active Directory tenant can access this app. As this app uses some application permissions, admin of the tenant should sign up (consent) first. Otherwise, users would be an error:

For more information, see Build a multi-tenant SaaS web application using Azure AD & OpenID Connect.

Data Access and Data Models

ASP.NET Identity uses Entity Framework Code First to implement all of its persistence mechanism. Package Microsoft.AspNet.Identity.EntityFramework is created for this.

In this sample ApplicationDbContext is created for access data from a SQL Database. It inherited from IdentityDbContext which is defined in the NuGet package mentioned above.

Below are the important Data Models (and their important properties) that used in this sample:

ApplicationUsers

Inherited from IdentityUser.

Property Description
Organization The tenant of the user. For local unlinked user, its value is null
O365UserId Used to link with an Office 365 account
O365Email The Email of the linked Office 365 account
JobTitle Used for demonstrating differential query
Department Used for demonstrating differential query
Mobile Used for demonstrating differential query
FavoriteColor Used for demonstrating local data

Organizations

A row in this table represents a tenant in AAD.

Property Description
TenantId Guid of the tenant
Name Name of the tenant
IsAdminConsented Is the tenant consented by any admin

Authentication flows

There are 4 authentication flows in this project.

The first 2 flows (Local/O365 Login) enable users to login in with either a local account or an Office 365 account, then link to the other account. They are implemented in the LinkController.

Local Login Authentication Flow

O365 Login Authentication Flow

Admin Login Authentication Flow

The flow is implemented in AdminController.

Application Authentication Flow

This flow in implemented in the SyncData WebJob.

An X509 certification is used. For more details, please check the following links:

Two Kinds of Graph API

There are two kinds of Graph API:

Azure AD Graph API Microsoft Graph API
Description The Azure Active Directory Graph API provides programmatic access to Azure Active Directory through REST API endpoints. Apps can use the Azure AD Graph API to perform create, read, update, and delete (CRUD) operations on directory data and directory objects, such as users, groups, and organizational contacts A unified API that also includes APIs from other Microsoft services like Outlook, OneDrive, OneNote, Planner, and Office Graph, all accessed through a single endpoint with a single access token.
Client Install-Package Microsoft.Azure.ActiveDirectory.GraphClient Install-Package Microsoft.Graph
End Point https://graph.windows.net https://graph.microsoft.com
API Explorer https://graphexplorer.cloudapp.net/ https://graph.microsoft.io/graph-explorer

In this sample, we use the hierarchy below to demonstrate how to use them.

The IGraphClient interface defines two method: GeCurrentUserAsync and GetTenantAsync.

AADGraphClient and MSGraphClient implement the IGraphClient interface with Azure AD Graph and Microsoft Graph client libraries separately.

The interface and the two classes resides in /Services/GraphClients folder of the web app. Some of code is list below to show how to get user and tenant with the two kinds of Graph APIs.

Azure AD Graph - AADGraphClient.cs

public async Task<UserInfo> GetCurrentUserAsync()
{
    var me = await activeDirectoryClient.Me.ExecuteAsync();
    return new UserInfo
    {
        Id = me.ObjectId,
        GivenName = me.GivenName,
        Surname = me.Surname,
        UserPrincipalName = me.UserPrincipalName,
        Roles = await GetRolesAsync(me)
    };
}
public async Task<TenantInfo> GetTenantAsync(string tenantId)
{
    var tenant = await activeDirectoryClient.TenantDetails
        .Where(i => i.ObjectId == tenantId)
        .ExecuteSingleAsync();
    return new TenantInfo
    {
        Id = tenant.ObjectId,
        Name = tenant.DisplayName
    };
}

Microsoft Graph - MSGraphClient.cs

public async Task<UserInfo> GetCurrentUserAsync()
{
    var me = await graphServiceClient.Me.Request()
        .Select("id,givenName,surname,userPrincipalName,assignedLicenses")
        .GetAsync();
    return new UserInfo
    {
        Id = me.Id,
        GivenName = me.GivenName,
        Surname = me.Surname,
        UserPrincipalName = me.UserPrincipalName,
        Roles = await GetRolesAsync(me)
    };
}
public async Task<TenantInfo> GetTenantAsync(string tenantId)
{
    var tenant = await graphServiceClient.Organization[tenantId].Request().GetAsync();
    return new TenantInfo
    {
        Id = tenant.Id,
        Name = tenant.DisplayName
    };
}

In AAD Application, you should configure permissions for them separately:

Office 365 Education API

Office 365 Education APIs help extract data from your Office 365 tenant which has been synced to the cloud by Microsoft School Data Sync. These results provide information about schools, sections, teachers, students and rosters. The Schools REST API provides access to school entities in Office 365 for Education tenants.

In the sample, the Microsoft.Education Class Library project was created to encapsulate Office 365 Education API.

EducationServiceClient is the core class of the library. With it we can get education data easily.

Get schools

// https://msdn.microsoft.com/office/office365/api/school-rest-operations#get-all-schools
public async Task<School[]> GetSchoolsAsync()
{
    return await HttpGetArrayAsync<School>("administrativeUnits?api-version=beta");
}
// https://msdn.microsoft.com/office/office365/api/school-rest-operations#get-a-school
public Task<School> GetSchoolAsync(string objectId)
{
    return HttpGetObjectAsync<School>($"administrativeUnits/{objectId}?api-version=beta");
}

Get sections

// https://msdn.microsoft.com/office/office365/api/school-rest-operations#get-sections-within-a-school
public Task<Section[]> GetAllSectionsAsync(string schoolId)
{
    var relativeUrl = $"/groups?api-version=beta&$expand=members&$filter=extension_fe2174665583431c953114ff7268b7b3_Education_ObjectType%20eq%20'Section'%20and%20extension_fe2174665583431c953114ff7268b7b3_Education_SyncSource_SchoolId%20eq%20'{schoolId}'";
    return HttpGetArrayAsync<Section>(relativeUrl);
}
public async Task<Section[]> GetMySectionsAsync(string schoolId)
{
    var me = await HttpGetObjectAsync<SectionUser>("/me?api-version=1.5");
    var sections = await GetAllSectionsAsync(schoolId);
    return sections
        .Where(i => i.Members.Any(j => j.Email == me.Email))
        .ToArray();
}
// https://msdn.microsoft.com/office/office365/api/section-rest-operations#get-a-section
public async Task<Section> GetSectionAsync(string sectionId)
{
    return await HttpGetObjectAsync<Section>($"groups/{sectionId}?api-version=beta&$expand=members");
}

Below are some screenshots of the sample app that show the education data.

Differential query

A differential query request returns all changes made to specified entities during the time between two consecutive requests. For example, if you make a differential query request an hour after the previous differential query request, only the changes made during that hour will be returned. This functionality is especially useful when synchronizing tenant directory data with an application’s data store.

The related code is in the following two folders of the EDUGraphAPI.Common project:

  • /DifferentialQuery: contains classes to send differential query and parse differential result.
  • /DataSync: contains classes that are used to demonstrate how to sync users.

Notice: that classes in DifferentialQuery folder uses some advanced .NET technologies. Please ingore the implementation and just focus on how to use them.

To sync users, we defined the User class:

public class User
{
    public string ObjectId { get; set; }
    public virtual string JobTitle { get; set; }
    public virtual string Department { get; set; }
    public virtual string Mobile { get; set; }
}

Notice that the changeable properties JobTitle, Department, Mobile are virtual. Classes in DifferentialQuery folder will create a proxy type for the User type and override these virtual properties for change tracking.

In UserSyncService class, we demonstrate how to use the DifferentialQueryService to send differential query and get differential result.

var differentialQueryService = new DifferentialQueryService(/**/);
DeltaResult<Delta<User>> result = await differentialQueryService.QueryAsync<User>(url);

And how to update (or delete) users in local database with the delta result:

foreach (var differentialUser in result.Items)
    await UpdateUserAsync(differentialUser);
//...
private async Task UpdateUserAsync(Delta<User> differentialUser) { /**/ }

DataSyncRecord data model is used to persistent deltaLinks.

Below is the logs generated by the SyncData WebJob:

Filters

In the /Infrastructure folder of the web project. There are several FilterAttributes.

EduAuthorizeAttribute

This is an authorization filter, inherited from AuthorizeAttribute.

It was created because that the web app is configured with multi-authentications and it could not redirect to the correct login page when unauthorized.

We overrided the HandleUnauthorizedRequest method to redirect the user to /Account/Login:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    filterContext.Result = new RedirectResult("/Account/Login");
}

HandleAdalExceptionAttribute

The AuthenticationHelper class exposes lots of methods of return access token or instance of a service client. Most of these methods invoke AuthenticationContext.AcquireTokenSilentAsync internally. In most time, AcquireTokenSilentAsyn could get the access token successfully, as tokens are cached in the database by ADALTokenCache.

In some situations, like the cached token is expired or a new resource token is requested. AcquireTokenSilentAsyn will thrown AdalException. HandleAdalExceptionAttribute was create to handle AdalException, and navigate the user to the authentication endpoint to get a new token.

In some case, we will redirect the user directly to the authentication endpoint by invoking:

filterContext.HttpContext.GetOwinContext().Authentication.Challenge(
   new AuthenticationProperties { RedirectUri = requestUrl },
   OpenIdConnectAuthenticationDefaults.AuthenticationType);

And in other cases, we want to show the user the page blow to tell the user the reason why he got redirected especially for a user who logged in with an local account.

We use a switch to control this. The switch value is retrieved by:

//public static readonly string ChallengeImmediatelyTempDataKey = "ChallengeImmediately";
var challengeImmediately = filterContext.Controller.TempData[ChallengeImmediatelyTempDataKey];

If the value is true, we will redirect the user to the authentication endpoint immediately. Otherwise, the page above will be shown first, and user click the Login button to proceed.

LinkedOrO365UsersOnlyAttribute

The is another authorization filter. With it we can only allow linked users or Office 365 user to visit the protected controllers/actions.

protected override bool AuthorizeCore(HttpContextBase httpContext)
{
    var applicationService = DependencyResolver.Current.GetService<ApplicationService>();
    var user = applicationService.GetUserContext();
    return user.AreAccountsLinked || user.IsO365Account;
}

For unauthorized user, we will show them the NoAccess page:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    filterContext.Result = new ViewResult { ViewName = "NoAccess" };
}

So far, It is only used on the SchoolsController.

Contributors

Roles Author(s)
Project Lead / Architect / Documentation Todd Baginski (Microsoft MVP, Canviz Consulting) @tbag
PM John Trivedi (Canviz Consulting)
Dev Leader Tyler Lu (Canviz Consulting) @TylerLu
Developer Benny Zhang (Canviz Consulting)
Testing Ring Li (Canviz Consulting)
Testing Melody She (Canviz Consulting)
UX Design Justin So (Canviz Consulting)
Sponsor / Support
Sponsor / Support
Sponsor / Support
Sponsor / Support

Version history

Version Date Comments
1.0 Nov 26, 2016 Initial release

Disclaimer

THIS CODE IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C# 49.1%
  • JavaScript 48.7%
  • CSS 2.2%