Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.

Commit 466fd90

Browse files
Fixes for Issue 809 (#1060)
1 parent 3736b26 commit 466fd90

File tree

6 files changed

+224
-11
lines changed

6 files changed

+224
-11
lines changed

libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ public ChannelServiceHandler(
4848
ChannelProvider channelProvider) {
4949

5050
if (credentialProvider == null) {
51-
throw new IllegalArgumentException("credentialprovider cannot be nul");
51+
throw new IllegalArgumentException("credentialprovider cannot be null");
5252
}
5353

5454
if (authConfiguration == null) {
55-
throw new IllegalArgumentException("authConfiguration cannot be nul");
55+
throw new IllegalArgumentException("authConfiguration cannot be null");
5656
}
5757

5858
this.credentialProvider = credentialProvider;
@@ -603,7 +603,7 @@ private CompletableFuture<ClaimsIdentity> authenticate(String authHeader) {
603603
return credentialProvider.isAuthenticationDisabled().thenCompose(isAuthDisabled -> {
604604
if (!isAuthDisabled) {
605605
return Async.completeExceptionally(
606-
// No auth header. Auth is required. Request is not authorized.
606+
// No auth header. Auth is required. Request is not authorized.
607607
new AuthenticationException("No auth header, Auth is required. Request is not authorized")
608608
);
609609
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MT License.
3+
4+
package com.microsoft.bot.builder;
5+
6+
import java.util.concurrent.CompletableFuture;
7+
8+
import com.microsoft.bot.connector.authentication.AuthenticationConfiguration;
9+
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
10+
import com.microsoft.bot.connector.authentication.ClaimsIdentity;
11+
import com.microsoft.bot.connector.authentication.JwtTokenValidation;
12+
import com.microsoft.bot.connector.authentication.SimpleCredentialProvider;
13+
import com.microsoft.bot.schema.Activity;
14+
import com.microsoft.bot.schema.ActivityTypes;
15+
import com.microsoft.bot.schema.ResourceResponse;
16+
17+
import org.junit.Assert;
18+
import org.junit.Test;
19+
20+
public class ChannelServiceHandlerTests {
21+
22+
@Test
23+
public void AuthenticateSetsAnonymousSkillClaim() {
24+
TestChannelServiceHandler sut = new TestChannelServiceHandler();
25+
sut.handleReplyToActivity(null, "123", "456", new Activity(ActivityTypes.MESSAGE));
26+
27+
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE,
28+
sut.getClaimsIdentity().getType());
29+
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID,
30+
JwtTokenValidation.getAppIdFromClaims(sut.getClaimsIdentity().claims()));
31+
}
32+
33+
/**
34+
* A {@link ChannelServiceHandler} with overrides for testings.
35+
*/
36+
private class TestChannelServiceHandler extends ChannelServiceHandler {
37+
TestChannelServiceHandler() {
38+
super(new SimpleCredentialProvider(), new AuthenticationConfiguration(), null);
39+
}
40+
41+
private ClaimsIdentity claimsIdentity;
42+
43+
@Override
44+
protected CompletableFuture<ResourceResponse> onReplyToActivity(
45+
ClaimsIdentity claimsIdentity,
46+
String conversationId,
47+
String activityId,
48+
Activity activity
49+
) {
50+
this.claimsIdentity = claimsIdentity;
51+
return CompletableFuture.completedFuture(new ResourceResponse());
52+
}
53+
/**
54+
* Gets the {@link ClaimsIdentity} sent to the different methods after
55+
* auth is done.
56+
* @return the ClaimsIdentity value as a getClaimsIdentity().
57+
*/
58+
public ClaimsIdentity getClaimsIdentity() {
59+
return this.claimsIdentity;
60+
}
61+
62+
/**
63+
* Gets the {@link ClaimsIdentity} sent to the different methods after
64+
* auth is done.
65+
* @param withClaimsIdentity The ClaimsIdentity value.
66+
*/
67+
private void setClaimsIdentity(ClaimsIdentity withClaimsIdentity) {
68+
this.claimsIdentity = withClaimsIdentity;
69+
}
70+
71+
}
72+
}
73+

libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ public CompletableFuture<String> getToken() {
242242
* @return true if the auth token should be added to the request.
243243
*/
244244
boolean shouldSetToken(String url) {
245+
if (StringUtils.isBlank(getAppId()) || getAppId().equals(AuthenticationConstants.ANONYMOUS_SKILL_APPID)) {
246+
return false;
247+
}
245248
return isTrustedServiceUrl(url);
246249
}
247250

libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
package com.microsoft.bot.connector.authentication;
55

66
import com.microsoft.bot.connector.Async;
7+
import com.microsoft.bot.connector.Channels;
78
import com.microsoft.bot.schema.Activity;
9+
import com.microsoft.bot.schema.RoleTypes;
10+
811
import java.util.Map;
912
import org.apache.commons.lang3.StringUtils;
1013

@@ -61,19 +64,31 @@ public static CompletableFuture<ClaimsIdentity> authenticateRequest(
6164
ChannelProvider channelProvider,
6265
AuthenticationConfiguration authConfig
6366
) {
67+
if (authConfig == null) {
68+
return Async.completeExceptionally(
69+
new IllegalArgumentException("authConfig cannot be null")
70+
);
71+
}
6472

65-
if (StringUtils.isEmpty(authHeader)) {
73+
if (StringUtils.isBlank(authHeader)) {
6674
// No auth header was sent. We might be on the anonymous code path.
6775
return credentials.isAuthenticationDisabled().thenApply(isAuthDisable -> {
68-
if (isAuthDisable) {
69-
// In the scenario where Auth is disabled, we still want to have the
70-
// IsAuthenticated flag set in the ClaimsIdentity. To do this requires
71-
// adding in an empty claim.
72-
return new ClaimsIdentity("anonymous");
76+
if (!isAuthDisable) {
77+
// No Auth Header. Auth is required. Request is not authorized.
78+
throw new AuthenticationException("No Auth Header. Auth is required.");
79+
}
80+
81+
if (activity.getChannelId() != null
82+
&& activity.getChannelId().equals(Channels.EMULATOR)
83+
&& activity.getRecipient() != null
84+
&& activity.getRecipient().getRole().equals(RoleTypes.SKILL)) {
85+
return SkillValidation.createAnonymousSkillClaim();
7386
}
7487

75-
// No Auth Header. Auth is required. Request is not authorized.
76-
throw new AuthenticationException("No Auth Header. Auth is required.");
88+
// In the scenario where Auth is disabled, we still want to have the
89+
// IsAuthenticated flag set in the ClaimsIdentity. To do this requires
90+
// adding in an empty claim.
91+
return new ClaimsIdentity(AuthenticationConstants.ANONYMOUS_AUTH_TYPE);
7792
});
7893
}
7994

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MT License.
3+
4+
package com.microsoft.bot.connector;
5+
6+
import java.io.IOException;
7+
import java.net.MalformedURLException;
8+
9+
import com.microsoft.bot.connector.authentication.AppCredentials;
10+
import com.microsoft.bot.connector.authentication.AppCredentialsInterceptor;
11+
import com.microsoft.bot.connector.authentication.AuthenticationConstants;
12+
import com.microsoft.bot.connector.authentication.Authenticator;
13+
import com.microsoft.bot.restclient.ServiceClient;
14+
15+
import org.junit.Assert;
16+
import org.junit.Test;
17+
18+
import okhttp3.Interceptor;
19+
import okhttp3.MediaType;
20+
import okhttp3.OkHttpClient;
21+
import okhttp3.Protocol;
22+
import okhttp3.Request;
23+
import okhttp3.Response;
24+
import okhttp3.ResponseBody;
25+
import retrofit2.Retrofit;
26+
27+
public class AppCredentialsTests {
28+
29+
@Test
30+
public void ConstructorTests() {
31+
TestAppCredentials shouldDefaultToChannelScope = new TestAppCredentials("irrelevant");
32+
Assert.assertEquals(AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE,
33+
shouldDefaultToChannelScope.oAuthScope());
34+
35+
TestAppCredentials shouldDefaultToCustomScope = new TestAppCredentials("irrelevant", "customScope");
36+
Assert.assertEquals("customScope", shouldDefaultToCustomScope.oAuthScope());
37+
}
38+
39+
@Test
40+
public void basicCredentialsTest() throws Exception {
41+
TestAppCredentials credentials = new TestAppCredentials("irrelevant", "pass");
42+
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
43+
credentials.applyCredentialsFilter(clientBuilder);
44+
clientBuilder.addInterceptor(
45+
new Interceptor() {
46+
@Override
47+
public Response intercept(Chain chain) throws IOException {
48+
String header = chain.request().header("Authorization");
49+
Assert.assertNull(header);
50+
return new Response.Builder()
51+
.request(chain.request())
52+
.code(200)
53+
.message("OK")
54+
.protocol(Protocol.HTTP_1_1)
55+
.body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks"))
56+
.build();
57+
}
58+
});
59+
ServiceClient serviceClient = new ServiceClient("http://localhost", clientBuilder, new Retrofit.Builder()) { };
60+
Response response = serviceClient.httpClient().newCall(
61+
new Request.Builder().url("http://localhost").build()).execute();
62+
Assert.assertEquals(200, response.code());
63+
}
64+
65+
private class TestAppCredentials extends AppCredentials {
66+
TestAppCredentials(String channelAuthTenant) {
67+
super(channelAuthTenant);
68+
}
69+
70+
TestAppCredentials(String channelAuthTenant, String oAuthScope) {
71+
super(channelAuthTenant, oAuthScope);
72+
}
73+
74+
@Override
75+
protected Authenticator buildAuthenticator() throws MalformedURLException {
76+
return null;
77+
}
78+
79+
/**
80+
* Apply the credentials to the HTTP request.
81+
*
82+
* <p>
83+
* Note: Provides the same functionality as dotnet ProcessHttpRequestAsync
84+
* </p>
85+
*
86+
* @param clientBuilder the builder for building up an {@link OkHttpClient}
87+
*/
88+
@Override
89+
public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) {
90+
clientBuilder.interceptors().add(new AppCredentialsInterceptor(this));
91+
}
92+
93+
}
94+
}
95+

libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
import com.microsoft.bot.connector.authentication.*;
77
import com.microsoft.bot.schema.Activity;
8+
import com.microsoft.bot.schema.ChannelAccount;
9+
import com.microsoft.bot.schema.ConversationReference;
10+
import com.microsoft.bot.schema.RoleTypes;
11+
812
import org.junit.Assert;
913
import org.junit.Test;
1014

@@ -209,6 +213,29 @@ public void ChannelAuthenticationDisabledShouldBeAnonymous() throws ExecutionExc
209213
Assert.assertEquals("anonymous", identity.getIssuer());
210214
}
211215

216+
/**
217+
* Tests with no authentication header and makes sure the service URL is not added to the trusted list.
218+
*/
219+
@Test
220+
public void ChannelAuthenticationDisabledAndSkillShouldBeAnonymous() throws ExecutionException, InterruptedException {
221+
String header = "";
222+
CredentialProvider credentials = new SimpleCredentialProvider("", "");
223+
224+
ClaimsIdentity identity = JwtTokenValidation.authenticateRequest(
225+
new Activity() {{
226+
setServiceUrl("https://webchat.botframework.com/");
227+
setChannelId(Channels.EMULATOR);
228+
setRelatesTo(new ConversationReference());
229+
setRecipient(new ChannelAccount() { { setRole(RoleTypes.SKILL); } });
230+
}},
231+
header,
232+
credentials,
233+
new SimpleChannelProvider()).join();
234+
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE, identity.getType());
235+
Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID, JwtTokenValidation.getAppIdFromClaims(identity.claims()));
236+
}
237+
238+
212239
@Test
213240
public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException, ExecutionException, InterruptedException {
214241
try {

0 commit comments

Comments
 (0)