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:
Родитель
466fd90b80
Коммит
837dca217f
|
@ -1,16 +1,13 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MT License.
|
// Licensed under the MT License.
|
||||||
|
|
||||||
package com.microsoft.bot.sample.dialogrootbot;
|
package com.microsoft.bot.builder.skills;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import com.microsoft.bot.builder.Storage;
|
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.connector.Async;
|
||||||
import com.microsoft.bot.schema.ConversationReference;
|
import com.microsoft.bot.schema.ConversationReference;
|
||||||
|
|
||||||
|
@ -25,6 +22,11 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
|
||||||
|
|
||||||
private Storage storage;
|
private Storage storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of a SkillConversationIdFactory.
|
||||||
|
*
|
||||||
|
* @param storage A storage instance for the factory.
|
||||||
|
*/
|
||||||
public SkillConversationIdFactory(Storage storage) {
|
public SkillConversationIdFactory(Storage storage) {
|
||||||
if (storage == null) {
|
if (storage == null) {
|
||||||
throw new IllegalArgumentException("Storage cannot be null.");
|
throw new IllegalArgumentException("Storage cannot be null.");
|
||||||
|
@ -32,18 +34,26 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
|
||||||
this.storage = storage;
|
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
|
@Override
|
||||||
public CompletableFuture<String> createSkillConversationId(SkillConversationIdFactoryOptions options) {
|
public CompletableFuture<String> createSkillConversationId(SkillConversationIdFactoryOptions options) {
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
Async.completeExceptionally(new IllegalArgumentException("options cannot be null."));
|
Async.completeExceptionally(new IllegalArgumentException("options cannot be null."));
|
||||||
}
|
}
|
||||||
ConversationReference conversationReference = options.getActivity().getConversationReference();
|
ConversationReference conversationReference = options.getActivity().getConversationReference();
|
||||||
String skillConversationId = String.format(
|
String skillConversationId = String.format("%s-%s-%s-skillconvo",
|
||||||
"%s-%s-%s-skillconvo",
|
conversationReference.getConversation().getId(), options.getBotFrameworkSkill().getId(),
|
||||||
conversationReference.getConversation().getId(),
|
conversationReference.getChannelId());
|
||||||
options.getBotFrameworkSkill().getId(),
|
|
||||||
conversationReference.getChannelId()
|
|
||||||
);
|
|
||||||
|
|
||||||
SkillConversationReference skillConversationReference = new SkillConversationReference();
|
SkillConversationReference skillConversationReference = new SkillConversationReference();
|
||||||
skillConversationReference.setConversationReference(conversationReference);
|
skillConversationReference.setConversationReference(conversationReference);
|
||||||
|
@ -51,9 +61,20 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
|
||||||
Map<String, Object> skillConversationInfo = new HashMap<String, Object>();
|
Map<String, Object> skillConversationInfo = new HashMap<String, Object>();
|
||||||
skillConversationInfo.put(skillConversationId, skillConversationReference);
|
skillConversationInfo.put(skillConversationId, skillConversationReference);
|
||||||
return storage.write(skillConversationInfo)
|
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
|
@Override
|
||||||
public CompletableFuture<SkillConversationReference> getSkillConversationReference(String skillConversationId) {
|
public CompletableFuture<SkillConversationReference> getSkillConversationReference(String skillConversationId) {
|
||||||
if (StringUtils.isAllBlank(skillConversationId)) {
|
if (StringUtils.isAllBlank(skillConversationId)) {
|
||||||
|
@ -63,13 +84,22 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase {
|
||||||
return storage.read(new String[] {skillConversationId}).thenCompose(skillConversationInfo -> {
|
return storage.read(new String[] {skillConversationId}).thenCompose(skillConversationInfo -> {
|
||||||
if (skillConversationInfo.size() > 0) {
|
if (skillConversationInfo.size() > 0) {
|
||||||
return CompletableFuture
|
return CompletableFuture
|
||||||
.completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId));
|
.completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId));
|
||||||
} else {
|
} else {
|
||||||
return CompletableFuture.completedFuture(null);
|
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
|
@Override
|
||||||
public CompletableFuture<Void> deleteConversationReference(String skillConversationId) {
|
public CompletableFuture<Void> deleteConversationReference(String skillConversationId) {
|
||||||
return storage.delete(new 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>
|
<version>4.6.0-preview9</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.microsoft.bot</groupId>
|
||||||
|
<artifactId>bot-builder</artifactId>
|
||||||
|
<version>4.6.0-preview9</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.microsoft.bot.builder.BotAdapter;
|
||||||
import com.microsoft.bot.builder.ChannelServiceHandler;
|
import com.microsoft.bot.builder.ChannelServiceHandler;
|
||||||
import com.microsoft.bot.builder.ConversationState;
|
import com.microsoft.bot.builder.ConversationState;
|
||||||
import com.microsoft.bot.builder.MemoryStorage;
|
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.SkillConversationIdFactoryBase;
|
||||||
import com.microsoft.bot.builder.skills.SkillHandler;
|
import com.microsoft.bot.builder.skills.SkillHandler;
|
||||||
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
||||||
|
@ -110,7 +111,7 @@ public class Application extends BotDependencyConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase() {
|
public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase() {
|
||||||
return new SkillConversationIdFactory();
|
return new SkillConversationIdFactory(getStorage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean public ChannelServiceHandler getChannelServiceHandler(
|
@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;
|
package com.microsoft.bot.sample.echoskillbot;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import com.microsoft.bot.builder.Bot;
|
import com.microsoft.bot.builder.Bot;
|
||||||
|
import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator;
|
||||||
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
||||||
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
|
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
|
||||||
import com.microsoft.bot.integration.Configuration;
|
import com.microsoft.bot.integration.Configuration;
|
||||||
import com.microsoft.bot.integration.spring.BotController;
|
import com.microsoft.bot.integration.spring.BotController;
|
||||||
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
|
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
|
||||||
import com.microsoft.bot.sample.echoskillbot.authentication.AllowedCallersClaimsValidator;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
@ -35,6 +37,8 @@ import org.springframework.context.annotation.Import;
|
||||||
*/
|
*/
|
||||||
public class Application extends BotDependencyConfiguration {
|
public class Application extends BotDependencyConfiguration {
|
||||||
|
|
||||||
|
private final String configKey = "AllowedCallers";
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(Application.class, args);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +61,9 @@ public class Application extends BotDependencyConfiguration {
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
|
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
|
||||||
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
|
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
|
||||||
authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration));
|
authenticationConfiguration.setClaimsValidator(
|
||||||
|
new AllowedCallersClaimsValidator(Arrays.asList(configuration.getProperties(configKey)))
|
||||||
|
);
|
||||||
return authenticationConfiguration;
|
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.ChannelServiceHandler;
|
||||||
import com.microsoft.bot.builder.ConversationState;
|
import com.microsoft.bot.builder.ConversationState;
|
||||||
import com.microsoft.bot.builder.Storage;
|
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.SkillConversationIdFactoryBase;
|
||||||
import com.microsoft.bot.builder.skills.SkillHandler;
|
import com.microsoft.bot.builder.skills.SkillHandler;
|
||||||
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
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.SkillHttpClient;
|
||||||
import com.microsoft.bot.integration.spring.BotController;
|
import com.microsoft.bot.integration.spring.BotController;
|
||||||
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
|
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.authentication.AllowedSkillsClaimsValidator;
|
||||||
|
import com.microsoft.bot.sample.dialogrootbot.bots.RootBot;
|
||||||
import com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog;
|
import com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MT License.
|
// 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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
|
@ -3,14 +3,16 @@
|
||||||
|
|
||||||
package com.microsoft.bot.sample.dialogskillbot;
|
package com.microsoft.bot.sample.dialogskillbot;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import com.microsoft.bot.builder.Bot;
|
import com.microsoft.bot.builder.Bot;
|
||||||
import com.microsoft.bot.builder.ConversationState;
|
import com.microsoft.bot.builder.ConversationState;
|
||||||
|
import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator;
|
||||||
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
||||||
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
|
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
|
||||||
import com.microsoft.bot.integration.Configuration;
|
import com.microsoft.bot.integration.Configuration;
|
||||||
import com.microsoft.bot.integration.spring.BotController;
|
import com.microsoft.bot.integration.spring.BotController;
|
||||||
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
|
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.bots.SkillBot;
|
||||||
import com.microsoft.bot.sample.dialogskillbot.dialogs.ActivityRouterDialog;
|
import com.microsoft.bot.sample.dialogskillbot.dialogs.ActivityRouterDialog;
|
||||||
import com.microsoft.bot.sample.dialogskillbot.dialogs.DialogSkillBotRecognizer;
|
import com.microsoft.bot.sample.dialogskillbot.dialogs.DialogSkillBotRecognizer;
|
||||||
|
@ -39,6 +41,8 @@ import org.springframework.context.annotation.Import;
|
||||||
*/
|
*/
|
||||||
public class Application extends BotDependencyConfiguration {
|
public class Application extends BotDependencyConfiguration {
|
||||||
|
|
||||||
|
private final String configKey = "AllowedCallers";
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(Application.class, args);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +69,9 @@ public class Application extends BotDependencyConfiguration {
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
|
public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) {
|
||||||
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
|
AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration();
|
||||||
authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration));
|
authenticationConfiguration.setClaimsValidator(
|
||||||
|
new AllowedCallersClaimsValidator(Arrays.asList(configuration.getProperties(configKey)))
|
||||||
|
);
|
||||||
return authenticationConfiguration;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
Загрузка…
Ссылка в новой задаче