-
Notifications
You must be signed in to change notification settings - Fork 0
/
GraphAPIController.java
206 lines (162 loc) · 9.26 KB
/
GraphAPIController.java
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package com.example.graphwebhook;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.time.Instant;
//Copyright (c) Microsoft Corporation. All rights reserved.
//Licensed under the MIT License.
import java.time.OffsetDateTime;
import java.time.Period;
import java.time.temporal.TemporalAmount;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.azure.core.models.CloudEvent;
import com.azure.core.util.BinaryData;
import com.microsoft.graph.logger.DefaultLogger;
import com.microsoft.graph.models.ChangeNotification;
import com.microsoft.graph.models.ChangeType;
import com.microsoft.graph.models.Subscription;
import com.microsoft.graph.serializer.DefaultSerializer;
import com.microsoft.graph.serializer.OffsetDateTimeSerializer;
/**
* <p>
* Sample class that contains controller methods to create, delete, and renew a
* Microsoft Graph API subscription that sends events to an Azure Event Grid
* partner topic.
*
* This code is meant to used along with full-blown sample in https://github.com/microsoftgraph/java-spring-webhooks-sample
* </p>
*/
@Controller
public class GraphAPIController {
private static final String CREATE_SUBSCRIPTION_ERROR = "Error creating subscription";
private static final String REDIRECT_HOME = "redirect:/";
private static final String REDIRECT_LOGOUT = "redirect:/logout";
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SubscriptionStoreService subscriptionStore;
// @Autowired
// private CertificateStoreService certificateStore;
@Autowired
private OAuth2AuthorizedClientService authorizedClientService;
@Value("${notifications.host}")
private String notificationHost;
/**
* Subscribes for all events related to changes to profile information in Microsoft Entra ID about
* the current ("me") user.
* @param model the model provided by Spring
* @param authentication authentication information for the request
* @param redirectAttributes redirect attributes provided by Spring
* @param oauthClient a delegated auth OAuth2 client for the authenticated user
* @return the name of the template used to render the response
*/
@GetMapping("/subscribe")
public CompletableFuture<String> delegatedUser(Model model, Authentication authentication,
RedirectAttributes redirectAttributes,
@RegisteredOAuth2AuthorizedClient("graph") OAuth2AuthorizedClient oauthClient) {
final var graphClient = GraphClientHelper.getGraphClient(oauthClient);
// Get the authenticated user's info. See https://docs.microsoft.com/en-us/graph/sdks/create-requests?tabs=java#use-select-to-control-the-properties-returned
// https://docs.microsoft.com/en-us/graph/query-parameters#select-parameter
// and https://docs.microsoft.com/en-us/graph/api/resources/users?view=graph-rest-1.0#common-properties
final var userFuture = graphClient.me().buildRequest()
.select("displayName,jobTitle,userPrincipalName").getAsync();
// Create the subscription
final var subscriptionRequest = new Subscription();
subscriptionRequest.changeType = ChangeType.UPDATED.toString();
// Use the following for event notifications via Event Grid's Partner Topic
subscriptionRequest.notificationUrl = "EventGrid:?azuresubscriptionid=8A8A8A8A-4B4B-4C4C-4D4D-12E12E12E12E&resourcegroup=yourResourceGroup&partnertopic=yourPartnerTopic&location=theNameOfAzureRegionFortheTopic";
subscriptionRequest.lifecycleNotificationUrl = "EventGrid:?azuresubscriptionid=8A8A8A8A-4B4B-4C4C-4D4D-12E12E12E12E&resourcegroup=yourResourceGroup&partnertopic=yourPartnerTopic&location=theNameOfAzureRegionFortheTopic";
subscriptionRequest.resource = "me";
subscriptionRequest.clientState = UUID.randomUUID().toString();
// subscriptionRequest.includeResourceData = false;
subscriptionRequest.expirationDateTime = OffsetDateTime.now().plusSeconds(3600);
final var subscriptionFuture =
graphClient.subscriptions().buildRequest().postAsync(subscriptionRequest);
return userFuture.thenCombine(subscriptionFuture, (user, subscription) -> {
log.info("*** Created subscription {} for user {}", subscription.id, user.displayName);
// Save the authorized client so we can use it later from the notification controller
authorizedClientService.saveAuthorizedClient(oauthClient, authentication);
// Add information to the model
model.addAttribute("user", user);
model.addAttribute("subscriptionId", subscription.id);
final var subscriptionJson =
graphClient.getHttpProvider().getSerializer().serializeObject(subscription);
model.addAttribute("subscription", subscriptionJson);
// Add record in subscription store
subscriptionStore.addSubscription(subscription, authentication.getName());
model.addAttribute("success", "Subscription created.");
return "delegatedUser";
}).exceptionally(e -> {
log.error(CREATE_SUBSCRIPTION_ERROR, e);
redirectAttributes.addFlashAttribute("error", CREATE_SUBSCRIPTION_ERROR);
redirectAttributes.addFlashAttribute("debug", e.getMessage());
return REDIRECT_HOME;
});
}
@PostMapping("/graphApiSubscriptionLifecycleEvents")
public CompletableFuture<ResponseEntity<String>> handleLifecycleEvent(@RequestBody CloudEvent lifecycleCloudEvent) throws ParseException {
log.info("***** Received lifecycle Event with ID and event type: " +
lifecycleCloudEvent.getId() + ", " + lifecycleCloudEvent.getType() + ".");
BinaryData eventData = lifecycleCloudEvent.getData();
byte [] eventBytes = eventData.toBytes();
ByteArrayInputStream eventNotificationDataInputStream = new ByteArrayInputStream(eventBytes);
final var serializer = new DefaultSerializer(new DefaultLogger());
final var notification =
serializer.deserializeObject(eventNotificationDataInputStream, ChangeNotification.class);
// Look up subscription in store
var subscription =
subscriptionStore.getSubscription(notification.subscriptionId.toString());
log.info("***** Subscription id of the received notification: " + notification.subscriptionId.toString());
log.info("***** Lifecycle event type received: " + lifecycleCloudEvent.getType());
if(subscription != null) {
// Get the authorized OAuth2 client for the relevant user
final var oauthClient =
authorizedClientService.loadAuthorizedClient("graph", subscription.userId);
final var graphClient = GraphClientHelper.getGraphClient(oauthClient);
Subscription renewedSbscription = new Subscription();
TemporalAmount threeMonths = Period.ofMonths(3);
String nowPlustThreeMonths = Instant.now().plus(threeMonths).toString();
log.info("**** renewing Graph API subscription with new expiraction time of " + nowPlustThreeMonths);
renewedSbscription.expirationDateTime = OffsetDateTimeSerializer.deserialize(nowPlustThreeMonths);
graphClient.subscriptions(subscription.subscriptionId)
.buildRequest()
.patch(renewedSbscription);
log.info("**** Graph API subscription renewed");
}
return CompletableFuture.completedFuture(ResponseEntity.ok().body(""));
}
/**
* Deletes a subscription and logs the user out
* @param subscriptionId the subscription ID to delete
* @param oauthClient a delegated auth OAuth2 client for the authenticated user
* @return a redirect to the logout page
*/
@GetMapping("/unsubscribe")
public CompletableFuture<String> unsubscribe(
@RequestParam(value = "subscriptionId") final String subscriptionId,
@RegisteredOAuth2AuthorizedClient("graph") OAuth2AuthorizedClient oauthClient) {
final var graphClient = GraphClientHelper.getGraphClient(oauthClient);
return graphClient.subscriptions(subscriptionId).buildRequest().deleteAsync()
.thenApply(sub -> {
// Remove subscription from store
subscriptionStore.deleteSubscription(subscriptionId);
// Logout user
return REDIRECT_LOGOUT;
});
}
}