Skip to content

Commit 141ab08

Browse files
committed
Use webapp token instead of plaintext credentials
1 parent 05a2759 commit 141ab08

File tree

3 files changed

+71
-31
lines changed

3 files changed

+71
-31
lines changed

src/commands/serverActions.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import {
1010
import { connectionTarget, terminalWithDocker, currentFile } from "../utils";
1111
import { mainCommandMenu, mainSourceControlMenu } from "./studio";
1212
import { AtelierAPI } from "../api";
13+
import { getCSPToken } from "../utils/getCSPToken";
1314

14-
type ServerAction = { detail: string; id: string; label: string };
15+
type ServerAction = { detail: string; id: string; label: string; rawLink?: string };
1516
export async function serverActions(): Promise<void> {
1617
const { apiTarget, configName: workspaceFolder } = connectionTarget();
1718
const api = new AtelierAPI(apiTarget);
18-
const { active, host = "", ns = "", https, port = 0, pathPrefix, username, password, docker } = api.config;
19+
const { active, host = "", ns = "", https, port = 0, pathPrefix, docker } = api.config;
1920
const explorerCount = (await explorerProvider.getChildren()).length;
2021
if (!explorerCount && (!docker || host === "")) {
2122
await vscode.commands.executeCommand("ObjectScriptExplorer.focus");
@@ -70,29 +71,24 @@ export async function serverActions(): Promise<void> {
7071
const file = currentFile();
7172
const classname = file && file.name.toLowerCase().endsWith(".cls") ? file.name.slice(0, -4) : "";
7273
const classnameEncoded = encodeURIComponent(classname);
73-
const connInfo = `${host}:${port}[${nsEncoded}]`;
74+
const connInfo = `${host}:${port}${pathPrefix}[${nsEncoded}]`;
7475
const serverUrl = `${https ? "https" : "http"}://${host}:${port}${pathPrefix}`;
75-
const portalUrl = `${serverUrl}/csp/sys/UtilHome.csp?$NAMESPACE=${nsEncoded}`;
76-
const classRef = `${serverUrl}/csp/documatic/%25CSP.Documatic.cls?LIBRARY=${nsEncoded}${
76+
const portalPath = `/csp/sys/UtilHome.csp?$NAMESPACE=${nsEncoded}`;
77+
const classRef = `/csp/documatic/%25CSP.Documatic.cls?LIBRARY=${nsEncoded}${
7778
classname ? "&CLASSNAME=" + classnameEncoded : ""
7879
}`;
79-
const iris = workspaceState.get(workspaceFolder + ":iris", false);
80-
const usernameEncoded = encodeURIComponent(username);
81-
const passwordEncoded = encodeURIComponent(password);
82-
const auth = iris
83-
? `&IRISUsername=${usernameEncoded}&IRISPassword=${passwordEncoded}`
84-
: `&CacheUserName=${usernameEncoded}&CachePassword=${passwordEncoded}`;
8580
let extraLinks = 0;
8681
for (const title in links) {
87-
let link = String(links[title]);
88-
if (classname == "" && (link.includes("${classname}") || link.includes("${classnameEncoded}"))) {
82+
const rawLink = String(links[title]);
83+
// Skip link if it requires a classname and we don't currently have one
84+
if (classname == "" && (rawLink.includes("${classname}") || rawLink.includes("${classnameEncoded}"))) {
8985
continue;
9086
}
91-
link = link
87+
const link = rawLink
9288
.replace("${host}", host)
9389
.replace("${port}", port.toString())
9490
.replace("${serverUrl}", serverUrl)
95-
.replace("${serverAuth}", auth)
91+
.replace("${serverAuth}", "")
9692
.replace("${ns}", nsEncoded)
9793
.replace("${namespace}", ns == "%SYS" ? "sys" : nsEncoded.toLowerCase())
9894
.replace("${classname}", classname)
@@ -101,6 +97,7 @@ export async function serverActions(): Promise<void> {
10197
id: "extraLink" + extraLinks++,
10298
label: title,
10399
detail: link,
100+
rawLink,
104101
});
105102
}
106103
if (workspaceState.get(workspaceFolder + ":docker", false)) {
@@ -113,12 +110,12 @@ export async function serverActions(): Promise<void> {
113110
actions.push({
114111
id: "openPortal",
115112
label: "Open Management Portal",
116-
detail: portalUrl,
113+
detail: serverUrl + portalPath,
117114
});
118115
actions.push({
119116
id: "openClassReference",
120117
label: "Open Class Reference" + (classname ? ` for ${classname}` : ""),
121-
detail: classRef,
118+
detail: serverUrl + classRef,
122119
});
123120
if (
124121
!vscode.window.activeTextEditor ||
@@ -141,17 +138,21 @@ export async function serverActions(): Promise<void> {
141138
placeHolder: `Select action for server: ${connInfo}`,
142139
})
143140
.then(connectionActionsHandler)
144-
.then((action) => {
141+
.then(async (action) => {
145142
if (!action) {
146143
return;
147144
}
148145
switch (action.id) {
149146
case "openPortal": {
150-
vscode.env.openExternal(vscode.Uri.parse(portalUrl + auth));
147+
const token = await getCSPToken(api, portalPath);
148+
const urlString = `${serverUrl}${portalPath}&CSPCHD=${token}`;
149+
vscode.env.openExternal(vscode.Uri.parse(urlString));
151150
break;
152151
}
153152
case "openClassReference": {
154-
vscode.env.openExternal(vscode.Uri.parse(classRef + auth));
153+
const token = await getCSPToken(api, classRef);
154+
const urlString = `${serverUrl}${classRef}&CSPCHD=${token}`;
155+
vscode.env.openExternal(vscode.Uri.parse(urlString));
155156
break;
156157
}
157158
case "openDockerTerminal": {
@@ -167,7 +168,15 @@ export async function serverActions(): Promise<void> {
167168
break;
168169
}
169170
default: {
170-
vscode.env.openExternal(vscode.Uri.parse(action.detail));
171+
let urlString = action.detail;
172+
if (action.rawLink?.startsWith("${serverUrl}")) {
173+
const path = vscode.Uri.parse(urlString).path;
174+
const token = await getCSPToken(api, path);
175+
if (token.length > 0) {
176+
urlString += `&CSPCHD=${token}`;
177+
}
178+
}
179+
vscode.env.openExternal(vscode.Uri.parse(urlString));
171180
}
172181
}
173182
});

src/commands/studio.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class StudioActions {
100100
return vscode.window
101101
.showWarningMessage(target, { modal: true }, "Yes", "No")
102102
.then((answer) => (answer === "Yes" ? "1" : answer === "No" ? "0" : "2"));
103-
case 2: // Run a CSP page/Template. The Target is the full url to the CSP page/Template
103+
case 2: // Run a CSP page/Template. The Target is the full path of CSP page/template on the connected server
104104
return new Promise((resolve) => {
105105
let answer = "2";
106106
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
@@ -120,16 +120,19 @@ class StudioActions {
120120
});
121121
panel.onDidDispose(() => resolve(answer));
122122

123+
const config = this.api.config;
123124
const url = new URL(
124-
`${this.api.config.https ? "https" : "http"}://${this.api.config.host}:${this.api.config.port}${target}`
125+
`${config.https ? "https" : "http"}://${config.host}:${config.port}${config.pathPrefix}${target}`
125126
);
126-
this.api
127-
.actionQuery("select %Atelier_v1_Utils.General_GetCSPToken(?) token", [url.toString()])
128-
.then((tokenObj) => {
129-
const csptoken = tokenObj.result.content[0].token;
130-
url.searchParams.set("CSPCHD", csptoken);
131-
url.searchParams.set("Namespace", this.api.config.ns);
132-
panel.webview.html = `
127+
128+
// Do this ourself instead of using our new getCSPToken wrapper function, because that function reuses tokens which causes issues with
129+
// webview when server is 2020.1.1 or greater, as session cookie scope is typically Strict, meaning that the webview
130+
// cannot store the cookie. Consequence is web sessions may build up (they get a 900 second timeout)
131+
this.api.actionQuery("select %Atelier_v1_Utils.General_GetCSPToken(?) token", [target]).then((tokenObj) => {
132+
const csptoken = tokenObj.result.content[0].token;
133+
url.searchParams.set("CSPCHD", csptoken);
134+
url.searchParams.set("Namespace", this.api.config.ns);
135+
panel.webview.html = `
133136
<!DOCTYPE html>
134137
<html lang="en">
135138
<head>
@@ -157,7 +160,7 @@ class StudioActions {
157160
</body>
158161
</html>
159162
`;
160-
});
163+
});
161164
});
162165
case 3: // Run an EXE on the client.
163166
throw new Error("processUserAction: Run EXE (Action=5) not supported");

src/utils/getCSPToken.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { AtelierAPI } from "../api";
2+
3+
const allTokens = new Map<string, Map<string, string>>();
4+
5+
// Get or extend CSP token that will give current connected user access to webapp at path
6+
export async function getCSPToken(api: AtelierAPI, path: string): Promise<string> {
7+
// Ignore any queryparams, and null out any page name
8+
const parts = path.split("?")[0].split("/");
9+
parts.pop();
10+
parts.push("");
11+
path = parts.join("/");
12+
13+
// The first key in map-of-maps where we record tokens represents the connection target
14+
const { https, host, port, pathPrefix, username } = api.config;
15+
const connKey = JSON.stringify({ https, host, port, pathPrefix, username });
16+
17+
const myTokens = allTokens.get(connKey) || new Map<string, string>();
18+
const previousToken = myTokens.get(path) || "";
19+
let token = "";
20+
return api
21+
.actionQuery("select %Atelier_v1_Utils.General_GetCSPToken(?, ?) token", [path, previousToken])
22+
.then((tokenObj) => {
23+
token = tokenObj.result.content[0].token;
24+
myTokens.set(path, token);
25+
allTokens.set(connKey, myTokens);
26+
return token;
27+
});
28+
}

0 commit comments

Comments
 (0)