Move allowed callers and skill conversation factory to SDK (#1062)

* Move allowed callers and skill convo factory

* Update for Application file.

Co-authored-by: tracyboehrer <tracyboehrer@users.noreply.github.com>
This commit is contained in:
Lee Parrish 2021-03-17 11:24:44 -05:00 коммит произвёл GitHub
Родитель 466fd90b80
Коммит 837dca217f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 332 добавлений и 204 удалений

Просмотреть файл

@ -1,16 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.sample.dialogrootbot;
package com.microsoft.bot.builder.skills;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.microsoft.bot.builder.Storage;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions;
import com.microsoft.bot.builder.skills.SkillConversationReference;
import com.microsoft.bot.connector.Async;
import com.microsoft.bot.schema.ConversationReference;
@ -25,6 +22,11 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
private Storage storage;
/**
* Creates an instance of a SkillConversationIdFactory.
*
* @param storage A storage instance for the factory.
*/
public SkillConversationIdFactory(Storage storage) {
if (storage == null) {
throw new IllegalArgumentException("Storage cannot be null.");
@ -32,18 +34,26 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
this.storage = storage;
}
/**
* Creates a conversation id for a skill conversation.
*
* @param options A {@link SkillConversationIdFactoryOptions} instance
* containing parameters for creating the conversation ID.
*
* @return A unique conversation ID used to communicate with the skill.
*
* It should be possible to use the returned String on a request URL and
* it should not contain special characters.
*/
@Override
public CompletableFuture<String> createSkillConversationId(SkillConversationIdFactoryOptions options) {
if (options == null) {
Async.completeExceptionally(new IllegalArgumentException("options cannot be null."));
}
ConversationReference conversationReference = options.getActivity().getConversationReference();
String skillConversationId = String.format(
"%s-%s-%s-skillconvo",
conversationReference.getConversation().getId(),
options.getBotFrameworkSkill().getId(),
conversationReference.getChannelId()
);
String skillConversationId = String.format("%s-%s-%s-skillconvo",
conversationReference.getConversation().getId(), options.getBotFrameworkSkill().getId(),
conversationReference.getChannelId());
SkillConversationReference skillConversationReference = new SkillConversationReference();
skillConversationReference.setConversationReference(conversationReference);
@ -51,9 +61,20 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
Map<String, Object> skillConversationInfo = new HashMap<String, Object>();
skillConversationInfo.put(skillConversationId, skillConversationReference);
return storage.write(skillConversationInfo)
.thenCompose(result -> CompletableFuture.completedFuture(skillConversationId));
.thenCompose(result -> CompletableFuture.completedFuture(skillConversationId));
}
/**
* Gets the {@link SkillConversationReference} created using
* {@link SkillConversationIdFactory#createSkillConversationId} for a
* skillConversationId.
*
* @param skillConversationId A skill conversationId created using
* {@link SkillConversationIdFactory#createSkillConversationId}.
*
* @return The caller's {@link ConversationReference} for a skillConversationId.
* null if not found.
*/
@Override
public CompletableFuture<SkillConversationReference> getSkillConversationReference(String skillConversationId) {
if (StringUtils.isAllBlank(skillConversationId)) {
@ -63,13 +84,22 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
return storage.read(new String[] {skillConversationId}).thenCompose(skillConversationInfo -> {
if (skillConversationInfo.size() > 0) {
return CompletableFuture
.completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId));
.completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId));
} else {
return CompletableFuture.completedFuture(null);
}
});
}
/**
* Deletes a {@link ConversationReference} .
*
* @param skillConversationId A skill conversationId created using {@link
* CreateSkillConversationId(SkillConversationIdFactoryOptions,System#getT
* reading()#getCancellationToken())} .
*
* @return A {@link CompletableFuture} representing the asynchronous operation.
*/
@Override
public CompletableFuture<Void> deleteConversationReference(String skillConversationId) {
return storage.delete(new String[] {skillConversationId});

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.builder;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
import com.microsoft.bot.builder.skills.BotFrameworkSkill;
import com.microsoft.bot.builder.skills.SkillConversationIdFactory;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions;
import com.microsoft.bot.builder.skills.SkillConversationReference;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ConversationAccount;
import com.microsoft.bot.schema.ConversationReference;
import org.junit.Test;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
public class SkillConversationIdFactoryTests {
private static final String SERVICE_URL = "http://testbot.com/api/messages";
private final String skillId = "skill";
private final SkillConversationIdFactory skillConversationIdFactory =
new SkillConversationIdFactory(new MemoryStorage());
private final String applicationId = UUID.randomUUID().toString();
private final String botId = UUID.randomUUID().toString();
@Test
public void SkillConversationIdFactoryHappyPath() {
ConversationReference conversationReference = buildConversationReference();
// Create skill conversation
SkillConversationIdFactoryOptions options = new SkillConversationIdFactoryOptions();
options.setActivity(buildMessageActivity(conversationReference));
options.setBotFrameworkSkill(this.buildBotFrameworkSkill());
options.setFromBotId(botId);
options.setFromBotOAuthScope(botId);
String skillConversationId = skillConversationIdFactory.createSkillConversationId(options).join();
Assert.assertFalse(StringUtils.isBlank(skillConversationId));
// Retrieve skill conversation
SkillConversationReference retrievedConversationReference =
skillConversationIdFactory.getSkillConversationReference(skillConversationId).join();
// Delete
skillConversationIdFactory.deleteConversationReference(skillConversationId);
// Retrieve again
SkillConversationReference deletedConversationReference =
skillConversationIdFactory.getSkillConversationReference(skillConversationId).join();
Assert.assertNotNull(retrievedConversationReference);
Assert.assertNotNull(retrievedConversationReference.getConversationReference());
Assert.assertTrue(compareConversationReferences(conversationReference,
retrievedConversationReference.getConversationReference()));
Assert.assertNull(deletedConversationReference);
}
private static ConversationReference buildConversationReference() {
ConversationReference conversationReference = new ConversationReference();
conversationReference.setConversation(new ConversationAccount(UUID.randomUUID().toString()));
conversationReference.setServiceUrl(SERVICE_URL);
return conversationReference;
}
private static Activity buildMessageActivity(ConversationReference conversationReference) {
if (conversationReference == null) {
throw new IllegalArgumentException("conversationReference cannot be null.");
}
Activity activity = Activity.createMessageActivity();
activity.applyConversationReference(conversationReference);
return activity;
}
private BotFrameworkSkill buildBotFrameworkSkill() {
BotFrameworkSkill skill = new BotFrameworkSkill();
skill.setAppId(applicationId);
skill.setId(skillId);
try {
skill.setSkillEndpoint(new URI(SERVICE_URL));
} catch (URISyntaxException e) {
e.printStackTrace();
}
return skill;
}
private static boolean compareConversationReferences(
ConversationReference reference1,
ConversationReference reference2
) {
return reference1.getConversation().getId() == reference2.getConversation().getId()
&& reference1.getServiceUrl() == reference2.getServiceUrl();
}
}

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.connector.authentication;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.microsoft.bot.connector.Async;
/**
* Sample claims validator that loads an allowed list from configuration if
* presentand checks that requests are coming from allowed parent bots.
*/
public class AllowedCallersClaimsValidator extends ClaimsValidator {
private List<String> allowedCallers;
/**
* Creates an instance of an {@link AllowedCallersClaimsValidator}.
* @param withAllowedCallers A List<String> that contains the list of allowed callers.
*/
public AllowedCallersClaimsValidator(List<String> withAllowedCallers) {
this.allowedCallers = withAllowedCallers != null ? withAllowedCallers : new ArrayList<String>();
}
/**
* Validates a Map of claims and should throw an exception if the
* validation fails.
*
* @param claims The Map of claims to validate.
*
* @return true if the validation is successful, false if not.
*/
@Override
public CompletableFuture<Void> validateClaims(Map<String, String> claims) {
if (claims == null) {
return Async.completeExceptionally(new IllegalArgumentException("Claims cannot be null"));
}
// If _allowedCallers contains an "*", we allow all callers.
if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) {
// Check that the appId claim in the skill request instanceof in the list of
// callers configured for this bot.
String appId = JwtTokenValidation.getAppIdFromClaims(claims);
if (!allowedCallers.contains(appId)) {
return Async.completeExceptionally(
new RuntimeException(
String.format(
"Received a request from a bot with an app ID of \"%s\". To enable requests from this "
+ "caller, add the app ID to the configured set of allowedCallers.",
appId
)
)
);
}
}
return CompletableFuture.completedFuture(null);
}
}

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.connector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator;
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Assert;
import org.junit.Test;
public class AllowedCallersClaimsValidationTests {
private final String version = "1.0";
private final String audienceClaim = UUID.randomUUID().toString();
public static List<Pair<String, List<String>>> getConfigureServicesSucceedsData() {
String primaryAppId = UUID.randomUUID().toString();
String secondaryAppId = UUID.randomUUID().toString();
List<Pair<String, List<String>>> resultList = new ArrayList<Pair<String, List<String>>>();
// Null allowed callers
resultList.add(Pair.of(null, null));
// Null configuration with attempted caller
resultList.add(Pair.of(primaryAppId, null));
// Empty allowed callers array
resultList.add(Pair.of(null, new ArrayList<String>()));
// Allow any caller
resultList.add(Pair.of(primaryAppId, new ArrayList<String>() { { add("*"); } }));
// Specify allowed caller
resultList.add((Pair.of(primaryAppId, new ArrayList<String>() { { add(primaryAppId); } })));
// Specify multiple callers
resultList.add((Pair.of(primaryAppId, new ArrayList<String>() { { add(primaryAppId);
add(secondaryAppId); } })));
// Blocked caller throws exception
resultList.add((Pair.of(primaryAppId, new ArrayList<String>() { { add(secondaryAppId); } })));
return resultList;
}
@Test
public void TestAcceptAllowedCallersArray() {
List<Pair<String, List<String>>> configuredServices = getConfigureServicesSucceedsData();
for (Pair<String, List<String>> item : configuredServices) {
acceptAllowedCallersArray(item.getLeft(), item.getRight());
}
}
public void acceptAllowedCallersArray(String allowedCallerClaimId, List<String> allowList) {
AllowedCallersClaimsValidator validator = new AllowedCallersClaimsValidator(allowList);
if (allowedCallerClaimId != null) {
Map<String, String> claims = createCallerClaims(allowedCallerClaimId);
if (allowList != null) {
if (allowList.contains(allowedCallerClaimId) || allowList.contains("*")) {
validator.validateClaims(claims);
} else {
validateUnauthorizedAccessException(allowedCallerClaimId, validator, claims);
}
} else {
validateUnauthorizedAccessException(allowedCallerClaimId, validator, claims);
}
}
}
private static void validateUnauthorizedAccessException(
String allowedCallerClaimId,
AllowedCallersClaimsValidator validator,
Map<String, String> claims) {
try {
validator.validateClaims(claims);
} catch (RuntimeException exception) {
Assert.assertTrue(exception.getMessage().contains(allowedCallerClaimId));
}
}
private Map<String, String> createCallerClaims(String appId) {
Map<String, String> callerClaimMap = new HashMap<String, String>();
callerClaimMap.put(AuthenticationConstants.APPID_CLAIM, appId);
callerClaimMap.put(AuthenticationConstants.VERSION_CLAIM, version);
callerClaimMap.put(AuthenticationConstants.AUDIENCE_CLAIM, audienceClaim);
return callerClaimMap;
}
}

Просмотреть файл

@ -84,6 +84,12 @@
<version>4.6.0-preview9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-builder</artifactId>
<version>4.6.0-preview9</version>
<scope>compile</scope>
</dependency>
</dependencies>
<profiles>

Просмотреть файл

@ -8,6 +8,7 @@ import com.microsoft.bot.builder.BotAdapter;
import com.microsoft.bot.builder.ChannelServiceHandler;
import com.microsoft.bot.builder.ConversationState;
import com.microsoft.bot.builder.MemoryStorage;
import com.microsoft.bot.builder.skills.SkillConversationIdFactory;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase;
import com.microsoft.bot.builder.skills.SkillHandler;
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
@ -110,7 +111,7 @@ public class Application extends BotDependencyConfiguration {
@Bean
public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase() {
return new SkillConversationIdFactory();
return new SkillConversationIdFactory(getStorage());
}
@Bean public ChannelServiceHandler getChannelServiceHandler(

Просмотреть файл

@ -1,51 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.sample.simplerootbot;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions;
import com.microsoft.bot.builder.skills.SkillConversationReference;
/**
* A {@link SkillConversationIdFactory} that uses an in memory
* {@link Map{TKey,TValue}} to store and retrieve {@link ConversationReference}
* instances.
*/
public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
private final Map<String, SkillConversationReference> _conversationRefs =
new HashMap<String, SkillConversationReference>();
@Override
public CompletableFuture<String> createSkillConversationId(SkillConversationIdFactoryOptions options) {
SkillConversationReference skillConversationReference = new SkillConversationReference();
skillConversationReference.setConversationReference(options.getActivity().getConversationReference());
skillConversationReference.setOAuthScope(options.getFromBotOAuthScope());
String key = String.format(
"%s-%s-%s-%s-skillconvo",
options.getFromBotId(),
options.getBotFrameworkSkill().getAppId(),
skillConversationReference.getConversationReference().getConversation().getId(),
skillConversationReference.getConversationReference().getChannelId()
);
_conversationRefs.put(key, skillConversationReference);
return CompletableFuture.completedFuture(key);
}
@Override
public CompletableFuture<SkillConversationReference> getSkillConversationReference(String skillConversationId) {
SkillConversationReference conversationReference = _conversationRefs.get(skillConversationId);
return CompletableFuture.completedFuture(conversationReference);
}
@Override
public CompletableFuture<Void> deleteConversationReference(String skillConversationId) {
_conversationRefs.remove(skillConversationId);
return CompletableFuture.completedFuture(null);
}
}

Просмотреть файл

@ -3,13 +3,15 @@
package com.microsoft.bot.sample.echoskillbot;
import java.util.Arrays;
import com.microsoft.bot.builder.Bot;
import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator;
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
import com.microsoft.bot.integration.Configuration;
import com.microsoft.bot.integration.spring.BotController;
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
import com.microsoft.bot.sample.echoskillbot.authentication.AllowedCallersClaimsValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -35,6 +37,8 @@ import org.springframework.context.annotation.Import;
*/
public class Application extends BotDependencyConfiguration {
private final String configKey = "AllowedCallers";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@ -57,7 +61,9 @@ public class Application extends BotDependencyConfiguration {
@Override
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration));
authenticationConfiguration.setClaimsValidator(
new AllowedCallersClaimsValidator(Arrays.asList(configuration.getProperties(configKey)))
);
return authenticationConfiguration;
}

Просмотреть файл

@ -1,67 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.sample.echoskillbot.authentication;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.microsoft.bot.connector.Async;
import com.microsoft.bot.connector.authentication.ClaimsValidator;
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
import com.microsoft.bot.connector.authentication.SkillValidation;
import com.microsoft.bot.integration.Configuration;
/**
* Sample claims validator that loads an allowed list from configuration if
* presentand checks that requests are coming from allowed parent bots.
*/
public class AllowedCallersClaimsValidator extends ClaimsValidator {
private final String configKey = "AllowedCallers";
private final List<String> allowedCallers;
public AllowedCallersClaimsValidator(Configuration config) {
if (config == null) {
throw new IllegalArgumentException("config cannot be null.");
}
// AllowedCallers instanceof the setting in the application.properties file
// that consists of the list of parent bot Ds that are allowed to access the
// skill.
// To add a new parent bot, simply edit the AllowedCallers and add
// the parent bot's Microsoft app ID to the list.
// In this sample, we allow all callers if AllowedCallers contains an "*".
String[] appsList = config.getProperties(configKey);
if (appsList == null) {
throw new IllegalStateException(String.format("\"%s\" not found in configuration.", configKey));
}
allowedCallers = Arrays.asList(appsList);
}
@Override
public CompletableFuture<Void> validateClaims(Map<String, String> claims) {
// If _allowedCallers contains an "*", we allow all callers.
if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) {
// Check that the appId claim in the skill request instanceof in the list of
// callers configured for this bot.
String appId = JwtTokenValidation.getAppIdFromClaims(claims);
if (!allowedCallers.contains(appId)) {
return Async.completeExceptionally(
new RuntimeException(
String.format(
"Received a request from a bot with an app ID of \"%s\". "
+ "To enable requests from this caller, add the app ID to your configuration file.",
appId
)
)
);
}
}
return CompletableFuture.completedFuture(null);
}
}

Просмотреть файл

@ -8,6 +8,7 @@ import com.microsoft.bot.builder.BotAdapter;
import com.microsoft.bot.builder.ChannelServiceHandler;
import com.microsoft.bot.builder.ConversationState;
import com.microsoft.bot.builder.Storage;
import com.microsoft.bot.builder.skills.SkillConversationIdFactory;
import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase;
import com.microsoft.bot.builder.skills.SkillHandler;
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
@ -18,8 +19,8 @@ import com.microsoft.bot.integration.Configuration;
import com.microsoft.bot.integration.SkillHttpClient;
import com.microsoft.bot.integration.spring.BotController;
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
import com.microsoft.bot.sample.dialogrootbot.Bots.RootBot;
import com.microsoft.bot.sample.dialogrootbot.authentication.AllowedSkillsClaimsValidator;
import com.microsoft.bot.sample.dialogrootbot.bots.RootBot;
import com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog;
import org.springframework.boot.SpringApplication;

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.sample.dialogrootbot.Bots;
package com.microsoft.bot.sample.dialogrootbot.bots;
import java.io.IOException;
import java.io.InputStream;

Просмотреть файл

@ -3,14 +3,16 @@
package com.microsoft.bot.sample.dialogskillbot;
import java.util.Arrays;
import com.microsoft.bot.builder.Bot;
import com.microsoft.bot.builder.ConversationState;
import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator;
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
import com.microsoft.bot.integration.Configuration;
import com.microsoft.bot.integration.spring.BotController;
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
import com.microsoft.bot.sample.dialogskillbot.authentication.AllowedCallersClaimsValidator;
import com.microsoft.bot.sample.dialogskillbot.bots.SkillBot;
import com.microsoft.bot.sample.dialogskillbot.dialogs.ActivityRouterDialog;
import com.microsoft.bot.sample.dialogskillbot.dialogs.DialogSkillBotRecognizer;
@ -39,6 +41,8 @@ import org.springframework.context.annotation.Import;
*/
public class Application extends BotDependencyConfiguration {
private final String configKey = "AllowedCallers";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@ -65,7 +69,9 @@ public class Application extends BotDependencyConfiguration {
@Override
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration));
authenticationConfiguration.setClaimsValidator(
new AllowedCallersClaimsValidator(Arrays.asList(configuration.getProperties(configKey)))
);
return authenticationConfiguration;
}

Просмотреть файл

@ -1,67 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MT License.
package com.microsoft.bot.sample.dialogskillbot.authentication;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.microsoft.bot.connector.Async;
import com.microsoft.bot.connector.authentication.ClaimsValidator;
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
import com.microsoft.bot.connector.authentication.SkillValidation;
import com.microsoft.bot.integration.Configuration;
/**
* Sample claims validator that loads an allowed list from configuration if
* presentand checks that requests are coming from allowed parent bots.
*/
public class AllowedCallersClaimsValidator extends ClaimsValidator {
private final String configKey = "AllowedCallers";
private final List<String> allowedCallers;
public AllowedCallersClaimsValidator(Configuration config) {
if (config == null) {
throw new IllegalArgumentException("config cannot be null.");
}
// AllowedCallers instanceof the setting in the application.properties file
// that consists of the list of parent bot Ds that are allowed to access the
// skill.
// To add a new parent bot, simply edit the AllowedCallers and add
// the parent bot's Microsoft app ID to the list.
// In this sample, we allow all callers if AllowedCallers contains an "*".
String[] appsList = config.getProperties(configKey);
if (appsList == null) {
throw new IllegalStateException(String.format("\"%s\" not found in configuration.", configKey));
}
allowedCallers = Arrays.asList(appsList);
}
@Override
public CompletableFuture<Void> validateClaims(Map<String, String> claims) {
// If _allowedCallers contains an "*", we allow all callers.
if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) {
// Check that the appId claim in the skill request instanceof in the list of
// callers configured for this bot.
String appId = JwtTokenValidation.getAppIdFromClaims(claims);
if (!allowedCallers.contains(appId)) {
return Async.completeExceptionally(
new RuntimeException(
String.format(
"Received a request from a bot with an app ID of \"%s\". "
+ "To enable requests from this caller, add the app ID to your configuration file.",
appId
)
)
);
}
}
return CompletableFuture.completedFuture(null);
}
}