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
11 changes: 9 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {
"installBicep": true,
"installUsingPython": true,
"version": "latest",
"bicepVersion": "latest"
},
"ghcr.io/dotnet/aspire-devcontainer-feature/dotnetaspire:1": {
"version": "9.0"
"version": "latest"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": true,
"azureDnsAutoDetection": true,
"installDockerBuildx": true,
"installDockerComposeSwitch": true,
"version": "latest",
"dockerDashComposeVersion": "v2"
}
}

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# dotenv files
.env
*.env

# local app settings files
appsettings.Local.json
Expand Down
1 change: 1 addition & 0 deletions Libraries/Microsoft.Teams.Api/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class Account

[JsonPropertyName("properties")]
[JsonPropertyOrder(5)]
[JsonExtensionData]
public Dictionary<string, object>? Properties { get; set; }
}

Expand Down
27 changes: 27 additions & 0 deletions Libraries/Microsoft.Teams.Api/Auth/AgenticIdentity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Microsoft.Teams.Api.Auth;

public class AgenticIdentity
{
public string? AgenticAppId { get; set; }
public string? AgenticUserId { get; set; }
public string? AgenticAppBlueprintId { get; set; }
public string? TenantId { get; set; }

public static AgenticIdentity? FromProperties(IDictionary<string, object> properties)
{
if (properties == null)
{
return null;
}

properties.TryGetValue("agenticAppId", out object? appIdObj);
properties.TryGetValue("agenticUserId", out object? userIdObj);
properties.TryGetValue("agenticAppBlueprintId", out object? bluePrintObj);
return new AgenticIdentity
{
AgenticAppId = appIdObj?.ToString(),
AgenticUserId = userIdObj?.ToString(),
AgenticAppBlueprintId = bluePrintObj?.ToString()
};
}
}
50 changes: 19 additions & 31 deletions Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
Original file line number Diff line number Diff line change
@@ -1,46 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Api.Auth;

public class ClientCredentials : IHttpCredentials
public class ClientCredentials(IAuthorizationHeaderProvider authorizationHeaderProvider) : IHttpCredentials
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string? TenantId { get; set; }

public ClientCredentials(string clientId, string clientSecret)
public async Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, AgenticIdentity agenticIdentity, CancellationToken cancellationToken = default)
{
ClientId = clientId;
ClientSecret = clientSecret;
}

public ClientCredentials(string clientId, string clientSecret, string? tenantId)
{
ClientId = clientId;
ClientSecret = clientSecret;
TenantId = tenantId;
}
AuthorizationHeaderProviderOptions options = new();

public async Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, CancellationToken cancellationToken = default)
{
var tenantId = TenantId ?? "botframework.com";
var request = HttpRequest.Post(
$"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"
);
string tokenResult;

if (agenticIdentity is not null)
{
options.WithAgentUserIdentity(agenticIdentity.AgenticAppId!, Guid.Parse(agenticIdentity.AgenticUserId!));
tokenResult = await authorizationHeaderProvider.CreateAuthorizationHeaderAsync(scopes, options, null, cancellationToken);
}
else
{
tokenResult = await authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(scopes[0], options, cancellationToken);
}

request.Headers.Add("Content-Type", ["application/x-www-form-urlencoded"]);
request.Body = new Dictionary<string, string>()
return new TokenResponse
{
{ "grant_type", "client_credentials" },
{ "client_id", ClientId },
{ "client_secret", ClientSecret },
{ "scope", string.Join(",", scopes) }
AccessToken = tokenResult.Substring("Bearer ".Length),
TokenType = "Bearer",
};

var res = await client.SendAsync<TokenResponse>(request, cancellationToken);
return res.Body;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Teams.Common.Http;
using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Api.Auth;

public interface IHttpCredentials
{
public Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, CancellationToken cancellationToken = default);
public Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, AgenticIdentity agenticIdentity, CancellationToken cancellationToken = default);
}
13 changes: 10 additions & 3 deletions Libraries/Microsoft.Teams.Api/Auth/TokenCredentials.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.


using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Api.Auth;

public delegate Task<ITokenResponse> TokenFactory(string? tenantId, params string[] scopes);
public delegate Task<ITokenResponse> TokenFactory(string? tenantId, AgenticIdentity agenticIdentity, params string[] scopes);


/// <summary>
/// a factory for adding a token to http requests
/// </summary>
public delegate object? HttpTokenFactory();

public class TokenCredentials : IHttpCredentials
{
Expand All @@ -26,8 +33,8 @@ public TokenCredentials(string clientId, string tenantId, TokenFactory token)
Token = token;
}

public async Task<ITokenResponse> Resolve(IHttpClient _client, string[] scopes, CancellationToken cancellationToken = default)
public async Task<ITokenResponse> Resolve(IHttpClient _client, string[] scopes, AgenticIdentity agenticIdentity, CancellationToken cancellationToken = default)
{
return await Token(TenantId, scopes);
return await Token(TenantId, agenticIdentity, scopes);
}
}
9 changes: 7 additions & 2 deletions Libraries/Microsoft.Teams.Api/Auth/TokenResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@

using System.Text.Json.Serialization;

using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Api.Auth;

public interface ITokenResponse
{
public string TokenType { get; }
public int? ExpiresIn { get; }
public string AccessToken { get; }
}

public class TokenResponse : ITokenResponse
{
[JsonPropertyName("token_type")]
Expand Down
26 changes: 19 additions & 7 deletions Libraries/Microsoft.Teams.Api/Clients/ActivityClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
using System.Text.Json;

using Microsoft.Teams.Api.Activities;
using Microsoft.Teams.Api.Auth;
using Microsoft.Teams.Common.Http;

using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;

namespace Microsoft.Teams.Api.Clients;

public class ActivityClient : Client
Expand All @@ -17,7 +20,7 @@ public ActivityClient(string serviceUrl, CancellationToken cancellationToken = d
ServiceUrl = serviceUrl;
}

public ActivityClient(string serviceUrl, IHttpClient client, CancellationToken cancellationToken = default) : base(client, cancellationToken)
public ActivityClient(string serviceUrl, IHttpClient client, string scope, CancellationToken cancellationToken = default) : base(client, scope, cancellationToken)
{
ServiceUrl = serviceUrl;
}
Expand All @@ -41,8 +44,12 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio
}

var req = HttpRequest.Post(url, body: activity);

var res = await _http.SendAsync(req, _cancellationToken);

AgenticIdentity? aid = activity.From?.Properties != null
? AgenticIdentity.FromProperties(activity.From.Properties)
: null;

var res = await _http.SendAsync(req, aid, _cancellationToken);

if (res.Body == string.Empty) return null;

Expand All @@ -60,7 +67,10 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio

var req = HttpRequest.Put(url, body: activity);

var res = await _http.SendAsync(req, _cancellationToken);
AgenticIdentity? aid = activity.From?.Properties != null
? AgenticIdentity.FromProperties(activity.From.Properties)
: null;
var res = await _http.SendAsync(req, aid, _cancellationToken);

if (res.Body == string.Empty) return null;

Expand All @@ -79,8 +89,10 @@ public ActivityClient(string serviceUrl, IHttpClientFactory factory, Cancellatio
}

var req = HttpRequest.Post(url, body: activity);

var res = await _http.SendAsync(req, _cancellationToken);
AgenticIdentity? aid = activity.From?.Properties != null
? AgenticIdentity.FromProperties(activity.From.Properties)
: null;
var res = await _http.SendAsync(req, aid, _cancellationToken);

if (res.Body == string.Empty) return null;

Expand All @@ -98,6 +110,6 @@ public async Task DeleteAsync(string conversationId, string id, bool isTargeted

var req = HttpRequest.Delete(url);

await _http.SendAsync(req, _cancellationToken);
await _http.SendAsync(req, null, _cancellationToken);
}
}
69 changes: 36 additions & 33 deletions Libraries/Microsoft.Teams.Api/Clients/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using Microsoft.Teams.Common.Http;
// using IHttpClientFactory = Microsoft.Teams.Common.Http.IHttpClientFactory;

namespace Microsoft.Teams.Api.Clients;

Expand All @@ -14,46 +15,48 @@ public class ApiClient : Client
public virtual TeamClient Teams { get; }
public virtual MeetingClient Meetings { get; }

public ApiClient(string serviceUrl, CancellationToken cancellationToken = default) : base(cancellationToken)
{
ServiceUrl = serviceUrl;
Bots = new BotClient(_http, cancellationToken);
Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
Users = new UserClient(_http, cancellationToken);
Teams = new TeamClient(serviceUrl, _http, cancellationToken);
Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
}

public ApiClient(string serviceUrl, IHttpClient client, CancellationToken cancellationToken = default) : base(client, cancellationToken)
{
ServiceUrl = serviceUrl;
Bots = new BotClient(_http, cancellationToken);
Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
Users = new UserClient(_http, cancellationToken);
Teams = new TeamClient(serviceUrl, _http, cancellationToken);
Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
}

public ApiClient(string serviceUrl, IHttpClientOptions options, CancellationToken cancellationToken = default) : base(options, cancellationToken)
{
ServiceUrl = serviceUrl;
Bots = new BotClient(_http, cancellationToken);
Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
Users = new UserClient(_http, cancellationToken);
Teams = new TeamClient(serviceUrl, _http, cancellationToken);
Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
}
//public ApiClient(string serviceUrl, CancellationToken cancellationToken = default) : base(cancellationToken)
//{
// ServiceUrl = serviceUrl;
// Bots = new BotClient(_http, cancellationToken);
// Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
// Users = new UserClient(_http, cancellationToken);
// Teams = new TeamClient(serviceUrl, _http, cancellationToken);
// Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
//}

public ApiClient(string serviceUrl, IHttpClientFactory factory, CancellationToken cancellationToken = default) : base(factory, cancellationToken)
public ApiClient(string serviceUrl, IHttpClient client, string scope, CancellationToken cancellationToken = default) : base(client, scope, cancellationToken)
{
ServiceUrl = serviceUrl;
Bots = new BotClient(_http, cancellationToken);
Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
Users = new UserClient(_http, cancellationToken);
Teams = new TeamClient(serviceUrl, _http, cancellationToken);
Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
Bots = new BotClient(_http, scope, cancellationToken);
Conversations = new ConversationClient(serviceUrl, _http, scope, cancellationToken);
Users = new UserClient(_http, scope, cancellationToken);
Teams = new TeamClient(serviceUrl, _http, scope, cancellationToken);
Meetings = new MeetingClient(serviceUrl, _http, scope, cancellationToken);
}

//public ApiClient(string serviceUrl, IHttpClientOptions options, CancellationToken cancellationToken = default) : base(options, cancellationToken)
//{
// ServiceUrl = serviceUrl;
// Bots = new BotClient(_http, cancellationToken);
// Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
// Users = new UserClient(_http, cancellationToken);
// Teams = new TeamClient(serviceUrl, _http, cancellationToken);
// Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
//}

//public ApiClient(string serviceUrl, IHttpClientFactory factory, CancellationToken cancellationToken = default) : base(factory, cancellationToken)
//{
// ServiceUrl = serviceUrl;
// Bots = new BotClient(_http, cancellationToken);
// Conversations = new ConversationClient(serviceUrl, _http, cancellationToken);
// Users = new UserClient(_http, cancellationToken);
// Teams = new TeamClient(serviceUrl, _http, cancellationToken);
// Meetings = new MeetingClient(serviceUrl, _http, cancellationToken);
//}

public ApiClient(ApiClient client) : base()
{
ServiceUrl = client.ServiceUrl;
Expand Down
Loading