Merge pull request #97 from microsoft/tb-auth

Updated Connector Authorization
This commit is contained in:
tracyboehrer 2019-09-03 15:29:19 -05:00 коммит произвёл GitHub
Родитель d848076788 32960b9f8d
Коммит 3014338782
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
74 изменённых файлов: 6114 добавлений и 4299 удалений

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

@ -10,7 +10,7 @@ import com.microsoft.bot.connector.customizations.CredentialProvider;
import com.microsoft.bot.connector.customizations.CredentialProviderImpl;
import com.microsoft.bot.connector.customizations.JwtTokenValidation;
import com.microsoft.bot.connector.customizations.MicrosoftAppCredentials;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.rest.ConnectorClientImpl;
import com.microsoft.bot.schema.models.Activity;
import com.microsoft.bot.schema.models.ActivityTypes;
import com.microsoft.bot.schema.models.ResourceResponse;

179
etc/bot-checkstyle.xml Normal file
Просмотреть файл

@ -0,0 +1,179 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the sun coding conventions from:
- the Java Language Specification at
https://docs.oracle.com/javase/specs/jls/se11/html/index.html
- the Sun Code Conventions at https://www.oracle.com/technetwork/java/codeconvtoc-136057.html
- the Javadoc guidelines at
https://www.oracle.com/technetwork/java/javase/documentation/index-137868.html
- the JDK Api documentation https://docs.oracle.com/en/java/javase/11/
- some best practices
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sourceforge.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
https://checkstyle.org/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocPackage -->
<module name="JavadocPackage"/>
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See http://checkstyle.sourceforge.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html -->
<module name="JavadocMethod"/>
<module name="JavadocType"/>
<module name="JavadocVariable"/>
<module name="JavadocStyle"/>
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sourceforge.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports">
<property name="processJavadoc" value="false"/>
</module>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="LineLength">
<property name="max" value="120"/>
</module>
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sourceforge.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sourceforge.net/config_coding.html -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="HiddenField"/>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MagicNumber"/>
<module name="MissingSwitchDefault"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
<module name="DesignForExtension"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
</module>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,310 +1,300 @@
package com.microsoft.bot.builder;
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import com.microsoft.bot.schema.models.Activity;
import org.joda.time.DateTime;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* The memory transcript store stores transcripts in volatile memory in a Dictionary.
* <p>
* <p>
* Because this uses an unbounded volitile dictionary this should only be used for unit tests or non-production environments.
*/
public class MemoryTranscriptStore implements TranscriptStore {
private HashMap<String, HashMap<String, ArrayList<Activity>>> channels = new HashMap<String, HashMap<String, ArrayList<Activity>>>();
final ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory() {
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName("TestFlow-" + worker.getPoolIndex());
return worker;
}
};
final ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, false);
/**
* Logs an activity to the transcript.
*
* @param activity The activity to log.
* @return A CompletableFuture that represents the work queued to execute.
*/
public final void LogActivityAsync(Activity activity) {
if (activity == null) {
throw new NullPointerException("activity cannot be null for LogActivity()");
}
synchronized (this.channels) {
HashMap<String, ArrayList<Activity>> channel;
if (!this.channels.containsKey(activity.channelId())) {
channel = new HashMap<String, ArrayList<Activity>>();
this.channels.put(activity.channelId(), channel);
} else {
channel = this.channels.get(activity.channelId());
}
ArrayList<Activity> transcript = null;
if (!channel.containsKey(activity.conversation().id())) {
transcript = new ArrayList<Activity>();
channel.put(activity.conversation().id(), transcript);
} else {
transcript = channel.get(activity.conversation().id());
}
transcript.add(activity);
}
}
/**
* Gets from the store activities that match a set of criteria.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation.
* @param continuationToken
* @return A task that represents the work queued to execute.
* If the task completes successfully, the result contains the matching activities.
*/
public final CompletableFuture<PagedResult<Activity>> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken) {
return GetTranscriptActivitiesAsync(channelId, conversationId, continuationToken, null);
}
/**
* Gets from the store activities that match a set of criteria.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation.
* @return A task that represents the work queued to execute.
* If the task completes successfully, the result contains the matching activities.
*/
public final CompletableFuture<PagedResult<Activity>> GetTranscriptActivitiesAsync(String channelId, String conversationId) {
return GetTranscriptActivitiesAsync(channelId, conversationId, null, null);
}
/**
* Gets from the store activities that match a set of criteria.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation.
* @param continuationToken
* @param startDate A cutoff date. Activities older than this date are not included.
* @return A task that represents the work queued to execute.
* If the task completes successfully, the result contains the matching activities.
*/
public final CompletableFuture<PagedResult<Activity>> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime startDate) {
return CompletableFuture.supplyAsync(() -> {
if (channelId == null) {
throw new NullPointerException(String.format("missing %1$s", "channelId"));
}
if (conversationId == null) {
throw new NullPointerException(String.format("missing %1$s", "conversationId"));
}
PagedResult<Activity> pagedResult = new PagedResult<Activity>();
synchronized (channels) {
HashMap<String, ArrayList<Activity>> channel;
if (!channels.containsKey(channelId)) {
return pagedResult;
}
channel = channels.get(channelId);
ArrayList<Activity> transcript;
if (!channel.containsKey(conversationId)) {
return pagedResult;
}
transcript = channel.get(conversationId);
if (continuationToken != null) {
List<Activity> items = transcript.stream()
.sorted(Comparator.comparing(Activity::timestamp))
.filter(a -> a.timestamp().compareTo(startDate) >= 0)
.filter(skipwhile(a -> !a.id().equals(continuationToken)))
.skip(1)
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Activity[items.size()]));
if (pagedResult.getItems().length == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).id());
}
} else {
List<Activity> items = transcript.stream()
.sorted(Comparator.comparing(Activity::timestamp))
.filter(a -> a.timestamp().compareTo((startDate == null) ? new DateTime(Long.MIN_VALUE) : startDate) >= 0)
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Activity[items.size()]));
if (items.size() == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).id());
}
}
}
return pagedResult;
}, this.executor);
}
/**
* Deletes conversation data from the store.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation to delete.
* @return A task that represents the work queued to execute.
*/
public final CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId) {
return CompletableFuture.runAsync(() -> {
if (channelId == null) {
throw new NullPointerException(String.format("%1$s should not be null", "channelId"));
}
if (conversationId == null) {
throw new NullPointerException(String.format("%1$s should not be null", "conversationId"));
}
synchronized (this.channels) {
if (!this.channels.containsKey(channelId)) {
return;
}
HashMap<String, ArrayList<Activity>> channel = this.channels.get(channelId);
if (channel.containsKey(conversationId)) {
channel.remove(conversationId);
}
}
}, this.executor);
}
/**
* Gets the conversations on a channel from the store.
*
* @param channelId The ID of the channel.
* @return A task that represents the work queued to execute.
*/
public final CompletableFuture<PagedResult<Transcript>> ListTranscriptsAsync(String channelId) {
return ListTranscriptsAsync(channelId, null);
}
/**
* Gets the conversations on a channel from the store.
*
* @param channelId The ID of the channel.
* @param continuationToken
* @return A task that represents the work queued to execute.
*/
public final CompletableFuture<PagedResult<Transcript>> ListTranscriptsAsync(String channelId, String continuationToken) {
return CompletableFuture.supplyAsync(() -> {
if (channelId == null) {
throw new NullPointerException(String.format("missing %1$s", "channelId"));
}
PagedResult<Transcript> pagedResult = new PagedResult<Transcript>();
synchronized (channels) {
if (!channels.containsKey(channelId)) {
return pagedResult;
}
HashMap<String, ArrayList<Activity>> channel = channels.get(channelId);
if (continuationToken != null) {
List<Transcript> items = channel.entrySet().stream()
.map(c -> {
OffsetDateTime offsetDateTime = null;
if (c.getValue().stream().findFirst().isPresent()) {
DateTime dt = c.getValue().stream().findFirst().get().timestamp();
// convert to DateTime to OffsetDateTime
Instant instant = Instant.ofEpochMilli(dt.getMillis());
ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
offsetDateTime = instant.atOffset(offset);
} else {
offsetDateTime = OffsetDateTime.now();
}
return new Transcript()
.withChannelId(channelId)
.withId(c.getKey())
.withCreated(offsetDateTime);
}
)
.sorted(Comparator.comparing(Transcript::getCreated))
.filter(skipwhile(c -> !c.getId().equals(continuationToken)))
.skip(1)
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Transcript[items.size()]));
if (items.size() == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
}
} else {
List<Transcript> items = channel.entrySet().stream()
.map(c -> {
OffsetDateTime offsetDateTime = null;
if (c.getValue().stream().findFirst().isPresent()) {
DateTime dt = c.getValue().stream().findFirst().get().timestamp();
// convert to DateTime to OffsetDateTime
Instant instant = Instant.ofEpochMilli(dt.getMillis());
ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
offsetDateTime = instant.atOffset(offset);
} else {
offsetDateTime = OffsetDateTime.now();
}
return new Transcript()
.withChannelId(channelId)
.withId(c.getKey())
.withCreated(offsetDateTime);
}
)
.sorted(Comparator.comparing(Transcript::getCreated))
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Transcript[items.size()]));
if (items.size() == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
}
}
}
return pagedResult;
}, this.executor);
}
/**
* Emulate C# SkipWhile.
* Stateful
*
* @param func1 predicate to apply
* @param <T> type
* @return if the predicate condition is true
*/
public static <T> Predicate<T> skipwhile(Function<? super T, Object> func1) {
final boolean[] started = {false};
return t -> started[0] || (started[0] = (boolean) func1.apply(t));
}
}
package com.microsoft.bot.builder;
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.schema.models.Activity;
import org.joda.time.DateTime;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* The memory transcript store stores transcripts in volatile memory in a Dictionary.
* <p>
* <p>
* Because this uses an unbounded volitile dictionary this should only be used for unit tests or non-production environments.
*/
public class MemoryTranscriptStore implements TranscriptStore {
private HashMap<String, HashMap<String, ArrayList<Activity>>> channels = new HashMap<String, HashMap<String, ArrayList<Activity>>>();
/**
* Logs an activity to the transcript.
*
* @param activity The activity to log.
* @return A CompletableFuture that represents the work queued to execute.
*/
public final void LogActivityAsync(Activity activity) {
if (activity == null) {
throw new NullPointerException("activity cannot be null for LogActivity()");
}
synchronized (this.channels) {
HashMap<String, ArrayList<Activity>> channel;
if (!this.channels.containsKey(activity.channelId())) {
channel = new HashMap<String, ArrayList<Activity>>();
this.channels.put(activity.channelId(), channel);
} else {
channel = this.channels.get(activity.channelId());
}
ArrayList<Activity> transcript = null;
if (!channel.containsKey(activity.conversation().id())) {
transcript = new ArrayList<Activity>();
channel.put(activity.conversation().id(), transcript);
} else {
transcript = channel.get(activity.conversation().id());
}
transcript.add(activity);
}
}
/**
* Gets from the store activities that match a set of criteria.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation.
* @param continuationToken
* @return A task that represents the work queued to execute.
* If the task completes successfully, the result contains the matching activities.
*/
public final CompletableFuture<PagedResult<Activity>> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken) {
return GetTranscriptActivitiesAsync(channelId, conversationId, continuationToken, null);
}
/**
* Gets from the store activities that match a set of criteria.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation.
* @return A task that represents the work queued to execute.
* If the task completes successfully, the result contains the matching activities.
*/
public final CompletableFuture<PagedResult<Activity>> GetTranscriptActivitiesAsync(String channelId, String conversationId) {
return GetTranscriptActivitiesAsync(channelId, conversationId, null, null);
}
/**
* Gets from the store activities that match a set of criteria.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation.
* @param continuationToken
* @param startDate A cutoff date. Activities older than this date are not included.
* @return A task that represents the work queued to execute.
* If the task completes successfully, the result contains the matching activities.
*/
public final CompletableFuture<PagedResult<Activity>> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime startDate) {
return CompletableFuture.supplyAsync(() -> {
if (channelId == null) {
throw new NullPointerException(String.format("missing %1$s", "channelId"));
}
if (conversationId == null) {
throw new NullPointerException(String.format("missing %1$s", "conversationId"));
}
PagedResult<Activity> pagedResult = new PagedResult<Activity>();
synchronized (channels) {
HashMap<String, ArrayList<Activity>> channel;
if (!channels.containsKey(channelId)) {
return pagedResult;
}
channel = channels.get(channelId);
ArrayList<Activity> transcript;
if (!channel.containsKey(conversationId)) {
return pagedResult;
}
transcript = channel.get(conversationId);
if (continuationToken != null) {
List<Activity> items = transcript.stream()
.sorted(Comparator.comparing(Activity::timestamp))
.filter(a -> a.timestamp().compareTo(startDate) >= 0)
.filter(skipwhile(a -> !a.id().equals(continuationToken)))
.skip(1)
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Activity[items.size()]));
if (pagedResult.getItems().length == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).id());
}
} else {
List<Activity> items = transcript.stream()
.sorted(Comparator.comparing(Activity::timestamp))
.filter(a -> a.timestamp().compareTo((startDate == null) ? new DateTime(Long.MIN_VALUE) : startDate) >= 0)
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Activity[items.size()]));
if (items.size() == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).id());
}
}
}
return pagedResult;
}, ExecutorFactory.getExecutor());
}
/**
* Deletes conversation data from the store.
*
* @param channelId The ID of the channel the conversation is in.
* @param conversationId The ID of the conversation to delete.
* @return A task that represents the work queued to execute.
*/
public final CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId) {
return CompletableFuture.runAsync(() -> {
if (channelId == null) {
throw new NullPointerException(String.format("%1$s should not be null", "channelId"));
}
if (conversationId == null) {
throw new NullPointerException(String.format("%1$s should not be null", "conversationId"));
}
synchronized (this.channels) {
if (!this.channels.containsKey(channelId)) {
return;
}
HashMap<String, ArrayList<Activity>> channel = this.channels.get(channelId);
if (channel.containsKey(conversationId)) {
channel.remove(conversationId);
}
}
}, ExecutorFactory.getExecutor());
}
/**
* Gets the conversations on a channel from the store.
*
* @param channelId The ID of the channel.
* @return A task that represents the work queued to execute.
*/
public final CompletableFuture<PagedResult<Transcript>> ListTranscriptsAsync(String channelId) {
return ListTranscriptsAsync(channelId, null);
}
/**
* Gets the conversations on a channel from the store.
*
* @param channelId The ID of the channel.
* @param continuationToken
* @return A task that represents the work queued to execute.
*/
public final CompletableFuture<PagedResult<Transcript>> ListTranscriptsAsync(String channelId, String continuationToken) {
return CompletableFuture.supplyAsync(() -> {
if (channelId == null) {
throw new NullPointerException(String.format("missing %1$s", "channelId"));
}
PagedResult<Transcript> pagedResult = new PagedResult<Transcript>();
synchronized (channels) {
if (!channels.containsKey(channelId)) {
return pagedResult;
}
HashMap<String, ArrayList<Activity>> channel = channels.get(channelId);
if (continuationToken != null) {
List<Transcript> items = channel.entrySet().stream()
.map(c -> {
OffsetDateTime offsetDateTime = null;
if (c.getValue().stream().findFirst().isPresent()) {
DateTime dt = c.getValue().stream().findFirst().get().timestamp();
// convert to DateTime to OffsetDateTime
Instant instant = Instant.ofEpochMilli(dt.getMillis());
ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
offsetDateTime = instant.atOffset(offset);
} else {
offsetDateTime = OffsetDateTime.now();
}
return new Transcript()
.withChannelId(channelId)
.withId(c.getKey())
.withCreated(offsetDateTime);
}
)
.sorted(Comparator.comparing(Transcript::getCreated))
.filter(skipwhile(c -> !c.getId().equals(continuationToken)))
.skip(1)
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Transcript[items.size()]));
if (items.size() == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
}
} else {
List<Transcript> items = channel.entrySet().stream()
.map(c -> {
OffsetDateTime offsetDateTime = null;
if (c.getValue().stream().findFirst().isPresent()) {
DateTime dt = c.getValue().stream().findFirst().get().timestamp();
// convert to DateTime to OffsetDateTime
Instant instant = Instant.ofEpochMilli(dt.getMillis());
ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
offsetDateTime = instant.atOffset(offset);
} else {
offsetDateTime = OffsetDateTime.now();
}
return new Transcript()
.withChannelId(channelId)
.withId(c.getKey())
.withCreated(offsetDateTime);
}
)
.sorted(Comparator.comparing(Transcript::getCreated))
.limit(20)
.collect(Collectors.toList());
pagedResult.items(items.toArray(new Transcript[items.size()]));
if (items.size() == 20) {
pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
}
}
}
return pagedResult;
}, ExecutorFactory.getExecutor());
}
/**
* Emulate C# SkipWhile.
* Stateful
*
* @param func1 predicate to apply
* @param <T> type
* @return if the predicate condition is true
*/
public static <T> Predicate<T> skipwhile(Function<? super T, Object> func1) {
final boolean[] started = {false};
return t -> started[0] || (started[0] = (boolean) func1.apply(t));
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,379 +1,379 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.builder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.adapters.TestFlow;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.schema.models.ChannelAccount;
import com.microsoft.bot.schema.models.ResourceResponse;
import com.microsoft.rest.RestClient;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
// [TestClass]
// [TestCategory("State Management")]
public class BotStateTest {
protected ConnectorClientImpl connector;
protected ChannelAccount bot;
protected ChannelAccount user;
protected void initializeClients(RestClient restClient, String botId, String userId) {
connector = new ConnectorClientImpl(restClient);
bot = new ChannelAccount().withId(botId);
user = new ChannelAccount().withId(userId);
}
protected void cleanUpResources() {
}
@Test
public void State_DoNOTRememberContextState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter();
new TestFlow(adapter, (context) -> {
TestPocoState obj = StateTurnContextExtensions.<TestPocoState>GetConversationState(context);
Assert.assertNull("context.state should not exist", obj); }
)
.Send("set value")
.StartTest();
}
//@Test
public void State_RememberIStoreItemUserState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new UserState<TestState>(new MemoryStorage(), TestState::new));
Consumer<TurnContext> callback = (context) -> {
System.out.print(String.format("State_RememberIStoreItemUserState CALLBACK called.."));
System.out.flush();
TestState userState = StateTurnContextExtensions.<TestState>GetUserState(context);
Assert.assertNotNull("user state should exist", userState);
switch (context.getActivity().text()) {
case "set value":
userState.withValue("test");
try {
((TurnContextImpl)context).SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(userState.value()));
((TurnContextImpl)context).SendActivity(userState.value());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
};
new TestFlow(adapter, callback)
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
@Test
public void State_RememberPocoUserState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new UserState<TestPocoState>(new MemoryStorage(), TestPocoState::new));
new TestFlow(adapter,
(context) ->
{
TestPocoState userState = StateTurnContextExtensions.<TestPocoState>GetUserState(context);
Assert.assertNotNull("user state should exist", userState);
switch (context.getActivity().text()) {
case "set value":
userState.setValue("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(userState.getValue()));
context.SendActivity(userState.getValue());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
//@Test
public void State_RememberIStoreItemConversationState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TestState>(new MemoryStorage(), TestState::new));
new TestFlow(adapter,
(context) ->
{
TestState conversationState = StateTurnContextExtensions.<TestState>GetConversationState(context);
Assert.assertNotNull("state.conversation should exist", conversationState);
switch (context.getActivity().text()) {
case "set value":
conversationState.withValue("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(conversationState.value()));
context.SendActivity(conversationState.value());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
//@Test
public void State_RememberPocoConversationState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TestPocoState>(new MemoryStorage(), TestPocoState::new));
new TestFlow(adapter,
(context) ->
{
TestPocoState conversationState = StateTurnContextExtensions.<TestPocoState>GetConversationState(context);
Assert.assertNotNull("state.conversation should exist", conversationState);
switch (context.getActivity().text()) {
case "set value":
conversationState.setValue("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(conversationState.getValue()));
context.SendActivity(conversationState.getValue());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
@Test
public void State_CustomStateManagerTest() throws ExecutionException, InterruptedException {
String testGuid = UUID.randomUUID().toString();
TestAdapter adapter = new TestAdapter()
.Use(new CustomKeyState(new MemoryStorage()));
new TestFlow(adapter,
(context) ->
{
CustomState customState = CustomKeyState.Get(context);
switch (context.getActivity().text()) {
case "set value":
customState.setCustomString(testGuid);
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(customState.getCustomString()));
context.SendActivity(customState.getCustomString());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", testGuid.toString())
.StartTest();
}
@Test
public void State_RoundTripTypedObjectwTrace() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TypedObject>(new MemoryStorage(), TypedObject::new));
new TestFlow(adapter,
(context) ->
{
System.out.println(String.format(">>Test Callback(tid:%s): STARTING : %s", Thread.currentThread().getId(), context.getActivity().text()));
System.out.flush();
TypedObject conversation = StateTurnContextExtensions.<TypedObject>GetConversationState(context);
Assert.assertNotNull("conversationstate should exist", conversation);
System.out.println(String.format(">>Test Callback(tid:%s): Text is : %s", Thread.currentThread().getId(), context.getActivity().text()));
System.out.flush();
switch (context.getActivity().text()) {
case "set value":
conversation.withName("test");
try {
System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
"value saved"));
System.out.flush();
ResourceResponse response = context.SendActivity("value saved");
System.out.println(String.format(">>Test Callback(tid:%s): Response Id: %s", Thread.currentThread().getId(),
response.id()));
System.out.flush();
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
"TypedObject"));
System.out.flush();
context.SendActivity("TypedObject");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Turn("set value", "value saved", "Description", 50000)
.Turn("get value", "TypedObject", "Description", 50000)
.StartTest();
}
@Test
public void State_RoundTripTypedObject() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TypedObject>(new MemoryStorage(), TypedObject::new));
new TestFlow(adapter,
(context) ->
{
TypedObject conversation = StateTurnContextExtensions.<TypedObject>GetConversationState(context);
Assert.assertNotNull("conversationstate should exist", conversation);
switch (context.getActivity().text()) {
case "set value":
conversation.withName("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
context.SendActivity("TypedObject");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "TypedObject")
.StartTest();
}
@Test
public void State_UseBotStateDirectly() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter();
new TestFlow(adapter,
(context) ->
{
BotState botStateManager = new BotState<CustomState>(new MemoryStorage(), "BotState:com.microsoft.bot.builder.core.extensions.BotState<CustomState>",
(ctx) -> String.format("botstate/%s/%s/com.microsoft.bot.builder.core.extensions.BotState<CustomState>",
ctx.getActivity().channelId(), ctx.getActivity().conversation().id()), CustomState::new);
// read initial state object
CustomState customState = null;
try {
customState = (CustomState) botStateManager.<CustomState>Read(context).join();
} catch (JsonProcessingException e) {
e.printStackTrace();
Assert.fail("Error reading custom state");
}
// this should be a 'new CustomState' as nothing is currently stored in storage
Assert.assertEquals(customState, new CustomState());
// amend property and write to storage
customState.setCustomString("test");
try {
botStateManager.Write(context, customState).join();
} catch (Exception e) {
e.printStackTrace();
Assert.fail("Could not write customstate");
}
// set customState to null before reading from storage
customState = null;
try {
customState = (CustomState) botStateManager.<CustomState>Read(context).join();
} catch (JsonProcessingException e) {
e.printStackTrace();
Assert.fail("Could not read customstate back");
}
// check object read from value has the correct value for CustomString
Assert.assertEquals(customState.getCustomString(), "test");
}
)
.StartTest();
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.builder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.adapters.TestFlow;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.models.ChannelAccount;
import com.microsoft.bot.schema.models.ResourceResponse;
import com.microsoft.rest.RestClient;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
// [TestClass]
// [TestCategory("State Management")]
public class BotStateTest {
protected RestConnectorClient connector;
protected ChannelAccount bot;
protected ChannelAccount user;
protected void initializeClients(RestClient restClient, String botId, String userId) {
connector = new RestConnectorClient(restClient);
bot = new ChannelAccount().withId(botId);
user = new ChannelAccount().withId(userId);
}
protected void cleanUpResources() {
}
@Test
public void State_DoNOTRememberContextState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter();
new TestFlow(adapter, (context) -> {
TestPocoState obj = StateTurnContextExtensions.<TestPocoState>GetConversationState(context);
Assert.assertNull("context.state should not exist", obj); }
)
.Send("set value")
.StartTest();
}
//@Test
public void State_RememberIStoreItemUserState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new UserState<TestState>(new MemoryStorage(), TestState::new));
Consumer<TurnContext> callback = (context) -> {
System.out.print(String.format("State_RememberIStoreItemUserState CALLBACK called.."));
System.out.flush();
TestState userState = StateTurnContextExtensions.<TestState>GetUserState(context);
Assert.assertNotNull("user state should exist", userState);
switch (context.getActivity().text()) {
case "set value":
userState.withValue("test");
try {
((TurnContextImpl)context).SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(userState.value()));
((TurnContextImpl)context).SendActivity(userState.value());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
};
new TestFlow(adapter, callback)
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
@Test
public void State_RememberPocoUserState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new UserState<TestPocoState>(new MemoryStorage(), TestPocoState::new));
new TestFlow(adapter,
(context) ->
{
TestPocoState userState = StateTurnContextExtensions.<TestPocoState>GetUserState(context);
Assert.assertNotNull("user state should exist", userState);
switch (context.getActivity().text()) {
case "set value":
userState.setValue("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(userState.getValue()));
context.SendActivity(userState.getValue());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
//@Test
public void State_RememberIStoreItemConversationState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TestState>(new MemoryStorage(), TestState::new));
new TestFlow(adapter,
(context) ->
{
TestState conversationState = StateTurnContextExtensions.<TestState>GetConversationState(context);
Assert.assertNotNull("state.conversation should exist", conversationState);
switch (context.getActivity().text()) {
case "set value":
conversationState.withValue("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(conversationState.value()));
context.SendActivity(conversationState.value());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
//@Test
public void State_RememberPocoConversationState() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TestPocoState>(new MemoryStorage(), TestPocoState::new));
new TestFlow(adapter,
(context) ->
{
TestPocoState conversationState = StateTurnContextExtensions.<TestPocoState>GetConversationState(context);
Assert.assertNotNull("state.conversation should exist", conversationState);
switch (context.getActivity().text()) {
case "set value":
conversationState.setValue("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(conversationState.getValue()));
context.SendActivity(conversationState.getValue());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "test")
.StartTest();
}
@Test
public void State_CustomStateManagerTest() throws ExecutionException, InterruptedException {
String testGuid = UUID.randomUUID().toString();
TestAdapter adapter = new TestAdapter()
.Use(new CustomKeyState(new MemoryStorage()));
new TestFlow(adapter,
(context) ->
{
CustomState customState = CustomKeyState.Get(context);
switch (context.getActivity().text()) {
case "set value":
customState.setCustomString(testGuid);
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
Assert.assertFalse(StringUtils.isBlank(customState.getCustomString()));
context.SendActivity(customState.getCustomString());
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", testGuid.toString())
.StartTest();
}
@Test
public void State_RoundTripTypedObjectwTrace() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TypedObject>(new MemoryStorage(), TypedObject::new));
new TestFlow(adapter,
(context) ->
{
System.out.println(String.format(">>Test Callback(tid:%s): STARTING : %s", Thread.currentThread().getId(), context.getActivity().text()));
System.out.flush();
TypedObject conversation = StateTurnContextExtensions.<TypedObject>GetConversationState(context);
Assert.assertNotNull("conversationstate should exist", conversation);
System.out.println(String.format(">>Test Callback(tid:%s): Text is : %s", Thread.currentThread().getId(), context.getActivity().text()));
System.out.flush();
switch (context.getActivity().text()) {
case "set value":
conversation.withName("test");
try {
System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
"value saved"));
System.out.flush();
ResourceResponse response = context.SendActivity("value saved");
System.out.println(String.format(">>Test Callback(tid:%s): Response Id: %s", Thread.currentThread().getId(),
response.id()));
System.out.flush();
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
"TypedObject"));
System.out.flush();
context.SendActivity("TypedObject");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Turn("set value", "value saved", "Description", 50000)
.Turn("get value", "TypedObject", "Description", 50000)
.StartTest();
}
@Test
public void State_RoundTripTypedObject() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new ConversationState<TypedObject>(new MemoryStorage(), TypedObject::new));
new TestFlow(adapter,
(context) ->
{
TypedObject conversation = StateTurnContextExtensions.<TypedObject>GetConversationState(context);
Assert.assertNotNull("conversationstate should exist", conversation);
switch (context.getActivity().text()) {
case "set value":
conversation.withName("test");
try {
context.SendActivity("value saved");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - set value"));
}
break;
case "get value":
try {
context.SendActivity("TypedObject");
} catch (Exception e) {
e.printStackTrace();
Assert.fail(String.format("Error sending activity! - get value"));
}
break;
}
})
.Test("set value", "value saved")
.Test("get value", "TypedObject")
.StartTest();
}
@Test
public void State_UseBotStateDirectly() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter();
new TestFlow(adapter,
(context) ->
{
BotState botStateManager = new BotState<CustomState>(new MemoryStorage(), "BotState:com.microsoft.bot.builder.core.extensions.BotState<CustomState>",
(ctx) -> String.format("botstate/%s/%s/com.microsoft.bot.builder.core.extensions.BotState<CustomState>",
ctx.getActivity().channelId(), ctx.getActivity().conversation().id()), CustomState::new);
// read initial state object
CustomState customState = null;
try {
customState = (CustomState) botStateManager.<CustomState>Read(context).join();
} catch (JsonProcessingException e) {
e.printStackTrace();
Assert.fail("Error reading custom state");
}
// this should be a 'new CustomState' as nothing is currently stored in storage
Assert.assertEquals(customState, new CustomState());
// amend property and write to storage
customState.setCustomString("test");
try {
botStateManager.Write(context, customState).join();
} catch (Exception e) {
e.printStackTrace();
Assert.fail("Could not write customstate");
}
// set customState to null before reading from storage
customState = null;
try {
customState = (CustomState) botStateManager.<CustomState>Read(context).join();
} catch (JsonProcessingException e) {
e.printStackTrace();
Assert.fail("Could not read customstate back");
}
// check object read from value has the correct value for CustomString
Assert.assertEquals(customState.getCustomString(), "test");
}
)
.StartTest();
}
}

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

@ -1,109 +1,110 @@
package com.microsoft.bot.builder;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.adapters.TestFlow;
import com.microsoft.bot.schema.ActivityImpl;
import com.microsoft.bot.schema.models.Activity;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CatchException_MiddlewareTest {
@Test
public void CatchException_TestMiddleware_TestStackedErrorMiddleware() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new CatchExceptionMiddleware<Exception>(new CallOnException() {
@Override
public <T> CompletableFuture apply(TurnContext context, T t) throws Exception {
return CompletableFuture.runAsync(() -> {
Activity activity = context.getActivity();
if (activity instanceof ActivityImpl) {
try {
context.SendActivity(((ActivityImpl) activity).CreateReply(t.toString()));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(String.format("CatchException_TestMiddleware_TestStackedErrorMiddleware:SendActivity failed %s", e.toString()));
}
} else
Assert.assertTrue("Test was built for ActivityImpl", false);
});
}
}, Exception.class))
// Add middleware to catch NullReferenceExceptions before throwing up to the general exception instance
.Use(new CatchExceptionMiddleware<NullPointerException>(new CallOnException() {
@Override
public <T> CompletableFuture apply(TurnContext context, T t) throws Exception {
context.SendActivity("Sorry - Null Reference Exception");
return CompletableFuture.completedFuture(null);
}
}, NullPointerException.class));
new TestFlow(adapter, (context) ->
{
if (context.getActivity().text() == "foo") {
try {
context.SendActivity(context.getActivity().text());
} catch (Exception e) {
e.printStackTrace();
}
}
if (context.getActivity().text() == "UnsupportedOperationException") {
throw new UnsupportedOperationException("Test");
}
}
)
.Send("foo")
.AssertReply("foo", "passthrough")
.Send("UnsupportedOperationException")
.AssertReply("Test")
.StartTest();
}
/* @Test
// [TestCategory("Middleware")]
public void CatchException_TestMiddleware_SpecificExceptionType()
{
TestAdapter adapter = new TestAdapter()
.Use(new CatchExceptionMiddleware<Exception>((context, exception) =>
{
context.SendActivity("Generic Exception Caught");
return CompletableFuture.CompletedTask;
}))
.Use(new CatchExceptionMiddleware<NullReferenceException>((context, exception) =>
{
context.SendActivity(exception.Message);
return CompletableFuture.CompletedTask;
}));
await new TestFlow(adapter, (context) =>
{
if (context.Activity.AsMessageActivity().Text == "foo")
{
context.SendActivity(context.Activity.AsMessageActivity().Text);
}
if (context.Activity.AsMessageActivity().Text == "NullReferenceException")
{
throw new NullReferenceException("Test");
}
return CompletableFuture.CompletedTask;
})
.Send("foo")
.AssertReply("foo", "passthrough")
.Send("NullReferenceException")
.AssertReply("Test")
.StartTest();
}*/
}
package com.microsoft.bot.builder;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.adapters.TestFlow;
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.schema.ActivityImpl;
import com.microsoft.bot.schema.models.Activity;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CatchException_MiddlewareTest {
@Test
public void CatchException_TestMiddleware_TestStackedErrorMiddleware() throws ExecutionException, InterruptedException {
TestAdapter adapter = new TestAdapter()
.Use(new CatchExceptionMiddleware<Exception>(new CallOnException() {
@Override
public <T> CompletableFuture apply(TurnContext context, T t) throws Exception {
return CompletableFuture.runAsync(() -> {
Activity activity = context.getActivity();
if (activity instanceof ActivityImpl) {
try {
context.SendActivity(((ActivityImpl) activity).CreateReply(t.toString()));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(String.format("CatchException_TestMiddleware_TestStackedErrorMiddleware:SendActivity failed %s", e.toString()));
}
} else
Assert.assertTrue("Test was built for ActivityImpl", false);
}, ExecutorFactory.getExecutor());
}
}, Exception.class))
// Add middleware to catch NullReferenceExceptions before throwing up to the general exception instance
.Use(new CatchExceptionMiddleware<NullPointerException>(new CallOnException() {
@Override
public <T> CompletableFuture apply(TurnContext context, T t) throws Exception {
context.SendActivity("Sorry - Null Reference Exception");
return CompletableFuture.completedFuture(null);
}
}, NullPointerException.class));
new TestFlow(adapter, (context) ->
{
if (context.getActivity().text() == "foo") {
try {
context.SendActivity(context.getActivity().text());
} catch (Exception e) {
e.printStackTrace();
}
}
if (context.getActivity().text() == "UnsupportedOperationException") {
throw new UnsupportedOperationException("Test");
}
}
)
.Send("foo")
.AssertReply("foo", "passthrough")
.Send("UnsupportedOperationException")
.AssertReply("Test")
.StartTest();
}
/* @Test
// [TestCategory("Middleware")]
public void CatchException_TestMiddleware_SpecificExceptionType()
{
TestAdapter adapter = new TestAdapter()
.Use(new CatchExceptionMiddleware<Exception>((context, exception) =>
{
context.SendActivity("Generic Exception Caught");
return CompletableFuture.CompletedTask;
}))
.Use(new CatchExceptionMiddleware<NullReferenceException>((context, exception) =>
{
context.SendActivity(exception.Message);
return CompletableFuture.CompletedTask;
}));
await new TestFlow(adapter, (context) =>
{
if (context.Activity.AsMessageActivity().Text == "foo")
{
context.SendActivity(context.Activity.AsMessageActivity().Text);
}
if (context.Activity.AsMessageActivity().Text == "NullReferenceException")
{
throw new NullReferenceException("Test");
}
return CompletableFuture.CompletedTask;
})
.Send("foo")
.AssertReply("foo", "passthrough")
.Send("NullReferenceException")
.AssertReply("Test")
.StartTest();
}*/
}

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

@ -1,129 +1,130 @@
package com.microsoft.bot.builder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
/**
* Models IStorage around a dictionary
*/
public class DictionaryStorage implements Storage {
private static ObjectMapper objectMapper;
// TODO: Object needs to be defined
private final Map<String, Object> memory;
private final Object syncroot = new Object();
private int _eTag = 0;
private final String typeNameForNonEntity = "__type_name_";
public DictionaryStorage() {
this(null);
}
public DictionaryStorage(Map<String, Object> dictionary ) {
DictionaryStorage.objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.findAndRegisterModules();
this.memory = (dictionary != null) ? dictionary : new HashMap<String, Object>();
}
public CompletableFuture Delete(String[] keys) {
synchronized (this.syncroot) {
for (String key : keys) {
Object o = this.memory.get(key);
this.memory.remove(o);
}
}
return completedFuture(null);
}
@Override
public CompletableFuture<Map<String, ?>> Read(String[] keys) throws JsonProcessingException {
return CompletableFuture.supplyAsync(() -> {
Map<String, Object> storeItems = new HashMap<String, Object>(keys.length);
synchronized (this.syncroot) {
for (String key : keys) {
if (this.memory.containsKey(key)) {
Object state = this.memory.get(key);
if (state != null) {
try {
if (!(state instanceof JsonNode))
throw new RuntimeException("DictionaryRead failed: entry not JsonNode");
JsonNode stateNode = (JsonNode) state;
// Check if type info is set for the class
if (!(stateNode.hasNonNull(this.typeNameForNonEntity))) {
throw new RuntimeException(String.format("DictionaryRead failed: Type info not present"));
}
String clsName = stateNode.get(this.typeNameForNonEntity).textValue();
// Load the class info
Class<?> cls;
try {
cls = Class.forName(clsName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(String.format("DictionaryRead failed: Could not load class %s", clsName));
}
// Populate dictionary
storeItems.put(key,DictionaryStorage.objectMapper.treeToValue(stateNode, cls ));
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(String.format("DictionaryRead failed: %s", e.toString()));
}
}
}
}
}
return storeItems;
});
}
@Override
public CompletableFuture Write(Map<String, ?> changes) throws Exception {
synchronized (this.syncroot) {
for (Map.Entry change : changes.entrySet()) {
Object newValue = change.getValue();
String oldStateETag = null; // default(string);
if (this.memory.containsValue(change.getKey())) {
Map oldState = (Map) this.memory.get(change.getKey());
if (oldState.containsValue("eTag")) {
Map.Entry eTagToken = (Map.Entry) oldState.get("eTag");
oldStateETag = (String) eTagToken.getValue();
}
}
// Dictionary stores Key:JsonNode (with type information held within the JsonNode)
JsonNode newState = DictionaryStorage.objectMapper.valueToTree(newValue);
((ObjectNode)newState).put(this.typeNameForNonEntity, newValue.getClass().getTypeName());
// Set ETag if applicable
if (newValue instanceof StoreItem) {
StoreItem newStoreItem = (StoreItem) newValue;
if(oldStateETag != null && newStoreItem.geteTag() != "*" &&
newStoreItem.geteTag() != oldStateETag) {
throw new Exception(String.format("Etag conflict.\r\n\r\nOriginal: %s\r\nCurrent: %s",
newStoreItem.geteTag(), oldStateETag));
}
Integer newTag = _eTag++;
((ObjectNode)newState).put("eTag", newTag.toString());
}
this.memory.put((String)change.getKey(), newState);
}
}
return completedFuture(null);
}
}
package com.microsoft.bot.builder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.microsoft.bot.connector.ExecutorFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static java.util.concurrent.CompletableFuture.completedFuture;
/**
* Models IStorage around a dictionary
*/
public class DictionaryStorage implements Storage {
private static ObjectMapper objectMapper;
// TODO: Object needs to be defined
private final Map<String, Object> memory;
private final Object syncroot = new Object();
private int _eTag = 0;
private final String typeNameForNonEntity = "__type_name_";
public DictionaryStorage() {
this(null);
}
public DictionaryStorage(Map<String, Object> dictionary ) {
DictionaryStorage.objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.findAndRegisterModules();
this.memory = (dictionary != null) ? dictionary : new HashMap<String, Object>();
}
public CompletableFuture Delete(String[] keys) {
synchronized (this.syncroot) {
for (String key : keys) {
Object o = this.memory.get(key);
this.memory.remove(o);
}
}
return completedFuture(null);
}
@Override
public CompletableFuture<Map<String, ?>> Read(String[] keys) throws JsonProcessingException {
return CompletableFuture.supplyAsync(() -> {
Map<String, Object> storeItems = new HashMap<String, Object>(keys.length);
synchronized (this.syncroot) {
for (String key : keys) {
if (this.memory.containsKey(key)) {
Object state = this.memory.get(key);
if (state != null) {
try {
if (!(state instanceof JsonNode))
throw new RuntimeException("DictionaryRead failed: entry not JsonNode");
JsonNode stateNode = (JsonNode) state;
// Check if type info is set for the class
if (!(stateNode.hasNonNull(this.typeNameForNonEntity))) {
throw new RuntimeException(String.format("DictionaryRead failed: Type info not present"));
}
String clsName = stateNode.get(this.typeNameForNonEntity).textValue();
// Load the class info
Class<?> cls;
try {
cls = Class.forName(clsName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(String.format("DictionaryRead failed: Could not load class %s", clsName));
}
// Populate dictionary
storeItems.put(key,DictionaryStorage.objectMapper.treeToValue(stateNode, cls ));
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(String.format("DictionaryRead failed: %s", e.toString()));
}
}
}
}
}
return storeItems;
}, ExecutorFactory.getExecutor());
}
@Override
public CompletableFuture Write(Map<String, ?> changes) throws Exception {
synchronized (this.syncroot) {
for (Map.Entry change : changes.entrySet()) {
Object newValue = change.getValue();
String oldStateETag = null; // default(string);
if (this.memory.containsValue(change.getKey())) {
Map oldState = (Map) this.memory.get(change.getKey());
if (oldState.containsValue("eTag")) {
Map.Entry eTagToken = (Map.Entry) oldState.get("eTag");
oldStateETag = (String) eTagToken.getValue();
}
}
// Dictionary stores Key:JsonNode (with type information held within the JsonNode)
JsonNode newState = DictionaryStorage.objectMapper.valueToTree(newValue);
((ObjectNode)newState).put(this.typeNameForNonEntity, newValue.getClass().getTypeName());
// Set ETag if applicable
if (newValue instanceof StoreItem) {
StoreItem newStoreItem = (StoreItem) newValue;
if(oldStateETag != null && newStoreItem.geteTag() != "*" &&
newStoreItem.geteTag() != oldStateETag) {
throw new Exception(String.format("Etag conflict.\r\n\r\nOriginal: %s\r\nCurrent: %s",
newStoreItem.geteTag(), oldStateETag));
}
Integer newTag = _eTag++;
((ObjectNode)newState).put("eTag", newTag.toString());
}
this.memory.put((String)change.getKey(), newState);
}
}
return completedFuture(null);
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,468 +1,469 @@
package com.microsoft.bot.builder.adapters;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.schema.ActivityImpl;
import com.microsoft.bot.schema.models.Activity;
import org.joda.time.DateTime;
import org.junit.Assert;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static java.util.concurrent.CompletableFuture.completedFuture;
public class TestFlow {
final TestAdapter adapter;
CompletableFuture<String> testTask;
Consumer<TurnContext> callback;
ArrayList<Supplier<String>> tasks = new ArrayList<Supplier<String>>();
ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory()
{
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool)
{
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName("TestFlow-" + worker.getPoolIndex());
return worker;
}
};
ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true);
public TestFlow(TestAdapter adapter) {
this(adapter, null);
}
public TestFlow(TestAdapter adapter, Consumer<TurnContext> callback) {
this.adapter = adapter;
this.callback = callback;
this.testTask = completedFuture(null);
}
public TestFlow(Supplier<String> testTask, TestFlow flow) {
this.tasks = flow.tasks;
if (testTask != null)
this.tasks.add(testTask);
this.callback = flow.callback;
this.adapter = flow.adapter;
}
/**
* Start the execution of the test flow
*
* @return
*/
public String StartTest() throws ExecutionException, InterruptedException {
System.out.printf("+------------------------------------------+\n");
int count = 0;
for (Supplier<String> task : this.tasks) {
System.out.printf("| Running task %s of %s\n", count++, this.tasks.size());
String result = null;
result = task.get();
System.out.printf("| --> Result: %s", result);
System.out.flush();
}
System.out.printf("+------------------------------------------+\n");
return "Completed";
}
/**
* Send a message from the user to the bot
*
* @param userSays
* @return
*/
public TestFlow Send(String userSays) throws IllegalArgumentException {
if (userSays == null)
throw new IllegalArgumentException("You have to pass a userSays parameter");
// Function<TurnContextImpl, CompletableFuture>
return new TestFlow((() -> {
System.out.print(String.format("USER SAYS: %s (Thread Id: %s)\n", userSays, Thread.currentThread().getId()));
System.out.flush();
try {
this.adapter.SendTextToBot(userSays, this.callback);
return "Successfully sent " + userSays;
} catch (Exception e) {
Assert.fail(e.getMessage());
return e.getMessage();
}
}), this);
}
/**
* Send an activity from the user to the bot
*
* @param userActivity
* @return
*/
public TestFlow Send(Activity userActivity) {
if (userActivity == null)
throw new IllegalArgumentException("You have to pass an Activity");
return new TestFlow((() -> {
System.out.printf("TestFlow(%s): Send with User Activity! %s", Thread.currentThread().getId(), userActivity.text());
System.out.flush();
try {
this.adapter.ProcessActivity((ActivityImpl) userActivity, this.callback);
return "TestFlow: Send() -> ProcessActivity: " + userActivity.text();
} catch (Exception e) {
return e.getMessage();
}
}), this);
}
/**
* Delay for time period
*
* @param ms
* @return
*/
public TestFlow Delay(int ms) {
return new TestFlow(() ->
{
System.out.printf("TestFlow(%s): Delay(%s ms) called. ", Thread.currentThread().getId(), ms);
System.out.flush();
try {
Thread.sleep((int) ms);
} catch (InterruptedException e) {
return e.getMessage();
}
return null;
}, this);
}
/**
* Assert that reply is expected text
*
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReply(String expected) {
return this.AssertReply(expected, null, 3000);
}
public TestFlow AssertReply(String expected, String description) {
return this.AssertReply(expected, description, 3000);
}
public TestFlow AssertReply(String expected, String description, int timeout) {
return this.AssertReply(this.adapter.MakeActivity(expected), description, timeout);
}
/**
* Assert that the reply is expected activity
*
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReply(Activity expected) {
String description = Thread.currentThread().getStackTrace()[1].getMethodName();
return AssertReply(expected, description, 3000);
}
public TestFlow AssertReply(Activity expected, String description, int timeout) {
if (description == null)
description = Thread.currentThread().getStackTrace()[1].getMethodName();
String finalDescription = description;
return this.AssertReply((reply) -> {
if (expected.type() != reply.type())
return String.format("%s: Type should match", finalDescription);
if (expected.text().equals(reply.text())) {
if (finalDescription == null)
return String.format("Expected:%s\nReceived:{reply.AsMessageActivity().Text}", expected.text());
else
return String.format("%s: Text should match", finalDescription);
}
// TODO, expand this to do all properties set on expected
return null;
}, description, timeout);
}
/**
* Assert that the reply matches a custom validation routine
*
* @param validateActivity
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReply(Function<Activity, String> validateActivity) {
String description = Thread.currentThread().getStackTrace()[1].getMethodName();
return AssertReply(validateActivity, description, 3000);
}
public TestFlow AssertReply(Function<Activity, String> validateActivity, String description) {
return AssertReply(validateActivity, description, 3000);
}
public TestFlow AssertReply(Function<Activity, String> validateActivity, String description, int timeout) {
return new TestFlow(() -> {
System.out.println(String.format("AssertReply: Starting loop : %s (Thread:%s)", description, Thread.currentThread().getId()));
System.out.flush();
int finalTimeout = Integer.MAX_VALUE;
if (isDebug())
finalTimeout = Integer.MAX_VALUE;
DateTime start = DateTime.now();
while (true) {
DateTime current = DateTime.now();
if ((current.getMillis() - start.getMillis()) > (long) finalTimeout) {
System.out.println("AssertReply: Timeout!\n");
System.out.flush();
return String.format("%d ms Timed out waiting for:'%s'", finalTimeout, description);
}
// System.out.println("Before GetNextReply\n");
// System.out.flush();
Activity replyActivity = this.adapter.GetNextReply();
// System.out.println("After GetNextReply\n");
// System.out.flush();
if (replyActivity != null) {
System.out.printf("AssertReply(tid:%s): Received Reply: %s ", Thread.currentThread().getId(), (replyActivity.text() == null) ? "No Text set" : replyActivity.text());
System.out.flush();
System.out.printf("=============\n From: %s\n To:%s\n ==========\n", (replyActivity.from() == null) ? "No from set" : replyActivity.from().name(),
(replyActivity.recipient() == null) ? "No recipient set" : replyActivity.recipient().name());
System.out.flush();
// if we have a reply
return validateActivity.apply(replyActivity);
} else {
System.out.printf("AssertReply(tid:%s): Waiting..\n", Thread.currentThread().getId());
System.out.flush();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, this);
}
// Hack to determine if debugger attached..
public boolean isDebug() {
for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
if (arg.contains("jdwp=")) {
return true;
}
}
return false;
}
/**
* @param userSays
* @param expected
* @return
*/
public TestFlow Turn(String userSays, String expected, String description, int timeout) {
String result = null;
try {
result = CompletableFuture.supplyAsync(() -> { // Send the message
if (userSays == null)
throw new IllegalArgumentException("You have to pass a userSays parameter");
System.out.print(String.format("TestTurn(%s): USER SAYS: %s \n", Thread.currentThread().getId(), userSays));
System.out.flush();
try {
this.adapter.SendTextToBot(userSays, this.callback);
return null;
} catch (Exception e) {
return e.getMessage();
}
})
.thenApply(arg -> { // Assert Reply
int finalTimeout = Integer.MAX_VALUE;
if (isDebug())
finalTimeout = Integer.MAX_VALUE;
Function<Activity, String> validateActivity = activity -> {
if (activity.text().equals(expected)) {
System.out.println(String.format("TestTurn(tid:%s): Validated text is: %s", Thread.currentThread().getId(), expected));
System.out.flush();
return "SUCCESS";
}
System.out.println(String.format("TestTurn(tid:%s): Failed validate text is: %s", Thread.currentThread().getId(), expected));
System.out.flush();
return String.format("FAIL: %s received in Activity.text (%s expected)", activity.text(), expected);
};
System.out.println(String.format("TestTurn(tid:%s): Started receive loop: %s", Thread.currentThread().getId(), description));
System.out.flush();
DateTime start = DateTime.now();
while (true) {
DateTime current = DateTime.now();
if ((current.getMillis() - start.getMillis()) > (long) finalTimeout)
return String.format("TestTurn: %d ms Timed out waiting for:'%s'", finalTimeout, description);
Activity replyActivity = this.adapter.GetNextReply();
if (replyActivity != null) {
// if we have a reply
System.out.println(String.format("TestTurn(tid:%s): Received Reply: %s",
Thread.currentThread().getId(),
String.format("\n========\n To:%s\n From:%s\n Msg:%s\n=======", replyActivity.recipient().name(), replyActivity.from().name(), replyActivity.text())
));
System.out.flush();
return validateActivity.apply(replyActivity);
} else {
System.out.println(String.format("TestTurn(tid:%s): No reply..", Thread.currentThread().getId()));
System.out.flush();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
})
.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return this;
}
/**
* Say() -> shortcut for .Send(user).AssertReply(Expected)
*
* @param userSays
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow Test(String userSays, String expected) {
return Test(userSays, expected, null, 3000);
}
public TestFlow Test(String userSays, String expected, String description) {
return Test(userSays, expected, description, 3000);
}
public TestFlow Test(String userSays, String expected, String description, int timeout) {
if (expected == null)
throw new IllegalArgumentException("expected");
return this.Send(userSays)
.AssertReply(expected, description, timeout);
}
/**
* Test() -> shortcut for .Send(user).AssertReply(Expected)
*
* @param userSays
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow Test(String userSays, Activity expected) {
return Test(userSays, expected, null, 3000);
}
public TestFlow Test(String userSays, Activity expected, String description) {
return Test(userSays, expected, description, 3000);
}
public TestFlow Test(String userSays, Activity expected, String description, int timeout) {
if (expected == null)
throw new IllegalArgumentException("expected");
return this.Send(userSays)
.AssertReply(expected, description, timeout);
}
/**
* Say() -> shortcut for .Send(user).AssertReply(Expected)
*
* @param userSays
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow Test(String userSays, Function<Activity, String> expected) {
return Test(userSays, expected, null, 3000);
}
public TestFlow Test(String userSays, Function<Activity, String> expected, String description) {
return Test(userSays, expected, description, 3000);
}
public TestFlow Test(String userSays, Function<Activity, String> expected, String description, int timeout) {
if (expected == null)
throw new IllegalArgumentException("expected");
return this.Send(userSays)
.AssertReply(expected, description, timeout);
}
/**
* Assert that reply is one of the candidate responses
*
* @param candidates
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReplyOneOf(String[] candidates) {
return AssertReplyOneOf(candidates, null, 3000);
}
public TestFlow AssertReplyOneOf(String[] candidates, String description) {
return AssertReplyOneOf(candidates, description, 3000);
}
public TestFlow AssertReplyOneOf(String[] candidates, String description, int timeout) {
if (candidates == null)
throw new IllegalArgumentException("candidates");
return this.AssertReply((reply) -> {
for (String candidate : candidates) {
if (reply.text() == candidate)
return null;
}
return String.format("%s: Not one of candidates: %s", description, String.join("\n ", candidates));
}, description, timeout);
}
}
package com.microsoft.bot.builder.adapters;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.schema.ActivityImpl;
import com.microsoft.bot.schema.models.Activity;
import org.joda.time.DateTime;
import org.junit.Assert;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static java.util.concurrent.CompletableFuture.completedFuture;
public class TestFlow {
final TestAdapter adapter;
CompletableFuture<String> testTask;
Consumer<TurnContext> callback;
ArrayList<Supplier<String>> tasks = new ArrayList<Supplier<String>>();
ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory()
{
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool)
{
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName("TestFlow-" + worker.getPoolIndex());
return worker;
}
};
ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true);
public TestFlow(TestAdapter adapter) {
this(adapter, null);
}
public TestFlow(TestAdapter adapter, Consumer<TurnContext> callback) {
this.adapter = adapter;
this.callback = callback;
this.testTask = completedFuture(null);
}
public TestFlow(Supplier<String> testTask, TestFlow flow) {
this.tasks = flow.tasks;
if (testTask != null)
this.tasks.add(testTask);
this.callback = flow.callback;
this.adapter = flow.adapter;
}
/**
* Start the execution of the test flow
*
* @return
*/
public String StartTest() throws ExecutionException, InterruptedException {
System.out.printf("+------------------------------------------+\n");
int count = 0;
for (Supplier<String> task : this.tasks) {
System.out.printf("| Running task %s of %s\n", count++, this.tasks.size());
String result = null;
result = task.get();
System.out.printf("| --> Result: %s", result);
System.out.flush();
}
System.out.printf("+------------------------------------------+\n");
return "Completed";
}
/**
* Send a message from the user to the bot
*
* @param userSays
* @return
*/
public TestFlow Send(String userSays) throws IllegalArgumentException {
if (userSays == null)
throw new IllegalArgumentException("You have to pass a userSays parameter");
// Function<TurnContextImpl, CompletableFuture>
return new TestFlow((() -> {
System.out.print(String.format("USER SAYS: %s (Thread Id: %s)\n", userSays, Thread.currentThread().getId()));
System.out.flush();
try {
this.adapter.SendTextToBot(userSays, this.callback);
return "Successfully sent " + userSays;
} catch (Exception e) {
Assert.fail(e.getMessage());
return e.getMessage();
}
}), this);
}
/**
* Send an activity from the user to the bot
*
* @param userActivity
* @return
*/
public TestFlow Send(Activity userActivity) {
if (userActivity == null)
throw new IllegalArgumentException("You have to pass an Activity");
return new TestFlow((() -> {
System.out.printf("TestFlow(%s): Send with User Activity! %s", Thread.currentThread().getId(), userActivity.text());
System.out.flush();
try {
this.adapter.ProcessActivity((ActivityImpl) userActivity, this.callback);
return "TestFlow: Send() -> ProcessActivity: " + userActivity.text();
} catch (Exception e) {
return e.getMessage();
}
}), this);
}
/**
* Delay for time period
*
* @param ms
* @return
*/
public TestFlow Delay(int ms) {
return new TestFlow(() ->
{
System.out.printf("TestFlow(%s): Delay(%s ms) called. ", Thread.currentThread().getId(), ms);
System.out.flush();
try {
Thread.sleep((int) ms);
} catch (InterruptedException e) {
return e.getMessage();
}
return null;
}, this);
}
/**
* Assert that reply is expected text
*
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReply(String expected) {
return this.AssertReply(expected, null, 3000);
}
public TestFlow AssertReply(String expected, String description) {
return this.AssertReply(expected, description, 3000);
}
public TestFlow AssertReply(String expected, String description, int timeout) {
return this.AssertReply(this.adapter.MakeActivity(expected), description, timeout);
}
/**
* Assert that the reply is expected activity
*
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReply(Activity expected) {
String description = Thread.currentThread().getStackTrace()[1].getMethodName();
return AssertReply(expected, description, 3000);
}
public TestFlow AssertReply(Activity expected, String description, int timeout) {
if (description == null)
description = Thread.currentThread().getStackTrace()[1].getMethodName();
String finalDescription = description;
return this.AssertReply((reply) -> {
if (expected.type() != reply.type())
return String.format("%s: Type should match", finalDescription);
if (expected.text().equals(reply.text())) {
if (finalDescription == null)
return String.format("Expected:%s\nReceived:{reply.AsMessageActivity().Text}", expected.text());
else
return String.format("%s: Text should match", finalDescription);
}
// TODO, expand this to do all properties set on expected
return null;
}, description, timeout);
}
/**
* Assert that the reply matches a custom validation routine
*
* @param validateActivity
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReply(Function<Activity, String> validateActivity) {
String description = Thread.currentThread().getStackTrace()[1].getMethodName();
return AssertReply(validateActivity, description, 3000);
}
public TestFlow AssertReply(Function<Activity, String> validateActivity, String description) {
return AssertReply(validateActivity, description, 3000);
}
public TestFlow AssertReply(Function<Activity, String> validateActivity, String description, int timeout) {
return new TestFlow(() -> {
System.out.println(String.format("AssertReply: Starting loop : %s (Thread:%s)", description, Thread.currentThread().getId()));
System.out.flush();
int finalTimeout = Integer.MAX_VALUE;
if (isDebug())
finalTimeout = Integer.MAX_VALUE;
DateTime start = DateTime.now();
while (true) {
DateTime current = DateTime.now();
if ((current.getMillis() - start.getMillis()) > (long) finalTimeout) {
System.out.println("AssertReply: Timeout!\n");
System.out.flush();
return String.format("%d ms Timed out waiting for:'%s'", finalTimeout, description);
}
// System.out.println("Before GetNextReply\n");
// System.out.flush();
Activity replyActivity = this.adapter.GetNextReply();
// System.out.println("After GetNextReply\n");
// System.out.flush();
if (replyActivity != null) {
System.out.printf("AssertReply(tid:%s): Received Reply: %s ", Thread.currentThread().getId(), (replyActivity.text() == null) ? "No Text set" : replyActivity.text());
System.out.flush();
System.out.printf("=============\n From: %s\n To:%s\n ==========\n", (replyActivity.from() == null) ? "No from set" : replyActivity.from().name(),
(replyActivity.recipient() == null) ? "No recipient set" : replyActivity.recipient().name());
System.out.flush();
// if we have a reply
return validateActivity.apply(replyActivity);
} else {
System.out.printf("AssertReply(tid:%s): Waiting..\n", Thread.currentThread().getId());
System.out.flush();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, this);
}
// Hack to determine if debugger attached..
public boolean isDebug() {
for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
if (arg.contains("jdwp=")) {
return true;
}
}
return false;
}
/**
* @param userSays
* @param expected
* @return
*/
public TestFlow Turn(String userSays, String expected, String description, int timeout) {
String result = null;
try {
result = CompletableFuture.supplyAsync(() -> { // Send the message
if (userSays == null)
throw new IllegalArgumentException("You have to pass a userSays parameter");
System.out.print(String.format("TestTurn(%s): USER SAYS: %s \n", Thread.currentThread().getId(), userSays));
System.out.flush();
try {
this.adapter.SendTextToBot(userSays, this.callback);
return null;
} catch (Exception e) {
return e.getMessage();
}
}, ExecutorFactory.getExecutor())
.thenApply(arg -> { // Assert Reply
int finalTimeout = Integer.MAX_VALUE;
if (isDebug())
finalTimeout = Integer.MAX_VALUE;
Function<Activity, String> validateActivity = activity -> {
if (activity.text().equals(expected)) {
System.out.println(String.format("TestTurn(tid:%s): Validated text is: %s", Thread.currentThread().getId(), expected));
System.out.flush();
return "SUCCESS";
}
System.out.println(String.format("TestTurn(tid:%s): Failed validate text is: %s", Thread.currentThread().getId(), expected));
System.out.flush();
return String.format("FAIL: %s received in Activity.text (%s expected)", activity.text(), expected);
};
System.out.println(String.format("TestTurn(tid:%s): Started receive loop: %s", Thread.currentThread().getId(), description));
System.out.flush();
DateTime start = DateTime.now();
while (true) {
DateTime current = DateTime.now();
if ((current.getMillis() - start.getMillis()) > (long) finalTimeout)
return String.format("TestTurn: %d ms Timed out waiting for:'%s'", finalTimeout, description);
Activity replyActivity = this.adapter.GetNextReply();
if (replyActivity != null) {
// if we have a reply
System.out.println(String.format("TestTurn(tid:%s): Received Reply: %s",
Thread.currentThread().getId(),
String.format("\n========\n To:%s\n From:%s\n Msg:%s\n=======", replyActivity.recipient().name(), replyActivity.from().name(), replyActivity.text())
));
System.out.flush();
return validateActivity.apply(replyActivity);
} else {
System.out.println(String.format("TestTurn(tid:%s): No reply..", Thread.currentThread().getId()));
System.out.flush();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
})
.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return this;
}
/**
* Say() -> shortcut for .Send(user).AssertReply(Expected)
*
* @param userSays
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow Test(String userSays, String expected) {
return Test(userSays, expected, null, 3000);
}
public TestFlow Test(String userSays, String expected, String description) {
return Test(userSays, expected, description, 3000);
}
public TestFlow Test(String userSays, String expected, String description, int timeout) {
if (expected == null)
throw new IllegalArgumentException("expected");
return this.Send(userSays)
.AssertReply(expected, description, timeout);
}
/**
* Test() -> shortcut for .Send(user).AssertReply(Expected)
*
* @param userSays
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow Test(String userSays, Activity expected) {
return Test(userSays, expected, null, 3000);
}
public TestFlow Test(String userSays, Activity expected, String description) {
return Test(userSays, expected, description, 3000);
}
public TestFlow Test(String userSays, Activity expected, String description, int timeout) {
if (expected == null)
throw new IllegalArgumentException("expected");
return this.Send(userSays)
.AssertReply(expected, description, timeout);
}
/**
* Say() -> shortcut for .Send(user).AssertReply(Expected)
*
* @param userSays
* @param expected
* @param description
* @param timeout
* @return
*/
public TestFlow Test(String userSays, Function<Activity, String> expected) {
return Test(userSays, expected, null, 3000);
}
public TestFlow Test(String userSays, Function<Activity, String> expected, String description) {
return Test(userSays, expected, description, 3000);
}
public TestFlow Test(String userSays, Function<Activity, String> expected, String description, int timeout) {
if (expected == null)
throw new IllegalArgumentException("expected");
return this.Send(userSays)
.AssertReply(expected, description, timeout);
}
/**
* Assert that reply is one of the candidate responses
*
* @param candidates
* @param description
* @param timeout
* @return
*/
public TestFlow AssertReplyOneOf(String[] candidates) {
return AssertReplyOneOf(candidates, null, 3000);
}
public TestFlow AssertReplyOneOf(String[] candidates, String description) {
return AssertReplyOneOf(candidates, description, 3000);
}
public TestFlow AssertReplyOneOf(String[] candidates, String description, int timeout) {
if (candidates == null)
throw new IllegalArgumentException("candidates");
return this.AssertReply((reply) -> {
for (String candidate : candidates) {
if (reply.text() == candidate)
return null;
}
return String.format("%s: Not one of candidates: %s", description, String.join("\n ", candidates));
}, description, timeout);
}
}

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

@ -86,6 +86,10 @@
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>

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

@ -2,22 +2,18 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
* Code generated by Microsoft (R) AutoRest Code Generator.
* Changes may cause incorrect behavior and will be lost if the code is
* regenerated.
*/
package com.microsoft.bot.connector;
import com.microsoft.bot.schema.models.AttachmentInfo;
import com.microsoft.bot.connector.models.ErrorResponseException;
import com.microsoft.rest.ServiceCallback;
import com.microsoft.rest.ServiceFuture;
import com.microsoft.rest.ServiceResponse;
import java.io.InputStream;
import rx.Observable;
import java.io.InputStream;
/**
* An instance of this class provides access to all the operations defined
* in Attachments.
@ -28,10 +24,9 @@ public interface Attachments {
* Get AttachmentInfo structure describing the attachment views.
*
* @param attachmentId attachment id
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the AttachmentInfo object if successful.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
*/
AttachmentInfo getAttachmentInfo(String attachmentId);
@ -39,10 +34,10 @@ public interface Attachments {
* GetAttachmentInfo.
* Get AttachmentInfo structure describing the attachment views.
*
* @param attachmentId attachment id
* @param attachmentId attachment id
* @param serviceCallback the async ServiceCallback to handle successful and failed responses.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @return the {@link ServiceFuture} object
* @throws IllegalArgumentException thrown if parameters fail the validation
*/
ServiceFuture<AttachmentInfo> getAttachmentInfoAsync(String attachmentId, final ServiceCallback<AttachmentInfo> serviceCallback);
@ -51,8 +46,8 @@ public interface Attachments {
* Get AttachmentInfo structure describing the attachment views.
*
* @param attachmentId attachment id
* @throws IllegalArgumentException thrown if parameters fail the validation
* @return the observable to the AttachmentInfo object
* @throws IllegalArgumentException thrown if parameters fail the validation
*/
Observable<AttachmentInfo> getAttachmentInfoAsync(String attachmentId);
@ -61,8 +56,8 @@ public interface Attachments {
* Get AttachmentInfo structure describing the attachment views.
*
* @param attachmentId attachment id
* @throws IllegalArgumentException thrown if parameters fail the validation
* @return the observable to the AttachmentInfo object
* @throws IllegalArgumentException thrown if parameters fail the validation
*/
Observable<ServiceResponse<AttachmentInfo>> getAttachmentInfoWithServiceResponseAsync(String attachmentId);
@ -71,11 +66,10 @@ public interface Attachments {
* Get the named view as binary content.
*
* @param attachmentId attachment id
* @param viewId View id from attachmentInfo
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @param viewId View id from attachmentInfo
* @return the InputStream object if successful.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
*/
InputStream getAttachment(String attachmentId, String viewId);
@ -83,11 +77,11 @@ public interface Attachments {
* GetAttachment.
* Get the named view as binary content.
*
* @param attachmentId attachment id
* @param viewId View id from attachmentInfo
* @param attachmentId attachment id
* @param viewId View id from attachmentInfo
* @param serviceCallback the async ServiceCallback to handle successful and failed responses.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @return the {@link ServiceFuture} object
* @throws IllegalArgumentException thrown if parameters fail the validation
*/
ServiceFuture<InputStream> getAttachmentAsync(String attachmentId, String viewId, final ServiceCallback<InputStream> serviceCallback);
@ -96,9 +90,9 @@ public interface Attachments {
* Get the named view as binary content.
*
* @param attachmentId attachment id
* @param viewId View id from attachmentInfo
* @throws IllegalArgumentException thrown if parameters fail the validation
* @param viewId View id from attachmentInfo
* @return the observable to the InputStream object
* @throws IllegalArgumentException thrown if parameters fail the validation
*/
Observable<InputStream> getAttachmentAsync(String attachmentId, String viewId);
@ -107,9 +101,9 @@ public interface Attachments {
* Get the named view as binary content.
*
* @param attachmentId attachment id
* @param viewId View id from attachmentInfo
* @throws IllegalArgumentException thrown if parameters fail the validation
* @param viewId View id from attachmentInfo
* @return the observable to the InputStream object
* @throws IllegalArgumentException thrown if parameters fail the validation
*/
Observable<ServiceResponse<InputStream>> getAttachmentWithServiceResponseAsync(String attachmentId, String viewId);

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

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
public class Channels {
/**
* Console channel.
*/
public static final String CONSOLE = "console";
/**
* Cortana channel.
*/
public static final String CORTANA = "cortana";
/**
* Direct Line channel.
*/
public static final String DIRECTLINE = "directline";
/**
* Email channel.
*/
public static final String EMAIL = "email";
/**
* Emulator channel.
*/
public static final String EMULATOR = "emulator";
/**
* Facebook channel.
*/
public static final String FACEBOOK = "facebook";
/**
* Group Me channel.
*/
public static final String GROUPME = "groupme";
/**
* Kik channel.
*/
public static final String KIK = "kik";
/**
* Line channel.
*/
public static final String LINE = "line";
/**
* MS Teams channel.
*/
public static final String MSTEAMS = "msteams";
/**
* Skype channel.
*/
public static final String SKYPE = "skype";
/**
* Skype for Business channel.
*/
public static final String SKYPEFORBUSINESS = "skypeforbusiness";
/**
* Slack channel.
*/
public static final String SLACK = "slack";
/**
* SMS (Twilio) channel.
*/
public static final String SMS = "sms";
/**
* Telegram channel.
*/
public static final String TELEGRAM = "telegram";
/**
* WebChat channel.
*/
public static final String WEBCHAT = "webchat";
/**
* Test channel.
*/
public static final String TEST = "test";
}

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

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
* <p>
* Code generated by Microsoft (R) AutoRest Code Generator.
* Changes may cause incorrect behavior and will be lost if the code is
* regenerated.
@ -21,7 +21,7 @@ public interface ConnectorClient {
* Gets the REST client.
*
* @return the {@link RestClient} object.
*/
*/
RestClient restClient();
/**

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

@ -1,41 +0,0 @@
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.rest.RestClient;
import com.microsoft.rest.credentials.ServiceClientCredentials;
public class ConnectorClientFuture extends ConnectorClientImpl {
/**
* Initializes an instance of ConnectorClient client.
*
* @param credentials the management credentials for Azure
*/
public ConnectorClientFuture(ServiceClientCredentials credentials) {
super(credentials);
}
/**
* Initializes an instance of ConnectorClient client.
*
* @param baseUrl the base URL of the host
* @param credentials the management credentials for Azure
*/
public ConnectorClientFuture(String baseUrl, ServiceClientCredentials credentials) {
super(baseUrl, credentials);
}
/**
* Initializes an instance of ConnectorClient client.
*
* @param restClient the REST client to connect to Azure.
*/
public ConnectorClientFuture(RestClient restClient) {
super(restClient);
}
@Override
public String userAgent() {
return "Microsoft-BotFramework/4.0";
}
}

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

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
* <p>
* Code generated by Microsoft (R) AutoRest Code Generator.
* Changes may cause incorrect behavior and will be lost if the code is
* regenerated.
@ -10,22 +10,14 @@
package com.microsoft.bot.connector;
import com.microsoft.bot.schema.models.Activity;
import com.microsoft.bot.schema.models.AttachmentData;
import com.microsoft.bot.schema.models.ChannelAccount;
import com.microsoft.bot.schema.models.ConversationParameters;
import com.microsoft.bot.schema.models.ConversationResourceResponse;
import com.microsoft.bot.schema.models.ConversationsResult;
import com.microsoft.bot.schema.models.PagedMembersResult;
import com.microsoft.bot.connector.models.ErrorResponseException;
import com.microsoft.bot.schema.models.ResourceResponse;
import com.microsoft.bot.schema.models.Transcript;
import com.microsoft.bot.schema.models.*;
import com.microsoft.rest.ServiceCallback;
import com.microsoft.rest.ServiceFuture;
import com.microsoft.rest.ServiceResponse;
import java.util.List;
import rx.Observable;
import java.util.List;
/**
* An instance of this class provides access to all the operations defined
* in Conversations.
@ -40,7 +32,6 @@ public interface Conversations {
Each ConversationMembers object contains the ID of the conversation and an array of ChannelAccounts that describe the members of the conversation.
*
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ConversationsResult object if successful.
*/
@ -85,6 +76,7 @@ public interface Conversations {
* @return the observable to the ConversationsResult object
*/
Observable<ServiceResponse<ConversationsResult>> getConversationsWithServiceResponseAsync();
/**
* GetConversations.
* List the Conversations in which this bot has participated.
@ -95,7 +87,6 @@ public interface Conversations {
*
* @param continuationToken skip or continuation token
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ConversationsResult object if successful.
*/
@ -161,7 +152,6 @@ public interface Conversations {
*
* @param parameters Parameters to create the conversation from
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ConversationResourceResponse object if successful.
*/
@ -243,7 +233,6 @@ public interface Conversations {
* @param conversationId Conversation ID
* @param activity Activity to send
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -308,7 +297,6 @@ public interface Conversations {
* @param activityId activityId to update
* @param activity replacement Activity
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -370,7 +358,6 @@ public interface Conversations {
* @param activityId activityId the reply is to (OPTIONAL)
* @param activity Activity to send
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -436,7 +423,6 @@ public interface Conversations {
* @param conversationId Conversation ID
* @param activityId activityId to delete
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
*/
void deleteActivity(String conversationId, String activityId);
@ -485,7 +471,6 @@ public interface Conversations {
*
* @param conversationId Conversation ID
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the List&lt;ChannelAccount&gt; object if successful.
*/
@ -534,7 +519,6 @@ public interface Conversations {
* @param conversationId Conversation ID
* @param memberId ID of the member to delete from this conversation
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
*/
void deleteConversationMember(String conversationId, String memberId);
@ -587,7 +571,6 @@ public interface Conversations {
* @param conversationId Conversation ID
* @param activityId Activity ID
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the List&lt;ChannelAccount&gt; object if successful.
*/
@ -639,7 +622,6 @@ public interface Conversations {
* @param conversationId Conversation ID
* @param attachmentUpload Attachment data
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -688,15 +670,14 @@ public interface Conversations {
/**
* This method allows you to upload the historic activities to the conversation.
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
* the client to render the activities in the right order.
*
* @param conversationId Conversation ID
* @param history Historic activities
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -704,16 +685,15 @@ public interface Conversations {
/**
* This method allows you to upload the historic activities to the conversation.
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
* the client to render the activities in the right order.
*
* @param conversationId Conversation ID
* @param history Historic activities
* @param serviceCallback the async ServiceCallback to handle successful and failed responses.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -721,15 +701,14 @@ public interface Conversations {
/**
* This method allows you to upload the historic activities to the conversation.
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
* the client to render the activities in the right order.
*
* @param conversationId Conversation ID
* @param history Historic activities
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
@ -737,38 +716,36 @@ public interface Conversations {
/**
* This method allows you to upload the historic activities to the conversation.
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
*
* Sender must ensure that the historic activities have unique ids and appropriate timestamps.
* The ids are used by the client to deal with duplicate activities and the timestamps are used by
* the client to render the activities in the right order.
*
* @param conversationId Conversation ID
* @param history Historic activities
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the ResourceResponse object if successful.
*/
Observable<ServiceResponse<ResourceResponse>> sendConversationHistoryWithServiceResponseAsync(String conversationId, Transcript history);
Observable<ServiceResponse<ResourceResponse>> sendConversationHistoryWithServiceResponseAsync(String conversationId, Transcript history);
/**
* Enumerate the members of a conversation one page at a time.
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
* of the conversation and a continuation token that can be used to get more values.
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
* conversation the Members will be empty or not present in the response.
*
* A response to a request that has a continuation token from a prior request may rarely return members
*
* A response to a request that has a continuation token from a prior request may rarely return members
* from a previous request.
*
* @param conversationId Conversation ID
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the PagedMembersResult object if successful.
*/
@ -776,23 +753,22 @@ public interface Conversations {
/**
* Enumerate the members of a conversation one page at a time.
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
* of the conversation and a continuation token that can be used to get more values.
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
* conversation the Members will be empty or not present in the response.
*
* A response to a request that has a continuation token from a prior request may rarely return members
*
* A response to a request that has a continuation token from a prior request may rarely return members
* from a previous request.
*
* @param conversationId Conversation ID
* @param serviceCallback the async ServiceCallback to handle successful and failed responses.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the PagedMembersResult object if successful.
*/
@ -800,22 +776,21 @@ public interface Conversations {
/**
* Enumerate the members of a conversation one page at a time.
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
* of the conversation and a continuation token that can be used to get more values.
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
* conversation the Members will be empty or not present in the response.
*
* A response to a request that has a continuation token from a prior request may rarely return members
*
* A response to a request that has a continuation token from a prior request may rarely return members
* from a previous request.
*
* @param conversationId Conversation ID
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the PagedMembersResult object if successful.
*/
@ -823,25 +798,24 @@ public interface Conversations {
/**
* Enumerate the members of a conversation one page at a time.
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
* of the conversation and a continuation token that can be used to get more values.
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
* conversation the Members will be empty or not present in the response.
*
* A response to a request that has a continuation token from a prior request may rarely return members
*
* A response to a request that has a continuation token from a prior request may rarely return members
* from a previous request.
*
* @param conversationId Conversation ID
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws ErrorResponseException thrown if the request is rejected by server
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the observable to the ResourceResponse object
*/
Observable<ServiceResponse<PagedMembersResult>> getConversationPagedMembersWithServiceResponseAsync(String conversationId);
}
}

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

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
package com.microsoft.bot.connector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
import java.util.concurrent.ForkJoinWorkerThread;
/**
* Provides a common Executor for Future operations.
*/
public class ExecutorFactory {
private static ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory() {
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName("Bot-" + worker.getPoolIndex());
return worker;
}
};
private static ExecutorService executor = new ForkJoinPool(
Runtime.getRuntime().availableProcessors() * 2,
factory,
null,
false);
public static ExecutorService getExecutor() {
return executor;
}
}

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

@ -1,6 +1,6 @@
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import java.io.IOException;
import java.io.InputStream;
@ -23,7 +23,7 @@ public class UserAgent {
String build_version;
final Properties properties = new Properties();
try {
InputStream propStream = ConnectorClientImpl.class.getClassLoader().getResourceAsStream("project.properties");
InputStream propStream = RestConnectorClient.class.getClassLoader().getResourceAsStream("project.properties");
properties.load(propStream);
build_version = properties.getProperty("version");
} catch (IOException e) {

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

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
package com.microsoft.bot.connector.authentication;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.bot.connector.ExecutorFactory;
import java.net.MalformedURLException;
import java.util.concurrent.Future;
public class AdalAuthenticator {
private AuthenticationContext context;
private OAuthConfiguration oAuthConfiguration;
private ClientCredential clientCredential;
public AdalAuthenticator(ClientCredential clientCredential, OAuthConfiguration configurationOAuth) throws MalformedURLException {
this.oAuthConfiguration = configurationOAuth;
this.clientCredential = clientCredential;
this.context = new AuthenticationContext(configurationOAuth.authority(), false, ExecutorFactory.getExecutor());
}
public Future<AuthenticationResult> acquireToken() {
return context.acquireToken(oAuthConfiguration.scope(), clientCredential, null);
}
}

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

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import java.util.ArrayList;
import java.util.List;
/**
* General configuration settings for authentication.
*/
public class AuthenticationConfiguration {
public List<String> requiredEndorsements() {
return new ArrayList<String>();
}
}

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

@ -7,23 +7,127 @@ import java.util.ArrayList;
import java.util.List;
public final class AuthenticationConstants {
public static final String ToChannelFromBotLoginUrl = "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token";
public static final String ToChannelFromBotOAuthScope = "https://api.botframework.com/.default";
public static final String ToBotFromChannelTokenIssuer = "https://api.botframework.com";
public static final String ToBotFromChannelOpenIdMetadataUrl = "https://login.botframework.com/v1/.well-known/openidconfiguration";
public static final String ToBotFromEmulatorOpenIdMetadataUrl = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
public static final List<String> AllowedSigningAlgorithms = new ArrayList<>();
public static final String AuthorizedParty = "azp";
public static final String AudienceClaim = "aud";
public static final String ServiceUrlClaim = "serviceurl";
public static final String VersionClaim = "ver";
public static final String AppIdClaim = "appid";
/**
* OAuth Url used to get a token from OAuthApiClient
* TO CHANNEL FROM BOT: Login URL.
*/
public static final String OAuthUrl = "https://api.botframework.com";
@Deprecated
public static final String TO_CHANNEL_FROM_BOT_LOGIN_URL = "https://login.microsoftonline.com/botframework.com";
/**
* TO CHANNEL FROM BOT: Login URL template string. Bot developer may specify
* which tenant to obtain an access token from. By default, the channels only
* accept tokens from "botframework.com". For more details see https://aka.ms/bots/tenant-restriction.
*/
public static final String TO_CHANNEL_FROM_BOT_LOGIN_URL_TEMPLATE = "https://login.microsoftonline.com/%s";
/**
* TO CHANNEL FROM BOT: OAuth scope to request.
*/
public static final String TO_CHANNEL_FROM_BOT_OAUTH_SCOPE = "https://api.botframework.com";
/**
* TO BOT FROM CHANNEL: Token issuer.
*/
public static final String TO_BOT_FROM_CHANNEL_TOKEN_ISSUER = "https://api.botframework.com";
/**
* TO BOT FROM CHANNEL: OpenID metadata document for tokens coming from MSA.
*/
public static final String TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL = "https://login.botframework.com/v1/.well-known/openidconfiguration";
/**
* TO BOT FROM EMULATOR: OpenID metadata document for tokens coming from MSA.
*/
public static final String TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
/**
* TO BOT FROM ENTERPRISE CHANNEL: OpenID metadata document for tokens coming from MSA.
*/
public static final String TO_BOT_FROM_ENTERPRISE_CHANNEL_OPENID_METADATA_URL_FORMAT = "https://%s.enterprisechannel.botframework.com/v1/.well-known/openidconfiguration";
/**
* Allowed token signing algorithms. Tokens come from channels to the bot. The code
* that uses this also supports tokens coming from the emulator.
*/
public static final List<String> AllowedSigningAlgorithms = new ArrayList<>();
/**
* Application Setting Key for the OAuthUrl value.
*/
public static final String OAUTH_URL_KEY = "OAuthApiEndpoint";
/**
* OAuth Url used to get a token from OAuthApiClient.
*/
public static final String OAUTH_URL = "https://api.botframework.com";
/**
* Application Settings Key for whether to emulate OAuthCards when using the emulator.
*/
public static final String EMULATE_OAUTH_CARDS_KEY = "EmulateOAuthCards";
/**
* Application Setting Key for the OpenIdMetadataUrl value.
*/
public static final String BOT_OPENID_METADATA_KEY = "BotOpenIdMetadata";
/**
* The default tenant to acquire bot to channel token from.
*/
public static final String DEFAULT_CHANNEL_AUTH_TENANT = "botframework.com";
/**
* "azp" Claim.
* Authorized party - the party to which the ID Token was issued.
* This claim follows the general format set forth in the OpenID Spec.
* http://openid.net/specs/openid-connect-core-1_0.html#IDToken.
*/
public static final String AUTHORIZED_PARTY = "azp";
/**
* Audience Claim. From RFC 7519.
* https://tools.ietf.org/html/rfc7519#section-4.1.3
* The "aud" (audience) claim identifies the recipients that the JWT is
* intended for. Each principal intended to process the JWT MUST
* identify itself with a value in the audience claim. If the principal
* processing the claim does not identify itself with a value in the
* "aud" claim when this claim is present, then the JWT MUST be
* rejected. In the general case, the "aud" value is an array of case-
* sensitive strings, each containing a StringOrURI value. In the
* special case when the JWT has one audience, the "aud" value MAY be a
* single case-sensitive string containing a StringOrURI value. The
* interpretation of audience values is generally application specific.
* Use of this claim is OPTIONAL.
*/
public static final String AUDIENCE_CLAIM = "aud";
/**
* From RFC 7515
* https://tools.ietf.org/html/rfc7515#section-4.1.4
* The "kid" (key ID) Header Parameter is a hint indicating which key
* was used to secure the JWS. This parameter allows originators to
* explicitly signal a change of key to recipients. The structure of
* the "kid" value is unspecified. Its value MUST be a case-sensitive
* string. Use of this Header Parameter is OPTIONAL.
* When used with a JWK, the "kid" value is used to match a JWK "kid"
* parameter value.
*/
public static final String KEY_ID_HEADER = "kid";
/**
* Service URL claim name. As used in Microsoft Bot Framework v3.1 auth.
*/
public static final String SERVICE_URL_CLAIM = "serviceurl";
/**
* Token version claim name. As used in Microsoft AAD tokens.
*/
public static final String VERSION_CLAIM = "ver";
/**
* App ID claim name. As used in Microsoft AAD 1.0 tokens.
*/
public static final String APPID_CLAIM = "appid";
static {
AllowedSigningAlgorithms.add("RS256");

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

@ -1,23 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
public class BotCredentials {
protected String appId;
protected String appPassword;
public String appId() { return this.appId; }
public String appPassword() { return this.appPassword; }
public BotCredentials withAppId(String appId) {
this.appId = appId;
return this;
}
public BotCredentials withAppPassword(String appPassword) {
this.appPassword = appPassword;
return this;
}
}

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

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import java.util.concurrent.CompletableFuture;
/**
* ChannelProvider interface. This interface allows Bots to provide their own
* implementation for the configuration parameters to connect to a Bot.
* Framework channel service.
*/
public interface ChannelProvider {
/**
* Gets the channel service property for this channel provider.
*
* @return The channel service property for the channel provider.
*/
CompletableFuture<String> getChannelService();
/**
* Gets a value of whether this provider represents a channel on Government Azure.
*
* @return True if this channel provider represents a channel on Government Azure.
*/
boolean isGovernment();
/**
* Gets a value of whether this provider represents a channel on Public Azure.
*
* @return True if this channel provider represents a channel on Public Azure.
*/
boolean isPublicAzure();
}

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

@ -4,88 +4,141 @@
package com.microsoft.bot.connector.authentication;
import com.microsoft.aad.adal4j.AuthenticationException;
import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ChannelValidation {
/**
* TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot
*/
public static final TokenValidationParameters ToBotFromChannelTokenValidationParameters = TokenValidationParameters.toBotFromChannelTokenValidationParameters();
public static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = new TokenValidationParameters() {{
this.validateIssuer = true;
this.validIssuers = new ArrayList<String>() {{
add(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER);
}};
this.validateAudience = false;
this.validateLifetime = true;
this.clockSkew = Duration.ofMinutes(5);
this.requireSignedTokens = true;
}};
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Service.
* @param authHeader The raw HTTP header in the format: "Bearer [longString]"
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelId ChannelId for endorsements validation.
* @param channelId ChannelId for endorsements validation.
* @return A valid ClaimsIdentity.
*
* On join:
* @throws AuthenticationException A token issued by the Bot Framework emulator will FAIL this check.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String channelId) throws ExecutionException, InterruptedException, AuthenticationException {
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String channelId) {
return authenticateToken(authHeader, credentials, channelId, new AuthenticationConfiguration());
}
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Service.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelId ChannelId for endorsements validation.
* @param authConfig The AuthenticationConfiguration.
* @return A valid ClaimsIdentity.
*
* On join:
* @throws AuthenticationException A token issued by the Bot Framework emulator will FAIL this check.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String channelId, AuthenticationConfiguration authConfig) {
JwtTokenExtractor tokenExtractor = new JwtTokenExtractor(
ToBotFromChannelTokenValidationParameters,
AuthenticationConstants.ToBotFromChannelOpenIdMetadataUrl,
TOKENVALIDATIONPARAMETERS,
AuthenticationConstants.TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL,
AuthenticationConstants.AllowedSigningAlgorithms);
ClaimsIdentity identity = tokenExtractor.getIdentityAsync(authHeader, channelId).get();
if (identity == null) {
// No valid identity. Not Authorized.
throw new AuthenticationException("Invalid Identity");
}
return tokenExtractor.getIdentityAsync(authHeader, channelId)
.thenApply(identity -> {
if (identity == null) {
// No valid identity. Not Authorized.
throw new AuthenticationException("Invalid Identity");
}
if (!identity.isAuthenticated()) {
// The token is in some way invalid. Not Authorized.
throw new AuthenticationException("Token Not Authenticated");
}
if (!identity.isAuthenticated()) {
// The token is in some way invalid. Not Authorized.
throw new AuthenticationException("Token Not Authenticated");
}
// Now check that the AppID in the claims set matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
// Now check that the AppID in the claims set matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
// Look for the "aud" claim, but only if issued from the Bot Framework
if (!identity.getIssuer().equalsIgnoreCase(AuthenticationConstants.ToBotFromChannelTokenIssuer)) {
throw new AuthenticationException("Token Not Authenticated");
}
// Look for the "aud" claim, but only if issued from the Bot Framework
if (!identity.getIssuer().equalsIgnoreCase(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER)) {
throw new AuthenticationException("Wrong Issuer");
}
// The AppId from the claim in the token must match the AppId specified by the developer. Note that
// the Bot Framework uses the Audience claim ("aud") to pass the AppID.
String appIdFromClaim = identity.claims().get(AuthenticationConstants.AudienceClaim);
if (appIdFromClaim == null || appIdFromClaim.isEmpty()) {
// Claim is present, but doesn't have a value. Not Authorized.
throw new AuthenticationException("Token Not Authenticated");
}
// The AppId from the claim in the token must match the AppId specified by the developer. Note that
// the Bot Framework uses the Audience claim ("aud") to pass the AppID.
String appIdFromAudienceClaim = identity.claims().get(AuthenticationConstants.AUDIENCE_CLAIM);
if (StringUtils.isEmpty(appIdFromAudienceClaim)) {
// Claim is present, but doesn't have a value. Not Authorized.
throw new AuthenticationException("No Audience Claim");
}
if (!credentials.isValidAppIdAsync(appIdFromClaim).get()) {
throw new AuthenticationException(String.format("Invalid AppId passed on token: '%s'.", appIdFromClaim));
}
if (!credentials.isValidAppIdAsync(appIdFromAudienceClaim).join()) {
throw new AuthenticationException(String.format("Invalid AppId passed on token: '%s'.", appIdFromAudienceClaim));
}
return CompletableFuture.completedFuture(identity);
return identity;
});
}
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Service.
* @param authHeader The raw HTTP header in the format: "Bearer [longString]"
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]"
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelId ChannelId for endorsements validation.
* @param serviceUrl Service url.
* @param channelId ChannelId for endorsements validation.
* @param serviceUrl Service url.
* @return A valid ClaimsIdentity.
*
* On join:
* @throws AuthenticationException A token issued by the Bot Framework emulator will FAIL this check.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader,CredentialProvider credentials, String channelId, String serviceUrl) throws ExecutionException, InterruptedException, AuthenticationException {
ClaimsIdentity identity = ChannelValidation.authenticateToken(authHeader, credentials, channelId).get();
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String channelId, String serviceUrl) {
return authenticateToken(authHeader, credentials, channelId, serviceUrl, new AuthenticationConfiguration());
}
if (!identity.claims().containsKey(AuthenticationConstants.ServiceUrlClaim)) {
// Claim must be present. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim is required on Channel Token.", AuthenticationConstants.ServiceUrlClaim));
}
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Service.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]"
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelId ChannelId for endorsements validation.
* @param serviceUrl Service url.
* @param authConfig The authentication configuration.
* @return A valid ClaimsIdentity.
*
* On join:
* @throws AuthenticationException A token issued by the Bot Framework emulator will FAIL this check.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String channelId, String serviceUrl, AuthenticationConfiguration authConfig) {
return ChannelValidation.authenticateToken(authHeader, credentials, channelId, authConfig)
.thenApply(identity -> {
if (!identity.claims().containsKey(AuthenticationConstants.SERVICE_URL_CLAIM)) {
// Claim must be present. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim is required on Channel Token.", AuthenticationConstants.SERVICE_URL_CLAIM));
}
if (!serviceUrl.equalsIgnoreCase(identity.claims().get(AuthenticationConstants.ServiceUrlClaim))) {
// Claim must match. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim does not match service url provided (%s).", AuthenticationConstants.ServiceUrlClaim, serviceUrl));
}
if (!serviceUrl.equalsIgnoreCase(identity.claims().get(AuthenticationConstants.SERVICE_URL_CLAIM))) {
// Claim must match. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim does not match service url provided (%s).", AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl));
}
return CompletableFuture.completedFuture(identity);
return identity;
});
}
}

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

@ -3,10 +3,45 @@
package com.microsoft.bot.connector.authentication;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.HashMap;
import java.util.Map;
public interface ClaimsIdentity {
boolean isAuthenticated();
Map<String, String> claims();
String getIssuer();
public class ClaimsIdentity {
private String issuer;
private Map<String, String> claims;
private ClaimsIdentity() {
this("", new HashMap<>());
}
public ClaimsIdentity(String authIssuer) {
this(authIssuer, new HashMap<>());
}
public ClaimsIdentity(String authIssuer, Map<String, String> claims) {
this.issuer = authIssuer;
this.claims = claims;
}
public ClaimsIdentity(DecodedJWT jwt) {
claims = new HashMap<>();
if (jwt.getClaims() != null) {
jwt.getClaims().forEach((k, v) -> claims.put(k, v.asString()));
}
issuer = jwt.getIssuer();
}
public boolean isAuthenticated() {
return this.issuer != null && !this.issuer.isEmpty();
}
public Map<String, String> claims() {
return this.claims;
}
public String getIssuer() {
return issuer;
}
}

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

@ -1,40 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import java.util.HashMap;
import java.util.Map;
public class ClaimsIdentityImpl implements ClaimsIdentity {
private String issuer;
private Map<String, String> claims;
public ClaimsIdentityImpl() {
this("", new HashMap<>());
}
public ClaimsIdentityImpl(String authType) {
this(authType, new HashMap<>());
}
public ClaimsIdentityImpl(String authType, Map<String, String> claims) {
this.issuer = authType;
this.claims = claims;
}
@Override
public boolean isAuthenticated() {
return this.issuer != null && !this.issuer.isEmpty();
}
@Override
public Map<String, String> claims() {
return this.claims;
}
@Override
public String getIssuer() {
return issuer;
}
}

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

@ -1,43 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import java.util.concurrent.CompletableFuture;
public class CredentialProviderImpl extends BotCredentials implements CredentialProvider {
public CredentialProviderImpl(String appId, String appPassword) {
this.appId = appId;
this.appPassword = appPassword;
}
public CredentialProviderImpl(BotCredentials credentials) {
this(credentials.appId, credentials.appPassword);
}
@Override
public CredentialProviderImpl withAppId(String appId) {
return (CredentialProviderImpl) super.withAppId(appId);
}
@Override
public CredentialProviderImpl withAppPassword(String appPassword) {
return (CredentialProviderImpl) super.withAppPassword(appPassword);
}
@Override
public CompletableFuture<Boolean> isValidAppIdAsync(String appId) {
return CompletableFuture.completedFuture(this.appId.equals(appId));
}
@Override
public CompletableFuture<String> getAppPasswordAsync(String appId) {
return CompletableFuture.completedFuture(this.appId.equals(appId) ? this.appPassword : null);
}
@Override
public CompletableFuture<Boolean> isAuthenticationDisabledAsync() {
return CompletableFuture.completedFuture(this.appId == null || this.appId.isEmpty());
}
}

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

@ -6,9 +6,11 @@ package com.microsoft.bot.connector.authentication;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.microsoft.aad.adal4j.AuthenticationException;
import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* Validates and Examines JWT tokens from the Bot Framework Emulator
@ -17,25 +19,40 @@ public class EmulatorValidation {
/**
* TO BOT FROM EMULATOR: Token validation parameters when connecting to a channel.
*/
public static final TokenValidationParameters ToBotFromEmulatorTokenValidationParameters = TokenValidationParameters.toBotFromEmulatorTokenValidationParameters();
private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = new TokenValidationParameters() {{
this.validateIssuer = true;
this.validIssuers = new ArrayList<String>() {{
add("https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/"); // Auth v3.1, 1.0 token
add("https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0"); // Auth v3.1, 2.0 token
add("https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/"); // Auth v3.2, 1.0 token
add("https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0"); // Auth v3.2, 2.0 token
add("https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/"); // Auth for US Gov, 1.0 token
add("https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0"); // Auth for US Gov, 2.0 token
}};
this.validateAudience = false;
this.validateLifetime = true;
this.clockSkew = Duration.ofMinutes(5);
this.requireSignedTokens = true;
}};
/**
* Determines if a given Auth header is from the Bot Framework Emulator
*
* @param authHeader Bearer Token, in the "Bearer [Long String]" Format.
* @return True, if the token was issued by the Emulator. Otherwise, false.
*/
public static CompletableFuture<Boolean> isTokenFromEmulator(String authHeader) {
public static Boolean isTokenFromEmulator(String authHeader) {
// The Auth Header generally looks like this:
// "Bearer eyJ0e[...Big Long String...]XAiO"
if (authHeader == null || authHeader.isEmpty()) {
if (StringUtils.isEmpty(authHeader)) {
// No token. Can't be an emulator token.
return CompletableFuture.completedFuture(false);
return false;
}
String[] parts = authHeader.split(" ");
if (parts.length != 2) {
// Emulator tokens MUST have exactly 2 parts. If we don't have 2 parts, it's not an emulator token
return CompletableFuture.completedFuture(false);
return false;
}
String schema = parts[0];
@ -43,91 +60,120 @@ public class EmulatorValidation {
if (!schema.equalsIgnoreCase("bearer")) {
// The scheme from the emulator MUST be "Bearer"
return CompletableFuture.completedFuture(false);
return false;
}
// Parse the Big Long String into an actual token.
DecodedJWT decodedJWT = JWT.decode(token);
try {
DecodedJWT decodedJWT = JWT.decode(token);
// Is there an Issuer?
if (decodedJWT.getIssuer().isEmpty()) {
// No Issuer, means it's not from the Emulator.
return CompletableFuture.completedFuture(false);
}
// Is there an Issuer?
if (StringUtils.isEmpty(decodedJWT.getIssuer())) {
// No Issuer, means it's not from the Emulator.
return false;
}
// Is the token issues by a source we consider to be the emulator?
if (!ToBotFromEmulatorTokenValidationParameters.validIssuers.contains(decodedJWT.getIssuer())) {
// Is the token issues by a source we consider to be the emulator?
// Not a Valid Issuer. This is NOT a Bot Framework Emulator Token.
return CompletableFuture.completedFuture(false);
return TOKENVALIDATIONPARAMETERS.validIssuers.contains(decodedJWT.getIssuer());
} catch (Throwable t) {
return false;
}
// The Token is from the Bot Framework Emulator. Success!
return CompletableFuture.completedFuture(true);
}
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Emulator.
* @param authHeader The raw HTTP header in the format: "Bearer [longString]"
* @param credentials The user defined set of valid credentials, such as the AppId.
* A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelProvider The channelService value that distinguishes public Azure from US Government Azure.
* @param channelId The ID of the channel to validate.
* @return A valid ClaimsIdentity.
* <p>
* On join:
* @throws AuthenticationException A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String channelId) throws ExecutionException, InterruptedException, AuthenticationException {
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, String channelId) {
return authenticateToken(authHeader, credentials, channelProvider, channelId, new AuthenticationConfiguration());
}
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Emulator.
* A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelProvider The channelService value that distinguishes public Azure from US Government Azure.
* @param channelId The ID of the channel to validate.
* @param authConfig The authentication configuration.
* @return A valid ClaimsIdentity.
* <p>
* On join:
* @throws AuthenticationException A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, String channelId, AuthenticationConfiguration authConfig) {
String openIdMetadataUrl = channelProvider != null && channelProvider.isGovernment() ?
GovernmentAuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL :
AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL;
JwtTokenExtractor tokenExtractor = new JwtTokenExtractor(
ToBotFromEmulatorTokenValidationParameters,
AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl,
AuthenticationConstants.AllowedSigningAlgorithms);
TOKENVALIDATIONPARAMETERS,
openIdMetadataUrl,
AuthenticationConstants.AllowedSigningAlgorithms);
ClaimsIdentity identity = tokenExtractor.getIdentityAsync(authHeader, channelId).get();
if (identity == null) {
// No valid identity. Not Authorized.
throw new AuthenticationException("Invalid Identity");
}
return tokenExtractor.getIdentityAsync(authHeader, channelId, authConfig.requiredEndorsements())
.thenApply(identity -> {
if (identity == null) {
// No valid identity. Not Authorized.
throw new AuthenticationException("Invalid Identity");
}
if (!identity.isAuthenticated()) {
// The token is in some way invalid. Not Authorized.
throw new AuthenticationException("Token Not Authenticated");
}
if (!identity.isAuthenticated()) {
// The token is in some way invalid. Not Authorized.
throw new AuthenticationException("Token Not Authenticated");
}
// Now check that the AppID in the claims set matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
if (!identity.claims().containsKey(AuthenticationConstants.VersionClaim)) {
throw new AuthenticationException(String.format("'%s' claim is required on Emulator Tokens.", AuthenticationConstants.VersionClaim));
}
// Now check that the AppID in the claims set matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
if (!identity.claims().containsKey(AuthenticationConstants.VERSION_CLAIM)) {
throw new AuthenticationException(String.format("'%s' claim is required on Emulator Tokens.", AuthenticationConstants.VERSION_CLAIM));
}
String tokenVersion = identity.claims().get(AuthenticationConstants.VersionClaim);
String appId = "";
String tokenVersion = identity.claims().get(AuthenticationConstants.VERSION_CLAIM);
String appId = "";
// The Emulator, depending on Version, sends the AppId via either the
// appid claim (Version 1) or the Authorized Party claim (Version 2).
if (tokenVersion.isEmpty() || tokenVersion.equalsIgnoreCase("1.0")) {
// either no Version or a version of "1.0" means we should look for
// the claim in the "appid" claim.
if (!identity.claims().containsKey(AuthenticationConstants.AppIdClaim)) {
// No claim around AppID. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim is required on Emulator Token version '1.0'.", AuthenticationConstants.AppIdClaim));
}
// The Emulator, depending on Version, sends the AppId via either the
// appid claim (Version 1) or the Authorized Party claim (Version 2).
if (StringUtils.isEmpty(tokenVersion) || tokenVersion.equalsIgnoreCase("1.0")) {
// either no Version or a version of "1.0" means we should look for
// the claim in the "appid" claim.
if (!identity.claims().containsKey(AuthenticationConstants.APPID_CLAIM)) {
// No claim around AppID. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim is required on Emulator Token version '1.0'.", AuthenticationConstants.APPID_CLAIM));
}
appId = identity.claims().get(AuthenticationConstants.AppIdClaim);
} else if (tokenVersion.equalsIgnoreCase("2.0")) {
// Emulator, "2.0" puts the AppId in the "azp" claim.
if (!identity.claims().containsKey(AuthenticationConstants.AuthorizedParty)) {
// No claim around AppID. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim is required on Emulator Token version '2.0'.", AuthenticationConstants.AuthorizedParty));
}
appId = identity.claims().get(AuthenticationConstants.APPID_CLAIM);
} else if (tokenVersion.equalsIgnoreCase("2.0")) {
// Emulator, "2.0" puts the AppId in the "azp" claim.
if (!identity.claims().containsKey(AuthenticationConstants.AUTHORIZED_PARTY)) {
// No claim around AppID. Not Authorized.
throw new AuthenticationException(String.format("'%s' claim is required on Emulator Token version '2.0'.", AuthenticationConstants.AUTHORIZED_PARTY));
}
appId = identity.claims().get(AuthenticationConstants.AuthorizedParty);
} else {
// Unknown Version. Not Authorized.
throw new AuthenticationException(String.format("Unknown Emulator Token version '%s'.", tokenVersion));
}
appId = identity.claims().get(AuthenticationConstants.AUTHORIZED_PARTY);
} else {
// Unknown Version. Not Authorized.
throw new AuthenticationException(String.format("Unknown Emulator Token version '%s'.", tokenVersion));
}
if (!credentials.isValidAppIdAsync(appId).get()) {
throw new AuthenticationException(String.format("Invalid AppId passed on token: '%s'.", appId));
}
if (!credentials.isValidAppIdAsync(appId).join()) {
throw new AuthenticationException(String.format("Invalid AppId passed on token: '%s'.", appId));
}
return CompletableFuture.completedFuture(identity);
return identity;
});
}
}

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

@ -1,25 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
public abstract class EndorsementsValidator {
/**
* Verify that the set of ChannelIds, which come from the incoming activities,
* all match the endorsements found on the JWT Token.
* For example, if an Activity comes from webchat, that channelId says
* says "webchat" and the jwt token endorsement MUST match that.
* @param channelId The channel name, typically extracted from the activity.ChannelId field, that
* to which the Activity is affinitized.
*
* @param channelId The channel name, typically extracted from the activity.ChannelId field, that
* to which the Activity is affinitized.
* @param endorsements Whoever signed the JWT token is permitted to send activities only for
* some specific channels. That list is the endorsement list, and is validated here against the channelId.
* some specific channels. That list is the endorsement list, and is validated here against the channelId.
* @return True is the channelId is found in the Endorsement set. False if the channelId is not found.
*/
public static boolean validate(String channelId, List<String> endorsements) {
// If the Activity came in and doesn't have a Channel ID then it's making no
// assertions as to who endorses it. This means it should pass.
if (channelId == null || channelId.isEmpty())
if (StringUtils.isEmpty(channelId))
return true;
if (endorsements == null)
@ -35,12 +40,6 @@ public abstract class EndorsementsValidator {
// JwtTokenExtractor
// Does the set of endorsements match the channelId that was passed in?
// ToDo: Consider moving this to a HashSet instead of a string
// array, to make lookups O(1) instead of O(N). To give a sense
// of scope, tokens from WebChat have about 10 endorsements, and
// tokens coming from Teams have about 20.
return endorsements.contains(channelId);
}
}

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

@ -0,0 +1,123 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.ExecutorFactory;
import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
public class EnterpriseChannelValidation {
private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = new TokenValidationParameters() {{
this.validateIssuer = true;
this.validIssuers = new ArrayList<String>() {{
add(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER);
}};
this.validateAudience = false;
this.validateLifetime = true;
this.clockSkew = Duration.ofMinutes(5);
this.requireSignedTokens = true;
}};
/**
* Validate the incoming Auth Header as a token sent from a Bot Framework Channel Service.
* A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelProvider The channelService value that distinguishes public Azure from US Government Azure.
* @param channelId The ID of the channel to validate.
* @return A valid ClaimsIdentity.
*
* On join:
* @throws AuthenticationException A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, String serviceUrl, String channelId) {
return authenticateToken(authHeader, credentials, channelProvider, serviceUrl, channelId, new AuthenticationConfiguration());
}
/**
* Validate the incoming Auth Header as a token sent from a Bot Framework Channel Service.
* A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelProvider The channelService value that distinguishes public Azure from US Government Azure.
* @param channelId The ID of the channel to validate.
* @param authConfig The authentication configuration.
* @return A valid ClaimsIdentity.
*
* On join:
* @throws AuthenticationException A token issued by the Bot Framework will FAIL this check. Only Emulator tokens will pass.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, String serviceUrl, String channelId, AuthenticationConfiguration authConfig) {
if (authConfig == null) {
throw new IllegalArgumentException("Missing AuthenticationConfiguration");
}
return channelProvider.getChannelService()
.thenCompose(channelService -> {
JwtTokenExtractor tokenExtractor = new JwtTokenExtractor(
TOKENVALIDATIONPARAMETERS,
String.format(AuthenticationConstants.TO_BOT_FROM_ENTERPRISE_CHANNEL_OPENID_METADATA_URL_FORMAT, channelService),
AuthenticationConstants.AllowedSigningAlgorithms);
return tokenExtractor.getIdentityAsync(authHeader, channelId, authConfig.requiredEndorsements());
})
.thenCompose(identity -> {
if (identity == null) {
// No valid identity. Not Authorized.
throw new AuthenticationException("Invalid Identity");
}
return validateIdentity(identity, credentials, serviceUrl);
});
}
public static CompletableFuture<ClaimsIdentity> validateIdentity(ClaimsIdentity identity, CredentialProvider credentials, String serviceUrl) {
return CompletableFuture.supplyAsync(() -> {
if (identity == null || !identity.isAuthenticated()) {
throw new AuthenticationException("Invalid Identity");
}
// Now check that the AppID in the claim set matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
if (!StringUtils.equalsIgnoreCase(identity.getIssuer(), AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER)) {
throw new AuthenticationException("Wrong Issuer");
}
// The AppId from the claim in the token must match the AppId specified by the developer. Note that
// the Bot Framework uses the Audience claim ("aud") to pass the AppID.
String appIdFromAudienceClaim = identity.claims().get(AuthenticationConstants.AUDIENCE_CLAIM);
if (StringUtils.isEmpty(appIdFromAudienceClaim)) {
// Claim is present, but doesn't have a value. Not Authorized.
throw new AuthenticationException("No Audience Claim");
}
boolean isValid = credentials.isValidAppIdAsync(appIdFromAudienceClaim).join();
if (!isValid) {
throw new AuthenticationException(String.format("Invalid AppId passed on token: '%s'.", appIdFromAudienceClaim));
}
String serviceUrlClaim = identity.claims().get(AuthenticationConstants.SERVICE_URL_CLAIM);
if (StringUtils.isEmpty(serviceUrl)) {
throw new AuthenticationException(String.format("Invalid serviceurl passed on token: '%s'.", serviceUrlClaim));
}
if (!StringUtils.equals(serviceUrl, serviceUrlClaim)) {
throw new AuthenticationException(String.format("serviceurl doesn't match claim: '%s'.", serviceUrlClaim));
}
return identity;
}, ExecutorFactory.getExecutor());
}
}

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

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
/**
* Values and Constants used for Authentication and Authorization by the Bot Framework Protocol
* to US Government DataCenters.
*/
public class GovernmentAuthenticationConstants {
public static final String CHANNELSERVICE = "https://botframework.azure.us";
/**
* TO GOVERNMENT CHANNEL FROM BOT: Login URL.
*/
public static final String TO_CHANNEL_FROM_BOT_LOGIN_URL = "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e";
/**
* TO GOVERNMENT CHANNEL FROM BOT: OAuth scope to request.
*/
public static final String TO_CHANNEL_FROM_BOT_OAUTH_SCOPE = "https://api.botframework.us";
/**
* TO BOT FROM GOVERNMENT CHANNEL: Token issuer.
*/
public static final String TO_BOT_FROM_CHANNEL_TOKEN_ISSUER = "https://api.botframework.us";
/**
* OAuth Url used to get a token from OAuthApiClient.
*/
public static final String OAUTH_URL_GOV = "https://api.botframework.azure.us";
/**
* TO BOT FROM GOVERNMANT CHANNEL: OpenID metadata document for tokens coming from MSA.
*/
public static final String TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL = "https://login.botframework.azure.us/v1/.well-known/openidconfiguration";
/**
* TO BOT FROM GOVERNMENT EMULATOR: OpenID metadata document for tokens coming from MSA.
*/
public static final String TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL = "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0/.well-known/openid-configuration";
}

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

@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.ExecutorFactory;
import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
/**
* TO BOT FROM GOVERNMENT CHANNEL: Token validation parameters when connecting to a bot.
*/
public class GovernmentChannelValidation {
private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = new TokenValidationParameters() {{
this.validateIssuer = true;
this.validIssuers = new ArrayList<String>() {{
add(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER);
}};
this.validateAudience = false;
this.validateLifetime = true;
this.clockSkew = Duration.ofMinutes(5);
this.requireSignedTokens = true;
}};
/**
* Validate the incoming Auth Header as a token sent from a Bot Framework Government Channel Service.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param serviceUrl The service url from the request.
* @param channelId The ID of the channel to validate.
* @return A CompletableFuture representing the asynchronous operation.
*
* On join:
* @throws AuthenticationException Authentication failed.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String serviceUrl, String channelId) {
return authenticateToken(authHeader, credentials, serviceUrl, channelId, new AuthenticationConfiguration());
}
/**
* Validate the incoming Auth Header as a token sent from a Bot Framework Government Channel Service.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param serviceUrl The service url from the request.
* @param channelId The ID of the channel to validate.
* @param authConfig The authentication configuration.
* @return A CompletableFuture representing the asynchronous operation.
*
* On join:
* @throws AuthenticationException Authentication failed.
*/
public static CompletableFuture<ClaimsIdentity> authenticateToken(String authHeader, CredentialProvider credentials, String serviceUrl, String channelId, AuthenticationConfiguration authConfig) {
JwtTokenExtractor tokenExtractor = new JwtTokenExtractor(
TOKENVALIDATIONPARAMETERS,
GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL,
AuthenticationConstants.AllowedSigningAlgorithms);
return tokenExtractor.getIdentityAsync(authHeader, channelId, authConfig.requiredEndorsements())
.thenCompose(identity -> {
return validateIdentity(identity, credentials, serviceUrl);
});
}
/**
* Validate the ClaimsIdentity as sent from a Bot Framework Government Channel Service.
*
* @param identity The claims identity to validate.
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param serviceUrl The service url from the request.
* @return A CompletableFuture representing the asynchronous operation.
*
* On join:
* @throws AuthenticationException Validation failed.
*/
public static CompletableFuture<ClaimsIdentity> validateIdentity(ClaimsIdentity identity, CredentialProvider credentials, String serviceUrl) {
return CompletableFuture.supplyAsync(() -> {
if (identity == null || !identity.isAuthenticated()) {
throw new AuthenticationException("Invalid Identity");
}
// Now check that the AppID in the claim set matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
if (!StringUtils.equalsIgnoreCase(identity.getIssuer(), GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER)) {
throw new AuthenticationException("Wrong Issuer");
}
// The AppId from the claim in the token must match the AppId specified by the developer. Note that
// the Bot Framework uses the Audience claim ("aud") to pass the AppID.
String appIdFromAudienceClaim = identity.claims().get(AuthenticationConstants.AUDIENCE_CLAIM);
if (StringUtils.isEmpty(appIdFromAudienceClaim)) {
// Claim is present, but doesn't have a value. Not Authorized.
throw new AuthenticationException("No Audience Claim");
}
boolean isValid = credentials.isValidAppIdAsync(appIdFromAudienceClaim).join();
if (!isValid) {
throw new AuthenticationException(String.format("Invalid AppId passed on token: '%s'.", appIdFromAudienceClaim));
}
String serviceUrlClaim = identity.claims().get(AuthenticationConstants.SERVICE_URL_CLAIM);
if (StringUtils.isEmpty(serviceUrl)) {
throw new AuthenticationException(String.format("Invalid serviceurl passed on token: '%s'.", serviceUrlClaim));
}
if (!StringUtils.equals(serviceUrl, serviceUrlClaim)) {
throw new AuthenticationException(String.format("serviceurl doesn't match claim: '%s'.", serviceUrlClaim));
}
return identity;
}, ExecutorFactory.getExecutor());
}
}

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

@ -9,19 +9,19 @@ import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.ExecutorFactory;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JwtTokenExtractor {
private static final Logger LOGGER = Logger.getLogger(OpenIdMetadata.class.getName());
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdMetadata.class);
private static final ConcurrentMap<String, OpenIdMetadata> openIdMetadataCache = new ConcurrentHashMap<>();
@ -37,30 +37,34 @@ public class JwtTokenExtractor {
}
public CompletableFuture<ClaimsIdentity> getIdentityAsync(String authorizationHeader, String channelId) {
return getIdentityAsync(authorizationHeader, channelId, new ArrayList<>());
}
public CompletableFuture<ClaimsIdentity> getIdentityAsync(String authorizationHeader, String channelId, List<String> requiredEndorsements) {
if (authorizationHeader == null) {
return CompletableFuture.completedFuture(null);
throw new IllegalArgumentException("authorizationHeader is required");
}
String[] parts = authorizationHeader.split(" ");
if (parts.length == 2) {
return getIdentityAsync(parts[0], parts[1], channelId);
return getIdentityAsync(parts[0], parts[1], channelId, requiredEndorsements);
}
return CompletableFuture.completedFuture(null);
}
public CompletableFuture<ClaimsIdentity> getIdentityAsync(String schema, String token, String channelId) {
public CompletableFuture<ClaimsIdentity> getIdentityAsync(String schema, String token, String channelId, List<String> requiredEndorsements) {
// No header in correct scheme or no token
if (!schema.equalsIgnoreCase("bearer") || token == null) {
return CompletableFuture.completedFuture(null);
}
// Issuer isn't allowed? No need to check signature
if (!this.hasAllowedIssuer(token)) {
if (!hasAllowedIssuer(token)) {
return CompletableFuture.completedFuture(null);
}
return this.validateTokenAsync(token, channelId);
return validateTokenAsync(token, channelId, requiredEndorsements);
}
private boolean hasAllowedIssuer(String token) {
@ -69,11 +73,15 @@ public class JwtTokenExtractor {
}
@SuppressWarnings("unchecked")
private CompletableFuture<ClaimsIdentity> validateTokenAsync(String token, String channelId) {
private CompletableFuture<ClaimsIdentity> validateTokenAsync(String token, String channelId, List<String> requiredEndorsements) {
DecodedJWT decodedJWT = JWT.decode(token);
OpenIdMetadataKey key = this.openIdMetadata.getKey(decodedJWT.getKeyId());
if (key != null) {
if (key == null) {
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.supplyAsync(() -> {
Verification verification = JWT.require(Algorithm.RSA256(key.key, null));
try {
verification.build().verify(token);
@ -87,26 +95,25 @@ public class JwtTokenExtractor {
if (!isEndorsed) {
throw new AuthenticationException(String.format("Could not validate endorsement for key: %s with endorsements: %s", key.key.toString(), StringUtils.join(key.endorsements)));
}
// Verify that additional endorsements are satisfied. If no additional endorsements are expected, the requirement is satisfied as well
boolean additionalEndorsementsSatisfied =
requiredEndorsements.stream().allMatch((endorsement) -> EndorsementsValidator.validate(endorsement, key.endorsements));
if (!additionalEndorsementsSatisfied) {
throw new AuthenticationException(String.format("Could not validate additional endorsement for key: %s with endorsements: %s", key.key.toString(), StringUtils.join(requiredEndorsements)));
}
}
if (!this.allowedSigningAlgorithms.contains(decodedJWT.getAlgorithm())) {
throw new AuthenticationException(String.format("Could not validate algorithm for key: %s with algorithms: %s", decodedJWT.getAlgorithm(), StringUtils.join(allowedSigningAlgorithms)));
}
Map<String, String> claims = new HashMap<>();
if (decodedJWT.getClaims() != null) {
decodedJWT.getClaims().forEach((k, v) -> claims.put(k, v.asString()));
}
return CompletableFuture.completedFuture(new ClaimsIdentityImpl(decodedJWT.getIssuer(), claims));
return new ClaimsIdentity(decodedJWT);
} catch (JWTVerificationException ex) {
String errorDescription = ex.getMessage();
LOGGER.log(Level.WARNING, errorDescription);
return CompletableFuture.completedFuture(null);
LOGGER.warn(errorDescription);
throw new AuthenticationException(ex);
}
}
return CompletableFuture.completedFuture(null);
}, ExecutorFactory.getExecutor());
}
}

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

@ -4,11 +4,16 @@
package com.microsoft.bot.connector.authentication;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.schema.models.Activity;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* Contains helper methods for authenticating incoming HTTP requests.
*/
public class JwtTokenValidation {
/**
@ -17,48 +22,106 @@ public class JwtTokenValidation {
* @param activity The incoming Activity from the Bot Framework or the Emulator
* @param authHeader The Bearer token included as part of the request
* @param credentials The set of valid credentials, such as the Bot Application ID
* @return Nothing
* @return A task that represents the work queued to execute.
* @throws AuthenticationException Throws on auth failed.
*/
public static CompletableFuture<ClaimsIdentity> authenticateRequest(Activity activity, String authHeader, CredentialProvider credentials) throws AuthenticationException, InterruptedException, ExecutionException {
if (authHeader == null || authHeader.isEmpty()) {
// No auth header was sent. We might be on the anonymous code path.
boolean isAuthDisable = credentials.isAuthenticationDisabledAsync().get();
if (isAuthDisable) {
// In the scenario where Auth is disabled, we still want to have the
// IsAuthenticated flag set in the ClaimsIdentity. To do this requires
// adding in an empty claim.
return CompletableFuture.completedFuture(new ClaimsIdentityImpl("anonymous"));
}
// No Auth Header. Auth is required. Request is not authorized.
throw new AuthenticationException("No Auth Header. Auth is required.");
}
// Go through the standard authentication path.
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(authHeader, credentials, activity.channelId(), activity.serviceUrl()).get();
// On the standard Auth path, we need to trust the URL that was incoming.
MicrosoftAppCredentials.trustServiceUrl(activity.serviceUrl());
return CompletableFuture.completedFuture(identity);
public static CompletableFuture<ClaimsIdentity> authenticateRequest(Activity activity, String authHeader, CredentialProvider credentials, ChannelProvider channelProvider) throws AuthenticationException, InterruptedException, ExecutionException {
return authenticateRequest(activity, authHeader, credentials, channelProvider, new AuthenticationConfiguration());
}
// TODO: Recieve httpClient and use ClientID
public static CompletableFuture<ClaimsIdentity> validateAuthHeader(String authHeader, CredentialProvider credentials, String channelId, String serviceUrl) throws ExecutionException, InterruptedException, AuthenticationException {
if (authHeader == null || authHeader.isEmpty()) {
/**
* Validates the security tokens required by the Bot Framework Protocol. Throws on any exceptions.
*
* @param activity The incoming Activity from the Bot Framework or the Emulator
* @param authHeader The Bearer token included as part of the request
* @param credentials The set of valid credentials, such as the Bot Application ID
* @param authConfig The authentication configuration.
* @return A task that represents the work queued to execute.
* @throws AuthenticationException Throws on auth failed.
*/
public static CompletableFuture<ClaimsIdentity> authenticateRequest(Activity activity, String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, AuthenticationConfiguration authConfig) throws AuthenticationException, InterruptedException, ExecutionException {
return CompletableFuture.supplyAsync(() -> {
if (StringUtils.isEmpty(authHeader)) {
// No auth header was sent. We might be on the anonymous code path.
boolean isAuthDisable = credentials.isAuthenticationDisabledAsync().join();
if (isAuthDisable) {
// In the scenario where Auth is disabled, we still want to have the
// IsAuthenticated flag set in the ClaimsIdentity. To do this requires
// adding in an empty claim.
return new ClaimsIdentity("anonymous");
}
// No Auth Header. Auth is required. Request is not authorized.
throw new AuthenticationException("No Auth Header. Auth is required.");
}
// Go through the standard authentication path. This will throw AuthenticationException if
// it fails.
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(authHeader, credentials, channelProvider, activity.channelId(), activity.serviceUrl(), authConfig).join();
// On the standard Auth path, we need to trust the URL that was incoming.
MicrosoftAppCredentials.trustServiceUrl(activity.serviceUrl());
return identity;
}, ExecutorFactory.getExecutor());
}
/**
* Validates the authentication header of an incoming request.
*
* @param authHeader The authentication header to validate.
* @param credentials The bot's credential provider.
* @param channelProvider The bot's channel service provider.
* @param channelId The ID of the channel that sent the request.
* @param serviceUrl The service URL for the activity.
* @return A task that represents the work queued to execute.
*
* On Call:
* @throws IllegalArgumentException Incorrect arguments supplied
*
* On join:
* @throws AuthenticationException Authentication Error
*/
public static CompletableFuture<ClaimsIdentity> validateAuthHeader(String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, String channelId, String serviceUrl) {
return validateAuthHeader(authHeader, credentials, channelProvider, channelId, serviceUrl, new AuthenticationConfiguration());
}
/**
* Validates the authentication header of an incoming request.
*
* @param authHeader The authentication header to validate.
* @param credentials The bot's credential provider.
* @param channelProvider The bot's channel service provider.
* @param channelId The ID of the channel that sent the request.
* @param serviceUrl The service URL for the activity.
* @param authConfig The authentication configuration.
* @return A task that represents the work queued to execute.
*
* On Call:
* @throws IllegalArgumentException Incorrect arguments supplied
*
* On Join:
* @throws AuthenticationException Authentication Error
*/
public static CompletableFuture<ClaimsIdentity> validateAuthHeader(String authHeader, CredentialProvider credentials, ChannelProvider channelProvider, String channelId, String serviceUrl, AuthenticationConfiguration authConfig) {
if (StringUtils.isEmpty(authHeader)) {
throw new IllegalArgumentException("No authHeader present. Auth is required.");
}
boolean usingEmulator = EmulatorValidation.isTokenFromEmulator(authHeader).get();
boolean usingEmulator = EmulatorValidation.isTokenFromEmulator(authHeader);
if (usingEmulator) {
return EmulatorValidation.authenticateToken(authHeader, credentials, channelId);
} else {
return EmulatorValidation.authenticateToken(authHeader, credentials, channelProvider, channelId, authConfig);
} else if (channelProvider == null || channelProvider.isPublicAzure()) {
// No empty or null check. Empty can point to issues. Null checks only.
if (serviceUrl != null) {
return ChannelValidation.authenticateToken(authHeader, credentials, channelId, serviceUrl);
return ChannelValidation.authenticateToken(authHeader, credentials, channelId, serviceUrl, authConfig);
} else {
return ChannelValidation.authenticateToken(authHeader, credentials, channelId);
return ChannelValidation.authenticateToken(authHeader, credentials, channelId, authConfig);
}
} else if (channelProvider.isGovernment()) {
return GovernmentChannelValidation.authenticateToken(authHeader, credentials, serviceUrl, channelId, authConfig);
} else {
return EnterpriseChannelValidation.authenticateToken(authHeader, credentials, channelProvider, serviceUrl, channelId, authConfig);
}
}
}

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

@ -3,111 +3,60 @@
package com.microsoft.bot.connector.authentication;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.rest.credentials.ServiceClientCredentials;
import okhttp3.*;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import static com.microsoft.bot.connector.authentication.AuthenticationConstants.ToChannelFromBotLoginUrl;
import static com.microsoft.bot.connector.authentication.AuthenticationConstants.ToChannelFromBotOAuthScope;
/**
* MicrosoftAppCredentials auth implementation
*/
public class MicrosoftAppCredentials implements ServiceClientCredentials {
public static final String MICROSOFTAPPID = "MicrosoftAppId";
public static final String MICROSOFTAPPPASSWORD = "MicrosoftAppPassword";
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static ConcurrentMap<String, LocalDateTime> trustHostNames = new ConcurrentHashMap<>();
static {
trustHostNames.put("api.botframework.com", LocalDateTime.MAX);
trustHostNames.put("token.botframework.com", LocalDateTime.MAX);
trustHostNames.put("api.botframework.azure.us", LocalDateTime.MAX);
trustHostNames.put("token.botframework.azure.us", LocalDateTime.MAX);
}
private String appId;
private String appPassword;
private OkHttpClient client;
private ObjectMapper mapper;
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
public static final MediaType FORM_ENCODE = MediaType.parse("application/x-www-form-urlencoded");
private String currentToken = null;
private long expiredTime = 0;
//private static final Object cacheSync = new Object();
protected static final HashMap<String, OAuthResponse> cache = new HashMap<String, OAuthResponse>();
public final String OAuthEndpoint = ToChannelFromBotLoginUrl;
public final String OAuthScope = ToChannelFromBotOAuthScope;
public String getTokenCacheKey() {
return String.format("%s-cache", this.appId);
}
private String channelAuthTenant;
private AdalAuthenticator authenticator;
public MicrosoftAppCredentials(String appId, String appPassword) {
this.appId = appId;
this.appPassword = appPassword;
this.client = new OkHttpClient.Builder().build();
this.mapper = new ObjectMapper().findAndRegisterModules();
}
public static final MicrosoftAppCredentials Empty = new MicrosoftAppCredentials(null, null);
public String microsoftAppId() {
return this.appId;
}
public MicrosoftAppCredentials withMicrosoftAppId(String appId) {
public MicrosoftAppCredentials(String appId, String appPassword, String channelAuthTenant) throws MalformedURLException {
this.appId = appId;
return this;
this.appPassword = appPassword;
setChannelAuthTenant(channelAuthTenant);
}
public String getToken(Request request) throws IOException {
if (System.currentTimeMillis() < expiredTime) {
return currentToken;
}
Request reqToken = request.newBuilder()
.url(ToChannelFromBotLoginUrl)
.post(new FormBody.Builder()
.add("grant_type", "client_credentials")
.add("client_id", this.appId)
.add("client_secret", this.appPassword)
.add("scope", ToChannelFromBotOAuthScope)
.build()).build();
Response response = this.client.newCall(reqToken).execute();
if (response.isSuccessful()) {
String payload = response.body().string();
AuthenticationResponse authResponse = this.mapper.readValue(payload, AuthenticationResponse.class);
this.expiredTime = System.currentTimeMillis() + (authResponse.expiresIn * 1000);
this.currentToken = authResponse.accessToken;
}
return this.currentToken;
public static MicrosoftAppCredentials empty() {
return new MicrosoftAppCredentials(null, null);
}
protected boolean ShouldSetToken(String url) {
if (isTrustedServiceUrl(url)) {
return true;
}
return false;
}
@Override
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
clientBuilder.interceptors().add(new MicrosoftAppCredentialsInterceptor(this));
}
private static class AuthenticationResponse {
@JsonProperty(value = "token_type")
String tokenType;
@JsonProperty(value = "expires_in")
long expiresIn;
@JsonProperty(value = "ext_expires_in")
long extExpiresIn;
@JsonProperty(value = "access_token")
String accessToken;
}
public static void trustServiceUrl(URI serviceUrl) {
trustServiceUrl(serviceUrl.toString(), LocalDateTime.now().plusDays(1));
}
@ -121,8 +70,7 @@ public class MicrosoftAppCredentials implements ServiceClientCredentials {
URL url = new URL(serviceUrl);
trustServiceUrl(url, expirationTime);
} catch (MalformedURLException e) {
//TODO: What's missing here?
e.printStackTrace();
LoggerFactory.getLogger(MicrosoftAppCredentials.class).error("trustServiceUrl", e);
}
}
@ -135,6 +83,7 @@ public class MicrosoftAppCredentials implements ServiceClientCredentials {
URL url = new URL(serviceUrl);
return isTrustedServiceUrl(url);
} catch (MalformedURLException e) {
LoggerFactory.getLogger(MicrosoftAppCredentials.class).error("trustServiceUrl", e);
return false;
}
}
@ -147,9 +96,77 @@ public class MicrosoftAppCredentials implements ServiceClientCredentials {
return !trustHostNames.getOrDefault(url.host(), LocalDateTime.MIN).isBefore(LocalDateTime.now().minusMinutes(5));
}
private static ConcurrentMap<String, LocalDateTime> trustHostNames = new ConcurrentHashMap<>();
public String appId() {
return this.appId;
}
static {
trustHostNames.put("state.botframework.com", LocalDateTime.MAX);
public String appPassword() {
return this.appPassword;
}
public MicrosoftAppCredentials withAppId(String appId) {
this.appId = appId;
return this;
}
public MicrosoftAppCredentials withAppPassword(String appPassword) {
this.appPassword = appPassword;
return this;
}
public String channelAuthTenant() {
return channelAuthTenant == null ? AuthenticationConstants.DEFAULT_CHANNEL_AUTH_TENANT : channelAuthTenant;
}
public void setChannelAuthTenant(String authTenant) throws MalformedURLException {
String originalAuthTenant = channelAuthTenant;
try {
channelAuthTenant = authTenant;
new URL(oAuthEndpoint()).toString();
} catch(MalformedURLException e) {
channelAuthTenant = originalAuthTenant;
}
}
public MicrosoftAppCredentials withChannelAuthTenant(String authTenant) throws MalformedURLException {
setChannelAuthTenant(authTenant);
return this;
}
public String oAuthEndpoint() {
return String.format(AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_TEMPLATE, channelAuthTenant());
}
public String oAuthScope() {
return AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE;
}
public Future<AuthenticationResult> getToken() {
return getAuthenticator().acquireToken();
}
protected boolean ShouldSetToken(String url) {
return isTrustedServiceUrl(url);
}
private AdalAuthenticator getAuthenticator() {
try {
if (this.authenticator == null) {
this.authenticator = new AdalAuthenticator(
new ClientCredential(this.appId, this.appPassword),
new OAuthConfiguration(oAuthEndpoint(), oAuthScope()));
}
} catch(MalformedURLException e) {
// intentional no-op. This class validates the URL on construction or setChannelAuthTenant.
// That is... this will never happen.
LoggerFactory.getLogger(MicrosoftAppCredentials.class).error("getAuthenticator", e);
}
return this.authenticator;
}
@Override
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
clientBuilder.interceptors().add(new MicrosoftAppCredentialsInterceptor(this));
}
}

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

@ -31,8 +31,15 @@ class MicrosoftAppCredentialsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
if (MicrosoftAppCredentials.isTrustedServiceUrl(chain.request().url().url().toString())) {
String token;
try {
token = this.credentials.getToken().get().getAccessToken();
} catch (Throwable t) {
throw new IOException(t);
}
Request newRequest = chain.request().newBuilder()
.header("Authorization", "Bearer " + this.credentials.getToken(chain.request()))
.header("Authorization", "Bearer " + token)
.build();
return chain.proceed(newRequest);
}

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

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
/**
* MicrosoftGovernmentAppCredentials auth implementation.
*/
public class MicrosoftGovernmentAppCredentials extends MicrosoftAppCredentials {
/**
* Initializes a new instance of the MicrosoftGovernmentAppCredentials class.
*
* @param appId The Microsoft app ID.
* @param password The Microsoft app password.
*/
public MicrosoftGovernmentAppCredentials(String appId, String password) {
super(appId, password);
}
public static MicrosoftGovernmentAppCredentials empty() {
return new MicrosoftGovernmentAppCredentials(null, null);
}
/**
* Gets the OAuth endpoint to use.
*
* @return The OAuth endpoint to use.
*/
@Override
public String oAuthEndpoint() {
return GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL;
}
/**
* Gets the OAuth scope to use.
*
* @return The OAuth scope to use.
*/
@Override
public String oAuthScope() {
return GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE;
}
}

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

@ -1,9 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.connector.UserAgent;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.TokenExchangeState;
import com.microsoft.bot.schema.models.Activity;
import com.microsoft.bot.schema.models.ConversationReference;
@ -41,13 +45,13 @@ import static java.util.stream.Collectors.joining;
* Uses the MicrosoftInterceptor class to add Authorization header from idp.
*/
public class OAuthClient extends ServiceClient {
private final ConnectorClientImpl client;
private final RestConnectorClient client;
private final String uri;
private ObjectMapper mapper;
public OAuthClient(ConnectorClientImpl client, String uri) throws URISyntaxException, MalformedURLException {
public OAuthClient(RestConnectorClient client, String uri) throws URISyntaxException, MalformedURLException {
super(client.restClient());
URI uriResult = new URI(uri);
@ -69,7 +73,7 @@ public class OAuthClient extends ServiceClient {
* @param userId
* @param connectionName
* @param magicCode
* @return CompletableFuture< TokenResponse > on success; otherwise null.
* @return CompletableFuture<TokenResponse> on success; otherwise null.
*/
public CompletableFuture<TokenResponse> GetUserTokenAsync(String userId, String connectionName, String magicCode) throws IOException, URISyntaxException, ExecutionException, InterruptedException {
return GetUserTokenAsync(userId, connectionName, magicCode, null);
@ -77,14 +81,14 @@ public class OAuthClient extends ServiceClient {
protected URI MakeUri(String uri, HashMap<String, String> queryStrings) throws URISyntaxException {
String newUri = queryStrings.keySet().stream()
.map(key -> {
try {
return key + "=" + URLEncoder.encode(queryStrings.get(key), StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
})
.collect(joining("&", uri.endsWith("?") ? uri : uri + "?", ""));
.map(key -> {
try {
return key + "=" + URLEncoder.encode(queryStrings.get(key), StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
})
.collect(joining("&", uri.endsWith("?") ? uri : uri + "?", ""));
return new URI(newUri);
@ -97,7 +101,7 @@ public class OAuthClient extends ServiceClient {
* @param connectionName
* @param magicCode
* @param customHeaders
* @return CompletableFuture< TokenResponse > on success; null otherwise.
* @return CompletableFuture<TokenResponse> on success; null otherwise.
*/
public CompletableFuture<TokenResponse> GetUserTokenAsync(String userId, String connectionName, String magicCode, Map<String, ArrayList<String>> customHeaders) throws IllegalArgumentException {
if (StringUtils.isEmpty(userId)) {
@ -132,13 +136,13 @@ public class OAuthClient extends ServiceClient {
// Later: Use client in clientimpl?
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new MicrosoftAppCredentialsInterceptor(appCredentials))
.build();
.addInterceptor(new MicrosoftAppCredentialsInterceptor(appCredentials))
.build();
Request request = new Request.Builder()
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.build();
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.build();
Response response = null;
try {
@ -158,7 +162,7 @@ public class OAuthClient extends ServiceClient {
response.body().close();
}
return null;
});
}, ExecutorFactory.getExecutor());
}
/**
@ -198,14 +202,14 @@ public class OAuthClient extends ServiceClient {
// Later: Use client in clientimpl?
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new MicrosoftAppCredentialsInterceptor(appCredentials))
.build();
.addInterceptor(new MicrosoftAppCredentialsInterceptor(appCredentials))
.build();
Request request = new Request.Builder()
.delete()
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.build();
.delete()
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.build();
Response response = null;
try {
@ -218,7 +222,7 @@ public class OAuthClient extends ServiceClient {
}
return false;
});
}, ExecutorFactory.getExecutor());
}
@ -238,15 +242,15 @@ public class OAuthClient extends ServiceClient {
}
final MicrosoftAppCredentials creds = (MicrosoftAppCredentials) this.client.restClient().credentials();
TokenExchangeState tokenExchangeState = new TokenExchangeState()
.withConnectionName(connectionName)
.withConversation(new ConversationReference()
.withActivityId(activity.id())
.withBot(activity.recipient())
.withChannelId(activity.channelId())
.withConversation(activity.conversation())
.withServiceUrl(activity.serviceUrl())
.withUser(activity.from()))
.withMsAppId((creds == null) ? null : creds.microsoftAppId());
.withConnectionName(connectionName)
.withConversation(new ConversationReference()
.withActivityId(activity.id())
.withBot(activity.recipient())
.withChannelId(activity.channelId())
.withConversation(activity.conversation())
.withServiceUrl(activity.serviceUrl())
.withUser(activity.from()))
.withMsAppId((creds == null) ? null : creds.appId());
String serializedState = this.mapper.writeValueAsString(tokenExchangeState);
@ -266,13 +270,13 @@ public class OAuthClient extends ServiceClient {
// Later: Use client in clientimpl?
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new MicrosoftAppCredentialsInterceptor(creds))
.build();
.addInterceptor(new MicrosoftAppCredentialsInterceptor(creds))
.build();
Request request = new Request.Builder()
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.build();
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.build();
Response response = null;
try {
@ -284,7 +288,7 @@ public class OAuthClient extends ServiceClient {
e.printStackTrace();
}
return null;
});
}, ExecutorFactory.getExecutor());
}
/**
@ -313,14 +317,14 @@ public class OAuthClient extends ServiceClient {
// Later: Use client in clientimpl?
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new MicrosoftAppCredentialsInterceptor(appCredentials))
.build();
.addInterceptor(new MicrosoftAppCredentialsInterceptor(appCredentials))
.build();
Request request = new Request.Builder()
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.post(body)
.build();
.url(tokenUrl.toString())
.header("User-Agent", UserAgent.value())
.post(body)
.build();
Response response = null;
try {
@ -336,6 +340,6 @@ public class OAuthClient extends ServiceClient {
// Apparently swallow any results
return;
});
}, ExecutorFactory.getExecutor());
}
}

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

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
package com.microsoft.bot.connector.authentication;
/**
* Configuration for OAuth client credential authentication.
*/
public class OAuthConfiguration {
private String scope;
private String authority;
public OAuthConfiguration(String authority, String scope) {
this.authority = authority;
this.scope = scope;
}
/**
* Sets oAuth Authority for authentication.
*
* @param authority
* @return This OAuthConfiguration object.
*/
public OAuthConfiguration withAuthority(String authority) {
this.authority = authority;
return this;
}
/**
* Gets oAuth Authority for authentication.
*
* @return OAuth Authority for authentication.
*/
public String authority() {
return authority;
}
/**
* Sets oAuth scope for authentication.
*
* @param scope
* @return This OAuthConfiguration object.
*/
public OAuthConfiguration withScope(String scope) {
this.scope = scope;
return this;
}
/**
* Gets oAuth scope for authentication.
*
* @return OAuth scope for authentication.
*/
public String scope() {
return scope;
}
}

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

@ -16,21 +16,21 @@ import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class OpenIdMetadata {
private static final Logger LOGGER = Logger.getLogger( OpenIdMetadata.class.getName() );
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdMetadata.class);
private String url;
private long lastUpdated;
private JwkProvider cacheKeys;
private ObjectMapper mapper;
private String url;
private long lastUpdated;
private JwkProvider cacheKeys;
private ObjectMapper mapper;
OpenIdMetadata(String url) {
this.url = url;
this.mapper = new ObjectMapper().findAndRegisterModules();
}
OpenIdMetadata(String url) {
this.url = url;
this.mapper = new ObjectMapper().findAndRegisterModules();
}
public OpenIdMetadataKey getKey(String keyId) {
// If keys are more than 5 days old, refresh them
@ -52,7 +52,7 @@ class OpenIdMetadata {
return IOUtils.toString(keysUrl);
} catch (IOException e) {
String errorDescription = String.format("Failed to load openID config: %s", e.getMessage());
LOGGER.log(Level.WARNING, errorDescription);
LOGGER.warn(errorDescription);
}
return null;
}
@ -67,7 +67,7 @@ class OpenIdMetadata {
return key;
} catch (JwkException e) {
String errorDescription = String.format("Failed to load keys: %s", e.getMessage());
LOGGER.log(Level.WARNING, errorDescription);
LOGGER.warn(errorDescription);
}
return null;
}

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

@ -1,24 +0,0 @@
package com.microsoft.bot.connector.authentication;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
public class ResponseFuture implements Callback {
public final CompletableFuture<Response> future = new CompletableFuture<Response>();
public Call call;
public ResponseFuture(Call call) {
this.call = call;
}
@Override public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
@Override public void onResponse(Call call, Response response) throws IOException {
future.complete(response);
}
}

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

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.CompletableFuture;
public class SimpleChannelProvider implements ChannelProvider {
private String channelService;
/**
* Creates a SimpleChannelProvider with no ChannelService which will use Public Azure.
*/
public SimpleChannelProvider() {
}
/**
* Creates a SimpleChannelProvider with the specified ChannelService.
*
* @param channelService The ChannelService to use. Null or empty strings represent Public Azure,
* the string 'https://botframework.us' represents US Government Azure, and
* other values are for private channels.
*/
public SimpleChannelProvider(String channelService) {
this.channelService = channelService;
}
@Override
public CompletableFuture<String> getChannelService() {
return CompletableFuture.completedFuture(channelService);
}
@Override
public boolean isGovernment() {
return GovernmentAuthenticationConstants.CHANNELSERVICE.equalsIgnoreCase(channelService);
}
@Override
public boolean isPublicAzure() {
return StringUtils.isEmpty(channelService);
}
}

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

@ -1,15 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector.authentication;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.CompletableFuture;
/**
* A simple implementation of the CredentialProvider interface.
*/
public class SimpleCredentialProvider implements CredentialProvider {
private String appId;
private String password;
/**
* Initializes a new instance with empty credentials.
*/
public SimpleCredentialProvider() {
}
/**
* Initializes a new instance with the provided credentials.
*
* @param appId The app ID.
* @param password The app password.
*/
public SimpleCredentialProvider(String appId, String password) {
this.appId = appId;
this.password = password;
@ -18,6 +34,7 @@ public class SimpleCredentialProvider implements CredentialProvider {
public String getAppId() {
return this.appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
@ -25,24 +42,42 @@ public class SimpleCredentialProvider implements CredentialProvider {
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* Validates an app ID.
*
* @param appId The app ID to validate.
* @return If the task is successful, the result is true if appId is valid for the controller; otherwise, false.
*/
@Override
public CompletableFuture<Boolean> isValidAppIdAsync(String appId) {
return CompletableFuture.completedFuture(appId == this.appId);
return CompletableFuture.completedFuture(StringUtils.equals(appId, this.appId));
}
/**
* Gets the app password for a given bot app ID.
*
* @param appId The ID of the app to get the password for.
* @return If the task is successful and the app ID is valid, the result
* contains the password; otherwise, null.
*/
@Override
public CompletableFuture<String> getAppPasswordAsync(String appId) {
return CompletableFuture.completedFuture((appId == this.appId) ? this.password : null);
return CompletableFuture.completedFuture(StringUtils.equals(appId, this.appId) ? this.password : null);
}
/**
* Checks whether bot authentication is disabled.
*
* @return A task that represents the work queued to execute If the task is successful and bot authentication
* is disabled, the result is true; otherwise, false.
*/
@Override
public CompletableFuture<Boolean> isAuthenticationDisabledAsync() {
return CompletableFuture.completedFuture(StringUtils.isEmpty(this.appId));
}
}

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

@ -4,11 +4,8 @@
package com.microsoft.bot.connector.authentication;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.bot.connector.authentication.AuthenticationConstants.ToBotFromChannelTokenIssuer;
public class TokenValidationParameters {
public boolean validateIssuer;
public List<String> validIssuers;
@ -32,33 +29,4 @@ public class TokenValidationParameters {
this.clockSkew = clockSkew;
this.requireSignedTokens = requireSignedTokens;
}
static TokenValidationParameters toBotFromEmulatorTokenValidationParameters() {
return new TokenValidationParameters() {{
this.validateIssuer = true;
this.validIssuers = new ArrayList<String>() {{
add("https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/");
add("https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0");
add("https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/");
add("https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0");
}};
this.validateAudience = false;
this.validateLifetime = true;
this.clockSkew = Duration.ofMinutes(5);
this.requireSignedTokens = true;
}};
}
static TokenValidationParameters toBotFromChannelTokenValidationParameters() {
return new TokenValidationParameters() {{
this.validateIssuer = true;
this.validIssuers = new ArrayList<String>() {{
add(ToBotFromChannelTokenIssuer);
}};
this.validateAudience = false;
this.validateLifetime = true;
this.clockSkew = Duration.ofMinutes(5);
this.requireSignedTokens = true;
}};
}
}

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

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
/**
* This package contains the implementation auth classes for ConnectorClient.
*/
package com.microsoft.bot.connector.authentication;

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

@ -1,2 +0,0 @@
package com.microsoft.bot.connector.implementation;

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

@ -1,25 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
/**
* This package contains the classes for ConnectorClient.
* The Bot Connector REST API allows your bot to send and receive messages to channels configured in the
[Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST
and JSON over HTTPS.
Client libraries for this REST API are available. See below for a list.
Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The
Bot State REST API allows a bot to store and retrieve state associated with users and conversations.
Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is
described in detail in the [Connector Authentication](/en-us/restapi/authentication) document.
# Client Libraries for the Bot Connector REST API
* [Bot Builder for C#](/en-us/csharp/builder/sdkreference/)
* [Bot Builder for Node.js](/en-us/node/builder/overview/)
* Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json)
© 2016 Microsoft.
* This package contains the classes for Bot-Connector.
*/
package com.microsoft.bot.connector;

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

@ -2,13 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
* Code generated by Microsoft (R) AutoRest Code Generator.
* Changes may cause incorrect behavior and will be lost if the code is
* regenerated.
*/
package com.microsoft.bot.connector.models;
package com.microsoft.bot.connector.rest;
import com.microsoft.rest.RestException;import com.microsoft.bot.schema.models.ErrorResponse;
import okhttp3.ResponseBody;

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

@ -2,19 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
* Code generated by Microsoft (R) AutoRest Code Generator.
* Changes may cause incorrect behavior and will be lost if the code is
* regenerated.
*/
package com.microsoft.bot.connector.implementation;
package com.microsoft.bot.connector.rest;
import retrofit2.Retrofit;
import com.microsoft.bot.connector.Attachments;
import com.google.common.reflect.TypeToken;
import com.microsoft.bot.schema.models.AttachmentInfo;
import com.microsoft.bot.connector.models.ErrorResponseException;
import com.microsoft.rest.ServiceCallback;
import com.microsoft.rest.ServiceFuture;
import com.microsoft.rest.ServiceResponse;
@ -34,11 +29,11 @@ import rx.Observable;
* An instance of this class provides access to all the operations defined
* in Attachments.
*/
public class AttachmentsImpl implements Attachments {
public class RestAttachments implements Attachments {
/** The Retrofit service to perform REST calls. */
private AttachmentsService service;
/** The service client containing this operation class. */
private ConnectorClientImpl client;
private RestConnectorClient client;
/**
* Initializes an instance of AttachmentsImpl.
@ -46,7 +41,7 @@ public class AttachmentsImpl implements Attachments {
* @param retrofit the Retrofit instance built from a Retrofit Builder.
* @param client the instance of the service client containing this operation class.
*/
public AttachmentsImpl(Retrofit retrofit, ConnectorClientImpl client) {
RestAttachments(Retrofit retrofit, RestConnectorClient client) {
this.service = retrofit.create(AttachmentsService.class);
this.client = client;
}

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

@ -2,10 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
*/
package com.microsoft.bot.connector.implementation;
package com.microsoft.bot.connector.rest;
import com.microsoft.azure.AzureClient;
import com.microsoft.azure.AzureResponseBuilder;
@ -24,13 +23,28 @@ import java.io.InputStream;
import java.util.Properties;
/**
* Initializes a new instance of the ConnectorClientImpl class.
* The Bot Connector REST API allows your bot to send and receive messages
* to channels configured in the
* [Bot Framework Developer Portal](https://dev.botframework.com). The
* Connector service uses industry-standard REST
* and JSON over HTTPS.
*
* Client libraries for this REST API are available. See below for a list.
*
* Many bots will use both the Bot Connector REST API and the associated
* [Bot State REST API](/en-us/restapi/state). The
* Bot State REST API allows a bot to store and retrieve state associated
* with users and conversations.
*
* Authentication for both the Bot Connector and Bot State REST APIs is
* accomplished with JWT Bearer tokens, and is
* described in detail in the [Connector
* Authentication](/en-us/restapi/authentication) document.
*/
public class ConnectorClientImpl extends AzureServiceClient implements ConnectorClient {
public class RestConnectorClient extends AzureServiceClient implements ConnectorClient {
/** the {@link AzureClient} used for long running operations. */
private AzureClient azureClient;
/**
* Gets the {@link AzureClient} used for long running operations.
* @return the azure client;
@ -58,7 +72,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
* @param acceptLanguage the acceptLanguage value.
* @return the service client itself
*/
public ConnectorClientImpl withAcceptLanguage(String acceptLanguage) {
public RestConnectorClient withAcceptLanguage(String acceptLanguage) {
this.acceptLanguage = acceptLanguage;
return this;
}
@ -68,7 +82,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
* TODO: Use this.
*/
private RetryStrategy retryStrategy = null;
public ConnectorClientImpl withRestRetryStrategy(RetryStrategy retryStrategy) {
public RestConnectorClient withRestRetryStrategy(RetryStrategy retryStrategy) {
this.retryStrategy = retryStrategy;
return this;
}
@ -94,7 +108,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
* @param longRunningOperationRetryTimeout the longRunningOperationRetryTimeout value.
* @return the service client itself
*/
public ConnectorClientImpl withLongRunningOperationRetryTimeout(int longRunningOperationRetryTimeout) {
public RestConnectorClient withLongRunningOperationRetryTimeout(int longRunningOperationRetryTimeout) {
this.longRunningOperationRetryTimeout = longRunningOperationRetryTimeout;
return this;
}
@ -117,7 +131,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
* @param generateClientRequestId the generateClientRequestId value.
* @return the service client itself
*/
public ConnectorClientImpl withGenerateClientRequestId(boolean generateClientRequestId) {
public RestConnectorClient withGenerateClientRequestId(boolean generateClientRequestId) {
this.generateClientRequestId = generateClientRequestId;
return this;
}
@ -138,14 +152,14 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
/**
* The Conversations object to access its operations.
*/
private ConversationsImpl conversations;
private RestConversations conversations;
/**
* Gets the Conversations object to access its operations.
* @return the Conversations object.
*/
@Override
public ConversationsImpl conversations() {
public RestConversations conversations() {
return this.conversations;
}
@ -154,7 +168,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
*
* @param credentials the management credentials for Azure
*/
public ConnectorClientImpl(ServiceClientCredentials credentials) {
public RestConnectorClient(ServiceClientCredentials credentials) {
this("https://api.botframework.com", credentials);
}
@ -164,7 +178,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
* @param baseUrl the base URL of the host
* @param credentials the management credentials for Azure
*/
public ConnectorClientImpl(String baseUrl, ServiceClientCredentials credentials) {
public RestConnectorClient(String baseUrl, ServiceClientCredentials credentials) {
super(baseUrl, credentials);
initialize();
}
@ -174,7 +188,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
*
* @param restClient the REST client to connect to Azure.
*/
public ConnectorClientImpl(RestClient restClient){
public RestConnectorClient(RestClient restClient){
super(restClient);
initialize();
}
@ -183,8 +197,8 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
this.acceptLanguage = "en-US";
this.longRunningOperationRetryTimeout = 30;
this.generateClientRequestId = true;
this.attachments = new AttachmentsImpl(restClient().retrofit(), this);
this.conversations = new ConversationsImpl(restClient().retrofit(), this);
this.attachments = new RestAttachments(restClient().retrofit(), this);
this.conversations = new RestConversations(restClient().retrofit(), this);
this.azureClient = new AzureClient(this);
@ -192,7 +206,7 @@ public class ConnectorClientImpl extends AzureServiceClient implements Connector
String build_version;
final Properties properties = new Properties();
try {
InputStream propStream = ConnectorClientImpl.class.getClassLoader().getResourceAsStream("project.properties");
InputStream propStream = RestConnectorClient.class.getClassLoader().getResourceAsStream("project.properties");
properties.load(propStream);
build_version = properties.getProperty("version");
} catch (IOException e) {

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

@ -2,12 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*
* NOT GENERATED.
* This uses Java 8 CompletionStage for async processing instead of JavaRX/Guava
*/
package com.microsoft.bot.connector.implementation;
package com.microsoft.bot.connector.rest;
import retrofit2.Retrofit;
import com.microsoft.bot.connector.Conversations;
@ -19,7 +16,6 @@ import com.microsoft.bot.schema.models.ConversationParameters;
import com.microsoft.bot.schema.models.ConversationResourceResponse;
import com.microsoft.bot.schema.models.ConversationsResult;
import com.microsoft.bot.schema.models.PagedMembersResult;
import com.microsoft.bot.connector.models.ErrorResponseException;
import com.microsoft.bot.schema.models.ResourceResponse;
import com.microsoft.bot.schema.models.Transcript;
import com.microsoft.rest.ServiceCallback;
@ -49,11 +45,11 @@ import rx.Observable;
* An instance of this class provides access to all the operations defined
* in Conversations.
*/
public class ConversationsImpl implements Conversations {
public class RestConversations implements Conversations {
/** The Retrofit service to perform REST calls. */
private ConversationsService service;
/** The service client containing this operation class. */
private ConnectorClientImpl client;
private RestConnectorClient client;
/**
* Initializes an instance of ConversationsImpl.
@ -61,7 +57,7 @@ public class ConversationsImpl implements Conversations {
* @param retrofit the Retrofit instance built from a Retrofit Builder.
* @param client the instance of the service client containing this operation class.
*/
public ConversationsImpl(Retrofit retrofit, ConnectorClientImpl client) {
RestConversations(Retrofit retrofit, RestConnectorClient client) {
this.service = retrofit.create(ConversationsService.class);
this.client = client;
}
@ -284,8 +280,8 @@ public class ConversationsImpl implements Conversations {
});
}
// FIXME: This isn't really a reasonable return value in this case.
// I know what it said about converting Observable to CompletableFuture, but this particular
// FIXME: This isn't really a reasonable return value in this case.
// I know what it said about converting Observable to CompletableFuture, but this particular
// conversion returns a result that changes the meaning. The response is not, and is never, a list.
public CompletableFuture<List<ConversationResourceResponse>> CreateConversationAsync(ConversationParameters parameters) {
CompletableFuture<List<ConversationResourceResponse>> future_result = completableFutureFromObservable(createConversationAsync(parameters));
@ -719,7 +715,7 @@ public class ConversationsImpl implements Conversations {
}
});
}
/**
* DeleteConversationMemberFuture
* Deletes a member from a converstion.

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

@ -22,4 +22,4 @@
* Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json)
© 2016 Microsoft.
*/
package com.microsoft.bot.connector.implementation;
package com.microsoft.bot.connector.rest;

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

@ -1,13 +1,7 @@
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.schema.models.TokenResponse;
import com.microsoft.rest.credentials.ServiceClientCredentials;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import static java.util.concurrent.CompletableFuture.completedFuture;
public class BotAccessTokenStub extends MicrosoftAppCredentials {
private final String token;
@ -26,8 +20,4 @@ public class BotAccessTokenStub extends MicrosoftAppCredentials {
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
clientBuilder.interceptors().add(new TestBearerTokenInterceptor(this.token));
}
}

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

@ -1,147 +0,0 @@
package com.microsoft.bot.connector;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.authentication.*;
import com.microsoft.bot.schema.models.Activity;
import okhttp3.Request;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
public class BotAuthenticatorTest {
private static final String AppId = "2cd87869-38a0-4182-9251-d056e8f0ac24";
private static final String AppPassword = "2.30Vs3VQLKt974F";
@Test
public void ConnectorAuthHeaderCorrectAppIdAndServiceUrlShouldValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl(AppId, "");
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(header, credentials, "", "https://webchat.botframework.com/").get();
Assert.assertTrue(identity.isAuthenticated());
}
@Test
public void ConnectorAuthHeaderBotAppIdDiffersShouldNotValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl("00000000-0000-0000-0000-000000000000", "");
try {
JwtTokenValidation.validateAuthHeader(header, credentials, "", null).get();
} catch (AuthenticationException e) {
Assert.assertTrue(e.getMessage().contains("Invalid AppId passed on token"));
}
}
@Test
public void ConnectorAuthHeaderBotWithNoCredentialsShouldNotValidate() throws IOException, ExecutionException, InterruptedException {
// token received and auth disabled
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl("", "");
try {
JwtTokenValidation.validateAuthHeader(header, credentials, "", null).get();
} catch (AuthenticationException e) {
Assert.assertTrue(e.getMessage().contains("Invalid AppId passed on token"));
}
}
@Test
public void EmptyHeaderBotWithNoCredentialsShouldThrow() throws ExecutionException, InterruptedException {
String header = "";
CredentialProvider credentials = new CredentialProviderImpl("", "");
try {
JwtTokenValidation.validateAuthHeader(header, credentials, "", null).get();
} catch (IllegalArgumentException e) {
Assert.assertTrue(e.getMessage().contains("authHeader"));
}
}
@Test
public void EmulatorMsaHeaderCorrectAppIdAndServiceUrlShouldValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl(AppId, "");
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(header, credentials, "", "https://webchat.botframework.com/").get();
Assert.assertTrue(identity.isAuthenticated());
}
@Test
public void EmulatorMsaHeaderBotAppIdDiffersShouldNotValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl("00000000-0000-0000-0000-000000000000", "");
try {
JwtTokenValidation.validateAuthHeader(header, credentials, "", null).get();
} catch (AuthenticationException e) {
Assert.assertTrue(e.getMessage().contains("Invalid AppId passed on token"));
}
}
/**
* Tests with a valid Token and service url; and ensures that Service url is added to Trusted service url list.
*/
@Test
public void ChannelMsaHeaderValidServiceUrlShouldBeTrusted() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl(AppId, "");
JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"),
header,
credentials);
Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"));
}
/**
* Tests with a valid Token and invalid service url; and ensures that Service url is NOT added to Trusted service url list.
*/
@Test
public void ChannelMsaHeaderInvalidServiceUrlShouldNotBeTrusted() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new CredentialProviderImpl("7f74513e-6f96-4dbc-be9d-9a81fea22b88", "");
try {
JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://webchat.botframework.com/"),
header,
credentials);
Assert.fail("Should have thrown AuthenticationException");
} catch (AuthenticationException ex) {
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://webchat.botframework.com/"));
}
}
/**
* Tests with no authentication header and makes sure the service URL is not added to the trusted list.
*/
@Test
public void ChannelAuthenticationDisabledShouldBeAnonymous() throws ExecutionException, InterruptedException {
String header = "";
CredentialProvider credentials = new CredentialProviderImpl("", "");
ClaimsIdentity identity = JwtTokenValidation.authenticateRequest(new Activity().withServiceUrl("https://webchat.botframework.com/"), header, credentials).get();
Assert.assertEquals("anonymous", identity.getIssuer());
}
/**
* Tests with no authentication header and makes sure the service URL is not added to the trusted list.
*/
@Test
public void ChannelAuthenticationDisabledServiceUrlShouldNotBeTrusted() throws ExecutionException, InterruptedException {
String header = "";
CredentialProvider credentials = new CredentialProviderImpl("", "");
ClaimsIdentity identity = JwtTokenValidation.authenticateRequest(new Activity().withServiceUrl("https://webchat.botframework.com/"), header, credentials).get();
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://webchat.botframework.com/"));
}
private static String getHeaderToken() throws IOException {
Request request = new Request.Builder().url(AuthenticationConstants.ToChannelFromBotLoginUrl).build();
return String.format("Bearer %s", new MicrosoftAppCredentials(AppId, AppPassword).getToken(request));
}
}

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

@ -1,12 +1,12 @@
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.base.TestBase;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.models.ChannelAccount;
import com.microsoft.rest.RestClient;
public class BotConnectorTestBase extends TestBase {
protected ConnectorClientImpl connector;
protected ConnectorClient connector;
protected ChannelAccount bot;
protected ChannelAccount user;
@ -20,7 +20,7 @@ public class BotConnectorTestBase extends TestBase {
@Override
protected void initializeClients(RestClient restClient, String botId, String userId) {
connector = new ConnectorClientImpl(restClient);
connector = new RestConnectorClient(restClient);
bot = new ChannelAccount().withId(botId);
user = new ChannelAccount().withId(userId);
}

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

@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.models.ErrorResponseException;
import com.microsoft.bot.connector.rest.ErrorResponseException;
import com.microsoft.bot.schema.models.*;
import org.junit.Assert;
import org.junit.Test;

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

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.authentication.EmulatorValidation;
import org.junit.Assert;
import org.junit.Test;
public class EmulatorValidationTests {
@Test
public void NoSchemeTokenIsNotFromEmulator() {
Assert.assertFalse(EmulatorValidation.isTokenFromEmulator("AbCdEf123456"));
}
@Test
public void OnePartTokenIsNotFromEmulator() {
Assert.assertFalse(EmulatorValidation.isTokenFromEmulator("Bearer AbCdEf123456"));
}
@Test
public void NoIssuerIsNotFromEmulator() {
Assert.assertFalse(EmulatorValidation.isTokenFromEmulator("Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZXNzYWdlIjoiSldUIFJ1bGVzISIsImlhdCI6MTQ1OTQ0ODExOSwiZXhwIjoxNDU5NDU0NTE5fQ.-yIVBD5b73C75osbmwwshQNRC7frWUYrqaTjTpza2y4"));
}
@Test
public void ValidTokenSuccess() {
String emToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCIsImtpZCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCJ9.eyJhdWQiOiI5YzI4NmUyZi1lMDcwLTRhZjUtYTNmMS0zNTBkNjY2MjE0ZWQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIvIiwiaWF0IjoxNTY2NDIyMTY3LCJuYmYiOjE1NjY0MjIxNjcsImV4cCI6MTU2NjQyNjA2NywiYWlvIjoiNDJGZ1lKaDBoK0VmRDAvNnVWaUx6NHZuL25UK0RnQT0iLCJhcHBpZCI6IjljMjg2ZTJmLWUwNzAtNGFmNS1hM2YxLTM1MGQ2NjYyMTRlZCIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2Q2ZDQ5NDIwLWYzOWItNGRmNy1hMWRjLWQ1OWE5MzU4NzFkYi8iLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJPUXNSLWExUlpFS2tJcG9seUNJUUFBIiwidmVyIjoiMS4wIn0.J9qHO11oZlrpDU3MJcTJe3ErUqj0kw-ZQioYKbkwZ7ZpAx5hl01BETts-LOaE14tImqYqM2K86ZyX5LuAp2snru9LJ4S6-cVZ1_lp_IY4r61UuUJRiVUzn25kRZEN-TFi8Aj1iyL-ueeNr52MM1Sr2UUH73fwrferH8_0qa1IYc7affhjlFEWxSte0SN7iT5WaYK32d_nsgzJdZiCMZJPCpG39U2FYnSI8q7vvYjNbp8wDJc46Q4Jdd3zXYRgHWRBGL_EEkzzk9IFpHN7WoVaqNtgMiA4Vf8bde3eAS5lBBtE5VZ0F6fG4Qeg6zjOAxPBZqvAASMpgyDlSQMknevOQ";
Assert.assertTrue(EmulatorValidation.isTokenFromEmulator(emToken));
}
}

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

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.authentication.EndorsementsValidator;

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

@ -1,18 +1,615 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.authentication.*;
import com.microsoft.bot.schema.models.Activity;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
public class JwtTokenValidationTests {
private static final String APPID = "2cd87869-38a0-4182-9251-d056e8f0ac24";
private static final String APPPASSWORD = "2.30Vs3VQLKt974F";
private static String getHeaderToken() throws ExecutionException, InterruptedException {
return String.format("Bearer %s", new MicrosoftAppCredentials(APPID, APPPASSWORD).getToken().get().getAccessToken());
}
private static String getGovHeaderToken() throws ExecutionException, InterruptedException {
return String.format("Bearer %s", new MicrosoftGovernmentAppCredentials(APPID, APPPASSWORD).getToken().get().getAccessToken());
}
@Test
public void Connector_AuthHeader_CorrectAppIdAndServiceUrl_ShouldValidate()
{
// MicrosoftAppCredentials credentials = new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F");
// String header = "Bearer " + credentials.
// var credentials = new SimpleCredentialProvider("2cd87869-38a0-4182-9251-d056e8f0ac24", string.Empty);
// var result = await JwtTokenValidation.ValidateAuthHeader(header, credentials, new SimpleChannelProvider(), string.Empty, "https://webchat.botframework.com/", client);
//
// Assert.True(result.IsAuthenticated);
public void ConnectorAuthHeaderCorrectAppIdAndServiceUrlShouldValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider(APPID, "");
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(
header,
credentials,
new SimpleChannelProvider(),
"",
"https://webchat.botframework.com/").join();
Assert.assertTrue(identity.isAuthenticated());
}
@Test
public void Connector_AuthHeader_CorrectAppIdAndServiceUrl_WithGovChannelService_ShouldValidate() throws IOException, ExecutionException, InterruptedException {
JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(
APPID,
APPPASSWORD,
GovernmentAuthenticationConstants.CHANNELSERVICE
);
}
@Test
public void ConnectorAuthHeaderBotAppIdDiffersShouldNotValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider("00000000-0000-0000-0000-000000000000", "");
try {
JwtTokenValidation.validateAuthHeader(
header,
credentials,
new SimpleChannelProvider(),
"",
null).join();
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void ConnectorAuthHeaderBotWithNoCredentialsShouldNotValidate() throws IOException, ExecutionException, InterruptedException {
// token received and auth disabled
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider("", "");
try {
JwtTokenValidation.validateAuthHeader(
header,
credentials,
new SimpleChannelProvider(),
"",
null).join();
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EmptyHeaderBotWithNoCredentialsShouldThrow() throws ExecutionException, InterruptedException {
String header = "";
CredentialProvider credentials = new SimpleCredentialProvider("", "");
try {
JwtTokenValidation.validateAuthHeader(
header,
credentials,
new SimpleChannelProvider(),
"",
null).join();
Assert.fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException e) {
Assert.assertTrue(e.getMessage().contains("authHeader"));
}
}
@Test
public void EmulatorMsaHeaderCorrectAppIdAndServiceUrlShouldValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider(APPID, "");
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(
header,
credentials,
new SimpleChannelProvider(),
"",
"https://webchat.botframework.com/").join();
Assert.assertTrue(identity.isAuthenticated());
}
@Test
public void EmulatorMsaHeaderBotAppIdDiffersShouldNotValidate() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider("00000000-0000-0000-0000-000000000000", "");
try {
JwtTokenValidation.validateAuthHeader(
header,
credentials,
new SimpleChannelProvider(),
"",
null).join();
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void Emulator_AuthHeader_CorrectAppIdAndServiceUrl_WithGovChannelService_ShouldValidate() throws IOException, ExecutionException, InterruptedException {
JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(
"2cd87869-38a0-4182-9251-d056e8f0ac24", // emulator creds
"2.30Vs3VQLKt974F",
GovernmentAuthenticationConstants.CHANNELSERVICE);
}
@Test
public void Emulator_AuthHeader_CorrectAppIdAndServiceUrl_WithPrivateChannelService_ShouldValidate() throws IOException, ExecutionException, InterruptedException {
JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(
"2cd87869-38a0-4182-9251-d056e8f0ac24", // emulator creds
"2.30Vs3VQLKt974F",
"TheChannel");
}
/**
* Tests with a valid Token and service url; and ensures that Service url is added to Trusted service url list.
*/
@Test
public void ChannelMsaHeaderValidServiceUrlShouldBeTrusted() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider(APPID, "");
JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"),
header,
credentials,
new SimpleChannelProvider()).join();
Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"));
}
/**
* Tests with a valid Token and invalid service url; and ensures that Service url is NOT added to Trusted service url list.
*/
@Test
public void ChannelMsaHeaderInvalidServiceUrlShouldNotBeTrusted() throws IOException, ExecutionException, InterruptedException {
String header = getHeaderToken();
CredentialProvider credentials = new SimpleCredentialProvider("7f74513e-6f96-4dbc-be9d-9a81fea22b88", "");
try {
JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://webchat.botframework.com/"),
header,
credentials,
new SimpleChannelProvider()).join();
Assert.fail("Should have thrown AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://webchat.botframework.com/"));
}
}
/**
* Tests with no authentication header and makes sure the service URL is not added to the trusted list.
*/
@Test
public void ChannelAuthenticationDisabledShouldBeAnonymous() throws ExecutionException, InterruptedException {
String header = "";
CredentialProvider credentials = new SimpleCredentialProvider("", "");
ClaimsIdentity identity = JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://webchat.botframework.com/"),
header,
credentials,
new SimpleChannelProvider()).join();
Assert.assertEquals("anonymous", identity.getIssuer());
}
@Test
public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException, ExecutionException, InterruptedException {
try {
String header = "";
CredentialProvider credentials = new SimpleCredentialProvider(APPID, APPPASSWORD);
JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"),
header,
credentials,
new SimpleChannelProvider()).join();
Assert.fail("Should have thrown AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"));
}
/**
* Tests with no authentication header and makes sure the service URL is not added to the trusted list.
*/
@Test
public void ChannelAuthenticationDisabledServiceUrlShouldNotBeTrusted() throws ExecutionException, InterruptedException {
String header = "";
CredentialProvider credentials = new SimpleCredentialProvider("", "");
ClaimsIdentity identity = JwtTokenValidation.authenticateRequest(
new Activity().withServiceUrl("https://webchat.botframework.com/"),
header,
credentials,
new SimpleChannelProvider()).join();
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://webchat.botframework.com/"));
}
@Test
public void EnterpriseChannelValidation_Succeeds() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
} catch (CompletionException e) {
Assert.fail("Should not have thrown " + e.getCause().getClass().getName());
}
}
@Test
public void EnterpriseChannelValidation_NoAuthentication_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(null, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EnterpriseChannelValidation_NoAudienceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EnterpriseChannelValidation_NoAudienceClaimValue_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, "");
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EnterpriseChannelValidation_WrongAudienceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, "abc");
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EnterpriseChannelValidation_NoServiceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EnterpriseChannelValidation_NoServiceClaimValue_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, "");
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void EnterpriseChannelValidation_WrongServiceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, null);
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, "other");
}};
ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
EnterpriseChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an AuthenticationException");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_Succeeds() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
} catch (Exception e) {
Assert.fail("Should not have thrown " + e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
}
}
@Test
public void GovernmentChannelValidation_NoAuthentication_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(null, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_NoAudienceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_NoAudienceClaimValue_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, "");
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_WrongAudienceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, "abc");
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_WrongAudienceClaimIssuer_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl);
}};
ClaimsIdentity identity = new ClaimsIdentity("https://wrongissuer.com", claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_NoServiceClaim_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_NoServiceClaimValue_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, "");
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
@Test
public void GovernmentChannelValidation_WrongServiceClaimValue_Fails() {
String appId = "1234567890";
String serviceUrl = "https://webchat.botframework.com/";
CredentialProvider credentials = new SimpleCredentialProvider(appId, "");
Map<String, String> claims = new HashMap<String, String>() {{
put(AuthenticationConstants.AUDIENCE_CLAIM, appId);
put(AuthenticationConstants.SERVICE_URL_CLAIM, "other");
}};
ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims);
try {
GovernmentChannelValidation.validateIdentity(identity, credentials, serviceUrl).join();
Assert.fail("Should have thrown an Authorization exception");
} catch (CompletionException e) {
Assert.assertTrue(e.getCause() instanceof AuthenticationException);
}
}
private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(String appId, String pwd, String channelService) throws IOException, ExecutionException, InterruptedException {
ChannelProvider channel = new SimpleChannelProvider(channelService);
String header = channel.isGovernment() ? getGovHeaderToken() : getHeaderToken();
JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(header, appId, pwd, channel);
}
private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(String header, String appId, String pwd, ChannelProvider channel) {
CredentialProvider credentials = new SimpleCredentialProvider(appId, pwd);
try {
ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(
header,
credentials,
channel,
"",
"https://webchat.botframework.com/").join();
Assert.assertTrue(identity.isAuthenticated());
} catch (Exception e) {
Assert.fail("Should not have thrown " + e.getClass().getName());
}
}
private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Throws(String header, String appId, String pwd, String channelService) throws ExecutionException, InterruptedException {
CredentialProvider credentials = new SimpleCredentialProvider(appId, pwd);
ChannelProvider channel = new SimpleChannelProvider(channelService);
try {
JwtTokenValidation.validateAuthHeader(
header,
credentials,
channel,
"",
"https://webchat.botframework.com/").join();
Assert.fail("Should have thrown AuthenticationException");
} catch (AuthenticationException e) {
Assert.assertTrue(true);
}
}
}

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

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutionException;
public class MicrosoftAppCredentialsTests {
@Test
public void ValidUrlTrusted() {
MicrosoftAppCredentials.trustServiceUrl("https://goodurl.com");
Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://goodurl.com"));
}
@Test
public void InvalidUrlTrusted() {
MicrosoftAppCredentials.trustServiceUrl("badurl");
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("badurl"));
}
@Test
public void TrustedUrlExpiration() throws InterruptedException {
// There is a +5 minute window for an expired url
MicrosoftAppCredentials.trustServiceUrl("https://goodurl.com", LocalDateTime.now().minusMinutes(6));
Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://goodurl.com"));
MicrosoftAppCredentials.trustServiceUrl("https://goodurl.com", LocalDateTime.now().minusMinutes(4));
Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://goodurl.com"));
}
@Test
public void ValidateAuthEndpoint() {
try {
// In Java, about the only thing that can cause a MalformedURLException in a missing or unknown protocol.
// At any rate, this should validate someone didn't mess up the oAuth Endpoint for the class.
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F");
new URL(credentials.oAuthEndpoint());
credentials.setChannelAuthTenant("tenant.com");
MicrosoftAppCredentials credentialsWithTenant =
new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F", "tenant.com");
} catch(MalformedURLException e) {
Assert.fail("Should not have thrown MalformedURLException");
}
}
@Test
public void GetToken() throws InterruptedException, ExecutionException {
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F");
AuthenticationResult token = credentials.getToken().get();
Assert.assertFalse(StringUtils.isEmpty(token.getAccessToken()));
}
}

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

@ -1,33 +1,21 @@
package com.microsoft.bot.connector;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.authentication.OAuthClient;
import com.microsoft.bot.connector.base.TestBase;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.schema.models.TokenResponse;
import com.microsoft.rest.RestClient;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static java.util.concurrent.CompletableFuture.completedFuture;
public class OAuthConnectorTest extends OAuthTestBase {
private ConnectorClientImpl mockConnectorClient;
private RestConnectorClient mockConnectorClient;
private MicrosoftAppCredentials credentials;
public OAuthConnectorTest() throws IOException, ExecutionException, InterruptedException, URISyntaxException {

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

@ -5,22 +5,17 @@ import com.microsoft.bot.connector.authentication.AuthenticationConstants;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.authentication.OAuthClient;
import com.microsoft.bot.connector.base.TestBase;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.models.ChannelAccount;
import com.microsoft.rest.RestClient;
import okhttp3.Request;
import org.apache.commons.io.FileSystemUtils;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import static java.util.concurrent.CompletableFuture.completedFuture;
public class OAuthTestBase extends TestBase
{
@ -32,7 +27,7 @@ public class OAuthTestBase extends TestBase
private String token;
protected ConnectorClientImpl connector;
protected RestConnectorClient connector;
private ChannelAccount bot;
public ChannelAccount getBot() {
@ -70,11 +65,11 @@ public class OAuthTestBase extends TestBase
}
}
this.connector = new ConnectorClientImpl(restClient);
this.connector = new RestConnectorClient(restClient);
if (this.clientId != null && this.clientSecret != null) {
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials(this.clientId, this.clientSecret);
this.token = credentials.getToken(new Request.Builder().build());
this.token = credentials.getToken().get().getAccessToken();
}
else {
this.token = null;
@ -116,14 +111,14 @@ public class OAuthTestBase extends TestBase
return CompletableFuture.runAsync(()->{
OAuthClient oauthClient = null;
try {
oauthClient = new OAuthClient(this.connector, AuthenticationConstants.OAuthUrl);
oauthClient = new OAuthClient(this.connector, AuthenticationConstants.OAUTH_URL);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
doTest.apply(oauthClient);
});
}, ExecutorFactory.getExecutor());
}
}

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

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants;
import com.microsoft.bot.connector.authentication.SimpleChannelProvider;
import org.junit.Assert;
import org.junit.Test;
public class SimpleChannelProviderTests {
@Test
public void PublicChannelProvider() {
SimpleChannelProvider channel = new SimpleChannelProvider();
Assert.assertTrue(channel.isPublicAzure());
Assert.assertFalse(channel.isGovernment());
}
@Test
public void GovernmentChannelProvider() {
SimpleChannelProvider channel = new SimpleChannelProvider(GovernmentAuthenticationConstants.CHANNELSERVICE);
Assert.assertFalse(channel.isPublicAzure());
Assert.assertTrue(channel.isGovernment());
}
@Test
public void GetChannelService() {
try {
SimpleChannelProvider channel = new SimpleChannelProvider(GovernmentAuthenticationConstants.CHANNELSERVICE);
String service = channel.getChannelService().join();
Assert.assertEquals(service, GovernmentAuthenticationConstants.CHANNELSERVICE);
} catch (Throwable t) {
Assert.fail("Should not have thrown " + t.getClass().getName());
}
}
}

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

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.connector;
import com.microsoft.bot.connector.authentication.SimpleCredentialProvider;
import org.junit.Assert;
import org.junit.Test;
public class SimpleCredentialProviderTests {
@Test
public void ValidAppIdAsync() {
SimpleCredentialProvider credentialProvider = new SimpleCredentialProvider("appid", "pwd");
Assert.assertTrue(credentialProvider.isValidAppIdAsync("appid").join());
Assert.assertFalse(credentialProvider.isValidAppIdAsync("wrongappid").join());
}
@Test
public void AppPasswordAsync() {
SimpleCredentialProvider credentialProvider = new SimpleCredentialProvider("appid", "pwd");
Assert.assertEquals(credentialProvider.getAppPasswordAsync("appid").join(), "pwd");
Assert.assertNull(credentialProvider.getAppPasswordAsync("wrongappid").join());
}
@Test
public void AuthenticationDisabledAsync() {
Assert.assertFalse(new SimpleCredentialProvider("appid", "pwd").isAuthenticationDisabledAsync().join());
Assert.assertTrue(new SimpleCredentialProvider(null, null).isAuthenticationDisabledAsync().join());
}
}

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

@ -0,0 +1,207 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-java</artifactId>
<version>4.0.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>bot-integration-core</artifactId>
<packaging>jar</packaging>
<version>4.0.0-SNAPSHOT</version>
<name>${project.groupId}:${project.artifactId}</name>
<description>Bot Framework Integration Core</description>
<url>https://dev.botframework.com/</url>
<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
</license>
</licenses>
<developers>
<developer>
<name>Bot Framework Development</name>
<email></email>
<organization>Microsoft</organization>
<organizationUrl>https://dev.botframework.com/</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/Microsoft/botbuilder-java</connection>
<developerConnection>scm:git:https://github.com/Microsoft/botbuilder-java</developerConnection>
<url>https://github.com/Microsoft/botbuilder-java</url>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-schema</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-connector</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>MyGet</id>
<url>${repo.url}</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>MyGet</id>
<url>${repo.url}</url>
</repository>
</distributionManagement>
<profiles>
<profile>
<id>build</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<configuration>
<repoToken>yourcoverallsprojectrepositorytoken</repoToken>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<configuration>
<outputDirectory>../../cobertura-report/bot-connector</outputDirectory>
<format>xml</format>
<maxmem>256m</maxmem>
<!-- aggregated reports for multi-module projects -->
<aggregate>true</aggregate>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>publish</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

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

@ -0,0 +1,30 @@
package com.microsoft.bot.integration;
import java.io.IOException;
import java.util.Properties;
import org.slf4j.LoggerFactory;
/**
* Provides access to properties defined in a Properties file located on the classpath.
*/
public class ClasspathPropertiesConfiguration implements Configuration {
private Properties properties;
/**
* Loads properties from the 'application.properties' file.
* @throws IOException
*/
public ClasspathPropertiesConfiguration() {
try {
properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties"));
}
catch (IOException e) {
(LoggerFactory.getLogger(ClasspathPropertiesConfiguration.class)).error("Unable to load properties", e);
}
}
public String getProperty(String key) {
return properties.getProperty(key);
}
}

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

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.integration;
/**
* Provides read-only access to configuration properties.
*/
public interface Configuration {
String getProperty(String key);
}

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

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.integration;
import com.microsoft.bot.connector.authentication.SimpleChannelProvider;
/**
* Channel provider which uses Configuration to lookup the channel service property.
*
* This will populate the SimpleChannelProvider.ChannelService from a configuration entry with
* the key of "ChannelService".
*/
public class ConfigurationChannelProvider extends SimpleChannelProvider {
public ConfigurationChannelProvider(Configuration configuration) {
super(configuration.getProperty("ChannelService"));
}
}

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

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.integration;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.authentication.SimpleCredentialProvider;
/**
* Credential provider which uses Configuration to lookup appId and password.
*/
public class ConfigurationCredentialProvider extends SimpleCredentialProvider {
public ConfigurationCredentialProvider(Configuration configuration) {
setAppId(configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID));
setPassword(configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPPASSWORD));
}
}

20
pom.xml
Просмотреть файл

@ -66,27 +66,27 @@
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.9.8</version>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.9.8</version>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.8</version>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.3.0</version>
<version>0.8.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@ -101,7 +101,12 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<version>3.9</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
@ -130,6 +135,7 @@
<module>libraries/bot-schema</module>
<module>libraries/bot-builder</module>
<module>libraries/bot-connector</module>
<module>libraries/bot-integration-core</module>
<module>samples/bot-connector-sample</module>
<module>samples/servlet-echo</module>
<module>samples/spring-echo</module>
@ -183,7 +189,7 @@
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${checkstyle.version}</version>
<configuration>
<!-- <configLocation>checkstyle.xml</configLocation>-->
<configLocation>./etc/bot-checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>false</consoleOutput>
<failsOnError>false</failsOnError>

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

@ -6,11 +6,9 @@ package com.microsoft.bot.connector.sample;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.authentication.CredentialProvider;
import com.microsoft.bot.connector.authentication.CredentialProviderImpl;
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.ConnectorClient;
import com.microsoft.bot.connector.authentication.*;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.models.Activity;
import com.microsoft.bot.schema.models.ActivityTypes;
import com.microsoft.bot.schema.models.ResourceResponse;
@ -30,7 +28,7 @@ public class App {
private static String appPassword = ""; // <-- app password -->
public static void main( String[] args ) throws IOException {
CredentialProvider credentialProvider = new CredentialProviderImpl(appId, appPassword);
CredentialProvider credentialProvider = new SimpleCredentialProvider(appId, appPassword);
HttpServer server = HttpServer.create(new InetSocketAddress(3978), 0);
server.createContext("/api/messages", new MessageHandle(credentialProvider));
server.setExecutor(null);
@ -55,7 +53,7 @@ public class App {
Activity activity = getActivity(httpExchange);
String authHeader = httpExchange.getRequestHeaders().getFirst("Authorization");
try {
JwtTokenValidation.authenticateRequest(activity, authHeader, credentialProvider);
JwtTokenValidation.authenticateRequest(activity, authHeader, credentialProvider, new SimpleChannelProvider());
// send ack to user activity
httpExchange.sendResponseHeaders(202, 0);
@ -63,7 +61,7 @@ public class App {
if (activity.type().equals(ActivityTypes.MESSAGE)) {
// reply activity with the same text
ConnectorClientImpl connector = new ConnectorClientImpl(activity.serviceUrl(), this.credentials);
ConnectorClient connector = new RestConnectorClient(activity.serviceUrl(), this.credentials);
ResourceResponse response = connector.conversations().sendToConversation(activity.conversation().id(),
new Activity()
.withType(ActivityTypes.MESSAGE)

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

@ -8,12 +8,10 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.authentication.ClaimsIdentity;
import com.microsoft.bot.connector.authentication.CredentialProvider;
import com.microsoft.bot.connector.authentication.CredentialProviderImpl;
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.ConnectorClient;
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.connector.authentication.*;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.models.Activity;
import com.microsoft.bot.schema.models.ActivityTypes;
import javax.servlet.*;
@ -24,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;
import java.util.Properties;
import java.util.concurrent.CompletionException;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -53,7 +52,7 @@ public class EchoServlet extends HttpServlet {
String appId = p.getProperty("MicrosoftAppId");
String appPassword = p.getProperty("MicrosoftAppPassword");
this.credentialProvider = new CredentialProviderImpl(appId, appPassword);
this.credentialProvider = new SimpleCredentialProvider(appId, appPassword);
this.credentials = new MicrosoftAppCredentials(appId, appPassword);
}
catch(IOException ioe){
@ -67,11 +66,11 @@ public class EchoServlet extends HttpServlet {
final Activity activity = getActivity(request);
String authHeader = request.getHeader("Authorization");
CompletableFuture<ClaimsIdentity> authenticateRequest = JwtTokenValidation.authenticateRequest(activity, authHeader, credentialProvider);
CompletableFuture<ClaimsIdentity> authenticateRequest = JwtTokenValidation.authenticateRequest(activity, authHeader, credentialProvider, new SimpleChannelProvider());
authenticateRequest.thenRunAsync(() -> {
if (activity.type().equals(ActivityTypes.MESSAGE)) {
// reply activity with the same text
ConnectorClientImpl connector = new ConnectorClientImpl(activity.serviceUrl(), this.credentials);
ConnectorClient connector = new RestConnectorClient(activity.serviceUrl(), this.credentials);
connector.conversations().sendToConversation(activity.conversation().id(),
new Activity()
.withType(ActivityTypes.MESSAGE)
@ -80,13 +79,21 @@ public class EchoServlet extends HttpServlet {
.withFrom(activity.recipient())
);
}
});
} catch (AuthenticationException ex) {
response.setStatus(401);
LOGGER.log(Level.WARNING, "Auth failed!", ex);
}, ExecutorFactory.getExecutor()).join();
response.setStatus(200);
} catch (CompletionException ex) {
if (ex.getCause() instanceof AuthenticationException) {
LOGGER.log(Level.WARNING, "Auth failed!", ex);
response.setStatus(401);
}
else {
LOGGER.log(Level.WARNING, "Execution failed", ex);
response.setStatus(500);
}
} catch (Exception ex) {
response.setStatus(500);
LOGGER.log(Level.WARNING, "Execution failed", ex);
response.setStatus(500);
}
}

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

@ -2,6 +2,9 @@
// Licensed under the MIT License.
package com.microsoft.bot.sample.spring;
import com.microsoft.bot.connector.ConnectorClient;
import com.microsoft.bot.connector.ExecutorFactory;
import com.microsoft.bot.connector.authentication.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -15,16 +18,11 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.bot.connector.authentication.ClaimsIdentity;
import com.microsoft.bot.connector.authentication.CredentialProvider;
import com.microsoft.bot.connector.authentication.CredentialProviderImpl;
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
import com.microsoft.bot.connector.rest.RestConnectorClient;
import com.microsoft.bot.schema.models.Activity;
import com.microsoft.bot.schema.models.ActivityTypes;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
/**
* This is the controller that will receive incoming Channel Activity messages.
@ -51,7 +49,7 @@ public class BotController {
*/
@PostConstruct
public void init() {
_credentialProvider = new CredentialProviderImpl(appId, appPassword);
_credentialProvider = new SimpleCredentialProvider(appId, appPassword);
_credentials = new MicrosoftAppCredentials(appId, appPassword);
}
@ -66,20 +64,25 @@ public class BotController {
public ResponseEntity<Object> incoming(@RequestBody Activity activity,
@RequestHeader(value = "Authorization", defaultValue = "") String authHeader) {
try {
CompletableFuture<ClaimsIdentity> authenticateRequest = JwtTokenValidation.authenticateRequest(activity, authHeader, _credentialProvider);
authenticateRequest.thenRunAsync(() -> {
if (activity.type().equals(ActivityTypes.MESSAGE)) {
logger.info("Received: " + activity.text());
JwtTokenValidation.authenticateRequest(activity, authHeader, _credentialProvider, new SimpleChannelProvider())
.thenRunAsync(() -> {
if (activity.type().equals(ActivityTypes.MESSAGE)) {
logger.info("Received: " + activity.text());
// reply activity with the same text
ConnectorClientImpl connector = new ConnectorClientImpl(activity.serviceUrl(), _credentials);
connector.conversations().sendToConversation(activity.conversation().id(),
new Activity().withType(ActivityTypes.MESSAGE).withText("Echo: " + activity.text())
.withRecipient(activity.from()).withFrom(activity.recipient()));
}
});
} catch (AuthenticationException ex) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
// reply activity with the same text
ConnectorClient connector = new RestConnectorClient(activity.serviceUrl(), _credentials);
connector.conversations().sendToConversation(activity.conversation().id(),
new Activity().withType(ActivityTypes.MESSAGE).withText("Echo: " + activity.text())
.withRecipient(activity.from()).withFrom(activity.recipient()));
}
}, ExecutorFactory.getExecutor()).join();
} catch (CompletionException ex) {
if (ex.getCause() instanceof AuthenticationException) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
else {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
} catch (Exception ex) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

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

@ -1,2 +1,2 @@
MicrosoftAppId=9c286e2f-e070-4af5-a3f1-350d666214ed
MicrosoftAppPassword=botframework_secret_goes_here
MicrosoftAppId=
MicrosoftAppPassword=