microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v2.0.8

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Libraries/Microsoft.Teams.Plugins/Microsoft.Teams.Plugins.AspNetCore.DevTools/DevToolsPlugin.cs

197lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4using System.Diagnostics.CodeAnalysis;
5using System.Reflection;
6using System.Text;
7
8using Microsoft.AspNetCore.Builder;
9using Microsoft.AspNetCore.Hosting.Server;
10using Microsoft.AspNetCore.Hosting.Server.Features;
11using Microsoft.AspNetCore.Http;
12using Microsoft.AspNetCore.Http.Features;
13using Microsoft.Extensions.DependencyInjection;
14using Microsoft.Extensions.FileProviders;
15using Microsoft.Extensions.Hosting;
16using Microsoft.Teams.Apps;
17using Microsoft.Teams.Apps.Events;
18using Microsoft.Teams.Apps.Plugins;
19using Microsoft.Teams.Common.Logging;
20using Microsoft.Teams.Common.Text;
21using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions;
22using Microsoft.Teams.Plugins.AspNetCore.DevTools.Models;
23
24namespace Microsoft.Teams.Plugins.AspNetCore.DevTools;
25
26[Plugin]
27[Obsolete("DevTools is deprecated and will be removed in a later version. Use Microsoft 365 Agents Playground instead.")]
28public class DevToolsPlugin : IAspNetCorePlugin
29{
30 [AllowNull]
31 [Dependency]
32 public ILogger Logger { get; set; }
33
34 [Dependency("AppId", optional: true)]
35 public string? AppId { get; set; }
36
37 [Dependency("AppName", optional: true)]
38 public string? AppName { get; set; }
39
40 public event EventFunction Events;
41
42 internal MetaData MetaData => new() { Id = AppId, Name = AppName, Pages = _pages };
43 internal readonly WebSocketCollection Sockets = [];
44
45 private readonly ISenderPlugin _sender;
46 private readonly IServiceProvider _services;
47 private readonly IList<Page> _pages = [];
48 private readonly TeamsDevToolsSettings _settings;
49
50 public DevToolsPlugin(AspNetCorePlugin sender, IServiceProvider provider)
51 {
52 _sender = sender;
53 _services = provider;
54 _settings = provider.GetRequiredService<TeamsDevToolsSettings>();
55 }
56
57 public IApplicationBuilder Configure(IApplicationBuilder builder)
58 {
59 // Reject cross-origin WebSocket upgrades. The DevTools UI is always
60 // loaded same-origin, so Origin must equal request Host; absent
61 // Origin is rejected. Headers are inspected directly so this
62 // middleware does not require UseWebSockets to run first.
63 builder.Use(async (context, next) =>
64 {
65 var upgrade = context.Request.Headers["Upgrade"].ToString();
66 if (string.Equals(upgrade, "websocket", StringComparison.OrdinalIgnoreCase))
67 {
68 var origin = context.Request.Headers["Origin"].ToString();
69 var expectedOrigin = $"{context.Request.Scheme}://{context.Request.Host}";
70 if (string.IsNullOrEmpty(origin) ||
71 !string.Equals(origin, expectedOrigin, StringComparison.OrdinalIgnoreCase))
72 {
73 context.Response.StatusCode = StatusCodes.Status403Forbidden;
74 return;
75 }
76 }
77 await next(context);
78 });
79
80 builder.UseWebSockets();
81
82 builder.UseStaticFiles(new StaticFileOptions()
83 {
84 FileProvider = new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "web"),
85 ServeUnknownFileTypes = true,
86 RequestPath = "/devtools"
87 });
88
89 builder.Use(async (context, next) =>
90 {
91 try
92 {
93 await next(context).ConfigureAwait(false);
94 }
95 catch (Exception ex)
96 {
97 Logger.Error(ex, "http error");
98 throw new Exception(ex.Message, innerException: ex);
99 }
100 });
101
102 return builder;
103 }
104
105 public DevToolsPlugin AddPage(Page page)
106 {
107 _pages.Add(page);
108 Logger.Debug($"page '{page.Name}' added at '{page.Url}'");
109 return this;
110 }
111
112 public Task OnInit(App app, CancellationToken cancellationToken = default)
113 {
114 var hostEnvironment = _services.GetService<IHostEnvironment>();
115 var isProduction = hostEnvironment?.IsProduction()
116 ?? string.Equals(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), "Production", StringComparison.OrdinalIgnoreCase);
117 if (isProduction)
118 {
119 throw new InvalidOperationException(
120 "Devtools plugin cannot be used in production environments. " +
121 "Remove the devtools plugin from your app configuration.");
122 }
123
124 foreach (var page in _settings.Pages)
125 {
126 AddPage(page);
127 }
128
129 Logger.Warn(
130 new StringBuilder()
131 .Bold(
132 new StringBuilder()
133 .Yellow("⚠️ Devtools are not secure and should not be used production environments ⚠️")
134 .ToString()
135 )
136 );
137
138 return Task.CompletedTask;
139 }
140
141 public Task OnStart(App app, CancellationToken cancellationToken = default)
142 {
143 var server = _services.GetRequiredService<IServer>();
144 var addresses = server.Features.GetRequiredFeature<IServerAddressesFeature>().Addresses;
145
146 foreach (var address in addresses)
147 {
148 Logger.Info($"Available at {address}/devtools");
149 }
150
151 Logger.Debug("OnStart");
152 return Task.CompletedTask;
153 }
154
155 public Task OnError(App app, IPlugin plugin, ErrorEvent @event, CancellationToken cancellationToken = default)
156 {
157 Logger.Debug("OnError");
158 return Task.CompletedTask;
159 }
160
161 public async Task OnActivity(App app, ISenderPlugin sender, ActivityEvent @event, CancellationToken cancellationToken = default)
162 {
163 Logger.Debug("OnActivity");
164
165 await Sockets.Emit(
166 DevTools.Events.ActivityEvent.Received(
167 @event.Activity,
168 @event.Activity.Conversation
169 ),
170 cancellationToken
171 ).ConfigureAwait(false);
172 }
173
174 public async Task OnActivitySent(App app, ISenderPlugin sender, ActivitySentEvent @event, CancellationToken cancellationToken = default)
175 {
176 Logger.Debug("OnActivitySent");
177
178 await Sockets.Emit(
179 DevTools.Events.ActivityEvent.Sent(
180 @event.Activity,
181 @event.Activity.Conversation
182 ),
183 cancellationToken
184 ).ConfigureAwait(false);
185 }
186
187 public Task OnActivityResponse(App app, ISenderPlugin sender, ActivityResponseEvent @event, CancellationToken cancellationToken = default)
188 {
189 Logger.Debug("OnActivityResponse");
190 return Task.CompletedTask;
191 }
192
193 public Task<Response> Do(ActivityEvent @event, CancellationToken cancellationToken = default)
194 {
195 return _sender.Do(@event, cancellationToken);
196 }
197}