Skip to content

Commit 46fd0cf

Browse files
authored
Merge pull request #411 from microsoftgraph/jasonjoh/sdkv6
Updated Graph SDK to v6
2 parents 669d5a6 + 502375e commit 46fd0cf

18 files changed

+610
-201
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"apponly",
2828
"autoconfigure",
2929
"Configurer",
30+
"Deserializers",
3031
"genkey",
3132
"graphwebhook",
3233
"HMACSHA",

graphwebhook/pom.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
<dependency>
5757
<groupId>com.microsoft.graph</groupId>
5858
<artifactId>microsoft-graph</artifactId>
59-
<version>5.80.0</version>
59+
<version>6.13.0</version>
6060
</dependency>
6161

6262
<dependency>
@@ -96,6 +96,18 @@
9696
<artifactId>bcpkix-jdk14</artifactId>
9797
<version>1.78.1</version>
9898
</dependency>
99+
100+
<dependency>
101+
<groupId>commons-codec</groupId>
102+
<artifactId>commons-codec</artifactId>
103+
<version>1.17.0</version>
104+
</dependency>
105+
106+
<dependency>
107+
<groupId>com.google.code.gson</groupId>
108+
<artifactId>gson</artifactId>
109+
<version>2.11.0</version>
110+
</dependency>
99111
</dependencies>
100112

101113
<build>

graphwebhook/src/main/java/com/example/graphwebhook/CertificateStoreService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import javax.crypto.spec.IvParameterSpec;
1818
import javax.crypto.spec.SecretKeySpec;
1919

20-
import org.apache.tomcat.util.codec.binary.Base64;
20+
import org.apache.commons.codec.binary.Base64;
2121
import org.bouncycastle.jce.provider.BouncyCastleProvider;
2222
import org.slf4j.Logger;
2323
import org.slf4j.LoggerFactory;

graphwebhook/src/main/java/com/example/graphwebhook/GraphClientHelper.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@
33

44
package com.example.graphwebhook;
55

6-
import com.microsoft.graph.logger.DefaultLogger;
7-
import com.microsoft.graph.logger.LoggerLevel;
8-
import com.microsoft.graph.requests.GraphServiceClient;
6+
import com.microsoft.graph.serviceclient.GraphServiceClient;
97
import org.springframework.lang.NonNull;
108
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
119

12-
import okhttp3.Request;
13-
1410
public class GraphClientHelper {
1511

1612
/**
@@ -25,14 +21,10 @@ private GraphClientHelper() {
2521
* @param oauthClient the authorized OAuth2 client to authenticate Graph requests with
2622
* @return A Graph client object that uses the provided OAuth2 client for access tokens
2723
*/
28-
public static GraphServiceClient<Request> getGraphClient(
24+
public static GraphServiceClient getGraphClient(
2925
@NonNull final OAuth2AuthorizedClient oauthClient) {
3026
final var authProvider = new SpringOAuth2AuthProvider(oauthClient);
3127

32-
final var logger = new DefaultLogger();
33-
logger.setLoggingLevel(LoggerLevel.ERROR);
34-
35-
return GraphServiceClient.builder().authenticationProvider(authProvider).logger(logger)
36-
.buildClient();
28+
return new GraphServiceClient(authProvider);
3729
}
3830
}

graphwebhook/src/main/java/com/example/graphwebhook/ListenController.java

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@
33

44
package com.example.graphwebhook;
55

6+
import java.io.IOException;
7+
import java.net.URI;
8+
import java.net.URISyntaxException;
9+
import java.util.List;
610
import java.util.Objects;
7-
import java.util.concurrent.CompletableFuture;
8-
import javax.annotation.Nonnull;
911
import com.corundumstudio.socketio.AckRequest;
1012
import com.corundumstudio.socketio.SocketIOClient;
1113
import com.corundumstudio.socketio.SocketIONamespace;
1214
import com.corundumstudio.socketio.SocketIOServer;
1315
import com.corundumstudio.socketio.listener.DataListener;
14-
import com.microsoft.graph.logger.DefaultLogger;
15-
import com.microsoft.graph.models.ChangeNotification;
16-
import com.microsoft.graph.models.ChangeNotificationCollection;
16+
import com.example.graphwebhook.notifications.ChangeNotification;
17+
import com.example.graphwebhook.notifications.ChangeNotificationCollection;
18+
import com.example.graphwebhook.notifications.ChangeNotificationEncryptedContent;
1719
import com.microsoft.graph.models.ChatMessage;
1820
import com.microsoft.graph.models.Message;
19-
import com.microsoft.graph.serializer.DefaultSerializer;
20-
21+
import com.microsoft.kiota.HttpMethod;
22+
import com.microsoft.kiota.RequestInformation;
23+
import com.microsoft.kiota.serialization.KiotaJsonSerialization;
2124
import org.slf4j.Logger;
2225
import org.slf4j.LoggerFactory;
2326
import org.springframework.beans.factory.annotation.Autowired;
@@ -97,50 +100,54 @@ public ResponseEntity<String> handleValidation(
97100
* @return A 202 Accepted response
98101
*/
99102
@PostMapping("/listen")
100-
public CompletableFuture<ResponseEntity<String>> handleNotification(
101-
@RequestBody @Nonnull final String jsonPayload) {
102-
// Deserialize the JSON body into a ChangeNotificationCollection
103-
final var serializer = new DefaultSerializer(new DefaultLogger());
104-
final var notifications =
105-
serializer.deserializeObject(jsonPayload, ChangeNotificationCollection.class);
106-
107-
if (notifications == null) {
108-
return CompletableFuture.completedFuture(ResponseEntity.accepted().body(""));
109-
}
103+
public ResponseEntity<String> handleNotification(
104+
@RequestBody @NonNull final String jsonPayload) {
105+
try {
106+
// Deserialize the JSON body into a ChangeNotificationCollection
107+
final ChangeNotificationCollection notifications =
108+
KiotaJsonSerialization.deserialize(jsonPayload,
109+
ChangeNotificationCollection::createFromDiscriminatorValue);
110+
111+
if (notifications == null) {
112+
return ResponseEntity.accepted().body("");
113+
}
110114

111-
// Check for validation tokens
112-
boolean areTokensValid = true;
113-
if (notifications.validationTokens != null
114-
&& !Objects.requireNonNull(notifications.validationTokens).isEmpty()) {
115-
areTokensValid = TokenHelper.areValidationTokensValid(new String[] {clientId},
116-
new String[] {tenantId}, Objects.requireNonNull(notifications.validationTokens),
117-
Objects.requireNonNull(keyDiscoveryUrl));
118-
}
115+
// Check for validation tokens
116+
boolean areTokensValid = true;
117+
final List<String> validationTokens = notifications.getValidationTokens();
118+
if (validationTokens != null && validationTokens.isEmpty()) {
119+
areTokensValid = TokenHelper.areValidationTokensValid(new String[] {clientId},
120+
new String[] {tenantId}, Objects.requireNonNull(validationTokens),
121+
Objects.requireNonNull(keyDiscoveryUrl));
122+
}
119123

120-
if (areTokensValid) {
121-
for (ChangeNotification notification : Objects.requireNonNull(notifications.value)) {
122-
// Look up subscription in store
123-
var subscription = subscriptionStore.getSubscription(
124-
Objects.requireNonNull(notification.subscriptionId).toString());
125-
126-
// Only process if we know about this subscription AND
127-
// the client state in the notification matches
128-
if (subscription != null
129-
&& subscription.clientState.equals(notification.clientState)) {
130-
if (notification.encryptedContent == null) {
131-
// No encrypted content, this is a new message notification
132-
// without resource data
133-
processNewMessageNotification(notification, subscription);
134-
} else {
135-
// With encrypted content, this is a new channel message
136-
// notification with encrypted resource data
137-
processNewChannelMessageNotification(notification, subscription);
124+
if (areTokensValid) {
125+
for (ChangeNotification notification : Objects.requireNonNull(notifications.getValue())) {
126+
// Look up subscription in store
127+
var subscription = subscriptionStore.getSubscription(
128+
Objects.requireNonNull(notification.getSubscriptionId()));
129+
130+
// Only process if we know about this subscription AND
131+
// the client state in the notification matches
132+
if (subscription != null
133+
&& subscription.clientState.equals(notification.getClientState())) {
134+
if (notification.getEncryptedContent() == null) {
135+
// No encrypted content, this is a new message notification
136+
// without resource data
137+
processNewMessageNotification(notification, subscription);
138+
} else {
139+
// With encrypted content, this is a new channel message
140+
// notification with encrypted resource data
141+
processNewChannelMessageNotification(notification, subscription);
142+
}
138143
}
139144
}
140145
}
146+
} catch (IOException e) {
147+
e.printStackTrace();
141148
}
142149

143-
return CompletableFuture.completedFuture(ResponseEntity.accepted().body(""));
150+
return ResponseEntity.accepted().body("");
144151
}
145152

146153

@@ -164,12 +171,24 @@ private void processNewMessageNotification(@NonNull final ChangeNotification not
164171
// so use the customRequest method instead of the fluent API
165172
// Once message has been retrieved, send the information via SocketIO
166173
// to subscribed clients
167-
graphClient.customRequest("/" + notification.resource, Message.class).buildRequest()
168-
.getAsync().thenAccept(message -> {
169-
if (message != null)
170-
socketIONamespace.getRoomOperations(subscription.subscriptionId).sendEvent(
171-
"notificationReceived", new NewMessageNotification(message));
172-
});
174+
175+
final RequestInformation request = new RequestInformation();
176+
request.httpMethod = HttpMethod.GET;
177+
URI messageUri;
178+
try {
179+
messageUri = new URI(
180+
graphClient.getRequestAdapter().getBaseUrl() + "/" + notification.getResource());
181+
} catch (URISyntaxException e) {
182+
e.printStackTrace();
183+
return;
184+
}
185+
186+
request.setUri(messageUri);
187+
final Message message = graphClient.getRequestAdapter().send(request, null,
188+
Message::createFromDiscriminatorValue);
189+
if (message != null)
190+
socketIONamespace.getRoomOperations(subscription.subscriptionId)
191+
.sendEvent("notificationReceived", new NewMessageNotification(message));
173192
}
174193

175194

@@ -183,24 +202,30 @@ private void processNewChannelMessageNotification(
183202
@NonNull final ChangeNotification notification,
184203
@NonNull final SubscriptionRecord subscription) {
185204
// Decrypt the encrypted key from the notification
205+
final ChangeNotificationEncryptedContent encryptedContent =
206+
Objects.requireNonNull(notification.getEncryptedContent());
186207
final var decryptedKey = Objects.requireNonNull(certificateStore
187-
.getEncryptionKey(Objects.requireNonNull(notification.encryptedContent).dataKey));
208+
.getEncryptionKey(encryptedContent.getDataKey()));
188209

189210
// Validate the signature
211+
final String data = encryptedContent.getData();
190212
if (certificateStore.isDataSignatureValid(decryptedKey,
191-
Objects.requireNonNull(notification.encryptedContent).data,
192-
Objects.requireNonNull(notification.encryptedContent).dataSignature)) {
213+
data,
214+
encryptedContent.getDataSignature())) {
193215
// Decrypt the data using the decrypted key
194-
final var decryptedData = certificateStore.getDecryptedData(decryptedKey,
195-
Objects.requireNonNull(notification.encryptedContent).data);
216+
final var decryptedData = certificateStore.getDecryptedData(decryptedKey, data);
196217

197218
// Deserialize the decrypted JSON into a ChatMessage
198-
final var serializer = new DefaultSerializer(new DefaultLogger());
199-
final var chatMessage = Objects.requireNonNull(serializer
200-
.deserializeObject(Utilities.ensureNonNull(decryptedData), ChatMessage.class));
201-
// Send the information to subscribed clients
202-
socketIONamespace.getRoomOperations(subscription.subscriptionId)
219+
ChatMessage chatMessage;
220+
try {
221+
chatMessage = KiotaJsonSerialization.deserialize(decryptedData,
222+
ChatMessage::createFromDiscriminatorValue);
223+
// Send the information to subscribed clients
224+
socketIONamespace.getRoomOperations(subscription.subscriptionId)
203225
.sendEvent("notificationReceived", new NewChatMessageNotification(chatMessage));
226+
} catch (IOException e) {
227+
e.printStackTrace();
228+
}
204229
}
205230
}
206231
}

graphwebhook/src/main/java/com/example/graphwebhook/NewChatMessageNotification.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class NewChatMessageNotification {
2424
public final String body;
2525

2626
public NewChatMessageNotification(@NonNull ChatMessage message) {
27-
sender = Objects.requireNonNull(Objects.requireNonNull(message.from).user).displayName;
28-
body = Objects.requireNonNull(message.body).content;
27+
sender = Objects.requireNonNull(Objects.requireNonNull(message.getFrom()).getUser()).getDisplayName();
28+
body = Objects.requireNonNull(message.getBody()).getContent();
2929
}
3030
}

graphwebhook/src/main/java/com/example/graphwebhook/NewMessageNotification.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class NewMessageNotification {
2525

2626
public NewMessageNotification(@NonNull Message message) {
2727
Objects.requireNonNull(message);
28-
subject = message.subject;
29-
id = message.id;
28+
subject = message.getSubject();
29+
id = message.getId();
3030
}
3131
}

graphwebhook/src/main/java/com/example/graphwebhook/SecurityConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class SecurityConfig {
2020
@Value("${app.protect.authenticated}")
2121
private String[] protectedRoutes;
2222

23+
@SuppressWarnings("removal")
2324
@Bean
2425
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2526
http.securityContext(context -> context.requireExplicitSave(false))

graphwebhook/src/main/java/com/example/graphwebhook/SpringOAuth2AuthProvider.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33

44
package com.example.graphwebhook;
55

6-
import java.net.URL;
6+
import java.net.URISyntaxException;
7+
import java.util.Map;
78
import java.util.Objects;
8-
import java.util.concurrent.CompletableFuture;
9-
import javax.annotation.Nonnull;
10-
import com.microsoft.graph.authentication.IAuthenticationProvider;
9+
import com.microsoft.kiota.RequestInformation;
10+
import com.microsoft.kiota.authentication.AuthenticationProvider;
1111
import org.springframework.lang.NonNull;
1212
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
1313

1414
/**
1515
* An implementation of IAuthenticationProvider that uses Spring's OAuth2AuthorizedClient to get
1616
* access tokens
1717
*/
18-
public class SpringOAuth2AuthProvider implements IAuthenticationProvider {
18+
public class SpringOAuth2AuthProvider implements AuthenticationProvider {
1919

2020
private OAuth2AuthorizedClient oauthClient;
2121

@@ -24,9 +24,16 @@ public SpringOAuth2AuthProvider(@NonNull OAuth2AuthorizedClient oauthClient) {
2424
}
2525

2626
@Override
27-
@Nonnull
28-
public CompletableFuture<String> getAuthorizationTokenAsync(@Nonnull URL requestUrl) {
29-
return Utilities.ensureNonNull(
30-
CompletableFuture.completedFuture(oauthClient.getAccessToken().getTokenValue()));
27+
public void authenticateRequest(RequestInformation request,
28+
Map<String, Object> additionalAuthenticationContext) {
29+
30+
try {
31+
if (request.getUri().getHost().equalsIgnoreCase("graph.microsoft.com")) {
32+
final String accessToken = oauthClient.getAccessToken().getTokenValue();
33+
request.headers.add("Authorization", "Bearer " + accessToken);
34+
}
35+
} catch (IllegalStateException | URISyntaxException e) {
36+
e.printStackTrace();
37+
}
3138
}
3239
}

graphwebhook/src/main/java/com/example/graphwebhook/SubscriptionStoreService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ public class SubscriptionStoreService {
3131
*/
3232
public void addSubscription(@NonNull @Nonnull final Subscription subscription,
3333
@NonNull final String userId) {
34-
var newRecord = new SubscriptionRecord(subscription.id, Objects.requireNonNull(userId),
35-
Objects.requireNonNull(subscription.clientState));
36-
subscriptions.put(subscription.id, newRecord);
34+
var newRecord = new SubscriptionRecord(subscription.getId(), Objects.requireNonNull(userId),
35+
Objects.requireNonNull(subscription.getClientState()));
36+
subscriptions.put(subscription.getId(), newRecord);
3737
}
3838

3939

0 commit comments

Comments
 (0)