diff --git a/Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs b/Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
index 7ae6d974..31935e9b 100644
--- a/Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
+++ b/Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
@@ -5,6 +5,10 @@
namespace Microsoft.Teams.Api.Auth;
+///
+/// ClientId / ClientSecret based credentials
+/// https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/azure-bot-create-single-secret
+///
public class ClientCredentials : IHttpCredentials
{
public string ClientId { get; set; }
diff --git a/Libraries/Microsoft.Teams.Api/Auth/TokenCredentials.cs b/Libraries/Microsoft.Teams.Api/Auth/TokenCredentials.cs
index 238e6f0a..2a2686f2 100644
--- a/Libraries/Microsoft.Teams.Api/Auth/TokenCredentials.cs
+++ b/Libraries/Microsoft.Teams.Api/Auth/TokenCredentials.cs
@@ -7,6 +7,12 @@ namespace Microsoft.Teams.Api.Auth;
public delegate Task TokenFactory(string? tenantId, params string[] scopes);
+///
+/// Provide a TokenFactory that will be invoked whenever
+/// the application needs a token.
+/// TokenCredentials should be used with 3rd party packages like MSAL/Azure.Identity
+/// to authenticate for any Federated/Managed Identity scenarios.
+///
public class TokenCredentials : IHttpCredentials
{
public string ClientId { get; set; }
@@ -26,7 +32,7 @@ public TokenCredentials(string clientId, string tenantId, TokenFactory token)
Token = token;
}
- public async Task Resolve(IHttpClient _client, string[] scopes, CancellationToken cancellationToken = default)
+ public async Task Resolve(IHttpClient _, string[] scopes, CancellationToken cancellationToken = default)
{
return await Token(TenantId, scopes);
}
diff --git a/Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs b/Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs
index b8cd2a58..7011397b 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs
@@ -32,7 +32,7 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio
ServiceUrl = serviceUrl;
}
- public async Task CreateAsync(string conversationId, IActivity activity, bool isTargeted = false)
+ public virtual async Task CreateAsync(string conversationId, IActivity activity, bool isTargeted = false)
{
var url = $"{ServiceUrl}v3/conversations/{conversationId}/activities";
if (isTargeted)
@@ -50,7 +50,7 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio
return body;
}
- public async Task UpdateAsync(string conversationId, string id, IActivity activity, bool isTargeted = false)
+ public virtual async Task UpdateAsync(string conversationId, string id, IActivity activity, bool isTargeted = false)
{
var url = $"{ServiceUrl}v3/conversations/{conversationId}/activities/{id}";
if (isTargeted)
@@ -68,7 +68,7 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio
return body;
}
- public async Task ReplyAsync(string conversationId, string id, IActivity activity, bool isTargeted = false)
+ public virtual async Task ReplyAsync(string conversationId, string id, IActivity activity, bool isTargeted = false)
{
activity.ReplyToId = id;
@@ -88,7 +88,7 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio
return body;
}
- public async Task DeleteAsync(string conversationId, string id, bool isTargeted = false)
+ public virtual async Task DeleteAsync(string conversationId, string id, bool isTargeted = false)
{
var url = $"{ServiceUrl}v3/conversations/{conversationId}/activities/{id}";
if (isTargeted)
diff --git a/Libraries/Microsoft.Teams.Api/Clients/BotClient.cs b/Libraries/Microsoft.Teams.Api/Clients/BotClient.cs
index ea7fda43..62e8f83e 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/BotClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/BotClient.cs
@@ -8,7 +8,7 @@ namespace Microsoft.Teams.Api.Clients;
public class BotClient : Client
{
public virtual BotTokenClient Token { get; }
- public BotSignInClient SignIn { get; }
+ public virtual BotSignInClient SignIn { get; }
public BotClient() : this(default)
{
diff --git a/Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs b/Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs
index 52217361..4d98c5cc 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/BotSignInClient.cs
@@ -27,7 +27,7 @@ public BotSignInClient(IHttpClientFactory factory, CancellationToken cancellatio
}
- public async Task GetUrlAsync(GetUrlRequest request)
+ public virtual async Task GetUrlAsync(GetUrlRequest request)
{
var query = QueryString.Serialize(request);
var req = HttpRequest.Get(
@@ -38,7 +38,7 @@ public async Task GetUrlAsync(GetUrlRequest request)
return res.Body;
}
- public async Task GetResourceAsync(GetResourceRequest request)
+ public virtual async Task GetResourceAsync(GetResourceRequest request)
{
var query = QueryString.Serialize(request);
var req = HttpRequest.Get(
diff --git a/Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs b/Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs
index 8255d89c..037b8d65 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/BotTokenClient.cs
@@ -10,7 +10,7 @@ public class BotTokenClient : Client
public static readonly string BotScope = "https://api.botframework.com/.default";
public static readonly string GraphScope = "https://graph.microsoft.com/.default";
- public BotTokenClient() : this(default)
+ public BotTokenClient() : base()
{
}
@@ -40,7 +40,7 @@ public virtual async Task GetAsync(IHttpCredentials credentials,
return await credentials.Resolve(http ?? _http, [BotScope], _cancellationToken);
}
- public async Task GetGraphAsync(IHttpCredentials credentials, IHttpClient? http = null)
+ public virtual async Task GetGraphAsync(IHttpCredentials credentials, IHttpClient? http = null)
{
return await credentials.Resolve(http ?? _http, [GraphScope], _cancellationToken);
}
diff --git a/Libraries/Microsoft.Teams.Api/Clients/ConversationClient.cs b/Libraries/Microsoft.Teams.Api/Clients/ConversationClient.cs
index 8523faae..dc1b8dfa 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/ConversationClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/ConversationClient.cs
@@ -11,8 +11,8 @@ namespace Microsoft.Teams.Api.Clients;
public class ConversationClient : Client
{
public readonly string ServiceUrl;
- public readonly ActivityClient Activities;
- public readonly MemberClient Members;
+ public virtual ActivityClient Activities { get; }
+ public virtual MemberClient Members { get; }
public ConversationClient(string serviceUrl, CancellationToken cancellationToken = default) : base(cancellationToken)
{
@@ -42,7 +42,7 @@ public ConversationClient(string serviceUrl, IHttpClientFactory factory, Cancell
Members = new MemberClient(serviceUrl, _http, cancellationToken);
}
- public async Task CreateAsync(CreateRequest request)
+ public virtual async Task CreateAsync(CreateRequest request)
{
var req = HttpRequest.Post($"{ServiceUrl}v3/conversations", body: request);
var res = await _http.SendAsync(req, _cancellationToken);
diff --git a/Libraries/Microsoft.Teams.Api/Clients/MeetingClient.cs b/Libraries/Microsoft.Teams.Api/Clients/MeetingClient.cs
index 9c8ef2da..fbc7125d 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/MeetingClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/MeetingClient.cs
@@ -32,14 +32,14 @@ public MeetingClient(string serviceUrl, IHttpClientFactory factory, Cancellation
ServiceUrl = serviceUrl;
}
- public async Task GetByIdAsync(string id)
+ public virtual async Task GetByIdAsync(string id)
{
var request = HttpRequest.Get($"{ServiceUrl}v1/meetings/{id}");
var response = await _http.SendAsync(request, _cancellationToken);
return response.Body;
}
- public async Task GetParticipantAsync(string meetingId, string id)
+ public virtual async Task GetParticipantAsync(string meetingId, string id)
{
var request = HttpRequest.Get($"{ServiceUrl}v1/meetings/{meetingId}/participants/{id}");
var response = await _http.SendAsync(request, _cancellationToken);
diff --git a/Libraries/Microsoft.Teams.Api/Clients/MemberClient.cs b/Libraries/Microsoft.Teams.Api/Clients/MemberClient.cs
index d69c96a2..330afb28 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/MemberClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/MemberClient.cs
@@ -29,21 +29,21 @@ public MemberClient(string serviceUrl, IHttpClientFactory factory, CancellationT
ServiceUrl = serviceUrl;
}
- public async Task> GetAsync(string conversationId)
+ public virtual async Task> GetAsync(string conversationId)
{
var request = HttpRequest.Get($"{ServiceUrl}v3/conversations/{conversationId}/members");
var response = await _http.SendAsync>(request, _cancellationToken);
return response.Body;
}
- public async Task GetByIdAsync(string conversationId, string memberId)
+ public virtual async Task GetByIdAsync(string conversationId, string memberId)
{
var request = HttpRequest.Get($"{ServiceUrl}v3/conversations/{conversationId}/members/{memberId}");
var response = await _http.SendAsync(request, _cancellationToken);
return response.Body;
}
- public async Task DeleteAsync(string conversationId, string memberId)
+ public virtual async Task DeleteAsync(string conversationId, string memberId)
{
var request = HttpRequest.Delete($"{ServiceUrl}v3/conversations/{conversationId}/members/{memberId}");
await _http.SendAsync(request, _cancellationToken);
diff --git a/Libraries/Microsoft.Teams.Api/Clients/TeamClient.cs b/Libraries/Microsoft.Teams.Api/Clients/TeamClient.cs
index 4ee05622..0d834d4f 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/TeamClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/TeamClient.cs
@@ -29,14 +29,14 @@ public TeamClient(string serviceUrl, IHttpClientFactory factory, CancellationTok
ServiceUrl = serviceUrl;
}
- public async Task GetByIdAsync(string id)
+ public virtual async Task GetByIdAsync(string id)
{
var request = HttpRequest.Get($"{ServiceUrl}v3/teams/{id}");
var response = await _http.SendAsync(request, _cancellationToken);
return response.Body;
}
- public async Task> GetConversationsAsync(string id)
+ public virtual async Task> GetConversationsAsync(string id)
{
var request = HttpRequest.Get($"{ServiceUrl}v3/teams/{id}/conversations");
var response = await _http.SendAsync>(request, _cancellationToken);
diff --git a/Libraries/Microsoft.Teams.Api/Clients/UserClient.cs b/Libraries/Microsoft.Teams.Api/Clients/UserClient.cs
index caf49aab..ae5a1d3b 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/UserClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/UserClient.cs
@@ -7,7 +7,12 @@ namespace Microsoft.Teams.Api.Clients;
public class UserClient : Client
{
- public UserTokenClient Token { get; }
+ public virtual UserTokenClient Token { get; }
+
+ public UserClient() : base()
+ {
+ Token = new UserTokenClient(_http, _cancellationToken);
+ }
public UserClient(CancellationToken cancellationToken = default) : base(cancellationToken)
{
diff --git a/Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs b/Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs
index cf264d6a..50394b36 100644
--- a/Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs
+++ b/Libraries/Microsoft.Teams.Api/Clients/UserTokenClient.cs
@@ -15,6 +15,11 @@ public class UserTokenClient : Client
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
+ public UserTokenClient() : base()
+ {
+
+ }
+
public UserTokenClient(CancellationToken cancellationToken = default) : base(cancellationToken)
{
@@ -35,7 +40,7 @@ public UserTokenClient(IHttpClientFactory factory, CancellationToken cancellatio
}
- public async Task GetAsync(GetTokenRequest request)
+ public virtual async Task GetAsync(GetTokenRequest request)
{
var query = QueryString.Serialize(request);
var req = HttpRequest.Get($"https://token.botframework.com/api/usertoken/GetToken?{query}");
@@ -43,7 +48,7 @@ public UserTokenClient(IHttpClientFactory factory, CancellationToken cancellatio
return res.Body;
}
- public async Task> GetAadAsync(GetAadTokenRequest request)
+ public virtual async Task> GetAadAsync(GetAadTokenRequest request)
{
var query = QueryString.Serialize(request);
var req = HttpRequest.Post($"https://token.botframework.com/api/usertoken/GetAadTokens?{query}", body: request);
@@ -51,7 +56,7 @@ public UserTokenClient(IHttpClientFactory factory, CancellationToken cancellatio
return res.Body;
}
- public async Task> GetStatusAsync(GetTokenStatusRequest request)
+ public virtual async Task> GetStatusAsync(GetTokenStatusRequest request)
{
var query = QueryString.Serialize(request);
var req = HttpRequest.Get($"https://token.botframework.com/api/usertoken/GetTokenStatus?{query}");
@@ -59,14 +64,14 @@ public UserTokenClient(IHttpClientFactory factory, CancellationToken cancellatio
return res.Body;
}
- public async Task SignOutAsync(SignOutRequest request)
+ public virtual async Task SignOutAsync(SignOutRequest request)
{
var query = QueryString.Serialize(request);
var req = HttpRequest.Delete($"https://token.botframework.com/api/usertoken/SignOut?{query}");
await _http.SendAsync(req, _cancellationToken);
}
- public async Task ExchangeAsync(ExchangeTokenRequest request)
+ public virtual async Task ExchangeAsync(ExchangeTokenRequest request)
{
var query = QueryString.Serialize(new
{
diff --git a/Libraries/Microsoft.Teams.Apps/AppBuilder.cs b/Libraries/Microsoft.Teams.Apps/AppBuilder.cs
index 63e5d6f5..3c4b485b 100644
--- a/Libraries/Microsoft.Teams.Apps/AppBuilder.cs
+++ b/Libraries/Microsoft.Teams.Apps/AppBuilder.cs
@@ -92,9 +92,16 @@ public AppBuilder AddPlugin(Func> @delegate)
return this;
}
+ public AppBuilder AddOAuth(OAuthSettings oauthSettings)
+ {
+ _options.OAuth = oauthSettings;
+ return this;
+ }
+
public AppBuilder AddOAuth(string defaultConnectionName)
{
- _options.OAuth = new OAuthSettings(defaultConnectionName);
+ _options.OAuth ??= new();
+ _options.OAuth.DefaultConnectionName = defaultConnectionName;
return this;
}
diff --git a/Libraries/Microsoft.Teams.Apps/AppOptions.cs b/Libraries/Microsoft.Teams.Apps/AppOptions.cs
index b923afa2..1a08bfac 100644
--- a/Libraries/Microsoft.Teams.Apps/AppOptions.cs
+++ b/Libraries/Microsoft.Teams.Apps/AppOptions.cs
@@ -7,14 +7,49 @@ namespace Microsoft.Teams.Apps;
public class AppOptions
{
+ ///
+ /// The applications optional storage provider that allows
+ /// the application to access shared dependencies.
+ ///
public IServiceProvider? Provider { get; set; }
+
+ ///
+ /// The applications optional ILogger instance.
+ ///
public Common.Logging.ILogger? Logger { get; set; }
+
+ ///
+ /// The applications optional IStorage instance.
+ ///
public Common.Storage.IStorage? Storage { get; set; }
+
+ ///
+ /// When provided, the application will use this IHttpClient instance
+ /// to send all http requests.
+ ///
public Common.Http.IHttpClient? Client { get; set; }
+
+ ///
+ /// When provided, the application will use this IHttpClientFactory to
+ /// initialize a new client whenever needed.
+ ///
public Common.Http.IHttpClientFactory? ClientFactory { get; set; }
+
+ ///
+ /// When provided, the application will use these credentials to resolve tokens it
+ /// uses to make API requests.
+ ///
public Common.Http.IHttpCredentials? Credentials { get; set; }
+
+ ///
+ /// A list of plugins to import into the application.
+ ///
public IList Plugins { get; set; } = [];
- public OAuthSettings OAuth { get; set; } = new OAuthSettings();
+
+ ///
+ /// User OAuth settings for the deferred (User) auth flows.
+ ///
+ public OAuthSettings OAuth { get; set; } = new();
public AppOptions()
{
diff --git a/Libraries/Microsoft.Teams.Apps/AppRouting.cs b/Libraries/Microsoft.Teams.Apps/AppRouting.cs
index add489de..4a60352c 100644
--- a/Libraries/Microsoft.Teams.Apps/AppRouting.cs
+++ b/Libraries/Microsoft.Teams.Apps/AppRouting.cs
@@ -154,7 +154,11 @@ await Events.Emit(
Token = res
}
);
- return new Response(HttpStatusCode.OK);
+
+ return new Response(HttpStatusCode.OK, OAuth.AccountLinkingUrl is null ? null : new
+ {
+ accountLinkingUrl = OAuth.AccountLinkingUrl
+ });
}
catch (HttpException ex)
{
diff --git a/Libraries/Microsoft.Teams.Apps/Contexts/Context.cs b/Libraries/Microsoft.Teams.Apps/Contexts/Context.cs
index 74baa25d..0b07a96e 100644
--- a/Libraries/Microsoft.Teams.Apps/Contexts/Context.cs
+++ b/Libraries/Microsoft.Teams.Apps/Contexts/Context.cs
@@ -112,6 +112,15 @@ public partial interface IContext where TActivity : IActivity
///
public Task