Compare commits

..
Author SHA1 Message Date
Brian DeHamer a7e08af9b5 remove hard-coded issuer from JWT verification
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-08 15:10:57 -07:00
30 changed files with 129 additions and 247 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: npm run bootstrap
- name: audit tools (without allow-list)
run: npm audit --audit-level=moderate --omit dev
run: npm audit --audit-level=moderate
- name: audit packages
run: npm run audit-all
-2
View File
@@ -1,7 +1,5 @@
name: Publish NPM
run-name: Publish NPM - ${{ github.event.inputs.package }}
on:
workflow_dispatch:
inputs:
+4 -5
View File
@@ -4925,13 +4925,12 @@
}
},
"node_modules/axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
-4
View File
@@ -63,8 +63,6 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".
sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
}
@@ -115,8 +113,6 @@ export type AttestProvenanceOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".
sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
// Issuer URL responsible for minting the OIDC token from which the
+12 -17
View File
@@ -1,36 +1,31 @@
# @actions/attest Releases
### 1.4.0
- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)
### 1.3.1
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)
- Fix bug with proxy support when retrieving JWKS for OIDC issuer
### 1.3.0
- Dynamic construction of Sigstore API URLs [#1735](https://github.com/actions/toolkit/pull/1735)
- Switch to new GH provenance build type [#1745](https://github.com/actions/toolkit/pull/1745)
- Fetch existing Rekor entry on 409 conflict error [#1759](https://github.com/actions/toolkit/pull/1759)
- Bump @sigstore/bundle from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
- Bump @sigstore/sign from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
- Dynamic construction of Sigstore API URLs
- Switch to new GH provenance build type
- Fetch existing Rekor entry on 409 conflict error
- Bump @sigstore/bundle from 2.3.0 to 2.3.2
- Bump @sigstore/sign from 2.3.0 to 2.3.2
### 1.2.1
- Retry request on attestation persistence failure [#1725](https://github.com/actions/toolkit/pull/1725)
- Retry request on attestation persistence failure
### 1.2.0
- Generate attestations using the v0.3 Sigstore bundle format [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/bundle from 2.2.0 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/sign from 2.2.3 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Remove dependency on make-fetch-happen [#1714](https://github.com/actions/toolkit/pull/1714)
- Generate attestations using the v0.3 Sigstore bundle format.
- Bump @sigstore/bundle from 2.2.0 to 2.3.0.
- Bump @sigstore/sign from 2.2.3 to 2.3.0.
- Remove dependency on make-fetch-happen
### 1.1.0
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement [#1693](https://github.com/actions/toolkit/pull/1693)
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement.
### 1.0.0
@@ -9,7 +9,7 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "main",
"repository": "https://foo.ghe.com/owner/repo",
"repository": "https://github.com/owner/repo",
},
},
"internalParameters": {
@@ -25,16 +25,16 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
"uri": "git+https://github.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
"id": "https://github.com/owner/workflows/.github/workflows/publish.yml@main",
},
"metadata": {
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
},
+6 -8
View File
@@ -4,7 +4,7 @@ import {getIDTokenClaims} from '../src/oidc'
describe('getIDTokenClaims', () => {
const originalEnv = process.env
const issuer = 'https://example.com'
const issuer = 'https://token.actions.example.ghe.com'
const audience = 'nobody'
const requestToken = 'token'
const openidConfigPath = '/.well-known/openid-configuration'
@@ -63,7 +63,7 @@ describe('getIDTokenClaims', () => {
})
it('returns the ID token claims', async () => {
const result = await getIDTokenClaims(issuer)
const result = await getIDTokenClaims()
expect(result).toEqual(claims)
})
})
@@ -83,7 +83,7 @@ describe('getIDTokenClaims', () => {
})
it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing claims/i)
await expect(getIDTokenClaims()).rejects.toThrow(/missing claims/i)
})
})
@@ -99,7 +99,7 @@ describe('getIDTokenClaims', () => {
})
it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "iss"/)
await expect(getIDTokenClaims()).rejects.toThrow(/issuer mismatch/i)
})
})
@@ -115,7 +115,7 @@ describe('getIDTokenClaims', () => {
})
it('throw an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "aud"/)
await expect(getIDTokenClaims()).rejects.toThrow(/verification failed/i)
})
})
@@ -140,9 +140,7 @@ describe('getIDTokenClaims', () => {
})
it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(
/failed to get id/i
)
await expect(getIDTokenClaims()).rejects.toThrow(/failed to get id/i)
})
})
})
+15 -10
View File
@@ -8,7 +8,7 @@ import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
describe('provenance functions', () => {
const originalEnv = process.env
const issuer = 'https://token.actions.foo.ghe.com'
const issuer = 'https://token.actions.githubusercontent.com'
const audience = 'nobody'
const jwksPath = '/.well-known/jwks.json'
const tokenPath = '/token'
@@ -38,7 +38,7 @@ describe('provenance functions', () => {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_SERVER_URL: 'https://github.com',
GITHUB_REPOSITORY: claims.repository
}
@@ -68,7 +68,7 @@ describe('provenance functions', () => {
describe('buildSLSAProvenancePredicate', () => {
it('returns a provenance hydrated from an OIDC token', async () => {
const predicate = await buildSLSAProvenancePredicate()
const predicate = await buildSLSAProvenancePredicate(issuer)
expect(predicate).toMatchSnapshot()
})
})
@@ -96,9 +96,9 @@ describe('provenance functions', () => {
})
describe('when using the github Sigstore instance', () => {
beforeEach(async () => {
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
beforeEach(async () => {
// Mock Sigstore
await mockFulcio({baseURL: fulcioURL, strict: false})
await mockTSA({baseURL: tsaServerURL})
@@ -118,7 +118,8 @@ describe('provenance functions', () => {
subjectName,
subjectDigest,
token: 'token',
sigstore: 'github'
sigstore: 'github',
issuer
})
expect(attestation).toBeDefined()
@@ -145,7 +146,8 @@ describe('provenance functions', () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
token: 'token'
token: 'token',
issuer
})
expect(attestation).toBeDefined()
@@ -181,7 +183,8 @@ describe('provenance functions', () => {
subjectName,
subjectDigest,
token: 'token',
sigstore: 'public-good'
sigstore: 'public-good',
issuer
})
expect(attestation).toBeDefined()
@@ -208,7 +211,8 @@ describe('provenance functions', () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
token: 'token'
token: 'token',
issuer
})
expect(attestation).toBeDefined()
@@ -234,7 +238,8 @@ describe('provenance functions', () => {
subjectDigest,
token: 'token',
sigstore: 'public-good',
skipWrite: true
skipWrite: true,
issuer
})
expect(attestation).toBeDefined()
+2 -5
View File
@@ -5,7 +5,6 @@ describe('writeAttestation', () => {
const originalEnv = process.env
const attestation = {foo: 'bar '}
const token = 'token'
const headers = {'X-GitHub-Foo': 'true'}
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
@@ -28,16 +27,14 @@ describe('writeAttestation', () => {
.intercept({
path: '/repos/foo/bar/attestations',
method: 'POST',
headers: {authorization: `token ${token}`, ...headers},
headers: {authorization: `token ${token}`},
body: JSON.stringify({bundle: attestation})
})
.reply(201, {id: '123'})
})
it('persists the attestation', async () => {
await expect(
writeAttestation(attestation, token, {headers})
).resolves.toEqual('123')
await expect(writeAttestation(attestation, token)).resolves.toEqual('123')
})
})
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@actions/attest",
"version": "1.4.0",
"version": "1.3.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/attest",
"version": "1.4.0",
"version": "1.3.1",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/attest",
"version": "1.4.0",
"version": "1.3.1",
"description": "Actions attestation lib",
"keywords": [
"github",
+1 -7
View File
@@ -28,8 +28,6 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".
sigstore?: SigstoreInstance
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
}
@@ -63,11 +61,7 @@ export async function attest(options: AttestOptions): Promise<Attestation> {
// Store the attestation
let attestationID: string | undefined
if (options.skipWrite !== true) {
attestationID = await writeAttestation(
bundleToJSON(bundle),
options.token,
{headers: options.headers}
)
attestationID = await writeAttestation(bundleToJSON(bundle), options.token)
}
return toAttestation(bundle, attestationID)
+23 -33
View File
@@ -2,13 +2,13 @@ import {getIDToken} from '@actions/core'
import {HttpClient} from '@actions/http-client'
import * as jose from 'jose'
const OIDC_AUDIENCE = 'nobody'
const VALID_SERVER_URLS = [
'https://github.com',
new RegExp('^https://[a-z0-9-]+\\.ghe\\.com$')
const VALID_ISSUERS = [
'https://token.actions.githubusercontent.com',
new RegExp('^https://token\\.actions\\.[a-z0-9-]+\\.ghe\\.com$')
] as const
const OIDC_AUDIENCE = 'nobody'
const REQUIRED_CLAIMS = [
'iss',
'ref',
@@ -30,11 +30,10 @@ type OIDCConfig = {
jwks_uri: string
}
export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
issuer = issuer || getIssuer()
export const getIDTokenClaims = async (): Promise<ClaimSet> => {
try {
const token = await getIDToken(OIDC_AUDIENCE)
const claims = await decodeOIDCToken(token, issuer)
const claims = await decodeOIDCToken(token)
assertClaimSet(claims)
return claims
} catch (error) {
@@ -42,15 +41,24 @@ export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
}
}
const decodeOIDCToken = async (
token: string,
issuer: string
): Promise<jose.JWTPayload> => {
const decodeOIDCToken = async (token: string): Promise<jose.JWTPayload> => {
// Decode is an unsafe operation (no signature verification) but we're
// verifying the signature below after retrieving the issuer.
const {iss} = jose.decodeJwt(token)
if (!iss) {
throw new Error('No issuer found')
}
// Issuer must match at least one of the valid issuers
if (!VALID_ISSUERS.some(allowed => iss.match(allowed))) {
throw new Error('Issuer mismatch')
}
// Verify and decode token
const jwks = jose.createLocalJWKSet(await getJWKS(issuer))
const jwks = jose.createLocalJWKSet(await getJWKS(iss))
const {payload} = await jose.jwtVerify(token, jwks, {
audience: OIDC_AUDIENCE,
issuer
audience: OIDC_AUDIENCE
})
return payload
@@ -88,21 +96,3 @@ function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
}
}
// Derive the current OIDC issuer based on the server URL
function getIssuer(): string {
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
// Ensure the server URL is a valid GitHub server URL
if (!VALID_SERVER_URLS.some(valid_url => serverURL.match(valid_url))) {
throw new Error(`Invalid server URL: ${serverURL}`)
}
let host = new URL(serverURL).hostname
if (host === 'github.com') {
host = 'githubusercontent.com'
}
return `https://token.actions.${host}`
}
+4 -2
View File
@@ -5,6 +5,8 @@ import type {Attestation, Predicate} from './shared.types'
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1'
const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'
export type AttestProvenanceOptions = Omit<
AttestOptions,
'predicate' | 'predicateType'
@@ -22,10 +24,10 @@ export type AttestProvenanceOptions = Omit<
* @returns The SLSA provenance predicate.
*/
export const buildSLSAProvenancePredicate = async (
issuer?: string
issuer: string = DEFAULT_ISSUER
): Promise<Predicate> => {
const serverURL = process.env.GITHUB_SERVER_URL
const claims = await getIDTokenClaims(issuer)
const claims = await getIDTokenClaims()
// Split just the path and ref from the workflow string.
// owner/repo/.github/workflows/main.yml@main =>
-3
View File
@@ -1,13 +1,11 @@
import * as github from '@actions/github'
import {retry} from '@octokit/plugin-retry'
import {RequestHeaders} from '@octokit/types'
const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
const DEFAULT_RETRY_COUNT = 5
export type WriteOptions = {
retry?: number
headers?: RequestHeaders
}
/**
* Writes an attestation to the repository's attestations endpoint.
@@ -28,7 +26,6 @@ export const writeAttestation = async (
const response = await octokit.request(CREATE_ATTESTATION_REQUEST, {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
headers: options.headers,
data: {bundle: attestation}
})
-3
View File
@@ -1,8 +1,5 @@
# @actions/glob Releases
### 0.5.0
- Added `excludeHiddenFiles` option, which is disabled by default to preserve existing behavior [#1791: Add glob option to ignore hidden files](https://github.com/actions/toolkit/pull/1791)
### 0.4.0
- Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318)
@@ -708,7 +708,7 @@ describe('globber', () => {
expect(itemPaths).toEqual([])
})
it('returns hidden files by default', async () => {
it('returns hidden files', async () => {
// Create the following layout:
// <root>
// <root>/.emptyFolder
@@ -734,26 +734,6 @@ describe('globber', () => {
])
})
it('ignores hidden files when excludeHiddenFiles is set', async () => {
// Create the following layout:
// <root>
// <root>/.emptyFolder
// <root>/.file
// <root>/.folder
// <root>/.folder/file
const root = path.join(getTestTemp(), 'ignores-hidden-files')
await createHiddenDirectory(path.join(root, '.emptyFolder'))
await createHiddenDirectory(path.join(root, '.folder'))
await createHiddenFile(path.join(root, '.file'), 'test .file content')
await fs.writeFile(
path.join(root, '.folder', 'file'),
'test .folder/file content'
)
const itemPaths = await glob(root, {excludeHiddenFiles: true})
expect(itemPaths).toEqual([root])
})
it('returns normalized paths', async () => {
// Create the following layout:
// <root>/hello/world.txt
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.5.0",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"description": "Actions glob lib",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.5.0",
"version": "0.4.0",
"preview": true,
"description": "Actions glob lib",
"keywords": [
@@ -9,8 +9,7 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
followSymbolicLinks: true,
implicitDescendants: true,
matchDirectories: true,
omitBrokenSymbolicLinks: true,
excludeHiddenFiles: false
omitBrokenSymbolicLinks: true
}
if (copy) {
@@ -33,11 +32,6 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
}
if (typeof copy.excludeHiddenFiles === 'boolean') {
result.excludeHiddenFiles = copy.excludeHiddenFiles
core.debug(`excludeHiddenFiles '${result.excludeHiddenFiles}'`)
}
}
return result
@@ -36,13 +36,4 @@ export interface GlobOptions {
* @default true
*/
omitBrokenSymbolicLinks?: boolean
/**
* Indicates whether to exclude hidden files (files and directories starting with a `.`).
* This does not apply to Windows files and directories with the hidden attribute unless
* they are also prefixed with a `.`.
*
* @default false
*/
excludeHiddenFiles?: boolean
}
-5
View File
@@ -128,11 +128,6 @@ export class DefaultGlobber implements Globber {
continue
}
// Hidden file or directory?
if (options.excludeHiddenFiles && path.basename(item.path).match(/^\./)) {
continue
}
// Directory
if (stats.isDirectory()) {
// Matched
-9
View File
@@ -1,14 +1,5 @@
## Releases
## 2.2.3
- Fixed an issue where proxy username and password were not handled correctly [#1799](https://github.com/actions/toolkit/pull/1799)
## 2.2.2
- Better handling of url encoded usernames and passwords in proxy config [#1782](https://github.com/actions/toolkit/pull/1782)
## 2.2.1
- Make sure RequestOptions.keepAlive is applied properly on node20 runtime [#1572](https://github.com/actions/toolkit/pull/1572)
## 2.2.0
- Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547)
+31 -32
View File
@@ -37,7 +37,7 @@ describe('basics', () => {
// "user-agent": "typed-test-client-tests"
// },
// "origin": "173.95.152.44",
// "url": "http://postman-echo.com/get"
// "url": "https://postman-echo.com/get"
// }
it('does basic http get request', async () => {
@@ -63,17 +63,16 @@ describe('basics', () => {
expect(obj.headers['user-agent']).toBeFalsy()
})
/* TODO write a mock rather then relying on a third party
it('does basic https get request', async () => {
const res: httpm.HttpClientResponse = await _http.get(
'http://postman-echo.com/get'
'https://postman-echo.com/get'
)
expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://postman-echo.com/get')
expect(obj.url).toBe('https://postman-echo.com/get')
})
*/
it('does basic http get request with default headers', async () => {
const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
@@ -126,12 +125,12 @@ describe('basics', () => {
it('pipes a get request', async () => {
return new Promise<void>(async resolve => {
const file = fs.createWriteStream(sampleFilePath)
;(await _http.get('http://postman-echo.com/get')).message
;(await _http.get('https://postman-echo.com/get')).message
.pipe(file)
.on('close', () => {
const body: string = fs.readFileSync(sampleFilePath).toString()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://postman-echo.com/get')
expect(obj.url).toBe('https://postman-echo.com/get')
resolve()
})
})
@@ -139,32 +138,32 @@ describe('basics', () => {
it('does basic get request with redirects', async () => {
const res: httpm.HttpClientResponse = await _http.get(
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'http://postman-echo.com/get'
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get'
)}`
)
expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://postman-echo.com/get')
expect(obj.url).toBe('https://postman-echo.com/get')
})
it('does basic get request with redirects (303)', async () => {
const res: httpm.HttpClientResponse = await _http.get(
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'http://postman-echo.com/get'
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get'
)}&status_code=303`
)
expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://postman-echo.com/get')
expect(obj.url).toBe('https://postman-echo.com/get')
})
it('returns 404 for not found get request on redirect', async () => {
const res: httpm.HttpClientResponse = await _http.get(
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'http://postman-echo.com/status/404'
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/status/404'
)}&status_code=303`
)
expect(res.message.statusCode).toBe(404)
@@ -178,8 +177,8 @@ describe('basics', () => {
{allowRedirects: false}
)
const res: httpm.HttpClientResponse = await http.get(
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'http://postman-echo.com/get'
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get'
)}`
)
expect(res.message.statusCode).toBe(302)
@@ -192,8 +191,8 @@ describe('basics', () => {
authorization: 'shhh'
}
const res: httpm.HttpClientResponse = await _http.get(
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'http://www.postman-echo.com/get'
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://www.postman-echo.com/get'
)}`,
headers
)
@@ -205,7 +204,7 @@ describe('basics', () => {
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('http://www.postman-echo.com/get')
expect(obj.url).toBe('https://www.postman-echo.com/get')
})
it('does not pass Auth with diff hostname redirects', async () => {
@@ -214,8 +213,8 @@ describe('basics', () => {
Authorization: 'shhh'
}
const res: httpm.HttpClientResponse = await _http.get(
`http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'http://www.postman-echo.com/get'
`https://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://www.postman-echo.com/get'
)}`,
headers
)
@@ -227,7 +226,7 @@ describe('basics', () => {
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('http://www.postman-echo.com/get')
expect(obj.url).toBe('https://www.postman-echo.com/get')
})
it('does basic head request', async () => {
@@ -290,11 +289,11 @@ describe('basics', () => {
it('gets a json object', async () => {
const jsonObj = await _http.getJson<HttpBinData>(
'http://postman-echo.com/get'
'https://postman-echo.com/get'
)
expect(jsonObj.statusCode).toBe(200)
expect(jsonObj.result).toBeDefined()
expect(jsonObj.result?.url).toBe('http://postman-echo.com/get')
expect(jsonObj.result?.url).toBe('https://postman-echo.com/get')
expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson
)
@@ -305,7 +304,7 @@ describe('basics', () => {
it('getting a non existent json object returns null', async () => {
const jsonObj = await _http.getJson<HttpBinData>(
'http://postman-echo.com/status/404'
'https://postman-echo.com/status/404'
)
expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull()
@@ -314,12 +313,12 @@ describe('basics', () => {
it('posts a json object', async () => {
const res = {name: 'foo'}
const restRes = await _http.postJson<HttpBinData>(
'http://postman-echo.com/post',
'https://postman-echo.com/post',
res
)
expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('http://postman-echo.com/post')
expect(restRes.result?.url).toBe('https://postman-echo.com/post')
expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson
@@ -335,12 +334,12 @@ describe('basics', () => {
it('puts a json object', async () => {
const res = {name: 'foo'}
const restRes = await _http.putJson<HttpBinData>(
'http://postman-echo.com/put',
'https://postman-echo.com/put',
res
)
expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('http://postman-echo.com/put')
expect(restRes.result?.url).toBe('https://postman-echo.com/put')
expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
@@ -357,12 +356,12 @@ describe('basics', () => {
it('patch a json object', async () => {
const res = {name: 'foo'}
const restRes = await _http.patchJson<HttpBinData>(
'http://postman-echo.com/patch',
'https://postman-echo.com/patch',
res
)
expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('http://postman-echo.com/patch')
expect(restRes.result?.url).toBe('https://postman-echo.com/patch')
expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson
@@ -12,7 +12,7 @@ describe('headers', () => {
it('preserves existing headers on getJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.getJson<any>(
'http://postman-echo.com/get',
'https://postman-echo.com/get',
additionalHeaders
)
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo')
@@ -26,7 +26,7 @@ describe('headers', () => {
[httpm.Headers.Accept]: 'baz'
}
}
jsonObj = await httpWithHeaders.getJson<any>('http://postman-echo.com/get')
jsonObj = await httpWithHeaders.getJson<any>('https://postman-echo.com/get')
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
expect(jsonObj.headers[httpm.Headers.ContentType]).toContain(
httpm.MediaTypes.ApplicationJson
@@ -36,7 +36,7 @@ describe('headers', () => {
it('preserves existing headers on postJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.postJson<any>(
'http://postman-echo.com/post',
'https://postman-echo.com/post',
{},
additionalHeaders
)
@@ -52,7 +52,7 @@ describe('headers', () => {
}
}
jsonObj = await httpWithHeaders.postJson<any>(
'http://postman-echo.com/post',
'https://postman-echo.com/post',
{}
)
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
@@ -64,7 +64,7 @@ describe('headers', () => {
it('preserves existing headers on putJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.putJson<any>(
'http://postman-echo.com/put',
'https://postman-echo.com/put',
{},
additionalHeaders
)
@@ -80,7 +80,7 @@ describe('headers', () => {
}
}
jsonObj = await httpWithHeaders.putJson<any>(
'http://postman-echo.com/put',
'https://postman-echo.com/put',
{}
)
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
@@ -92,7 +92,7 @@ describe('headers', () => {
it('preserves existing headers on patchJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.patchJson<any>(
'http://postman-echo.com/patch',
'https://postman-echo.com/patch',
{},
additionalHeaders
)
@@ -108,7 +108,7 @@ describe('headers', () => {
}
}
jsonObj = await httpWithHeaders.patchJson<any>(
'http://postman-echo.com/patch',
'https://postman-echo.com/patch',
{}
)
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
+6 -21
View File
@@ -222,33 +222,30 @@ describe('proxy', () => {
expect(_proxyConnects).toHaveLength(0)
})
// TODO mock this out so we don't rely on a third party
/*
it('HttpClient does basic https get request through proxy', async () => {
process.env['https_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await httpClient.get(
'http://postman-echo.com/get'
'https://postman-echo.com/get'
)
expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://postman-echo.com/get')
expect(obj.url).toBe('https://postman-echo.com/get')
expect(_proxyConnects).toEqual(['postman-echo.com:443'])
})
*/
it('HttpClient does basic http get request when bypass proxy', async () => {
process.env['http_proxy'] = _proxyUrl
it('HttpClient does basic https get request when bypass proxy', async () => {
process.env['https_proxy'] = _proxyUrl
process.env['no_proxy'] = 'postman-echo.com'
const httpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await httpClient.get(
'http://postman-echo.com/get'
'https://postman-echo.com/get'
)
expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://postman-echo.com/get')
expect(obj.url).toBe('https://postman-echo.com/get')
expect(_proxyConnects).toHaveLength(0)
})
@@ -307,18 +304,6 @@ describe('proxy', () => {
console.log(agent)
expect(agent instanceof ProxyAgent).toBe(true)
})
it('proxyAuth is set in tunnel agent when authentication is provided with URIencoding', async () => {
process.env['https_proxy'] =
'http://user%40github.com:p%[email protected]:8080'
const httpClient = new httpm.HttpClient()
const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080')
expect(agent.proxyOptions.proxyAuth).toBe('[email protected]:p@ssword')
})
})
function _clearVars(): void {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/http-client",
"version": "2.2.3",
"version": "2.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/http-client",
"version": "2.2.3",
"version": "2.2.1",
"description": "Actions Http Client",
"keywords": [
"github",
+1 -3
View File
@@ -726,9 +726,7 @@ export class HttpClient {
uri: proxyUrl.href,
pipelining: !this._keepAlive ? 0 : 1,
...((proxyUrl.username || proxyUrl.password) && {
token: `Basic ${Buffer.from(
`${proxyUrl.username}:${proxyUrl.password}`
).toString('base64')}`
token: `${proxyUrl.username}:${proxyUrl.password}`
})
})
this._proxyAgentDispatcher = proxyAgent
+2 -21
View File
@@ -15,10 +15,10 @@ export function getProxyUrl(reqUrl: URL): URL | undefined {
if (proxyVar) {
try {
return new DecodedURL(proxyVar)
return new URL(proxyVar)
} catch {
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
return new DecodedURL(`http://${proxyVar}`)
return new URL(`http://${proxyVar}`)
}
} else {
return undefined
@@ -87,22 +87,3 @@ function isLoopbackAddress(host: string): boolean {
hostLower.startsWith('[0:0:0:0:0:0:0:1]')
)
}
class DecodedURL extends URL {
private _decodedUsername: string
private _decodedPassword: string
constructor(url: string | URL, base?: string | URL) {
super(url, base)
this._decodedUsername = decodeURIComponent(super.username)
this._decodedPassword = decodeURIComponent(super.password)
}
get username(): string {
return this._decodedUsername
}
get password(): string {
return this._decodedPassword
}
}