Skip to content
Draft
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
6 changes: 4 additions & 2 deletions Libraries/Microsoft.Teams.Apps/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public partial class App
internal IServiceProvider? Provider { get; set; }
internal IContainer Container { get; set; }

private readonly IEnumerable<string>? _additionalAllowedDomains;
private readonly IReadOnlyList<string>? _additionalAllowedDomains;
private readonly CloudEnvironment _cloud;
internal string UserAgent
{
Expand All @@ -63,7 +63,9 @@ public App(AppOptions? options = null)
Plugins = options?.Plugins ?? [];
OAuth = options?.OAuth ?? new OAuthSettings();
Provider = options?.Provider;
_additionalAllowedDomains = options?.AdditionalAllowedDomains;
// Defensive copy so a caller-provided list can't mutate validator behavior
// after construction (IEnumerable may be lazy or mutable).
_additionalAllowedDomains = options?.AdditionalAllowedDomains?.ToList();

if (_additionalAllowedDomains?.Contains("*") == true)
{
Expand Down
7 changes: 5 additions & 2 deletions Libraries/Microsoft.Teams.Apps/AppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ public class AppOptions
public CloudEnvironment? Cloud { get; set; }

/// <summary>
/// Additional allowed service URL hostnames beyond the built-in defaults.
/// Use this if your bot receives activities from non-standard channels.
/// Additional service URL hostnames accepted beyond the cloud preset.
/// Entries must be bare hostnames matched exactly (case-insensitive)
/// wildcard patterns like <c>"*.example.com"</c>, URL suffixes, or full URLs are NOT supported.
Comment thread
corinagum marked this conversation as resolved.
/// Pass <c>["*"]</c> as the sole wildcard to accept any hostname (disables service-URL validation).
Comment thread
corinagum marked this conversation as resolved.
/// </summary>
/// <example>new[] { "api.my-custom-channel.com" }</example>
public IEnumerable<string>? AdditionalAllowedDomains { get; set; }
Comment thread
corinagum marked this conversation as resolved.

public AppOptions()
Expand Down
35 changes: 35 additions & 0 deletions Tests/Microsoft.Teams.Apps.Tests/AppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,4 +367,39 @@ await Assert.ThrowsAsync<ArgumentException>(() =>
app.Reply("19:abc@thread.skype", "not-a-number", new MessageActivity("Hello")));
}

[Fact]
public void Test_App_AdditionalAllowedDomains_NullConstructionSucceeds()
{
var options = new AppOptions { AdditionalAllowedDomains = null };
var exception = Record.Exception(() => new App(options));
Assert.Null(exception);
}

[Fact]
public void Test_App_AdditionalAllowedDomains_EmptyConstructionSucceeds()
{
var options = new AppOptions { AdditionalAllowedDomains = Array.Empty<string>() };
var exception = Record.Exception(() => new App(options));
Assert.Null(exception);
}

[Fact]
public void Test_App_AdditionalAllowedDomains_MaterializesLazyEnumerable()
{
// A lazy IEnumerable must be materialized at construction (not held and
// re-enumerated later), otherwise mutation between iterations could change
// validator behavior. Asserts the caller's lazy is invoked exactly once.
var callCount = 0;
IEnumerable<string> Lazy()
{
callCount++;
yield return "api.custom-channel.com";
}

var options = new AppOptions { AdditionalAllowedDomains = Lazy() };
_ = new App(options);

Assert.Equal(1, callCount);
}

}
Loading