Fixes for Issue 809 (#1060)
This commit is contained in:
Родитель
3736b2656a
Коммит
466fd90b80
|
@ -48,11 +48,11 @@ public class ChannelServiceHandler {
|
||||||
ChannelProvider channelProvider) {
|
ChannelProvider channelProvider) {
|
||||||
|
|
||||||
if (credentialProvider == null) {
|
if (credentialProvider == null) {
|
||||||
throw new IllegalArgumentException("credentialprovider cannot be nul");
|
throw new IllegalArgumentException("credentialprovider cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authConfiguration == null) {
|
if (authConfiguration == null) {
|
||||||
throw new IllegalArgumentException("authConfiguration cannot be nul");
|
throw new IllegalArgumentException("authConfiguration cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.credentialProvider = credentialProvider;
|
this.credentialProvider = credentialProvider;
|
||||||
|
@ -603,7 +603,7 @@ public class ChannelServiceHandler {
|
||||||
return credentialProvider.isAuthenticationDisabled().thenCompose(isAuthDisabled -> {
|
return credentialProvider.isAuthenticationDisabled().thenCompose(isAuthDisabled -> {
|
||||||
if (!isAuthDisabled) {
|
if (!isAuthDisabled) {
|
||||||
return Async.completeExceptionally(
|
return Async.completeExceptionally(
|
||||||
// No auth header. Auth is required. Request is not authorized.
|
// No auth header. Auth is required. Request is not authorized.
|
||||||
new AuthenticationException("No auth header, Auth is required. Request is not authorized")
|
new AuthenticationException("No auth header, Auth is required. Request is not authorized")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MT License.
|
||||||
|
|
||||||
|
package com.microsoft.bot.builder;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
|
||||||
|
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
|
||||||
|
import com.microsoft.bot.connector.authentication.ClaimsIdentity;
|
||||||
|
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
|
||||||
|
import com.microsoft.bot.connector.authentication.SimpleCredentialProvider;
|
||||||
|
import com.microsoft.bot.schema.Activity;
|
||||||
|
import com.microsoft.bot.schema.ActivityTypes;
|
||||||
|
import com.microsoft.bot.schema.ResourceResponse;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ChannelServiceHandlerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void AuthenticateSetsAnonymousSkillClaim() {
|
||||||
|
TestChannelServiceHandler sut = new TestChannelServiceHandler();
|
||||||
|
sut.handleReplyToActivity(null, "123", "456", new Activity(ActivityTypes.MESSAGE));
|
||||||
|
|
||||||
|
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE,
|
||||||
|
sut.getClaimsIdentity().getType());
|
||||||
|
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID,
|
||||||
|
JwtTokenValidation.getAppIdFromClaims(sut.getClaimsIdentity().claims()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ChannelServiceHandler} with overrides for testings.
|
||||||
|
*/
|
||||||
|
private class TestChannelServiceHandler extends ChannelServiceHandler {
|
||||||
|
TestChannelServiceHandler() {
|
||||||
|
super(new SimpleCredentialProvider(), new AuthenticationConfiguration(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClaimsIdentity claimsIdentity;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<ResourceResponse> onReplyToActivity(
|
||||||
|
ClaimsIdentity claimsIdentity,
|
||||||
|
String conversationId,
|
||||||
|
String activityId,
|
||||||
|
Activity activity
|
||||||
|
) {
|
||||||
|
this.claimsIdentity = claimsIdentity;
|
||||||
|
return CompletableFuture.completedFuture(new ResourceResponse());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets the {@link ClaimsIdentity} sent to the different methods after
|
||||||
|
* auth is done.
|
||||||
|
* @return the ClaimsIdentity value as a getClaimsIdentity().
|
||||||
|
*/
|
||||||
|
public ClaimsIdentity getClaimsIdentity() {
|
||||||
|
return this.claimsIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link ClaimsIdentity} sent to the different methods after
|
||||||
|
* auth is done.
|
||||||
|
* @param withClaimsIdentity The ClaimsIdentity value.
|
||||||
|
*/
|
||||||
|
private void setClaimsIdentity(ClaimsIdentity withClaimsIdentity) {
|
||||||
|
this.claimsIdentity = withClaimsIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -242,6 +242,9 @@ public abstract class AppCredentials implements ServiceClientCredentials {
|
||||||
* @return true if the auth token should be added to the request.
|
* @return true if the auth token should be added to the request.
|
||||||
*/
|
*/
|
||||||
boolean shouldSetToken(String url) {
|
boolean shouldSetToken(String url) {
|
||||||
|
if (StringUtils.isBlank(getAppId()) || getAppId().equals(AuthenticationConstants.ANONYMOUS_SKILL_APPID)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return isTrustedServiceUrl(url);
|
return isTrustedServiceUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
package com.microsoft.bot.connector.authentication;
|
package com.microsoft.bot.connector.authentication;
|
||||||
|
|
||||||
import com.microsoft.bot.connector.Async;
|
import com.microsoft.bot.connector.Async;
|
||||||
|
import com.microsoft.bot.connector.Channels;
|
||||||
import com.microsoft.bot.schema.Activity;
|
import com.microsoft.bot.schema.Activity;
|
||||||
|
import com.microsoft.bot.schema.RoleTypes;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@ -61,19 +64,31 @@ public final class JwtTokenValidation {
|
||||||
ChannelProvider channelProvider,
|
ChannelProvider channelProvider,
|
||||||
AuthenticationConfiguration authConfig
|
AuthenticationConfiguration authConfig
|
||||||
) {
|
) {
|
||||||
|
if (authConfig == null) {
|
||||||
|
return Async.completeExceptionally(
|
||||||
|
new IllegalArgumentException("authConfig cannot be null")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isEmpty(authHeader)) {
|
if (StringUtils.isBlank(authHeader)) {
|
||||||
// No auth header was sent. We might be on the anonymous code path.
|
// No auth header was sent. We might be on the anonymous code path.
|
||||||
return credentials.isAuthenticationDisabled().thenApply(isAuthDisable -> {
|
return credentials.isAuthenticationDisabled().thenApply(isAuthDisable -> {
|
||||||
if (isAuthDisable) {
|
if (!isAuthDisable) {
|
||||||
// In the scenario where Auth is disabled, we still want to have the
|
// No Auth Header. Auth is required. Request is not authorized.
|
||||||
// IsAuthenticated flag set in the ClaimsIdentity. To do this requires
|
throw new AuthenticationException("No Auth Header. Auth is required.");
|
||||||
// adding in an empty claim.
|
|
||||||
return new ClaimsIdentity("anonymous");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No Auth Header. Auth is required. Request is not authorized.
|
if (activity.getChannelId() != null
|
||||||
throw new AuthenticationException("No Auth Header. Auth is required.");
|
&& activity.getChannelId().equals(Channels.EMULATOR)
|
||||||
|
&& activity.getRecipient() != null
|
||||||
|
&& activity.getRecipient().getRole().equals(RoleTypes.SKILL)) {
|
||||||
|
return SkillValidation.createAnonymousSkillClaim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(AuthenticationConstants.ANONYMOUS_AUTH_TYPE);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MT License.
|
||||||
|
|
||||||
|
package com.microsoft.bot.connector;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
|
||||||
|
import com.microsoft.bot.connector.authentication.AppCredentials;
|
||||||
|
import com.microsoft.bot.connector.authentication.AppCredentialsInterceptor;
|
||||||
|
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
|
||||||
|
import com.microsoft.bot.connector.authentication.Authenticator;
|
||||||
|
import com.microsoft.bot.restclient.ServiceClient;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Protocol;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
|
||||||
|
public class AppCredentialsTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ConstructorTests() {
|
||||||
|
TestAppCredentials shouldDefaultToChannelScope = new TestAppCredentials("irrelevant");
|
||||||
|
Assert.assertEquals(AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
|
||||||
|
shouldDefaultToChannelScope.oAuthScope());
|
||||||
|
|
||||||
|
TestAppCredentials shouldDefaultToCustomScope = new TestAppCredentials("irrelevant", "customScope");
|
||||||
|
Assert.assertEquals("customScope", shouldDefaultToCustomScope.oAuthScope());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void basicCredentialsTest() throws Exception {
|
||||||
|
TestAppCredentials credentials = new TestAppCredentials("irrelevant", "pass");
|
||||||
|
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
|
||||||
|
credentials.applyCredentialsFilter(clientBuilder);
|
||||||
|
clientBuilder.addInterceptor(
|
||||||
|
new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
String header = chain.request().header("Authorization");
|
||||||
|
Assert.assertNull(header);
|
||||||
|
return new Response.Builder()
|
||||||
|
.request(chain.request())
|
||||||
|
.code(200)
|
||||||
|
.message("OK")
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ServiceClient serviceClient = new ServiceClient("http://localhost", clientBuilder, new Retrofit.Builder()) { };
|
||||||
|
Response response = serviceClient.httpClient().newCall(
|
||||||
|
new Request.Builder().url("http://localhost").build()).execute();
|
||||||
|
Assert.assertEquals(200, response.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestAppCredentials extends AppCredentials {
|
||||||
|
TestAppCredentials(String channelAuthTenant) {
|
||||||
|
super(channelAuthTenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestAppCredentials(String channelAuthTenant, String oAuthScope) {
|
||||||
|
super(channelAuthTenant, oAuthScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Authenticator buildAuthenticator() throws MalformedURLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the credentials to the HTTP request.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note: Provides the same functionality as dotnet ProcessHttpRequestAsync
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param clientBuilder the builder for building up an {@link OkHttpClient}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
|
||||||
|
clientBuilder.interceptors().add(new AppCredentialsInterceptor(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@ package com.microsoft.bot.connector;
|
||||||
|
|
||||||
import com.microsoft.bot.connector.authentication.*;
|
import com.microsoft.bot.connector.authentication.*;
|
||||||
import com.microsoft.bot.schema.Activity;
|
import com.microsoft.bot.schema.Activity;
|
||||||
|
import com.microsoft.bot.schema.ChannelAccount;
|
||||||
|
import com.microsoft.bot.schema.ConversationReference;
|
||||||
|
import com.microsoft.bot.schema.RoleTypes;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -209,6 +213,29 @@ public class JwtTokenValidationTests {
|
||||||
Assert.assertEquals("anonymous", identity.getIssuer());
|
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 ChannelAuthenticationDisabledAndSkillShouldBeAnonymous() throws ExecutionException, InterruptedException {
|
||||||
|
String header = "";
|
||||||
|
CredentialProvider credentials = new SimpleCredentialProvider("", "");
|
||||||
|
|
||||||
|
ClaimsIdentity identity = JwtTokenValidation.authenticateRequest(
|
||||||
|
new Activity() {{
|
||||||
|
setServiceUrl("https://webchat.botframework.com/");
|
||||||
|
setChannelId(Channels.EMULATOR);
|
||||||
|
setRelatesTo(new ConversationReference());
|
||||||
|
setRecipient(new ChannelAccount() { { setRole(RoleTypes.SKILL); } });
|
||||||
|
}},
|
||||||
|
header,
|
||||||
|
credentials,
|
||||||
|
new SimpleChannelProvider()).join();
|
||||||
|
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE, identity.getType());
|
||||||
|
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID, JwtTokenValidation.getAppIdFromClaims(identity.claims()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException, ExecutionException, InterruptedException {
|
public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException, ExecutionException, InterruptedException {
|
||||||
try {
|
try {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче