Skip to content

Commit 678e63c

Browse files
authored
Feature/1356 openid configuration (#1357)
Fixes #1356. Definitely still needs work (should only try the initOAuth if an OAuth based security scheme is actually present, but it gets the idea across. Unfortunately, at least using the default Swagger Authentication system, I don't think I can do what I wanted where one, in the auth button, could have a login that would automatically work and pass the additional required query parameter. The SwaggerUI code simply doesn't do anything with extension values provided in the scheme. However, that really only affects the SwaggerUI not usages of the application itself. So I'm inclined not to worry about it and will strip of some of the extra elements I added to the code. Good news is, does pre-populate the client id, and the login flow behaves correctly; I was redirected to the local docker-compose keycloak and saw the kc_idp_hint field on the request so in environment should behave the same.
1 parent 20c6e77 commit 678e63c

File tree

12 files changed

+203
-112
lines changed

12 files changed

+203
-112
lines changed

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ ENV cwms.dataapi.access.providers="KeyAccessManager,OpenID"
5656
ENV cwms.dataapi.access.openid.wellKnownUrl="https://<prefix>/.well-known/openid-configuration"
5757
ENV cwms.dataapi.access.openid.issuer="<issuer>"
5858
ENV cwms.dataapi.access.openid.timeout="604800"
59+
# Putting default values here to easy configuration
60+
ENV cwms.dataapi.access.openid.clientId=cwms
61+
ENV cwms.dataapi.access.openid.idpHint=federation-eams
5962
#ENV cwms.dataapi.access.openid.altAuthUrl="https://identityc-test.cwbi.us/auth/realms/cwbi"
6063

6164
# used to simplify redeploy in certain contexts. Update to match -<marker> in image label

cda-gui/package-lock.json

Lines changed: 15 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cda-gui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"react-dom": "^18.2.0",
3030
"react-icons": "^5.0.1",
3131
"react-router-dom": "^7.1.2",
32-
"swagger-ui-dist": "^5.17.7",
32+
"swagger-ui-dist": "^5.29.5",
3333
"use-debounce": "^10.0.5"
3434
},
3535
"devDependencies": {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<body>
4+
<script src="oauth2-redirect.js"></script>
5+
</body>
6+
</html>

cda-gui/public/oauth2-redirect.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"use strict"
2+
function run () {
3+
var oauth2 = window.opener.swaggerUIRedirectOauth2
4+
var sentState = oauth2.state
5+
var redirectUrl = oauth2.redirectUrl
6+
var isValid, qp, arr
7+
8+
if (/code|token|error/.test(window.location.hash)) {
9+
qp = window.location.hash.substring(1).replace("?", "&")
10+
} else {
11+
qp = location.search.substring(1)
12+
}
13+
14+
arr = qp.split("&")
15+
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace("=", '":"') + '"' })
16+
qp = qp ? JSON.parse("{" + arr.join() + "}",
17+
function (key, value) {
18+
return key === "" ? value : decodeURIComponent(value)
19+
}
20+
) : {}
21+
22+
isValid = qp.state === sentState
23+
24+
if ((
25+
oauth2.auth.schema.get("flow") === "accessCode" ||
26+
oauth2.auth.schema.get("flow") === "authorizationCode" ||
27+
oauth2.auth.schema.get("flow") === "authorization_code"
28+
) && !oauth2.auth.code) {
29+
if (!isValid) {
30+
oauth2.errCb({
31+
authId: oauth2.auth.name,
32+
source: "auth",
33+
level: "warning",
34+
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
35+
})
36+
}
37+
38+
if (qp.code) {
39+
delete oauth2.state
40+
oauth2.auth.code = qp.code
41+
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl})
42+
} else {
43+
let oauthErrorMsg
44+
if (qp.error) {
45+
oauthErrorMsg = "["+qp.error+"]: " +
46+
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
47+
(qp.error_uri ? "More info: "+qp.error_uri : "")
48+
}
49+
50+
oauth2.errCb({
51+
authId: oauth2.auth.name,
52+
source: "auth",
53+
level: "error",
54+
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
55+
})
56+
}
57+
} else {
58+
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl})
59+
}
60+
window.close()
61+
}
62+
63+
if( document.readyState !== "loading" ) {
64+
run()
65+
} else {
66+
document.addEventListener("DOMContentLoaded", function () {
67+
run()
68+
})
69+
}

cda-gui/src/pages/swagger-ui/index.jsx

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,63 @@ import { useEffect } from "react";
55
import { getBasePath } from "../../utils/base";
66

77
export default function SwaggerUI() {
8-
useEffect(() => {
9-
// document.querySelector("#swagger-ui").prepend(Index)
10-
// TODO: Add page index to top of page
11-
// Alter the page title to match the swagger page
12-
document.title = "CWMS Data API for Data Retrieval - Swagger UI";
13-
// Begin Swagger UI call region
14-
// TODO: add endpoint that dynamic returns swagger generated doc
15-
SwaggerUIBundle({
16-
url: getBasePath() + "/swagger-docs",
17-
dom_id: "#swagger-ui",
18-
deepLinking: false,
19-
presets: [SwaggerUIBundle.presets.apis],
20-
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
21-
requestInterceptor: (req) => {
22-
// Add a cache-busting query param
23-
const sep = req.url.includes("?") ? "&" : "?";
24-
req.url = `${req.url}${sep}_cb=${Date.now()}`;
8+
useEffect(() => {
9+
// document.querySelector("#swagger-ui").prepend(Index)
10+
// TODO: Add page index to top of page
11+
// Alter the page title to match the swagger page
12+
document.title = "CWMS Data API for Data Retrieval - Swagger UI";
13+
// Begin Swagger UI call region
14+
// TODO: add endpoint that dynamic returns swagger generated doc
2515

26-
// Also ask intermediaries not to serve from cache
27-
req.headers["Cache-Control"] = "no-cache, no-store, max-age=0";
28-
req.headers["Pragma"] = "no-cache";
16+
const ui = SwaggerUIBundle({
17+
url: getBasePath() + "/swagger-docs",
18+
19+
dom_id: "#swagger-ui",
20+
deepLinking: false,
21+
presets: [SwaggerUIBundle.presets.apis],
22+
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
23+
requestInterceptor: (req) => {
24+
// Add a cache-busting query param... but only if it's to our api. Some
25+
// external systems, like keycloak, don't allow random unknown parameters.
26+
const origin = window.location.origin;
27+
const re = new RegExp(`^${origin}.*`)
28+
if (re.test(req.url))
29+
{
30+
const sep = req.url.includes("?") ? "&" : "?";
31+
req.url = `${req.url}${sep}_cb=${Date.now()}`;
2932

30-
return req;
31-
},
32-
});
33-
}, []);
33+
// Also ask intermediaries not to serve from cache
34+
req.headers["Cache-Control"] = "no-cache, no-store, max-age=0";
35+
req.headers["Pragma"] = "no-cache";
36+
}
37+
return req;
38+
},
39+
onComplete: () => {
40+
const spec = JSON.parse(ui.spec().get("spec"));
41+
for (const schemeName in spec.components.securitySchemes) {
42+
const scheme = spec.components.securitySchemes[schemeName];
43+
if (scheme.type === "openIdConnect") {
44+
let additionalParams = null;
45+
let hints = scheme["x-kc_idp_hint"];
46+
if (hints) {
47+
additionalParams = {
48+
// Since getting the interface to allow users to choose
49+
// is likely impossible, we will assume the first in the list
50+
// is the "primary" auth system
51+
"kc_idp_hint": hints.values[0]
52+
};
53+
}
54+
ui.initOAuth({
55+
clientId: scheme["x-oidc-client-id"],
56+
usePkceWithAuthorizationCodeGrant: true,
57+
additionalQueryStringParams: additionalParams,
58+
});
59+
break;
60+
}
61+
}
62+
},
63+
});
64+
}, []);
3465

3566
return <div id="swagger-ui"></div>;
3667
}

cda-gui/vite.config.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import react from "@vitejs/plugin-react";
33

44
// https://vitejs.dev/config/
55
export default defineConfig(({ mode }) => {
6-
// const env = loadEnv(mode, process.cwd(), "");
6+
const env = loadEnv(mode, process.cwd(), "");
77
// const BASE_PATH = env?.BASE_PATH ?? "/cwms-data";
88
return {
99
base: "/cwms-data",
@@ -14,12 +14,22 @@ export default defineConfig(({ mode }) => {
1414
server: {
1515
proxy: {
1616
"^/cwms-data/timeseries/.*": {
17-
target: "https://cwms-data.usace.army.mil",
17+
target: env.CDA_API_ROOT,
1818
changeOrigin: true,
1919
secure: false,
2020
},
2121
"^/cwms-data/catalog/.*": {
22-
target: "https://cwms-data.usace.army.mil",
22+
target: env.CDA_API_ROOT,
23+
changeOrigin: true,
24+
secure: false,
25+
},
26+
"^/cwms-data/auth/.*": {
27+
target: env.CDA_API_ROOT,
28+
changeOrigin: true,
29+
secure: false,
30+
},
31+
"^/cwms-data/swagger-docs$": {
32+
target: env.CDA_API_ROOT,
2333
changeOrigin: true,
2434
secure: false,
2535
},

compose_files/keycloak/realm.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,8 @@
663663
"clientAuthenticatorType": "client-secret",
664664
"redirectUris": [
665665
"https://cwms-data.test:8444/*",
666-
"https://localhost:5010/*"
666+
"https://localhost:5010/*",
667+
"http://localhost:*"
667668
],
668669
"webOrigins": [
669670
"*"

cwms-data-api/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ task run(type: JavaExec) {
229229
mainClass = "fixtures.TomcatServer"
230230
systemProperties += project.properties.findAll { k, v -> k.startsWith("RADAR") }
231231
systemProperties += project.properties.findAll { k, v -> k.startsWith("CDA") }
232+
systemProperties += project.properties.findAll { k, v -> k.startsWith("cwms") }
232233

233234
def context = project.findProperty("cda.war.context") ?: "spk-data"
234235

0 commit comments

Comments
 (0)