mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-28 22:27:21 +02:00
feat(importer-openapi): fill the OAuth redirect URI from an environment variable (#610)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b7b8ae5f94
commit
0aae516f3a
@@ -25,7 +25,7 @@ type ImportedAuthentication = Pick<HttpRequest, "authentication" | "authenticati
|
|||||||
urlParameters: HttpUrlParameter[];
|
urlParameters: HttpUrlParameter[];
|
||||||
};
|
};
|
||||||
type AuthenticationVariableRegistry = Map<string, { name: string; value: string }>;
|
type AuthenticationVariableRegistry = Map<string, { name: string; value: string }>;
|
||||||
type OAuthVariableNames = { clientId: string; clientSecret: string };
|
type OAuthVariableNames = { clientId: string; clientSecret: string; redirectUri: string };
|
||||||
type ServerOverrideVariable = { name: string; value: string };
|
type ServerOverrideVariable = { name: string; value: string };
|
||||||
|
|
||||||
const HTTP_METHODS = ["delete", "get", "head", "options", "patch", "post", "put", "query", "trace"];
|
const HTTP_METHODS = ["delete", "get", "head", "options", "patch", "post", "put", "query", "trace"];
|
||||||
@@ -172,6 +172,18 @@ export async function convertOpenApi(contents: string): Promise<ImportPluginResp
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
// Only redirect-based grants reference the redirect variable, so don't
|
||||||
|
// create it for specs that import as client_credentials or password
|
||||||
|
for (const { redirectUri } of oauthVariablesByScheme.values()) {
|
||||||
|
const used = authenticationConfigs.some(
|
||||||
|
(model) =>
|
||||||
|
model.authenticationType === "oauth2" &&
|
||||||
|
Object.values(toRecord(model.authentication)).some(
|
||||||
|
(value) => typeof value === "string" && value.includes(templateVariable(redirectUri)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (used) variableNames.add(redirectUri);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
authenticationConfigs.some(
|
authenticationConfigs.some(
|
||||||
(model) =>
|
(model) =>
|
||||||
@@ -565,7 +577,10 @@ function importOperationName(operation: UnknownRecord, method: string, path: str
|
|||||||
* description, since a name that long is no easier to scan than the path.
|
* description, since a name that long is no easier to scan than the path.
|
||||||
*/
|
*/
|
||||||
function firstLine(value: string | undefined): string | undefined {
|
function firstLine(value: string | undefined): string | undefined {
|
||||||
const line = value?.split("\n").find((l) => l.trim().length > 0)?.trim();
|
const line = value
|
||||||
|
?.split("\n")
|
||||||
|
.find((l) => l.trim().length > 0)
|
||||||
|
?.trim();
|
||||||
if (line == null || line.length > MAX_NAME_LENGTH) return undefined;
|
if (line == null || line.length > MAX_NAME_LENGTH) return undefined;
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -738,8 +753,7 @@ function shouldInlinePathParameter(
|
|||||||
// so `{id}` and `{id}:cancel` stay placeholders while `report.{format}` can't
|
// so `{id}` and `{id}:cancel` stay placeholders while `report.{format}` can't
|
||||||
const placeholderExpressible = matchingSegments.every(
|
const placeholderExpressible = matchingSegments.every(
|
||||||
(segment) =>
|
(segment) =>
|
||||||
segment === template ||
|
segment === template || (segment.startsWith(template) && segment[template.length] === ":"),
|
||||||
(segment.startsWith(template) && segment[template.length] === ":"),
|
|
||||||
);
|
);
|
||||||
if (matchingSegments.length === 0 || !placeholderExpressible) return true;
|
if (matchingSegments.length === 0 || !placeholderExpressible) return true;
|
||||||
if (isRecord(parameter.content)) return false;
|
if (isRecord(parameter.content)) return false;
|
||||||
@@ -1009,9 +1023,7 @@ function serializeCookieParameter(parameter: UnknownRecord, importState: ImportS
|
|||||||
if (isRecord(value)) {
|
if (isRecord(value)) {
|
||||||
const entries = Object.entries(value);
|
const entries = Object.entries(value);
|
||||||
return explode
|
return explode
|
||||||
? entries
|
? entries.map(([key, entryValue]) => `${key}=${stringifyExampleValue(entryValue)}`).join("; ")
|
||||||
.map(([key, entryValue]) => `${key}=${stringifyExampleValue(entryValue)}`)
|
|
||||||
.join("; ")
|
|
||||||
: `${name}=${entries.flat().map(stringifyExampleValue).join(",")}`;
|
: `${name}=${entries.flat().map(stringifyExampleValue).join(",")}`;
|
||||||
}
|
}
|
||||||
return `${name}=${stringifyExampleValue(value)}`;
|
return `${name}=${stringifyExampleValue(value)}`;
|
||||||
@@ -1158,7 +1170,9 @@ function importBody({
|
|||||||
.filter((p) => stringAt(p, "in") === "formData");
|
.filter((p) => stringAt(p, "in") === "formData");
|
||||||
if (formParameters.length > 0) {
|
if (formParameters.length > 0) {
|
||||||
const contentType =
|
const contentType =
|
||||||
toArray(operation.consumes ?? spec.consumes).find((c): c is string => typeof c === "string") ??
|
toArray(operation.consumes ?? spec.consumes).find(
|
||||||
|
(c): c is string => typeof c === "string",
|
||||||
|
) ??
|
||||||
(formParameters.some((p) => stringAt(p, "type") === "file")
|
(formParameters.some((p) => stringAt(p, "type") === "file")
|
||||||
? "multipart/form-data"
|
? "multipart/form-data"
|
||||||
: "application/x-www-form-urlencoded");
|
: "application/x-www-form-urlencoded");
|
||||||
@@ -1411,7 +1425,11 @@ function mediaTypeExample(mediaType: UnknownRecord, importState: ImportState): u
|
|||||||
return schemaToExample(mediaType.schema, importState);
|
return schemaToExample(mediaType.schema, importState);
|
||||||
}
|
}
|
||||||
|
|
||||||
function schemaToFormParameters(schema: unknown, importState: ImportState, example?: UnknownRecord) {
|
function schemaToFormParameters(
|
||||||
|
schema: unknown,
|
||||||
|
importState: ImportState,
|
||||||
|
example?: UnknownRecord,
|
||||||
|
) {
|
||||||
const resolvedSchema = toRecord(importState.resolveSchema(schema));
|
const resolvedSchema = toRecord(importState.resolveSchema(schema));
|
||||||
const required = toArray(resolvedSchema.required).filter(
|
const required = toArray(resolvedSchema.required).filter(
|
||||||
(name): name is string => typeof name === "string",
|
(name): name is string => typeof name === "string",
|
||||||
@@ -1575,7 +1593,6 @@ function coerceToDeclaredType(example: unknown, schema: UnknownRecord): unknown
|
|||||||
return example;
|
return example;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function inferSchemaType(schema: UnknownRecord): string {
|
function inferSchemaType(schema: UnknownRecord): string {
|
||||||
const rawType = schema.type;
|
const rawType = schema.type;
|
||||||
if (typeof rawType === "string") return rawType;
|
if (typeof rawType === "string") return rawType;
|
||||||
@@ -1688,6 +1705,7 @@ function importSecurityRequirement({
|
|||||||
oauthVariablesByScheme.get(schemeName) ?? {
|
oauthVariablesByScheme.get(schemeName) ?? {
|
||||||
clientId: "oauth_client_id",
|
clientId: "oauth_client_id",
|
||||||
clientSecret: "oauth_client_secret",
|
clientSecret: "oauth_client_secret",
|
||||||
|
redirectUri: "oauth_redirect_uri",
|
||||||
},
|
},
|
||||||
useDynamicServerUrls,
|
useDynamicServerUrls,
|
||||||
);
|
);
|
||||||
@@ -1876,9 +1894,13 @@ function importOAuth2(
|
|||||||
authorizationUrl,
|
authorizationUrl,
|
||||||
accessTokenUrl,
|
accessTokenUrl,
|
||||||
clientSecret: templateVariable(variableNames.clientSecret),
|
clientSecret: templateVariable(variableNames.clientSecret),
|
||||||
|
redirectUri: templateVariable(variableNames.redirectUri),
|
||||||
}
|
}
|
||||||
: grantType === "implicit"
|
: grantType === "implicit"
|
||||||
? { authorizationUrl }
|
? {
|
||||||
|
authorizationUrl,
|
||||||
|
redirectUri: templateVariable(variableNames.redirectUri),
|
||||||
|
}
|
||||||
: grantType === "password"
|
: grantType === "password"
|
||||||
? {
|
? {
|
||||||
accessTokenUrl,
|
accessTokenUrl,
|
||||||
@@ -1969,7 +1991,11 @@ function buildOAuthVariablesByScheme(
|
|||||||
usedPrefixes.add(prefix);
|
usedPrefixes.add(prefix);
|
||||||
return [
|
return [
|
||||||
schemeName,
|
schemeName,
|
||||||
{ clientId: `${prefix}_client_id`, clientSecret: `${prefix}_client_secret` },
|
{
|
||||||
|
clientId: `${prefix}_client_id`,
|
||||||
|
clientSecret: `${prefix}_client_secret`,
|
||||||
|
redirectUri: `${prefix}_redirect_uri`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ describe("importer-openapi", () => {
|
|||||||
{ name: "baseUrl", value: "https://api.example.com/v1" },
|
{ name: "baseUrl", value: "https://api.example.com/v1" },
|
||||||
{ name: "oauth_client_id", value: "" },
|
{ name: "oauth_client_id", value: "" },
|
||||||
{ name: "oauth_client_secret", value: "" },
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "oauth_redirect_uri", value: "" },
|
||||||
{ name: "baseUrlOrigin", value: "https://api.example.com" },
|
{ name: "baseUrlOrigin", value: "https://api.example.com" },
|
||||||
{ name: "auth_api_key_key", value: "" },
|
{ name: "auth_api_key_key", value: "" },
|
||||||
],
|
],
|
||||||
@@ -395,6 +396,7 @@ describe("importer-openapi", () => {
|
|||||||
{ name: "baseUrl", value: "https://sandbox.example.com/v1" },
|
{ name: "baseUrl", value: "https://sandbox.example.com/v1" },
|
||||||
{ name: "oauth_client_id", value: "" },
|
{ name: "oauth_client_id", value: "" },
|
||||||
{ name: "oauth_client_secret", value: "" },
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "oauth_redirect_uri", value: "" },
|
||||||
{ name: "baseUrlOrigin", value: "https://sandbox.example.com" },
|
{ name: "baseUrlOrigin", value: "https://sandbox.example.com" },
|
||||||
{ name: "auth_api_key_key", value: "" },
|
{ name: "auth_api_key_key", value: "" },
|
||||||
],
|
],
|
||||||
@@ -484,6 +486,7 @@ describe("importer-openapi", () => {
|
|||||||
clientId: "${[oauth_implicitOauth_client_id]}",
|
clientId: "${[oauth_implicitOauth_client_id]}",
|
||||||
headerPrefix: "Bearer",
|
headerPrefix: "Bearer",
|
||||||
authorizationUrl: "https://example.com/authorize",
|
authorizationUrl: "https://example.com/authorize",
|
||||||
|
redirectUri: "${[oauth_implicitOauth_redirect_uri]}",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -497,6 +500,7 @@ describe("importer-openapi", () => {
|
|||||||
{ name: "oauth_oauth_client_secret", value: "" },
|
{ name: "oauth_oauth_client_secret", value: "" },
|
||||||
{ name: "oauth_implicitOauth_client_id", value: "" },
|
{ name: "oauth_implicitOauth_client_id", value: "" },
|
||||||
{ name: "oauth_implicitOauth_client_secret", value: "" },
|
{ name: "oauth_implicitOauth_client_secret", value: "" },
|
||||||
|
{ name: "oauth_implicitOauth_redirect_uri", value: "" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -539,6 +543,7 @@ describe("importer-openapi", () => {
|
|||||||
{ name: "baseUrl", value: "https://api.example.com" },
|
{ name: "baseUrl", value: "https://api.example.com" },
|
||||||
{ name: "oauth_client_id", value: "" },
|
{ name: "oauth_client_id", value: "" },
|
||||||
{ name: "oauth_client_secret", value: "" },
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "oauth_redirect_uri", value: "" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -596,6 +601,7 @@ describe("importer-openapi", () => {
|
|||||||
{ name: "baseUrl", value: "/api/v1" },
|
{ name: "baseUrl", value: "/api/v1" },
|
||||||
{ name: "oauth_client_id", value: "" },
|
{ name: "oauth_client_id", value: "" },
|
||||||
{ name: "oauth_client_secret", value: "" },
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "oauth_redirect_uri", value: "" },
|
||||||
{ name: "baseUrlOrigin", value: "" },
|
{ name: "baseUrlOrigin", value: "" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -633,6 +639,7 @@ describe("importer-openapi", () => {
|
|||||||
scope: "admin",
|
scope: "admin",
|
||||||
authorizationUrl: "https://example.com/authorize",
|
authorizationUrl: "https://example.com/authorize",
|
||||||
accessTokenUrl: "https://example.com/token",
|
accessTokenUrl: "https://example.com/token",
|
||||||
|
redirectUri: "${[oauth_redirect_uri]}",
|
||||||
},
|
},
|
||||||
headers: [{ enabled: true, name: "Accept", value: "application/json" }],
|
headers: [{ enabled: true, name: "Accept", value: "application/json" }],
|
||||||
}),
|
}),
|
||||||
@@ -1376,9 +1383,7 @@ describe("importer-openapi", () => {
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
schema: {
|
schema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
allOf: [
|
allOf: [{ type: "object", properties: { fromAllOf: { example: "a" } } }],
|
||||||
{ type: "object", properties: { fromAllOf: { example: "a" } } },
|
|
||||||
],
|
|
||||||
properties: { sibling: { example: "b" } },
|
properties: { sibling: { example: "b" } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1398,7 +1403,17 @@ describe("importer-openapi", () => {
|
|||||||
|
|
||||||
test("Accepts unquoted YAML version numbers", async () => {
|
test("Accepts unquoted YAML version numbers", async () => {
|
||||||
const imported = await convertOpenApi(
|
const imported = await convertOpenApi(
|
||||||
["swagger: 2.0", "info:", " title: Unquoted Test", ' version: "1"', "host: example.com", "paths:", " /a:", " get:", " responses: {}"].join("\n"),
|
[
|
||||||
|
"swagger: 2.0",
|
||||||
|
"info:",
|
||||||
|
" title: Unquoted Test",
|
||||||
|
' version: "1"',
|
||||||
|
"host: example.com",
|
||||||
|
"paths:",
|
||||||
|
" /a:",
|
||||||
|
" get:",
|
||||||
|
" responses: {}",
|
||||||
|
].join("\n"),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(imported?.resources.httpRequests[0]?.url).toBe("${[baseUrl]}/a");
|
expect(imported?.resources.httpRequests[0]?.url).toBe("${[baseUrl]}/a");
|
||||||
|
|||||||
Reference in New Issue
Block a user