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

Fix for Issue 809 #1060

Merged
merged 1 commit into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ public ChannelServiceHandler(
ChannelProvider channelProvider) {

if (credentialProvider == null) {
throw new IllegalArgumentException("credentialprovider cannot be nul");
throw new IllegalArgumentException("credentialprovider cannot be null");
}

if (authConfiguration == null) {
throw new IllegalArgumentException("authConfiguration cannot be nul");
throw new IllegalArgumentException("authConfiguration cannot be null");
}

this.credentialProvider = credentialProvider;
Expand Down Expand Up @@ -603,7 +603,7 @@ private CompletableFuture<ClaimsIdentity> authenticate(String authHeader) {
return credentialProvider.isAuthenticationDisabled().thenCompose(isAuthDisabled -> {
if (!isAuthDisabled) {
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")
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
}

Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ public CompletableFuture<String> getToken() {
* @return true if the auth token should be added to the request.
*/
boolean shouldSetToken(String url) {
if (StringUtils.isBlank(getAppId()) || getAppId().equals(AuthenticationConstants.ANONYMOUS_SKILL_APPID)) {
return false;
}
return isTrustedServiceUrl(url);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package com.microsoft.bot.connector.authentication;

import com.microsoft.bot.connector.Async;
import com.microsoft.bot.connector.Channels;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.RoleTypes;

import java.util.Map;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -61,19 +64,31 @@ public static CompletableFuture<ClaimsIdentity> authenticateRequest(
ChannelProvider channelProvider,
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.
return credentials.isAuthenticationDisabled().thenApply(isAuthDisable -> {
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");
if (!isAuthDisable) {
// No Auth Header. Auth is required. Request is not authorized.
throw new AuthenticationException("No Auth Header. Auth is required.");
}

if (activity.getChannelId() != null
&& activity.getChannelId().equals(Channels.EMULATOR)
&& activity.getRecipient() != null
&& activity.getRecipient().getRole().equals(RoleTypes.SKILL)) {
return SkillValidation.createAnonymousSkillClaim();
}

// No Auth Header. Auth is required. Request is not authorized.
throw new AuthenticationException("No Auth Header. Auth is required.");
// 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);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}

}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

import com.microsoft.bot.connector.authentication.*;
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.Test;

Expand Down Expand Up @@ -209,6 +213,29 @@ public void ChannelAuthenticationDisabledShouldBeAnonymous() throws ExecutionExc
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
public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException, ExecutionException, InterruptedException {
try {
Expand Down