├── .classpath ├── .gitattributes ├── .gitignore ├── .project ├── .settings ├── .jsdtscope ├── org.eclipse.jdt.core.prefs ├── org.eclipse.m2e.core.prefs ├── org.eclipse.wst.common.component ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.wst.jsdt.ui.superType.name └── org.eclipse.wst.validation.prefs ├── .springBeans ├── README.md ├── pom.xml ├── readme-images ├── add-context-namespace.PNG ├── app-registration.PNG ├── auth-code.PNG ├── hello-world.PNG ├── inbox-list.PNG ├── installed-jre.PNG ├── java-build-path.PNG ├── jetty-default.PNG ├── new-maven-project.PNG ├── new-password.PNG └── project-facets.PNG └── src └── main ├── java └── com │ └── outlook │ └── dev │ ├── auth │ ├── AuthHelper.java │ ├── IdToken.java │ ├── TokenResponse.java │ └── TokenService.java │ ├── controller │ ├── AuthorizeController.java │ ├── ContactsController.java │ ├── EventsController.java │ ├── IndexController.java │ └── MailController.java │ └── service │ ├── Contact.java │ ├── DateTimeTimeZone.java │ ├── EmailAddress.java │ ├── Event.java │ ├── Message.java │ ├── OutlookService.java │ ├── OutlookServiceBuilder.java │ ├── OutlookUser.java │ ├── PagedResult.java │ └── Recipient.java ├── resources └── auth.properties └── webapp ├── WEB-INF ├── defs │ └── pages.xml ├── dispatcher-servlet.xml ├── jsp │ ├── contacts.jsp │ ├── events.jsp │ ├── index.jsp │ └── mail.jsp ├── layout │ └── base.jsp └── web.xml └── index.html /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | # Java/Maven/STS 50 | target -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | java-tutorial 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.wst.common.project.facet.core.builder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.springframework.ide.eclipse.core.springbuilder 25 | 26 | 27 | 28 | 29 | org.eclipse.m2e.core.maven2Builder 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.ide.eclipse.core.springnature 36 | org.eclipse.jem.workbench.JavaEMFNature 37 | org.eclipse.wst.common.modulecore.ModuleCoreNature 38 | org.eclipse.jdt.core.javanature 39 | org.eclipse.m2e.core.maven2Nature 40 | org.eclipse.wst.common.project.facet.core.nature 41 | org.eclipse.wst.jsdt.core.jsNature 42 | 43 | 44 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.compliance=1.8 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 7 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 8 | org.eclipse.jdt.core.compiler.source=1.8 9 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.validation.prefs: -------------------------------------------------------------------------------- 1 | disabled=06target 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /.springBeans: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | src/main/webapp/WEB-INF/dispatcher-servlet.xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with the Microsoft Graph Outlook Mail API and Java 2 | 3 | The sample code in this repository is the end result of going through the [Java tutorial on the Outlook Dev Center](https://docs.microsoft.com/en-us/outlook/rest/java-tutorial). If you go through that tutorial yourself, you should end up with code very similar to this. If you download or fork this repository, you'll need to follow the steps in [Configure the sample](#configure-the-sample) to run it. 4 | 5 | > **NOTE:** Looking for the version of this tutorial that used the Outlook API directly instead of Microsoft Graph? Check out the `outlook-api` branch. Note that Microsoft recommends using the Microsoft Graph to access mail, calendar, and contacts. You should use the Outlook APIs directly (via https://outlook.office.com/api) only if you require a feature that is not available on the Graph endpoints. 6 | 7 | ## Prerequisites 8 | 9 | - Java Development Kit 8. 10 | - Spring Tool Suite 11 | 12 | ## Register the app 13 | 14 | Head over to https://apps.dev.microsoft.com to quickly get a application ID and password. Click the **Sign in** link and sign in with either your Microsoft account (Outlook.com), or your work or school account (Office 365). 15 | 16 | Once you're signed in, click the **Add an app** button. Enter `java-tutorial` for the name and click **Create application**. After the app is created, locate the **Application Secrets** section, and click the **Generate New Password** button. Copy the password now and save it to a safe place. Once you've copied the password, click **Ok**. 17 | 18 | ![The new password dialog.](./readme-images/new-password.PNG) 19 | 20 | Locate the **Platforms** section, and click **Add Platform**. Choose **Web**, then enter `http://localhost:8080/authorize.html` under **Redirect URIs**. 21 | 22 | > **NOTE:** The values in **Redirect URIs** are case-sensitive, so be sure to match the case! 23 | 24 | Click **Save** to complete the registration. Copy the **Application Id** and save it along with the password you copied earlier. We'll need those values soon. 25 | 26 | Here's what the details of your app registration should look like when you are done. 27 | 28 | ![The completed registration properties.](./readme-images/app-registration.PNG) 29 | 30 | ## Configure the sample 31 | 32 | 1. Open the `./src/main/resources/auth.properties` file. 33 | 1. Replace `YOUR_APP_ID_HERE` with the **Application Id** from the registration you just created. 34 | 1. Replace `YOUR_APP_PASSWORD_HERE` with the password you copied earlier. 35 | 1. If you use an IDE, Right-click the project and choose **Run as**, then **Maven build**. 36 | 1. From a console, run **mvn package** and **mvn jetty:run** to start up a server 37 | 1. Access `http://localhost:8080` 38 | 39 | ## Copyright ## 40 | 41 | Copyright (c) Microsoft. All rights reserved. 42 | 43 | ---------- 44 | Connect with me on Twitter [@JasonJohMSFT](https://twitter.com/JasonJohMSFT) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.outlook.dev 5 | java-tutorial 6 | 0.0.1-SNAPSHOT 7 | war 8 | 9 | 10 | 11 | org.eclipse.jetty 12 | jetty-maven-plugin 13 | 9.3.8.v20160314 14 | 15 | 16 | org.apache.maven.plugins 17 | maven-compiler-plugin 18 | 3.5.1 19 | 20 | 1.8 21 | 1.8 22 | 23 | 24 | 25 | 26 | 27 | 28 | 4.2.5.RELEASE 29 | 3.0.5 30 | 2.7.4 31 | 2.0.2 32 | 33 | 34 | 35 | 36 | 37 | org.springframework 38 | spring-framework-bom 39 | ${org.springframework.version} 40 | pom 41 | import 42 | 43 | 44 | 45 | 46 | 47 | 48 | javax.servlet 49 | javax.servlet-api 50 | 4.0.0-b01 51 | provided 52 | 53 | 54 | 55 | javax.servlet.jsp 56 | javax.servlet.jsp-api 57 | 2.3.2-b02 58 | provided 59 | 60 | 61 | 62 | jstl 63 | jstl 64 | 1.2 65 | 66 | 67 | 68 | org.springframework 69 | spring-core 70 | 71 | 72 | 73 | org.springframework 74 | spring-expression 75 | 76 | 77 | 78 | org.springframework 79 | spring-beans 80 | 81 | 82 | 83 | org.springframework 84 | spring-aop 85 | 86 | 87 | 88 | org.springframework 89 | spring-context 90 | 91 | 92 | 93 | org.springframework 94 | spring-context-support 95 | 96 | 97 | 98 | org.springframework 99 | spring-tx 100 | 101 | 102 | 103 | org.springframework 104 | spring-jdbc 105 | 106 | 107 | 108 | org.springframework 109 | spring-orm 110 | 111 | 112 | 113 | org.springframework 114 | spring-oxm 115 | 116 | 117 | 118 | org.springframework 119 | spring-web 120 | 121 | 122 | 123 | org.springframework 124 | spring-webmvc 125 | 126 | 127 | 128 | org.springframework 129 | spring-webmvc-portlet 130 | 131 | 132 | 133 | org.apache.tiles 134 | tiles-core 135 | ${apache.tiles.version} 136 | 137 | 138 | 139 | org.apache.tiles 140 | tiles-jsp 141 | ${apache.tiles.version} 142 | 143 | 144 | 145 | org.slf4j 146 | slf4j-log4j12 147 | 1.7.21 148 | 149 | 150 | 151 | com.fasterxml.jackson.core 152 | jackson-core 153 | ${jackson.version} 154 | 155 | 156 | 157 | com.fasterxml.jackson.core 158 | jackson-annotations 159 | ${jackson.version} 160 | 161 | 162 | 163 | com.fasterxml.jackson.core 164 | jackson-databind 165 | ${jackson.version} 166 | 167 | 168 | 169 | com.squareup.retrofit2 170 | retrofit 171 | ${retrofit.version} 172 | 173 | 174 | 175 | com.squareup.retrofit2 176 | converter-jackson 177 | ${retrofit.version} 178 | 179 | 180 | 181 | com.squareup.okhttp3 182 | logging-interceptor 183 | 3.2.0 184 | 185 | 186 | -------------------------------------------------------------------------------- /readme-images/add-context-namespace.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/add-context-namespace.PNG -------------------------------------------------------------------------------- /readme-images/app-registration.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/app-registration.PNG -------------------------------------------------------------------------------- /readme-images/auth-code.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/auth-code.PNG -------------------------------------------------------------------------------- /readme-images/hello-world.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/hello-world.PNG -------------------------------------------------------------------------------- /readme-images/inbox-list.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/inbox-list.PNG -------------------------------------------------------------------------------- /readme-images/installed-jre.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/installed-jre.PNG -------------------------------------------------------------------------------- /readme-images/java-build-path.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/java-build-path.PNG -------------------------------------------------------------------------------- /readme-images/jetty-default.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/jetty-default.PNG -------------------------------------------------------------------------------- /readme-images/new-maven-project.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/new-maven-project.PNG -------------------------------------------------------------------------------- /readme-images/new-password.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/new-password.PNG -------------------------------------------------------------------------------- /readme-images/project-facets.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/readme-images/project-facets.PNG -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/auth/AuthHelper.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.auth; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.Calendar; 7 | import java.util.Properties; 8 | import java.util.UUID; 9 | 10 | import org.springframework.web.util.UriComponentsBuilder; 11 | 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.logging.HttpLoggingInterceptor; 14 | import retrofit2.Retrofit; 15 | import retrofit2.converter.jackson.JacksonConverterFactory; 16 | 17 | public class AuthHelper { 18 | private static final String authority = "https://login.microsoftonline.com"; 19 | private static final String authorizeUrl = authority + "/common/oauth2/v2.0/authorize"; 20 | 21 | private static String[] scopes = { 22 | "openid", 23 | "offline_access", 24 | "profile", 25 | "User.Read", 26 | "Mail.Read", 27 | "Calendars.Read", 28 | "Contacts.Read" 29 | }; 30 | 31 | private static String appId = null; 32 | private static String appPassword = null; 33 | private static String redirectUrl = null; 34 | 35 | private static String getAppId() { 36 | if (appId == null) { 37 | try { 38 | loadConfig(); 39 | } catch (Exception e) { 40 | return null; 41 | } 42 | } 43 | return appId; 44 | } 45 | private static String getAppPassword() { 46 | if (appPassword == null) { 47 | try { 48 | loadConfig(); 49 | } catch (Exception e) { 50 | return null; 51 | } 52 | } 53 | return appPassword; 54 | } 55 | 56 | private static String getRedirectUrl() { 57 | if (redirectUrl == null) { 58 | try { 59 | loadConfig(); 60 | } catch (Exception e) { 61 | return null; 62 | } 63 | } 64 | return redirectUrl; 65 | } 66 | 67 | private static String getScopes() { 68 | StringBuilder sb = new StringBuilder(); 69 | for (String scope: scopes) { 70 | sb.append(scope + " "); 71 | } 72 | return sb.toString().trim(); 73 | } 74 | 75 | private static void loadConfig() throws IOException { 76 | String authConfigFile = "auth.properties"; 77 | InputStream authConfigStream = AuthHelper.class.getClassLoader().getResourceAsStream(authConfigFile); 78 | 79 | if (authConfigStream != null) { 80 | Properties authProps = new Properties(); 81 | try { 82 | authProps.load(authConfigStream); 83 | appId = authProps.getProperty("appId"); 84 | appPassword = authProps.getProperty("appPassword"); 85 | redirectUrl = authProps.getProperty("redirectUrl"); 86 | } finally { 87 | authConfigStream.close(); 88 | } 89 | } 90 | else { 91 | throw new FileNotFoundException("Property file '" + authConfigFile + "' not found in the classpath."); 92 | } 93 | } 94 | 95 | public static String getLoginUrl(UUID state, UUID nonce) { 96 | 97 | UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(authorizeUrl); 98 | urlBuilder.queryParam("client_id", getAppId()); 99 | urlBuilder.queryParam("redirect_uri", getRedirectUrl()); 100 | urlBuilder.queryParam("response_type", "code id_token"); 101 | urlBuilder.queryParam("scope", getScopes()); 102 | urlBuilder.queryParam("state", state); 103 | urlBuilder.queryParam("nonce", nonce); 104 | urlBuilder.queryParam("response_mode", "form_post"); 105 | 106 | return urlBuilder.toUriString(); 107 | } 108 | 109 | public static TokenResponse getTokenFromAuthCode(String authCode, String tenantId) { 110 | // Create a logging interceptor to log request and responses 111 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 112 | interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 113 | 114 | OkHttpClient client = new OkHttpClient.Builder() 115 | .addInterceptor(interceptor).build(); 116 | 117 | // Create and configure the Retrofit object 118 | Retrofit retrofit = new Retrofit.Builder() 119 | .baseUrl(authority) 120 | .client(client) 121 | .addConverterFactory(JacksonConverterFactory.create()) 122 | .build(); 123 | 124 | // Generate the token service 125 | TokenService tokenService = retrofit.create(TokenService.class); 126 | 127 | try { 128 | return tokenService.getAccessTokenFromAuthCode(tenantId, getAppId(), getAppPassword(), 129 | "authorization_code", authCode, getRedirectUrl()).execute().body(); 130 | } catch (IOException e) { 131 | TokenResponse error = new TokenResponse(); 132 | error.setError("IOException"); 133 | error.setErrorDescription(e.getMessage()); 134 | return error; 135 | } 136 | } 137 | 138 | public static TokenResponse ensureTokens(TokenResponse tokens, String tenantId) { 139 | // Are tokens still valid? 140 | Calendar now = Calendar.getInstance(); 141 | if (now.getTime().after(tokens.getExpirationTime())) { 142 | // Still valid, return them as-is 143 | return tokens; 144 | } 145 | else { 146 | // Expired, refresh the tokens 147 | // Create a logging interceptor to log request and responses 148 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 149 | interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 150 | 151 | OkHttpClient client = new OkHttpClient.Builder() 152 | .addInterceptor(interceptor).build(); 153 | 154 | // Create and configure the Retrofit object 155 | Retrofit retrofit = new Retrofit.Builder() 156 | .baseUrl(authority) 157 | .client(client) 158 | .addConverterFactory(JacksonConverterFactory.create()) 159 | .build(); 160 | 161 | // Generate the token service 162 | TokenService tokenService = retrofit.create(TokenService.class); 163 | 164 | try { 165 | return tokenService.getAccessTokenFromRefreshToken(tenantId, getAppId(), getAppPassword(), 166 | "refresh_token", tokens.getRefreshToken(), getRedirectUrl()).execute().body(); 167 | } catch (IOException e) { 168 | TokenResponse error = new TokenResponse(); 169 | error.setError("IOException"); 170 | error.setErrorDescription(e.getMessage()); 171 | return error; 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/auth/IdToken.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.auth; 2 | 3 | import java.util.Base64; 4 | import java.util.Date; 5 | 6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | 10 | @JsonIgnoreProperties(ignoreUnknown = true) 11 | public class IdToken { 12 | // NOTE: This is just a subset of the claims returned in the 13 | // ID token. For a full listing, see: 14 | // https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-tokens/#idtokens 15 | @JsonProperty("exp") 16 | private long expirationTime; 17 | @JsonProperty("nbf") 18 | private long notBefore; 19 | @JsonProperty("tid") 20 | private String tenantId; 21 | private String nonce; 22 | private String name; 23 | private String email; 24 | @JsonProperty("preferred_username") 25 | private String preferredUsername; 26 | @JsonProperty("oid") 27 | private String objectId; 28 | 29 | public static IdToken parseEncodedToken(String encodedToken, String nonce) { 30 | // Encoded token is in three parts, separated by '.' 31 | String[] tokenParts = encodedToken.split("\\."); 32 | 33 | // The three parts are: header.token.signature 34 | String idToken = tokenParts[1]; 35 | 36 | byte[] decodedBytes = Base64.getUrlDecoder().decode(idToken); 37 | 38 | ObjectMapper mapper = new ObjectMapper(); 39 | IdToken newToken = null; 40 | try { 41 | newToken = mapper.readValue(decodedBytes, IdToken.class); 42 | if (!newToken.isValid(nonce)) { 43 | return null; 44 | } 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | } 48 | return newToken; 49 | } 50 | 51 | public long getExpirationTime() { 52 | return expirationTime; 53 | } 54 | 55 | public void setExpirationTime(long expirationTime) { 56 | this.expirationTime = expirationTime; 57 | } 58 | 59 | public long getNotBefore() { 60 | return notBefore; 61 | } 62 | 63 | public void setNotBefore(long notBefore) { 64 | this.notBefore = notBefore; 65 | } 66 | 67 | public String getTenantId() { 68 | return tenantId; 69 | } 70 | 71 | public void setTenantId(String tenantId) { 72 | this.tenantId = tenantId; 73 | } 74 | 75 | public String getNonce() { 76 | return nonce; 77 | } 78 | 79 | public void setNonce(String nonce) { 80 | this.nonce = nonce; 81 | } 82 | 83 | public String getName() { 84 | return name; 85 | } 86 | 87 | public void setName(String name) { 88 | this.name = name; 89 | } 90 | 91 | public String getEmail() { 92 | return email; 93 | } 94 | 95 | public void setEmail(String email) { 96 | this.email = email; 97 | } 98 | 99 | public String getPreferredUsername() { 100 | return preferredUsername; 101 | } 102 | 103 | public void setPreferredUsername(String preferredUsername) { 104 | this.preferredUsername = preferredUsername; 105 | } 106 | 107 | public String getObjectId() { 108 | return objectId; 109 | } 110 | 111 | public void setObjectId(String objectId) { 112 | this.objectId = objectId; 113 | } 114 | 115 | private Date getUnixEpochAsDate(long epoch) { 116 | // Epoch timestamps are in seconds, 117 | // but Jackson converts integers as milliseconds. 118 | // Rather than create a custom deserializer, this helper will do 119 | // the conversion. 120 | return new Date(epoch * 1000); 121 | } 122 | 123 | private boolean isValid(String nonce) { 124 | // This method does some basic validation 125 | // For more information on validation of ID tokens, see 126 | // https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-tokens/#validating-tokens 127 | Date now = new Date(); 128 | 129 | // Check expiration and not before times 130 | if (now.after(this.getUnixEpochAsDate(this.expirationTime)) || 131 | now.before(this.getUnixEpochAsDate(this.notBefore))) { 132 | // Token is not within it's valid "time" 133 | return false; 134 | } 135 | 136 | // Check nonce 137 | if (!nonce.equals(this.getNonce())) { 138 | // Nonce mismatch 139 | return false; 140 | } 141 | 142 | return true; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/auth/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.auth; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | 6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public class TokenResponse { 11 | @JsonProperty("token_type") 12 | private String tokenType; 13 | private String scope; 14 | @JsonProperty("expires_in") 15 | private int expiresIn; 16 | @JsonProperty("access_token") 17 | private String accessToken; 18 | @JsonProperty("refresh_token") 19 | private String refreshToken; 20 | @JsonProperty("id_token") 21 | private String idToken; 22 | private String error; 23 | @JsonProperty("error_description") 24 | private String errorDescription; 25 | @JsonProperty("error_codes") 26 | private int[] errorCodes; 27 | private Date expirationTime; 28 | 29 | public String getTokenType() { 30 | return tokenType; 31 | } 32 | public void setTokenType(String tokenType) { 33 | this.tokenType = tokenType; 34 | } 35 | public String getScope() { 36 | return scope; 37 | } 38 | public void setScope(String scope) { 39 | this.scope = scope; 40 | } 41 | public int getExpiresIn() { 42 | return expiresIn; 43 | } 44 | public void setExpiresIn(int expiresIn) { 45 | this.expiresIn = expiresIn; 46 | Calendar now = Calendar.getInstance(); 47 | now.add(Calendar.SECOND, expiresIn); 48 | this.expirationTime = now.getTime(); 49 | } 50 | public String getAccessToken() { 51 | return accessToken; 52 | } 53 | public void setAccessToken(String accessToken) { 54 | this.accessToken = accessToken; 55 | } 56 | public String getRefreshToken() { 57 | return refreshToken; 58 | } 59 | public void setRefreshToken(String refreshToken) { 60 | this.refreshToken = refreshToken; 61 | } 62 | public String getIdToken() { 63 | return idToken; 64 | } 65 | public void setIdToken(String idToken) { 66 | this.idToken = idToken; 67 | } 68 | public String getError() { 69 | return error; 70 | } 71 | public void setError(String error) { 72 | this.error = error; 73 | } 74 | public String getErrorDescription() { 75 | return errorDescription; 76 | } 77 | public void setErrorDescription(String errorDescription) { 78 | this.errorDescription = errorDescription; 79 | } 80 | public int[] getErrorCodes() { 81 | return errorCodes; 82 | } 83 | public void setErrorCodes(int[] errorCodes) { 84 | this.errorCodes = errorCodes; 85 | } 86 | public Date getExpirationTime() { 87 | return expirationTime; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/auth/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.auth; 2 | 3 | import retrofit2.Call; 4 | import retrofit2.http.Field; 5 | import retrofit2.http.FormUrlEncoded; 6 | import retrofit2.http.POST; 7 | import retrofit2.http.Path; 8 | 9 | public interface TokenService { 10 | 11 | @FormUrlEncoded 12 | @POST("/{tenantid}/oauth2/v2.0/token") 13 | Call getAccessTokenFromAuthCode( 14 | @Path("tenantid") String tenantId, 15 | @Field("client_id") String clientId, 16 | @Field("client_secret") String clientSecret, 17 | @Field("grant_type") String grantType, 18 | @Field("code") String code, 19 | @Field("redirect_uri") String redirectUrl 20 | ); 21 | 22 | @FormUrlEncoded 23 | @POST("/{tenantid}/oauth2/v2.0/token") 24 | Call getAccessTokenFromRefreshToken( 25 | @Path("tenantid") String tenantId, 26 | @Field("client_id") String clientId, 27 | @Field("client_secret") String clientSecret, 28 | @Field("grant_type") String grantType, 29 | @Field("refresh_token") String code, 30 | @Field("redirect_uri") String redirectUrl 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/controller/AuthorizeController.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.UUID; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpSession; 8 | 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | 14 | import com.outlook.dev.auth.AuthHelper; 15 | import com.outlook.dev.auth.IdToken; 16 | import com.outlook.dev.auth.TokenResponse; 17 | import com.outlook.dev.service.OutlookService; 18 | import com.outlook.dev.service.OutlookServiceBuilder; 19 | import com.outlook.dev.service.OutlookUser; 20 | 21 | @Controller 22 | public class AuthorizeController { 23 | 24 | @RequestMapping(value="/authorize", method=RequestMethod.POST) 25 | public String authorize( 26 | @RequestParam("code") String code, 27 | @RequestParam("id_token") String idToken, 28 | @RequestParam("state") UUID state, 29 | HttpServletRequest request) { 30 | // Get the expected state value from the session 31 | HttpSession session = request.getSession(); 32 | UUID expectedState = (UUID) session.getAttribute("expected_state"); 33 | UUID expectedNonce = (UUID) session.getAttribute("expected_nonce"); 34 | session.removeAttribute("expected_state"); 35 | session.removeAttribute("expected_nonce"); 36 | 37 | // Make sure that the state query parameter returned matches 38 | // the expected state 39 | if (state.equals(expectedState)) { 40 | IdToken idTokenObj = IdToken.parseEncodedToken(idToken, expectedNonce.toString()); 41 | if (idTokenObj != null) { 42 | TokenResponse tokenResponse = AuthHelper.getTokenFromAuthCode(code, idTokenObj.getTenantId()); 43 | session.setAttribute("tokens", tokenResponse); 44 | session.setAttribute("userConnected", true); 45 | session.setAttribute("userName", idTokenObj.getName()); 46 | session.setAttribute("userTenantId", idTokenObj.getTenantId()); 47 | // Get user info 48 | OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokenResponse.getAccessToken(), null); 49 | OutlookUser user; 50 | try { 51 | user = outlookService.getCurrentUser().execute().body(); 52 | session.setAttribute("userEmail", user.getMail()); 53 | } catch (IOException e) { 54 | session.setAttribute("error", e.getMessage()); 55 | } 56 | } else { 57 | session.setAttribute("error", "ID token failed validation."); 58 | } 59 | } else { 60 | session.setAttribute("error", "Unexpected state returned from authority."); 61 | } 62 | return "redirect:/mail.html"; 63 | } 64 | 65 | @RequestMapping("/logout") 66 | public String logout(HttpServletRequest request) { 67 | HttpSession session = request.getSession(); 68 | session.invalidate(); 69 | return "redirect:/index.html"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/controller/ContactsController.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpSession; 8 | 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 13 | 14 | import com.outlook.dev.auth.TokenResponse; 15 | import com.outlook.dev.service.Contact; 16 | import com.outlook.dev.service.OutlookService; 17 | import com.outlook.dev.service.OutlookServiceBuilder; 18 | import com.outlook.dev.service.PagedResult; 19 | 20 | @Controller 21 | public class ContactsController { 22 | @RequestMapping("/contacts") 23 | public String contacts(Model model, HttpServletRequest request, RedirectAttributes redirectAttributes) { 24 | HttpSession session = request.getSession(); 25 | TokenResponse tokens = (TokenResponse)session.getAttribute("tokens"); 26 | if (tokens == null) { 27 | // No tokens in session, user needs to sign in 28 | redirectAttributes.addFlashAttribute("error", "Please sign in to continue."); 29 | return "redirect:/index.html"; 30 | } 31 | 32 | Date now = new Date(); 33 | if (now.after(tokens.getExpirationTime())) { 34 | // Token expired 35 | // TODO: Use the refresh token to request a new token from the token endpoint 36 | // For now, just complain 37 | redirectAttributes.addFlashAttribute("error", "The access token has expired. Please logout and re-login."); 38 | return "redirect:/index.html"; 39 | } 40 | 41 | String email = (String)session.getAttribute("userEmail"); 42 | 43 | OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokens.getAccessToken(), email); 44 | 45 | // Sort by given name in ascending order (A-Z) 46 | String sort = "givenName ASC"; 47 | // Only return the properties we care about 48 | String properties = "givenName,surname,companyName,emailAddresses"; 49 | // Return at most 10 contacts 50 | Integer maxResults = 10; 51 | 52 | try { 53 | PagedResult contacts = outlookService.getContacts( 54 | sort, properties, maxResults) 55 | .execute().body(); 56 | model.addAttribute("contacts", contacts.getValue()); 57 | } catch (IOException e) { 58 | redirectAttributes.addFlashAttribute("error", e.getMessage()); 59 | return "redirect:/index.html"; 60 | } 61 | 62 | return "contacts"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/controller/EventsController.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.controller; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpSession; 8 | 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 13 | 14 | import com.outlook.dev.auth.TokenResponse; 15 | import com.outlook.dev.service.Event; 16 | import com.outlook.dev.service.OutlookService; 17 | import com.outlook.dev.service.OutlookServiceBuilder; 18 | import com.outlook.dev.service.PagedResult; 19 | 20 | @Controller 21 | public class EventsController { 22 | 23 | @RequestMapping("/events") 24 | public String events(Model model, HttpServletRequest request, RedirectAttributes redirectAttributes) { 25 | HttpSession session = request.getSession(); 26 | TokenResponse tokens = (TokenResponse)session.getAttribute("tokens"); 27 | if (tokens == null) { 28 | // No tokens in session, user needs to sign in 29 | redirectAttributes.addFlashAttribute("error", "Please sign in to continue."); 30 | return "redirect:/index.html"; 31 | } 32 | 33 | Date now = new Date(); 34 | if (now.after(tokens.getExpirationTime())) { 35 | // Token expired 36 | // TODO: Use the refresh token to request a new token from the token endpoint 37 | // For now, just complain 38 | redirectAttributes.addFlashAttribute("error", "The access token has expired. Please logout and re-login."); 39 | return "redirect:/index.html"; 40 | } 41 | 42 | String email = (String)session.getAttribute("userEmail"); 43 | 44 | OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokens.getAccessToken(), email); 45 | 46 | // Sort by start time in descending order 47 | String sort = "start/DateTime DESC"; 48 | // Only return the properties we care about 49 | String properties = "organizer,subject,start,end"; 50 | // Return at most 10 events 51 | Integer maxResults = 10; 52 | 53 | try { 54 | PagedResult events = outlookService.getEvents( 55 | sort, properties, maxResults) 56 | .execute().body(); 57 | model.addAttribute("events", events.getValue()); 58 | } catch (IOException e) { 59 | redirectAttributes.addFlashAttribute("error", e.getMessage()); 60 | return "redirect:/index.html"; 61 | } 62 | 63 | return "events"; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.controller; 2 | 3 | import java.util.UUID; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpSession; 7 | 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | import com.outlook.dev.auth.AuthHelper; 13 | 14 | @Controller 15 | public class IndexController { 16 | 17 | @RequestMapping("/index") 18 | public String index(Model model, HttpServletRequest request) { 19 | UUID state = UUID.randomUUID(); 20 | UUID nonce = UUID.randomUUID(); 21 | 22 | // Save the state and nonce in the session so we can 23 | // verify after the auth process redirects back 24 | HttpSession session = request.getSession(); 25 | session.setAttribute("expected_state", state); 26 | session.setAttribute("expected_nonce", nonce); 27 | 28 | String loginUrl = AuthHelper.getLoginUrl(state, nonce); 29 | model.addAttribute("loginUrl", loginUrl); 30 | // Name of a definition in WEB-INF/defs/pages.xml 31 | return "index"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/controller/MailController.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.controller; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpSession; 7 | 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 12 | 13 | import com.outlook.dev.auth.AuthHelper; 14 | import com.outlook.dev.auth.TokenResponse; 15 | import com.outlook.dev.service.Message; 16 | import com.outlook.dev.service.OutlookService; 17 | import com.outlook.dev.service.OutlookServiceBuilder; 18 | import com.outlook.dev.service.PagedResult; 19 | 20 | @Controller 21 | public class MailController { 22 | 23 | @RequestMapping("/mail") 24 | public String mail(Model model, HttpServletRequest request, RedirectAttributes redirectAttributes) { 25 | HttpSession session = request.getSession(); 26 | TokenResponse tokens = (TokenResponse)session.getAttribute("tokens"); 27 | if (tokens == null) { 28 | // No tokens in session, user needs to sign in 29 | redirectAttributes.addFlashAttribute("error", "Please sign in to continue."); 30 | return "redirect:/index.html"; 31 | } 32 | 33 | String tenantId = (String)session.getAttribute("userTenantId"); 34 | 35 | tokens = AuthHelper.ensureTokens(tokens, tenantId); 36 | 37 | String email = (String)session.getAttribute("userEmail"); 38 | 39 | OutlookService outlookService = OutlookServiceBuilder.getOutlookService(tokens.getAccessToken(), email); 40 | 41 | // Retrieve messages from the inbox 42 | String folder = "inbox"; 43 | // Sort by time received in descending order 44 | String sort = "receivedDateTime DESC"; 45 | // Only return the properties we care about 46 | String properties = "receivedDateTime,from,isRead,subject,bodyPreview"; 47 | // Return at most 10 messages 48 | Integer maxResults = 10; 49 | 50 | try { 51 | PagedResult messages = outlookService.getMessages( 52 | folder, sort, properties, maxResults) 53 | .execute().body(); 54 | model.addAttribute("messages", messages.getValue()); 55 | } catch (IOException e) { 56 | redirectAttributes.addFlashAttribute("error", e.getMessage()); 57 | return "redirect:/index.html"; 58 | } 59 | 60 | return "mail"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/Contact.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class Contact { 7 | private String id; 8 | private String givenName; 9 | private String surname; 10 | private String companyName; 11 | private EmailAddress[] emailAddresses; 12 | 13 | public String getId() { 14 | return id; 15 | } 16 | public void setId(String id) { 17 | this.id = id; 18 | } 19 | public String getGivenName() { 20 | return givenName; 21 | } 22 | public void setGivenName(String givenName) { 23 | this.givenName = givenName; 24 | } 25 | public String getSurname() { 26 | return surname; 27 | } 28 | public void setSurname(String surname) { 29 | this.surname = surname; 30 | } 31 | public String getCompanyName() { 32 | return companyName; 33 | } 34 | public void setCompanyName(String companyName) { 35 | this.companyName = companyName; 36 | } 37 | public EmailAddress[] getEmailAddresses() { 38 | return emailAddresses; 39 | } 40 | public void setEmailAddresses(EmailAddress[] emailAddresses) { 41 | this.emailAddresses = emailAddresses; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/DateTimeTimeZone.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public class DateTimeTimeZone { 9 | private Date dateTime; 10 | private String timeZone; 11 | 12 | public Date getDateTime() { 13 | return dateTime; 14 | } 15 | public void setDateTime(Date dateTime) { 16 | this.dateTime = dateTime; 17 | } 18 | public String getTimeZone() { 19 | return timeZone; 20 | } 21 | public void setTimeZone(String timeZone) { 22 | this.timeZone = timeZone; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/EmailAddress.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class EmailAddress { 7 | private String name; 8 | private String address; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | public String getAddress() { 17 | return address; 18 | } 19 | public void setAddress(String address) { 20 | this.address = address; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/Event.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class Event { 7 | private String id; 8 | private String subject; 9 | private Recipient organizer; 10 | private DateTimeTimeZone start; 11 | private DateTimeTimeZone end; 12 | 13 | public String getId() { 14 | return id; 15 | } 16 | public void setId(String id) { 17 | this.id = id; 18 | } 19 | public String getSubject() { 20 | return subject; 21 | } 22 | public void setSubject(String subject) { 23 | this.subject = subject; 24 | } 25 | public Recipient getOrganizer() { 26 | return organizer; 27 | } 28 | public void setOrganizer(Recipient organizer) { 29 | this.organizer = organizer; 30 | } 31 | public DateTimeTimeZone getStart() { 32 | return start; 33 | } 34 | public void setStart(DateTimeTimeZone start) { 35 | this.start = start; 36 | } 37 | public DateTimeTimeZone getEnd() { 38 | return end; 39 | } 40 | public void setEnd(DateTimeTimeZone end) { 41 | this.end = end; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/Message.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public class Message { 9 | private String id; 10 | private Date receivedDateTime; 11 | private Recipient from; 12 | private Boolean isRead; 13 | private String subject; 14 | private String bodyPreview; 15 | 16 | public String getId() { 17 | return id; 18 | } 19 | public void setId(String id) { 20 | this.id = id; 21 | } 22 | public Date getReceivedDateTime() { 23 | return receivedDateTime; 24 | } 25 | public void setReceivedDateTime(Date receivedDateTime) { 26 | this.receivedDateTime = receivedDateTime; 27 | } 28 | public Recipient getFrom() { 29 | return from; 30 | } 31 | public void setFrom(Recipient from) { 32 | this.from = from; 33 | } 34 | public Boolean getIsRead() { 35 | return isRead; 36 | } 37 | public void setIsRead(Boolean isRead) { 38 | this.isRead = isRead; 39 | } 40 | public String getSubject() { 41 | return subject; 42 | } 43 | public void setSubject(String subject) { 44 | this.subject = subject; 45 | } 46 | public String getBodyPreview() { 47 | return bodyPreview; 48 | } 49 | public void setBodyPreview(String bodyPreview) { 50 | this.bodyPreview = bodyPreview; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/OutlookService.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import retrofit2.Call; 4 | import retrofit2.http.GET; 5 | import retrofit2.http.Path; 6 | import retrofit2.http.Query; 7 | 8 | public interface OutlookService { 9 | 10 | @GET("/v1.0/me") 11 | Call getCurrentUser(); 12 | 13 | @GET("/v1.0/me/mailfolders/{folderid}/messages") 14 | Call> getMessages( 15 | @Path("folderid") String folderId, 16 | @Query("$orderby") String orderBy, 17 | @Query("$select") String select, 18 | @Query("$top") Integer maxResults 19 | ); 20 | 21 | @GET("/v1.0/me/events") 22 | Call> getEvents( 23 | @Query("$orderby") String orderBy, 24 | @Query("$select") String select, 25 | @Query("$top") Integer maxResults 26 | ); 27 | 28 | @GET("/v1.0/me/contacts") 29 | Call> getContacts( 30 | @Query("$orderby") String orderBy, 31 | @Query("$select") String select, 32 | @Query("$top") Integer maxResults 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/OutlookServiceBuilder.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import java.io.IOException; 4 | import java.util.UUID; 5 | 6 | import okhttp3.Interceptor; 7 | import okhttp3.OkHttpClient; 8 | import okhttp3.Request; 9 | import okhttp3.Request.Builder; 10 | import okhttp3.Response; 11 | import okhttp3.logging.HttpLoggingInterceptor; 12 | import retrofit2.Retrofit; 13 | import retrofit2.converter.jackson.JacksonConverterFactory; 14 | 15 | public class OutlookServiceBuilder { 16 | 17 | public static OutlookService getOutlookService(String accessToken, String userEmail) { 18 | // Create a request interceptor to add headers that belong on 19 | // every request 20 | Interceptor requestInterceptor = new Interceptor() { 21 | @Override 22 | public Response intercept(Interceptor.Chain chain) throws IOException { 23 | Request original = chain.request(); 24 | Builder builder = original.newBuilder() 25 | .header("User-Agent", "java-tutorial") 26 | .header("client-request-id", UUID.randomUUID().toString()) 27 | .header("return-client-request-id", "true") 28 | .header("Authorization", String.format("Bearer %s", accessToken)) 29 | .method(original.method(), original.body()); 30 | 31 | Request request = builder.build(); 32 | return chain.proceed(request); 33 | } 34 | }; 35 | 36 | // Create a logging interceptor to log request and responses 37 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); 38 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 39 | 40 | OkHttpClient client = new OkHttpClient.Builder() 41 | .addInterceptor(requestInterceptor) 42 | .addInterceptor(loggingInterceptor) 43 | .build(); 44 | 45 | // Create and configure the Retrofit object 46 | Retrofit retrofit = new Retrofit.Builder() 47 | .baseUrl("https://graph.microsoft.com") 48 | .client(client) 49 | .addConverterFactory(JacksonConverterFactory.create()) 50 | .build(); 51 | 52 | // Generate the token service 53 | return retrofit.create(OutlookService.class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/OutlookUser.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class OutlookUser { 7 | private String id; 8 | private String mail; 9 | private String displayName; 10 | 11 | public String getId() { 12 | return id; 13 | } 14 | public void setId(String id) { 15 | this.id = id; 16 | } 17 | public String getMail() { 18 | return mail; 19 | } 20 | public void setMail(String emailAddress) { 21 | this.mail = emailAddress; 22 | } 23 | public String getDisplayName() { 24 | return displayName; 25 | } 26 | public void setDisplayName(String displayName) { 27 | this.displayName = displayName; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/PagedResult.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class PagedResult { 8 | @JsonProperty("@odata.nextLink") 9 | private String nextPageLink; 10 | private T[] value; 11 | 12 | public String getNextPageLink() { 13 | return nextPageLink; 14 | } 15 | public void setNextPageLink(String nextPageLink) { 16 | this.nextPageLink = nextPageLink; 17 | } 18 | public T[] getValue() { 19 | return value; 20 | } 21 | public void setValue(T[] value) { 22 | this.value = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/outlook/dev/service/Recipient.java: -------------------------------------------------------------------------------- 1 | package com.outlook.dev.service; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class Recipient { 7 | private EmailAddress emailAddress; 8 | 9 | public EmailAddress getEmailAddress() { 10 | return emailAddress; 11 | } 12 | 13 | public void setEmailAddress(EmailAddress emailAddress) { 14 | this.emailAddress = emailAddress; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/auth.properties: -------------------------------------------------------------------------------- 1 | appId=YOUR_APP_ID_HERE 2 | appPassword=YOUR_APP_PASSWORD_HERE 3 | redirectUrl=http://localhost:8080/authorize.html -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/defs/pages.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/dispatcher-servlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | /WEB-INF/defs/pages.xml 19 | 20 | 21 | 22 | 23 | 25 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/contacts.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | 4 | 5 |
Error: ${error}
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 |
Contacts
NameCompanyEmail
23 |
    24 | 25 |
  • 26 |
    27 |
28 |
-------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/events.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | 4 | 5 |
Error: ${error}
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Calendar
OrganizerSubjectStartEnd
-------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 | <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | 5 | 6 |
${error}
7 |
8 |
9 |

Java Web App Tutorial

10 |

This sample uses the Mail API to read messages in your inbox.

11 |

">Click here to login

12 |
-------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/mail.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | 4 | 5 |
Error: ${error}
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Inbox
FromSubjectReceivedPreview
23 | 24 | 25 | 26 |
-------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/layout/base.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 | <%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> 3 | <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> 4 | <%@ taglib uri="http://tiles.apache.org/tags-tiles-extras" prefix="tilesx" %> 5 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 6 | 7 | 8 | 9 | 10 | <tiles:getAsString name="title"></tiles:getAsString> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 63 | 64 | 65 |
66 | 67 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | java-tutorial 4 | 5 | index.html 6 | index.htm 7 | index.jsp 8 | default.html 9 | default.htm 10 | default.jsp 11 | 12 | 13 | 14 | dispatcher 15 | org.springframework.web.servlet.DispatcherServlet 16 | 1 17 | 18 | 19 | 20 | dispatcher 21 | *.html 22 | *.htm 23 | *.json 24 | *.xml 25 | 26 | -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonjoh/java-tutorial/482829de38ea80a905cbebee013dcf8c5d7d6d4b/src/main/webapp/index.html --------------------------------------------------------------------------------