Skip to content

Commit c0c4659

Browse files
authored
b/290701797 Embed default app protocols (#1059)
* Include default app protocols (Chrome, MySQL Shell) * Embed IAPC files in assembly so that they are immutable
1 parent 8024c33 commit c0c4659

File tree

9 files changed

+222
-23
lines changed

9 files changed

+222
-23
lines changed

sources/Google.Solutions.IapDesktop.Core.Test/ClientModel/Protocol/TestAppProtocolConfigurationFile.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ namespace Google.Solutions.IapDesktop.Core.Test.ClientModel.Protocol
3333
public class TestAppProtocolConfigurationFile
3434
{
3535
//---------------------------------------------------------------------
36-
// FromJson.
36+
// ReadJson.
3737
//---------------------------------------------------------------------
3838

3939
[Test]
40-
public void WhenJsonIsNullOrEmptyOrMalformed_ThenFromJsonThrowsException(
40+
public void WhenJsonIsNullOrEmptyOrMalformed_ThenReadJsonThrowsException(
4141
[Values(null, " ", "{,", "{}")] string json)
4242
{
4343
Assert.Throws<InvalidAppProtocolException>(
4444
() => AppProtocolConfigurationFile.ReadJson(json));
4545
}
4646

4747
[Test]
48-
public void WhenJsonContainsNoVersion_ThenFromJsonThrowsException()
48+
public void WhenJsonContainsNoVersion_ThenReadJsonThrowsException()
4949
{
5050
var json = @"
5151
{
@@ -59,7 +59,7 @@ public void WhenJsonContainsNoVersion_ThenFromJsonThrowsException()
5959
}
6060

6161
[Test]
62-
public void WhenJsonContainsUnsupportedVersion_ThenFromJsonThrowsException()
62+
public void WhenJsonContainsUnsupportedVersion_ThenReadJsonThrowsException()
6363
{
6464
var json = @"
6565
{
@@ -74,7 +74,7 @@ public void WhenJsonContainsUnsupportedVersion_ThenFromJsonThrowsException()
7474
}
7575

7676
[Test]
77-
public void WhenJsonIsValid_ThenFromJsonReturnsProtocol()
77+
public void WhenJsonIsValid_ThenReadJsonReturnsProtocol()
7878
{
7979
var json = @"
8080
{
@@ -97,11 +97,29 @@ public void WhenJsonIsValid_ThenFromJsonReturnsProtocol()
9797
}
9898

9999
//---------------------------------------------------------------------
100-
// FromJson.
100+
// ReadStreamAsync.
101101
//---------------------------------------------------------------------
102102

103103
[Test]
104-
public void WhenFileNotFound_ThenFromFileThrowsException()
104+
public void WhenStreamDataEmptyOrMalformed_ThenReadStreamThrowsException(
105+
[Values("", " ", "{,", "{}")] string json)
106+
{
107+
var filePath = Path.GetTempFileName();
108+
File.WriteAllText(filePath, json);
109+
110+
using (var stream = File.OpenRead(filePath))
111+
{
112+
ExceptionAssert.ThrowsAggregateException<InvalidAppProtocolException>(
113+
() => AppProtocolConfigurationFile.ReadStreamAsync(stream).Wait());
114+
}
115+
}
116+
117+
//---------------------------------------------------------------------
118+
// ReadFileAsync.
119+
//---------------------------------------------------------------------
120+
121+
[Test]
122+
public void WhenFileNotFound_ThenReadFileThrowsException()
105123
{
106124
ExceptionAssert.ThrowsAggregateException<FileNotFoundException>(
107125
() => AppProtocolConfigurationFile.ReadFileAsync("doesnotexist.json").Wait());
@@ -110,7 +128,7 @@ public void WhenFileNotFound_ThenFromFileThrowsException()
110128
}
111129

112130
[Test]
113-
public void WhenFileEmptyOrMalformed_ThenFromFileThrowsException(
131+
public void WhenFileEmptyOrMalformed_ThenReadFileThrowsException(
114132
[Values("", " ", "{,", "{}")] string json)
115133
{
116134
var filePath = Path.GetTempFileName();

sources/Google.Solutions.IapDesktop.Core/ClientModel/Protocol/AppProtocolConfigurationFile.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public static class AppProtocolConfigurationFile
7878
// Deserialization.
7979
//---------------------------------------------------------------------
8080

81-
private static AppProtocol FromSection(MainSection section)
81+
private static AppProtocol ReadSection(MainSection section)
8282
{
8383
if (section == null)
8484
{
@@ -105,7 +105,7 @@ public static AppProtocol ReadJson(string json)
105105
{
106106
try
107107
{
108-
return FromSection(NewtonsoftJsonSerializer
108+
return ReadSection(NewtonsoftJsonSerializer
109109
.Instance
110110
.Deserialize<MainSection>(json));
111111
}
@@ -116,6 +116,29 @@ public static AppProtocol ReadJson(string json)
116116
}
117117
}
118118

119+
public static Task<AppProtocol> ReadStreamAsync(Stream stream)
120+
{
121+
return Task.Run(() =>
122+
{
123+
try
124+
{
125+
return ReadSection(NewtonsoftJsonSerializer
126+
.Instance
127+
.Deserialize<MainSection>(stream));
128+
}
129+
catch (InvalidAppProtocolException e)
130+
{
131+
throw new InvalidAppProtocolException(
132+
$"The protocol configuration contains format errors", e);
133+
}
134+
catch (JsonException e)
135+
{
136+
throw new InvalidAppProtocolException(
137+
$"The protocol configuration contains format errors", e);
138+
}
139+
});
140+
}
141+
119142
public static Task<AppProtocol> ReadFileAsync(string path)
120143
{
121144
return Task.Run(() =>
@@ -124,7 +147,7 @@ public static Task<AppProtocol> ReadFileAsync(string path)
124147
{
125148
using (var stream = File.OpenRead(path))
126149
{
127-
return FromSection(NewtonsoftJsonSerializer
150+
return ReadSection(NewtonsoftJsonSerializer
128151
.Instance
129152
.Deserialize<MainSection>(stream));
130153
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// Copyright 2023 Google LLC
3+
//
4+
// Licensed to the Apache Software Foundation (ASF) under one
5+
// or more contributor license agreements. See the NOTICE file
6+
// distributed with this work for additional information
7+
// regarding copyright ownership. The ASF licenses this file
8+
// to you under the Apache License, Version 2.0 (the
9+
// "License"); you may not use this file except in compliance
10+
// with the License. You may obtain a copy of the License at
11+
//
12+
// http://www.apache.org/licenses/LICENSE-2.0
13+
//
14+
// Unless required by applicable law or agreed to in writing,
15+
// software distributed under the License is distributed on an
16+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
// KIND, either express or implied. See the License for the
18+
// specific language governing permissions and limitations
19+
// under the License.
20+
//
21+
using Google.Solutions.IapDesktop.Core.ClientModel.Protocol;
22+
using NUnit.Framework;
23+
using System.IO;
24+
using System.Threading.Tasks;
25+
26+
namespace Google.Solutions.IapDesktop.Extensions.Session.Test
27+
{
28+
[TestFixture]
29+
public class TestInitializeSessionExtension
30+
{
31+
//---------------------------------------------------------------------
32+
// LoadAndRegisterDefaultAppProtocols.
33+
//---------------------------------------------------------------------
34+
35+
[Test]
36+
public async Task LoadAndRegisterDefaultAppProtocols()
37+
{
38+
var registry = new ProtocolRegistry();
39+
await InitializeSessionExtension
40+
.LoadAndRegisterDefaultAppProtocolsAsync(registry)
41+
.ConfigureAwait(false);
42+
43+
CollectionAssert.IsNotEmpty(registry.Protocols);
44+
}
45+
46+
//---------------------------------------------------------------------
47+
// LoadAndRegisterCustomAppProtocols.
48+
//---------------------------------------------------------------------
49+
50+
[Test]
51+
public async Task WhenDirectoryNotFound_ThenLoadAndRegisterCustomAppProtocolsReturns()
52+
{
53+
var registry = new ProtocolRegistry();
54+
await InitializeSessionExtension
55+
.LoadAndRegisterCustomAppProtocolsAsync("b:\\notfound", registry)
56+
.ConfigureAwait(false);
57+
58+
CollectionAssert.IsEmpty(registry.Protocols);
59+
}
60+
61+
[Test]
62+
public async Task WhenDirectoryContainsIapcFiles_ThenLoadAndRegisterCustomAppProtocolsLoadsFiles()
63+
{
64+
var dir = Directory.CreateDirectory(
65+
Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
66+
67+
File.WriteAllText(
68+
$"{dir.FullName}\\sample.iapc",
69+
@"
70+
{
71+
'version': 1,
72+
'name': 'protocol-1',
73+
'condition': 'isWindows()',
74+
'remotePort': 8080,
75+
'client': {
76+
'executable': 'cmd'
77+
}
78+
}");
79+
80+
var registry = new ProtocolRegistry();
81+
await InitializeSessionExtension
82+
.LoadAndRegisterCustomAppProtocolsAsync(dir.FullName, registry)
83+
.ConfigureAwait(false);
84+
85+
CollectionAssert.IsNotEmpty(registry.Protocols);
86+
}
87+
}
88+
}

sources/Google.Solutions.IapDesktop.Extensions.Session/Google.Solutions.IapDesktop.Extensions.Session.csproj

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
copy /Y $(MSBuildProjectDirectory)\$(OutputPath)\$(TargetFramework)\*oslogin* $(SolutionDir)\Google.Solutions.IapDesktop\$(OutputPath)\$(TargetFramework)\
1616
copy /Y $(MSBuildProjectDirectory)\$(OutputPath)\$(TargetFramework)\vtnetcore.* $(SolutionDir)\Google.Solutions.IapDesktop\$(OutputPath)\$(TargetFramework)\
1717
copy /Y $(MSBuildProjectDirectory)\$(OutputPath)\$(TargetFramework)\*tsc* $(SolutionDir)\Google.Solutions.IapDesktop\$(OutputPath)\$(TargetFramework)\
18-
xcopy /S /Y /I $(MSBuildProjectDirectory)\Properties\Config $(SolutionDir)\Google.Solutions.IapDesktop\$(OutputPath)\$(TargetFramework)\Config
1918
</PostBuildEvent>
2019
</PropertyGroup>
2120
<ItemGroup>
@@ -227,4 +226,14 @@
227226
<ItemGroup>
228227
<Compile Remove="obj\x86\Release\.NETFramework,Version=v4.6.2.AssemblyAttributes.cs" />
229228
</ItemGroup>
229+
<ItemGroup>
230+
<None Remove="Properties\DefaultAppProtocols\chrome-80.iapc" />
231+
<None Remove="Properties\DefaultAppProtocols\chrome-8080.iapc" />
232+
<None Remove="Properties\DefaultAppProtocols\mysql.iapc" />
233+
</ItemGroup>
234+
<ItemGroup>
235+
<EmbeddedResource Include="Properties\DefaultAppProtocols\chrome-80.iapc" />
236+
<EmbeddedResource Include="Properties\DefaultAppProtocols\chrome-8080.iapc" />
237+
<EmbeddedResource Include="Properties\DefaultAppProtocols\mysql.iapc" />
238+
</ItemGroup>
230239
</Project>

sources/Google.Solutions.IapDesktop.Extensions.Session/InitializeSessionExtension.cs

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
using Google.Solutions.IapDesktop.Application.Windows.Dialog;
3131
using Google.Solutions.IapDesktop.Core.ClientModel.Protocol;
3232
using Google.Solutions.IapDesktop.Core.ClientModel.Traits;
33-
using Google.Solutions.IapDesktop.Core.ClientModel.Transport.Policies;
3433
using Google.Solutions.IapDesktop.Core.ObjectModel;
3534
using Google.Solutions.IapDesktop.Core.ProjectModel;
3635
using Google.Solutions.IapDesktop.Extensions.Session.Properties;
@@ -45,13 +44,11 @@
4544
using Google.Solutions.IapDesktop.Extensions.Session.ToolWindows.SshKeys;
4645
using Google.Solutions.IapDesktop.Extensions.Session.ToolWindows.Tunnels;
4746
using Google.Solutions.Mvvm.Binding.Commands;
48-
using Google.Solutions.Platform.Dispatch;
4947
using System;
5048
using System.Collections.Specialized;
5149
using System.Diagnostics;
5250
using System.IO;
5351
using System.Linq;
54-
using System.Reflection;
5552
using System.Threading.Tasks;
5653
using System.Windows.Forms;
5754

@@ -116,13 +113,41 @@ await this.serviceProvider.GetService<ICreateCredentialsWorkflow>()
116113
}
117114
}
118115

119-
private async Task LoadAndRegisterAppProtocolsAsync( // TODO: Test
120-
IWin32Window window,
116+
/// <summary>
117+
/// Load the default app protocols embedded into the assembly.
118+
/// </summary>
119+
internal static async Task LoadAndRegisterDefaultAppProtocolsAsync(
120+
ProtocolRegistry protocolRegistry)
121+
{
122+
var assembly = typeof(InitializeSessionExtension).Assembly;
123+
var loadTasks = assembly
124+
.GetManifestResourceNames()
125+
.Where(s => s.EndsWith(AppProtocolConfigurationFile.FileExtension))
126+
.Select(async resourceName =>
127+
{
128+
using (var stream = assembly.GetManifestResourceStream(resourceName))
129+
{
130+
var protocol = await AppProtocolConfigurationFile
131+
.ReadStreamAsync(stream)
132+
.ConfigureAwait(false);
133+
134+
protocolRegistry.RegisterProtocol(protocol);
135+
}
136+
})
137+
.ToList();
138+
139+
await Task
140+
.WhenAll(loadTasks)
141+
.ConfigureAwait(false);
142+
}
143+
144+
/// <summary>
145+
/// Load user-defined app protocols from the file system.
146+
/// </summary>
147+
internal static async Task LoadAndRegisterCustomAppProtocolsAsync(
148+
string protocolsPath,
121149
ProtocolRegistry protocolRegistry)
122150
{
123-
var protocolsPath = Path.Combine(
124-
this.serviceProvider.GetService<IInstall>().BaseDirectory,
125-
"Config");
126151
if (!Directory.Exists(protocolsPath))
127152
{
128153
return;
@@ -157,11 +182,28 @@ private async Task LoadAndRegisterAppProtocolsAsync( // TODO: Test
157182
}
158183
})
159184
.ToList();
160-
185+
186+
await Task
187+
.WhenAll(loadTasks)
188+
.ConfigureAwait(false);
189+
}
190+
191+
private async Task LoadAndRegisterAppProtocolsAsync(
192+
IWin32Window window,
193+
ProtocolRegistry protocolRegistry)
194+
{
161195
try
162196
{
197+
var protocolsPath = Path.Combine(
198+
this.serviceProvider.GetService<IInstall>().BaseDirectory,
199+
"Config");
200+
163201
await Task
164-
.WhenAll(loadTasks)
202+
.WhenAll(
203+
LoadAndRegisterDefaultAppProtocolsAsync(protocolRegistry),
204+
LoadAndRegisterCustomAppProtocolsAsync(
205+
protocolsPath,
206+
protocolRegistry))
165207
.ConfigureAwait(true); // Back to UI thread (for exception dialog).
166208
}
167209
catch (Exception e)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"version": 1,
3+
"name": "Chrome (port 80)",
4+
"condition": "isInstance()",
5+
"remotePort": "80",
6+
"client": {
7+
//
8+
// Force Chrome to launch a new process (as opposed to passing the URL
9+
// to one of its existing processes) so that IAP Desktop knows when to
10+
// close the tunnel again.
11+
//
12+
// Use guest mode to prevent any browsing history or cookies from bleeding
13+
// to or from the IAP-protected resource.
14+
//
15+
"executable": "chrome.exe",
16+
"arguments": "--user-data-dir=%temp%\\chrome-guest-{port} --guest \"http://{host}:{port}/"
17+
}
18+
}

sources/Google.Solutions.IapDesktop.Extensions.Session/ToolWindows/App/AppCommands.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ public IEnumerable<IContextCommand<IProjectModelNode>> ConnectWithAppCommands
8080
foreach (var protocol in this.protocolRegistry
8181
.Protocols
8282
.OfType<AppProtocol>()
83-
.Where(p => p.Client != null))
83+
.Where(p => p.Client != null)
84+
.OrderBy(p => p.Name))
8485
{
8586
var factory = new AppProtocolContextFactory(
8687
protocol,

0 commit comments

Comments
 (0)