mirror of
https://github.com/github/codeql-action.git
synced 2026-10-30 09:37:15 +00:00
Type result of parsing JSON as unknown until narrowed
This commit is contained in:
@@ -17,11 +17,11 @@ import {
|
||||
Feature,
|
||||
FeatureEnablement,
|
||||
} from "./feature-flags";
|
||||
import * as json from "./json";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
Address,
|
||||
RawCredential,
|
||||
Registry,
|
||||
Credential,
|
||||
AuthConfig,
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
JFrogConfig,
|
||||
isUsernamePassword,
|
||||
hasUsername,
|
||||
RawCredential,
|
||||
} from "./start-proxy/types";
|
||||
import {
|
||||
ActionName,
|
||||
@@ -267,13 +268,19 @@ const NEW_LANGUAGE_TO_REGISTRY_TYPE: Required<RegistryMapping> = {
|
||||
*
|
||||
* @throws A `ConfigurationError` if the `Registry` value contains neither a `url` or `host` field.
|
||||
*/
|
||||
function getRegistryAddress(registry: Partial<Registry>): Address {
|
||||
if (isDefined(registry.url)) {
|
||||
function getRegistryAddress(
|
||||
registry: json.UnvalidatedObject<Registry>,
|
||||
): Address {
|
||||
if (
|
||||
isDefined(registry.url) &&
|
||||
json.isString(registry.url) &&
|
||||
json.isStringOrUndefined(registry.host)
|
||||
) {
|
||||
return {
|
||||
url: registry.url,
|
||||
host: registry.host,
|
||||
};
|
||||
} else if (isDefined(registry.host)) {
|
||||
} else if (isDefined(registry.host) && json.isString(registry.host)) {
|
||||
return {
|
||||
url: undefined,
|
||||
host: registry.host,
|
||||
@@ -287,7 +294,9 @@ function getRegistryAddress(registry: Partial<Registry>): Address {
|
||||
}
|
||||
|
||||
/** Extracts an `AuthConfig` value from `config`. */
|
||||
export function getAuthConfig(config: Partial<AuthConfig>): AuthConfig {
|
||||
export function getAuthConfig(
|
||||
config: json.UnvalidatedObject<AuthConfig>,
|
||||
): AuthConfig {
|
||||
// Start by checking for the OIDC configurations, since they have required properties
|
||||
// which we can use to identify them.
|
||||
if (isAzureConfig(config)) {
|
||||
@@ -311,25 +320,44 @@ export function getAuthConfig(config: Partial<AuthConfig>): AuthConfig {
|
||||
audience: config.audience,
|
||||
} satisfies JFrogConfig;
|
||||
} else if (isToken(config)) {
|
||||
// For token-based authentication, both the token and username are optional.
|
||||
// If the token is absent, then it doesn't matter if we end up treating it
|
||||
// as a `UsernamePassword` object internally.
|
||||
// There are three scenarios for non-OIDC authentication based on the registry type:
|
||||
//
|
||||
// 1. `username`+`token`
|
||||
// 2. A `token` that combines the username and actual token, seperated by ':'.
|
||||
// 3. `username`+`password`
|
||||
//
|
||||
// In all three cases, all fields are optional. If the `token` field is present,
|
||||
// we accept the configuration as a `Token` typed configuration, with the `token`
|
||||
// value and an optional `username`. Otherwise, we accept the configuration
|
||||
// typed as `UsernamePassword` (in the `else` clause below) with optional
|
||||
// username and password. I.e. a private registry type that uses 1. or 2.,
|
||||
// but has no `token` configured, will get accepted as `UsernamePassword` here.
|
||||
|
||||
// Mask token to reduce chance of accidental leakage in logs, if we have one.
|
||||
if (isDefined(config.token)) {
|
||||
// Mask token to reduce chance of accidental leakage in logs, if we have one.
|
||||
core.setSecret(config.token);
|
||||
}
|
||||
|
||||
return { username: config.username, token: config.token } satisfies Token;
|
||||
} else {
|
||||
// Mask password to reduce chance of accidental leakage in logs, if we have one.
|
||||
if ("password" in config && isDefined(config.password)) {
|
||||
let username: string | undefined = undefined;
|
||||
let password: string | undefined = undefined;
|
||||
|
||||
// Both "username" and "password" are optional. If we have reached this point, we need
|
||||
// to validate which of them are present and that they have the correct type if so.
|
||||
if ("password" in config && json.isString(config.password)) {
|
||||
// Mask password to reduce chance of accidental leakage in logs, if we have one.
|
||||
core.setSecret(config.password);
|
||||
password = config.password;
|
||||
}
|
||||
if ("username" in config && json.isString(config.username)) {
|
||||
username = config.username;
|
||||
}
|
||||
|
||||
// Return the `UsernamePassword` object. Both username and password may be undefined.
|
||||
return {
|
||||
username: "username" in config ? config.username : undefined,
|
||||
password: "password" in config ? config.password : undefined,
|
||||
username,
|
||||
password,
|
||||
} satisfies UsernamePassword;
|
||||
}
|
||||
}
|
||||
@@ -364,9 +392,9 @@ export function getCredentials(
|
||||
}
|
||||
|
||||
// Parse and validate the credentials
|
||||
let parsed: RawCredential[];
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(credentialsStr) as RawCredential[];
|
||||
parsed = json.parseString(credentialsStr);
|
||||
} catch {
|
||||
// Don't log the error since it might contain sensitive information.
|
||||
logger.error("Failed to parse the credentials data.");
|
||||
@@ -374,7 +402,7 @@ export function getCredentials(
|
||||
}
|
||||
|
||||
// Check that the parsed data is indeed an array.
|
||||
if (!Array.isArray(parsed)) {
|
||||
if (!json.isArray(parsed)) {
|
||||
throw new ConfigurationError(
|
||||
"Expected credentials data to be an array of configurations, but it is not.",
|
||||
);
|
||||
@@ -382,12 +410,12 @@ export function getCredentials(
|
||||
|
||||
const out: Credential[] = [];
|
||||
for (const e of parsed) {
|
||||
if (e === null || typeof e !== "object") {
|
||||
if (e === null || !json.isObject<RawCredential>(e)) {
|
||||
throw new ConfigurationError("Invalid credentials - must be an object");
|
||||
}
|
||||
|
||||
// The configuration must have a type.
|
||||
if (!isDefined(e.type)) {
|
||||
if (!isDefined(e.type) || !json.isString(e.type)) {
|
||||
throw new ConfigurationError("Invalid credentials - must have a type");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { UnvalidatedObject } from "../json";
|
||||
import * as json from "../json";
|
||||
import { isDefined } from "../util";
|
||||
|
||||
/**
|
||||
@@ -5,7 +7,7 @@ import { isDefined } from "../util";
|
||||
* present or not. This type is used to represent such values, which we expect to be
|
||||
* `Credential` values, but haven't validated yet.
|
||||
*/
|
||||
export type RawCredential = Partial<Credential>;
|
||||
export type RawCredential = UnvalidatedObject<Credential>;
|
||||
|
||||
/** Usernames may be present for both authentication with tokens or passwords. */
|
||||
export type Username = {
|
||||
@@ -14,7 +16,7 @@ export type Username = {
|
||||
};
|
||||
|
||||
/** Decides whether `config` has a username. */
|
||||
export function hasUsername(config: Partial<AuthConfig>): config is Username {
|
||||
export function hasUsername(config: AuthConfig): config is Username {
|
||||
return "username" in config;
|
||||
}
|
||||
|
||||
@@ -44,8 +46,10 @@ export type Token = {
|
||||
} & Username;
|
||||
|
||||
/** Decides whether `config` is token-based. */
|
||||
export function isToken(config: Partial<AuthConfig>): config is Token {
|
||||
return "token" in config;
|
||||
export function isToken(
|
||||
config: UnvalidatedObject<AuthConfig>,
|
||||
): config is Token {
|
||||
return "token" in config && json.isStringOrUndefined(config.token);
|
||||
}
|
||||
|
||||
/** Configuration for Azure OIDC. */
|
||||
@@ -53,13 +57,15 @@ export type AzureConfig = { tenant_id: string; client_id: string };
|
||||
|
||||
/** Decides whether `config` is an Azure OIDC configuration. */
|
||||
export function isAzureConfig(
|
||||
config: Partial<AuthConfig>,
|
||||
config: UnvalidatedObject<AuthConfig>,
|
||||
): config is AzureConfig {
|
||||
return (
|
||||
"tenant_id" in config &&
|
||||
"client_id" in config &&
|
||||
isDefined(config.tenant_id) &&
|
||||
isDefined(config.client_id)
|
||||
isDefined(config.client_id) &&
|
||||
json.isString(config.tenant_id) &&
|
||||
json.isString(config.client_id)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +80,9 @@ export type AWSConfig = {
|
||||
};
|
||||
|
||||
/** Decides whether `config` is an AWS OIDC configuration. */
|
||||
export function isAWSConfig(config: Partial<AuthConfig>): config is AWSConfig {
|
||||
export function isAWSConfig(
|
||||
config: UnvalidatedObject<AuthConfig>,
|
||||
): config is AWSConfig {
|
||||
// All of these properties are required.
|
||||
const requiredProperties = [
|
||||
"aws_region",
|
||||
@@ -85,10 +93,20 @@ export function isAWSConfig(config: Partial<AuthConfig>): config is AWSConfig {
|
||||
];
|
||||
|
||||
for (const property of requiredProperties) {
|
||||
if (!(property in config) || !isDefined(config[property])) {
|
||||
if (
|
||||
!(property in config) ||
|
||||
!isDefined(config[property]) ||
|
||||
!json.isString(config[property])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The "audience" field is optional, but should be a string if present.
|
||||
if ("audience" in config && !json.isStringOrUndefined(config.audience)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,11 +119,23 @@ export type JFrogConfig = {
|
||||
|
||||
/** Decides whether `config` is a JFrog OIDC configuration. */
|
||||
export function isJFrogConfig(
|
||||
config: Partial<AuthConfig>,
|
||||
config: UnvalidatedObject<AuthConfig>,
|
||||
): config is JFrogConfig {
|
||||
// The "audience" and "identity_mapping_name" fields is optional, but should be strings if present.
|
||||
if ("audience" in config && !json.isStringOrUndefined(config.audience)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
"identity_mapping_name" in config &&
|
||||
!json.isStringOrUndefined(config.identity_mapping_name)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
"jfrog_oidc_provider_name" in config &&
|
||||
isDefined(config.jfrog_oidc_provider_name)
|
||||
isDefined(config.jfrog_oidc_provider_name) &&
|
||||
json.isString(config.jfrog_oidc_provider_name)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user