Skip to content

Commit 20effab

Browse files
rokulkaheaths
andauthored
Enable AAD access for Question Answering Service (Azure#29665)
* Enable AAD access for QuestionAnswering * Enable AAD access for QuestionAnsweringProjectsClient * Update Tests * Add Audience property to support multiple clouds * Update AAD tests * Fix for AAD QuestionAnsweringClient * Fix for AAD QuestionAnsweringClient * Update Live Test Session Records * Ignore AAD support tests Not supported in the PPE environment. Need to switch over to the same static test resources for CLU. See Azure#29958 * Update changelog Co-authored-by: Heath Stewart <[email protected]>
1 parent 4924afe commit 20effab

13 files changed

+696
-10
lines changed

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/CHANGELOG.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
# Release History
22

3-
## 1.1.0-beta.2 (Unreleased)
3+
## 1.1.0-beta.2 (2022-07-19)
44

55
### Features Added
66

7-
### Breaking Changes
8-
9-
### Bugs Fixed
10-
11-
### Other Changes
7+
- Added support for Azure Active Directory (AAD) authentication.
128

139
## 1.1.0-beta.1 (2022-02-08)
1410

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.ComponentModel;
6+
using Azure.Core;
7+
8+
namespace Azure.AI.Language.QuestionAnswering
9+
{
10+
/// <summary> Cloud audiences available for QuestionAnswering. </summary>
11+
public readonly partial struct QuestionAnsweringAudience : IEquatable<QuestionAnsweringAudience>
12+
{
13+
private readonly string _value;
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="QuestionAnsweringAudience"/> object.
17+
/// </summary>
18+
/// <param name="value">The Azure Active Directory audience to use when forming authorization scopes.For the Language service, this value corresponds to a URL that identifies the Azure cloud where the resource is located. For more information: <see href="https://docs.microsoft.com/azure/azure-government/documentation-government-cognitiveservices" />.</param>
19+
/// <exception cref="ArgumentNullException"> <paramref name="value"/> is null. </exception>
20+
/// <remarks>Please use one of the constant members over creating a custom value unless you have special needs for doing so.</remarks>
21+
public QuestionAnsweringAudience(string value)
22+
{
23+
Argument.AssertNotNullOrEmpty(value, nameof(value));
24+
_value = value;
25+
}
26+
27+
private const string AzureChinaValue = "https://cognitiveservices.azure.cn";
28+
private const string AzureGovernmentValue = "https://cognitiveservices.azure.us";
29+
private const string AzurePublicCloudValue = "https://cognitiveservices.azure.com";
30+
31+
/// <summary> Azure China. </summary>
32+
public static QuestionAnsweringAudience AzureChina { get; } = new QuestionAnsweringAudience(AzureChinaValue);
33+
34+
/// <summary> Azure Government. </summary>
35+
public static QuestionAnsweringAudience AzureGovernment { get; } = new QuestionAnsweringAudience(AzureGovernmentValue);
36+
37+
/// <summary> Azure Public Cloud. </summary>
38+
public static QuestionAnsweringAudience AzurePublicCloud { get; } = new QuestionAnsweringAudience(AzurePublicCloudValue);
39+
40+
/// <summary> Determines if two <see cref="QuestionAnsweringAudience"/> values are the same. </summary>
41+
public static bool operator ==(QuestionAnsweringAudience left, QuestionAnsweringAudience right) => left.Equals(right);
42+
/// <summary> Determines if two <see cref="QuestionAnsweringAudience"/> values are not the same. </summary>
43+
public static bool operator !=(QuestionAnsweringAudience left, QuestionAnsweringAudience right) => !left.Equals(right);
44+
/// <summary> Converts a string to a <see cref="QuestionAnsweringAudience"/>. </summary>
45+
public static implicit operator QuestionAnsweringAudience(string value) => new QuestionAnsweringAudience(value);
46+
47+
/// <inheritdoc />
48+
[EditorBrowsable(EditorBrowsableState.Never)]
49+
public override bool Equals(object obj) => obj is QuestionAnsweringAudience other && Equals(other);
50+
/// <inheritdoc />
51+
public bool Equals(QuestionAnsweringAudience other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase);
52+
53+
/// <inheritdoc />
54+
[EditorBrowsable(EditorBrowsableState.Never)]
55+
public override int GetHashCode() => _value?.GetHashCode() ?? 0;
56+
/// <inheritdoc />
57+
public override string ToString() => _value;
58+
}
59+
}

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/src/QuestionAnsweringClient.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,34 @@ public QuestionAnsweringClient(Uri endpoint, AzureKeyCredential credential, Ques
5252
_restClient = new(Diagnostics, Pipeline, Endpoint, Options.Version);
5353
}
5454

55+
/// <summary> Initializes a new instance of QuestionAnsweringClient. </summary>
56+
/// <param name="endpoint"> Supported Cognitive Services endpoint (e.g., https://&lt;resource-name&gt;.cognitiveservices.azure.com). </param>
57+
/// <param name="credential"> A credential used to authenticate to an Azure Service. </param>
58+
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="credential"/> is null. </exception>
59+
public QuestionAnsweringClient(Uri endpoint, TokenCredential credential) : this(endpoint, credential, new QuestionAnsweringClientOptions())
60+
{
61+
}
62+
63+
/// <summary> Initializes a new instance of QuestionAnsweringClient. </summary>
64+
/// <param name="endpoint"> Supported Cognitive Services endpoint (e.g., https://&lt;resource-name&gt;.cognitiveservices.azure.com). </param>
65+
/// <param name="credential"> A credential used to authenticate to an Azure Service. </param>
66+
/// <param name="options"> The options for configuring the client. </param>
67+
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="credential"/> is null. </exception>
68+
public QuestionAnsweringClient(Uri endpoint, TokenCredential credential, QuestionAnsweringClientOptions options)
69+
{
70+
Argument.AssertNotNull(endpoint, nameof(endpoint));
71+
Argument.AssertNotNull(credential, nameof(credential));
72+
Options = options ?? new QuestionAnsweringClientOptions();
73+
74+
var authorizationScope = $"{(string.IsNullOrEmpty(Options.Audience?.ToString()) ? QuestionAnsweringAudience.AzurePublicCloud : Options.Audience)}/.default";
75+
76+
Diagnostics = new ClientDiagnostics(Options, true);
77+
Pipeline = HttpPipelineBuilder.Build(Options, new HttpPipelinePolicy[] { new BearerTokenAuthenticationPolicy(credential, authorizationScope) }, Array.Empty<HttpPipelinePolicy>(), new ResponseClassifier());
78+
Endpoint = endpoint;
79+
80+
_restClient = new(Diagnostics, Pipeline, Endpoint, Options.Version);
81+
}
82+
5583
/// <summary>
5684
/// Protected constructor to allow mocking.
5785
/// </summary>

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/src/QuestionAnsweringClientOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public enum ServiceVersion
4343

4444
internal string Version { get; }
4545

46+
/// <summary>
47+
/// Gets or sets the Audience to use for authentication with Azure Active Directory (AAD). The audience is not considered when using a shared key.
48+
/// </summary>
49+
/// <value>If <c>null</c>, <see cref="QuestionAnsweringAudience.AzurePublicCloud" /> will be assumed.</value>
50+
public QuestionAnsweringAudience? Audience { get; set; }
51+
4652
/// <summary>
4753
/// Initializes new instance of QuestionAnsweringClientOptions.
4854
/// </summary>

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/src/QuestionAnsweringProjectsClient.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,39 @@
55
using System.Collections.Generic;
66
using System.Text;
77
using Azure.Core;
8+
using Azure.Core.Pipeline;
89

910
namespace Azure.AI.Language.QuestionAnswering.Projects
1011
{
1112
/// <summary> The QuestionAnsweringProjects service client. </summary>
1213
[CodeGenType("QuestionAnsweringProjectsClient")]
1314
public partial class QuestionAnsweringProjectsClient
1415
{
16+
/// <summary> Initializes a new instance of QuestionAnsweringProjectsClient. </summary>
17+
/// <param name="endpoint"> Supported Cognitive Services endpoint (e.g., https://&lt;resource-name&gt;.cognitiveservices.azure.com). </param>
18+
/// <param name="credential"> A credential used to authenticate to an Azure Service. </param>
19+
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="credential"/> is null. </exception>
20+
public QuestionAnsweringProjectsClient(Uri endpoint, TokenCredential credential) : this(endpoint, credential, new QuestionAnsweringClientOptions())
21+
{
22+
}
23+
24+
/// <summary> Initializes a new instance of QuestionAnsweringProjectsClient. </summary>
25+
/// <param name="endpoint"> Supported Cognitive Services endpoint (e.g., https://&lt;resource-name&gt;.cognitiveservices.azure.com). </param>
26+
/// <param name="credential"> A credential used to authenticate to an Azure Service. </param>
27+
/// <param name="options"> The options for configuring the client. </param>
28+
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="credential"/> is null. </exception>
29+
public QuestionAnsweringProjectsClient(Uri endpoint, TokenCredential credential, QuestionAnsweringClientOptions options)
30+
{
31+
Argument.AssertNotNull(endpoint, nameof(endpoint));
32+
Argument.AssertNotNull(credential, nameof(credential));
33+
options ??= new QuestionAnsweringClientOptions();
34+
35+
var authorizationScope = $"{(string.IsNullOrEmpty(options.Audience?.ToString()) ? QuestionAnsweringAudience.AzurePublicCloud : options.Audience)}/.default";
36+
37+
ClientDiagnostics = new ClientDiagnostics(options, true);
38+
_pipeline = HttpPipelineBuilder.Build(options, new HttpPipelinePolicy[] { new BearerTokenAuthenticationPolicy(credential, authorizationScope) }, Array.Empty<HttpPipelinePolicy>(), new ResponseClassifier());
39+
_endpoint = endpoint;
40+
_apiVersion = options.Version;
41+
}
1542
}
1643
}

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/tests/QuestionAnsweringClientLiveTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,38 @@ public QuestionAnsweringClientLiveTests(bool isAsync, QuestionAnsweringClientOpt
1616
{
1717
}
1818

19+
[RecordedTest]
20+
[Ignore("https://github.com/Azure/azure-sdk-for-net/issues/29958")]
21+
public async Task SupportsAadAuthentication()
22+
{
23+
QuestionAnsweringClientOptions clientOptions = new QuestionAnsweringClientOptions()
24+
{
25+
DefaultLanguage = "en",
26+
};
27+
28+
QuestionAnsweringClient client = CreateClient<QuestionAnsweringClient>(
29+
TestEnvironment.Endpoint,
30+
TestEnvironment.Credential,
31+
InstrumentClientOptions(clientOptions));
32+
33+
Response<AnswersFromTextResult> response = await client.GetAnswersFromTextAsync(
34+
"How long it takes to charge surface?",
35+
new[]
36+
{
37+
"Power and charging. It takes two to four hours to charge the Surface Pro 4 battery fully from an empty state. " +
38+
"It can take longer if you’re using your Surface for power-intensive activities like gaming or video streaming while you’re charging it.",
39+
40+
"You can use the USB port on your Surface Pro 4 power supply to charge other devices, like a phone, while your Surface charges. " +
41+
"The USB port on the power supply is only for charging, not for data transfer. If you want to use a USB device, plug it into the USB port on your Surface.",
42+
});
43+
44+
Assert.That(response.Value.Answers.Count, Is.EqualTo(3));
45+
46+
IList<TextAnswer> answers = response.Value.Answers.Where(answer => answer.Confidence > 0.9).ToList();
47+
Assert.That(answers, Has.Count.AtLeast(2));
48+
Assert.That(answers, Has.All.Matches<TextAnswer>(answer => answer.Id == "1" && answer.ShortAnswer.Text?.Trim() == "two to four hours"));
49+
}
50+
1951
[RecordedTest]
2052
public async Task AnswersKnowledgeBaseQuestion()
2153
{

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/tests/QuestionAnsweringClientTests.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using Azure.Core;
56
using NUnit.Framework;
67

78
namespace Azure.AI.Language.QuestionAnswering.Tests
@@ -16,7 +17,7 @@ public class QuestionAnsweringClientTests
1617
public void QuestionAnswerClientEndpointNull()
1718
{
1819
ArgumentException ex = Assert.Throws<ArgumentNullException>(
19-
() => new QuestionAnsweringClient(null, null));
20+
() => new QuestionAnsweringClient(null, (AzureKeyCredential)null));
2021
Assert.AreEqual("endpoint", ex.ParamName);
2122
}
2223

@@ -26,7 +27,25 @@ public void QuestionAnsweringClientCredentialNull()
2627
Uri endpoint = new Uri("https://test.api.cognitive.microsoft.com", UriKind.Absolute);
2728

2829
ArgumentException ex = Assert.Throws<ArgumentNullException>(
29-
() => new QuestionAnsweringClient(endpoint, null));
30+
() => new QuestionAnsweringClient(endpoint, (AzureKeyCredential)null));
31+
Assert.AreEqual("credential", ex.ParamName);
32+
}
33+
34+
[Test]
35+
public void QuestionAnsweringClientEndpointNullUsingTokenCredential()
36+
{
37+
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
38+
() => new QuestionAnsweringClient(null, (TokenCredential)null));
39+
Assert.AreEqual("endpoint", ex.ParamName);
40+
}
41+
42+
[Test]
43+
public void QuestionAnsweringClientCredentialNullUsingTokenCredential()
44+
{
45+
Uri endpoint = new("https://test.cognitive.microsoft.com", UriKind.Absolute);
46+
47+
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
48+
() => new QuestionAnsweringClient(endpoint, (TokenCredential)null));
3049
Assert.AreEqual("credential", ex.ParamName);
3150
}
3251

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/tests/QuestionAnsweringProjectsClientLiveTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,40 @@ public QuestionAnsweringProjectsClientLiveTests(bool isAsync, QuestionAnsweringC
2121
{
2222
}
2323

24+
[RecordedTest]
25+
[Ignore("https://github.com/Azure/azure-sdk-for-net/issues/29958")]
26+
public async Task SupportsAadAuthentication()
27+
{
28+
QuestionAnsweringProjectsClient client = CreateClient<QuestionAnsweringProjectsClient>(
29+
TestEnvironment.Endpoint,
30+
TestEnvironment.Credential,
31+
InstrumentClientOptions(
32+
new QuestionAnsweringClientOptions()));
33+
34+
string testProjectName = CreateTestProjectName();
35+
36+
RequestContent creationRequestContent = RequestContent.Create(
37+
new
38+
{
39+
description = "This is the description for a test project",
40+
language = "en",
41+
multilingualResource = false,
42+
settings = new
43+
{
44+
defaultAnswer = "No answer found for your question."
45+
}
46+
}
47+
);
48+
49+
Response createProjectResponse = await client.CreateProjectAsync(testProjectName, creationRequestContent);
50+
Response projectDetailsResponse = await client.GetProjectDetailsAsync(testProjectName);
51+
52+
Assert.AreEqual(201, createProjectResponse.Status);
53+
Assert.AreEqual(200, projectDetailsResponse.Status);
54+
55+
await client.DeleteProjectAsync(WaitUntil.Completed, testProjectName);
56+
}
57+
2458
[RecordedTest]
2559
public async Task CreateProject()
2660
{

sdk/cognitivelanguage/Azure.AI.Language.QuestionAnswering/tests/QuestionAnsweringProjectsClientTests.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NUnit.Framework;
66
using Azure.AI.Language.QuestionAnswering.Projects;
77
using Azure.Core.TestFramework;
8+
using Azure.Core;
89

910
namespace Azure.AI.Language.QuestionAnswering.Tests
1011
{
@@ -22,7 +23,7 @@ public class QuestionAnsweringProjectsClientTests
2223
public void QuestionAnswerProjectsClientEndpointNull()
2324
{
2425
ArgumentException ex = Assert.Throws<ArgumentNullException>(
25-
() => new QuestionAnsweringProjectsClient(null, null));
26+
() => new QuestionAnsweringProjectsClient(null, (AzureKeyCredential)null));
2627
Assert.AreEqual("endpoint", ex.ParamName);
2728
}
2829

@@ -32,7 +33,25 @@ public void QuestionAnsweringProjectsClientCredentialNull()
3233
Uri endpoint = new Uri("https://test.api.cognitive.microsoft.com", UriKind.Absolute);
3334

3435
ArgumentException ex = Assert.Throws<ArgumentNullException>(
35-
() => new QuestionAnsweringProjectsClient(endpoint, null));
36+
() => new QuestionAnsweringProjectsClient(endpoint, (AzureKeyCredential)null));
37+
Assert.AreEqual("credential", ex.ParamName);
38+
}
39+
40+
[Test]
41+
public void QuestionAnsweringProjectsClientEndpointNullUsingTokenCredential()
42+
{
43+
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
44+
() => new QuestionAnsweringClient(null, (TokenCredential)null));
45+
Assert.AreEqual("endpoint", ex.ParamName);
46+
}
47+
48+
[Test]
49+
public void QuestionAnsweringProjectsClientCredentialNullUsingTokenCredential()
50+
{
51+
Uri endpoint = new("https://test.cognitive.microsoft.com", UriKind.Absolute);
52+
53+
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
54+
() => new QuestionAnsweringClient(endpoint, (TokenCredential)null));
3655
Assert.AreEqual("credential", ex.ParamName);
3756
}
3857

0 commit comments

Comments
 (0)