Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6b3851d

Browse files
authored
feat: add check for coder:// URI authority section (#97)
Fixes#52Checks for the authority string, i.e. `coder.example.com` in `coder://coder.example.com/v0/open/...` links matches the HTTP(S) URL we are signed into. This ensures that the names we use are properly scoped and links generated on one Coder deployment won't accidentally open workspaces on another.
1 parenta6f7bb6 commit6b3851d

File tree

2 files changed

+124
-13
lines changed

2 files changed

+124
-13
lines changed

‎App/Services/UriHandler.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public class UriHandler(
2020
ILogger<UriHandler>logger,
2121
IRpcControllerrpcController,
2222
IUserNotifieruserNotifier,
23-
IRdpConnectorrdpConnector):IUriHandler
23+
IRdpConnectorrdpConnector,
24+
ICredentialManagercredentialManager):IUriHandler
2425
{
2526
privateconststringOpenWorkspacePrefix="/v0/open/ws/";
2627

@@ -64,11 +65,13 @@ private async Task HandleUriThrowingErrors(Uri uri, CancellationToken ct = defau
6465
publicasyncTaskHandleOpenWorkspaceApp(Uriuri,CancellationTokenct=default)
6566
{
6667
conststringerrTitle="Open Workspace Application Error";
68+
CheckAuthority(uri,errTitle);
69+
6770
varsubpath=uri.AbsolutePath[OpenWorkspacePrefix.Length..];
6871
varcomponents=subpath.Split("/");
6972
if(components.Length!=4||components[1]!="agent")
7073
{
71-
logger.LogWarning("unsupported open workspace app format in URI {path}",uri.AbsolutePath);
74+
logger.LogWarning("unsupported open workspace app format in URI'{path}'",uri.AbsolutePath);
7275
thrownewUriException(errTitle,$"Failed to open '{uri.AbsolutePath}' because the format is unsupported.");
7376
}
7477

@@ -120,6 +123,36 @@ public async Task HandleOpenWorkspaceApp(Uri uri, CancellationToken ct = default
120123
awaitOpenRDP(agent.Fqdn.First(),uri.Query,ct);
121124
}
122125

126+
privatevoidCheckAuthority(Uriuri,stringerrTitle)
127+
{
128+
if(string.IsNullOrEmpty(uri.Authority))
129+
{
130+
logger.LogWarning("cannot open workspace app without a URI authority on path '{path}'",uri.AbsolutePath);
131+
thrownewUriException(errTitle,
132+
$"Failed to open '{uri.AbsolutePath}' because no Coder server was given in the URI");
133+
}
134+
135+
varcredentialModel=credentialManager.GetCachedCredentials();
136+
if(credentialModel.State!=CredentialState.Valid)
137+
{
138+
logger.LogWarning("cannot open workspace app because credentials are '{state}'",credentialModel.State);
139+
thrownewUriException(errTitle,
140+
$"Failed to open '{uri.AbsolutePath}' because you are not signed in.");
141+
}
142+
143+
// here we assume that the URL is non-null since the credentials are marked valid. If not it's an internal error
144+
// and the App will handle catching the exception and logging it.
145+
varcoderUri=credentialModel.CoderUrl!;
146+
if(uri.Authority!=coderUri.Authority)
147+
{
148+
logger.LogWarning(
149+
"cannot open workspace app because it was for '{uri_authority}', be we are signed into '{signed_in_authority}'",
150+
uri.Authority,coderUri.Authority);
151+
thrownewUriException(errTitle,
152+
$"Failed to open workspace app because it was for '{uri.Authority}', be we are signed into '{coderUri.Authority}'");
153+
}
154+
}
155+
123156
publicasyncTaskOpenRDP(stringdomainName,stringqueryString,CancellationTokenct=default)
124157
{
125158
conststringerrTitle="Workspace Remote Desktop Error";

‎Tests.App/Services/UriHandlerTest.cs

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,23 @@ public void SetupMocksAndUriHandler()
2323
_mUserNotifier=newMock<IUserNotifier>(MockBehavior.Strict);
2424
_mRdpConnector=newMock<IRdpConnector>(MockBehavior.Strict);
2525
_mRpcController=newMock<IRpcController>(MockBehavior.Strict);
26+
_mCredentialManager=newMock<ICredentialManager>(MockBehavior.Strict);
2627

27-
uriHandler=newUriHandler(logger,_mRpcController.Object,_mUserNotifier.Object,_mRdpConnector.Object);
28+
uriHandler=newUriHandler(logger,
29+
_mRpcController.Object,
30+
_mUserNotifier.Object,
31+
_mRdpConnector.Object,
32+
_mCredentialManager.Object);
2833
}
2934

3035
privateMock<IUserNotifier>_mUserNotifier;
3136
privateMock<IRdpConnector>_mRdpConnector;
3237
privateMock<IRpcController>_mRpcController;
38+
privateMock<ICredentialManager>_mCredentialManager;
3339
privateUriHandleruriHandler;// Unit under test.
3440

3541
[SetUp]
36-
publicvoidAgentAndWorkspaceFixtures()
42+
publicvoidAgentWorkspaceAndCredentialFixtures()
3743
{
3844
agent11=newAgent();
3945
agent11.Fqdn.Add("workspace1.coder");
@@ -54,94 +60,116 @@ public void AgentAndWorkspaceFixtures()
5460
Workspaces=[workspace1],
5561
Agents=[agent11],
5662
};
63+
64+
credentialModel1=newCredentialModel
65+
{
66+
State=CredentialState.Valid,
67+
CoderUrl=newUri("https://coder.test"),
68+
};
5769
}
5870

5971
privateAgentagent11;
6072
privateWorkspaceworkspace1;
6173
privateRpcModelmodelWithWorkspace1;
74+
privateCredentialModelcredentialModel1;
6275

6376
[Test(Description="Open RDP with username & password")]
6477
[CancelAfter(30_000)]
6578
publicasyncTaskMainline(CancellationTokenct)
6679
{
67-
varinput=newUri("coder:/v0/open/ws/workspace1/agent/agent11/rdp?username=testy&password=sesame");
80+
varinput=newUri(
81+
"coder://coder.test/v0/open/ws/workspace1/agent/agent11/rdp?username=testy&password=sesame");
6882

83+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
6984
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
7085
varexpectedCred=newRdpCredentials("testy","sesame");
7186
_=_mRdpConnector.Setup(m=>m.WriteCredentials(agent11.Fqdn[0],expectedCred));
7287
_=_mRdpConnector.Setup(m=>m.Connect(agent11.Fqdn[0],IRdpConnector.DefaultPort,ct))
7388
.Returns(Task.CompletedTask);
7489
awaituriHandler.HandleUri(input,ct);
90+
_mRdpConnector.Verify(m=>m.WriteCredentials(It.IsAny<string>(),It.IsAny<RdpCredentials>()));
91+
_mRdpConnector.Verify(m=>m.Connect(It.IsAny<string>(),It.IsAny<int>(),ct),Times.Once);
7592
}
7693

7794
[Test(Description="Open RDP with no credentials")]
7895
[CancelAfter(30_000)]
7996
publicasyncTaskNoCredentials(CancellationTokenct)
8097
{
81-
varinput=newUri("coder:/v0/open/ws/workspace1/agent/agent11/rdp");
98+
varinput=newUri("coder://coder.test/v0/open/ws/workspace1/agent/agent11/rdp");
8299

100+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
83101
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
84102
_=_mRdpConnector.Setup(m=>m.Connect(agent11.Fqdn[0],IRdpConnector.DefaultPort,ct))
85103
.Returns(Task.CompletedTask);
86104
awaituriHandler.HandleUri(input,ct);
105+
_mRdpConnector.Verify(m=>m.Connect(It.IsAny<string>(),It.IsAny<int>(),ct),Times.Once);
87106
}
88107

89108
[Test(Description="Unknown app slug")]
90109
[CancelAfter(30_000)]
91110
publicasyncTaskUnknownApp(CancellationTokenct)
92111
{
93-
varinput=newUri("coder:/v0/open/ws/workspace1/agent/agent11/someapp");
112+
varinput=newUri("coder://coder.test/v0/open/ws/workspace1/agent/agent11/someapp");
94113

114+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
95115
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
96116
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex("someapp"),ct))
97117
.Returns(Task.CompletedTask);
98118
awaituriHandler.HandleUri(input,ct);
119+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
99120
}
100121

101122
[Test(Description="Unknown agent name")]
102123
[CancelAfter(30_000)]
103124
publicasyncTaskUnknownAgent(CancellationTokenct)
104125
{
105-
varinput=newUri("coder:/v0/open/ws/workspace1/agent/wrongagent/rdp");
126+
varinput=newUri("coder://coder.test/v0/open/ws/workspace1/agent/wrongagent/rdp");
106127

128+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
107129
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
108130
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex("wrongagent"),ct))
109131
.Returns(Task.CompletedTask);
110132
awaituriHandler.HandleUri(input,ct);
133+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
111134
}
112135

113136
[Test(Description="Unknown workspace name")]
114137
[CancelAfter(30_000)]
115138
publicasyncTaskUnknownWorkspace(CancellationTokenct)
116139
{
117-
varinput=newUri("coder:/v0/open/ws/wrongworkspace/agent/agent11/rdp");
140+
varinput=newUri("coder://coder.test/v0/open/ws/wrongworkspace/agent/agent11/rdp");
118141

142+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
119143
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
120144
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex("wrongworkspace"),ct))
121145
.Returns(Task.CompletedTask);
122146
awaituriHandler.HandleUri(input,ct);
147+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
123148
}
124149

125150
[Test(Description="Malformed Query String")]
126151
[CancelAfter(30_000)]
127152
publicasyncTaskMalformedQuery(CancellationTokenct)
128153
{
129154
// there might be some query string that gets the parser to throw an exception, but I could not find one.
130-
varinput=newUri("coder:/v0/open/ws/workspace1/agent/agent11/rdp?%&##");
155+
varinput=newUri("coder://coder.test/v0/open/ws/workspace1/agent/agent11/rdp?%&##");
131156

157+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
132158
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
133159
// treated the same as if we just didn't include credentials
134160
_=_mRdpConnector.Setup(m=>m.Connect(agent11.Fqdn[0],IRdpConnector.DefaultPort,ct))
135161
.Returns(Task.CompletedTask);
136162
awaituriHandler.HandleUri(input,ct);
163+
_mRdpConnector.Verify(m=>m.Connect(It.IsAny<string>(),It.IsAny<int>(),ct),Times.Once);
137164
}
138165

139166
[Test(Description="VPN not started")]
140167
[CancelAfter(30_000)]
141168
publicasyncTaskVPNNotStarted(CancellationTokenct)
142169
{
143-
varinput=newUri("coder:/v0/open/ws/wrongworkspace/agent/agent11/rdp");
170+
varinput=newUri("coder://coder.test/v0/open/ws/wrongworkspace/agent/agent11/rdp");
144171

172+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
145173
_mRpcController.Setup(m=>m.GetState()).Returns(newRpcModel
146174
{
147175
VpnLifecycle=VpnLifecycle.Starting,
@@ -150,29 +178,79 @@ public async Task VPNNotStarted(CancellationToken ct)
150178
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex("Coder Connect"),ct))
151179
.Returns(Task.CompletedTask);
152180
awaituriHandler.HandleUri(input,ct);
181+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
153182
}
154183

155184
[Test(Description="Wrong number of components")]
156185
[CancelAfter(30_000)]
157186
publicasyncTaskUnknownNumComponents(CancellationTokenct)
158187
{
159-
varinput=newUri("coder:/v0/open/ws/wrongworkspace/agent11/rdp");
188+
varinput=newUri("coder://coder.test/v0/open/ws/wrongworkspace/agent11/rdp");
160189

190+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
161191
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
162192
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct))
163193
.Returns(Task.CompletedTask);
164194
awaituriHandler.HandleUri(input,ct);
195+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
165196
}
166197

167198
[Test(Description="Unknown prefix")]
168199
[CancelAfter(30_000)]
169200
publicasyncTaskUnknownPrefix(CancellationTokenct)
170201
{
171-
varinput=newUri("coder:/v300/open/ws/workspace1/agent/agent11/rdp");
202+
varinput=newUri("coder://coder.test/v300/open/ws/workspace1/agent/agent11/rdp");
172203

204+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
173205
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
174206
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct))
175207
.Returns(Task.CompletedTask);
176208
awaituriHandler.HandleUri(input,ct);
209+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
210+
}
211+
212+
[Test(Description="Unknown authority")]
213+
[CancelAfter(30_000)]
214+
publicasyncTaskUnknownAuthority(CancellationTokenct)
215+
{
216+
varinput=newUri("coder://unknown.test/v0/open/ws/workspace1/agent/agent11/rdp");
217+
218+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
219+
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
220+
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex(@"unknown\.test"),ct))
221+
.Returns(Task.CompletedTask);
222+
awaituriHandler.HandleUri(input,ct);
223+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
224+
}
225+
226+
[Test(Description="Missing authority")]
227+
[CancelAfter(30_000)]
228+
publicasyncTaskMissingAuthority(CancellationTokenct)
229+
{
230+
varinput=newUri("coder:/v0/open/ws/workspace1/agent/agent11/rdp");
231+
232+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(credentialModel1);
233+
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
234+
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex("Coder server"),ct))
235+
.Returns(Task.CompletedTask);
236+
awaituriHandler.HandleUri(input,ct);
237+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
238+
}
239+
240+
[Test(Description="Not signed in")]
241+
[CancelAfter(30_000)]
242+
publicasyncTaskNotSignedIn(CancellationTokenct)
243+
{
244+
varinput=newUri("coder://coder.test/v0/open/ws/workspace1/agent/agent11/rdp");
245+
246+
_mCredentialManager.Setup(m=>m.GetCachedCredentials()).Returns(newCredentialModel()
247+
{
248+
State=CredentialState.Invalid,
249+
});
250+
_mRpcController.Setup(m=>m.GetState()).Returns(modelWithWorkspace1);
251+
_mUserNotifier.Setup(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsRegex("signed in"),ct))
252+
.Returns(Task.CompletedTask);
253+
awaituriHandler.HandleUri(input,ct);
254+
_mUserNotifier.Verify(m=>m.ShowErrorNotification(It.IsAny<string>(),It.IsAny<string>(),ct),Times.Once());
177255
}
178256
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp