1 | ;
|
2 | // Copyright 2021 Google LLC
|
3 | //
|
4 | // Licensed under the Apache License, Version 2.0 (the "License");
|
5 | // you may not use this file except in compliance with the License.
|
6 | // You may obtain a copy of the License at
|
7 | //
|
8 | // http://www.apache.org/licenses/LICENSE-2.0
|
9 | //
|
10 | // Unless required by applicable law or agreed to in writing, software
|
11 | // distributed under the License is distributed on an "AS IS" BASIS,
|
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | // See the License for the specific language governing permissions and
|
14 | // limitations under the License.
|
15 | var _a, _b, _c;
|
16 | Object.defineProperty(exports, "__esModule", { value: true });
|
17 | exports.IdentityPoolClient = void 0;
|
18 | const fs = require("fs");
|
19 | const util_1 = require("util");
|
20 | const baseexternalclient_1 = require("./baseexternalclient");
|
21 | // fs.readfile is undefined in browser karma tests causing
|
22 | // `npm run browser-test` to fail as test.oauth2.ts imports this file via
|
23 | // src/index.ts.
|
24 | // Fallback to void function to avoid promisify throwing a TypeError.
|
25 | const readFile = util_1.promisify((_a = fs.readFile) !== null && _a !== void 0 ? _a : (() => { }));
|
26 | const realpath = util_1.promisify((_b = fs.realpath) !== null && _b !== void 0 ? _b : (() => { }));
|
27 | const lstat = util_1.promisify((_c = fs.lstat) !== null && _c !== void 0 ? _c : (() => { }));
|
28 | /**
|
29 | * Defines the Url-sourced and file-sourced external account clients mainly
|
30 | * used for K8s and Azure workloads.
|
31 | */
|
32 | class IdentityPoolClient extends baseexternalclient_1.BaseExternalAccountClient {
|
33 | /**
|
34 | * Instantiate an IdentityPoolClient instance using the provided JSON
|
35 | * object loaded from an external account credentials file.
|
36 | * An error is thrown if the credential is not a valid file-sourced or
|
37 | * url-sourced credential or a workforce pool user project is provided
|
38 | * with a non workforce audience.
|
39 | * @param options The external account options object typically loaded
|
40 | * from the external account JSON credential file.
|
41 | * @param additionalOptions Optional additional behavior customization
|
42 | * options. These currently customize expiration threshold time and
|
43 | * whether to retry on 401/403 API request errors.
|
44 | */
|
45 | constructor(options, additionalOptions) {
|
46 | var _a, _b;
|
47 | super(options, additionalOptions);
|
48 | this.file = options.credential_source.file;
|
49 | this.url = options.credential_source.url;
|
50 | this.headers = options.credential_source.headers;
|
51 | if (!this.file && !this.url) {
|
52 | throw new Error('No valid Identity Pool "credential_source" provided');
|
53 | }
|
54 | // Text is the default format type.
|
55 | this.formatType = ((_a = options.credential_source.format) === null || _a === void 0 ? void 0 : _a.type) || 'text';
|
56 | this.formatSubjectTokenFieldName = (_b = options.credential_source.format) === null || _b === void 0 ? void 0 : _b.subject_token_field_name;
|
57 | if (this.formatType !== 'json' && this.formatType !== 'text') {
|
58 | throw new Error(`Invalid credential_source format "${this.formatType}"`);
|
59 | }
|
60 | if (this.formatType === 'json' && !this.formatSubjectTokenFieldName) {
|
61 | throw new Error('Missing subject_token_field_name for JSON credential_source format');
|
62 | }
|
63 | }
|
64 | /**
|
65 | * Triggered when a external subject token is needed to be exchanged for a GCP
|
66 | * access token via GCP STS endpoint.
|
67 | * This uses the `options.credential_source` object to figure out how
|
68 | * to retrieve the token using the current environment. In this case,
|
69 | * this either retrieves the local credential from a file location (k8s
|
70 | * workload) or by sending a GET request to a local metadata server (Azure
|
71 | * workloads).
|
72 | * @return A promise that resolves with the external subject token.
|
73 | */
|
74 | async retrieveSubjectToken() {
|
75 | if (this.file) {
|
76 | return await this.getTokenFromFile(this.file, this.formatType, this.formatSubjectTokenFieldName);
|
77 | }
|
78 | return await this.getTokenFromUrl(this.url, this.formatType, this.formatSubjectTokenFieldName, this.headers);
|
79 | }
|
80 | /**
|
81 | * Looks up the external subject token in the file path provided and
|
82 | * resolves with that token.
|
83 | * @param file The file path where the external credential is located.
|
84 | * @param formatType The token file or URL response type (JSON or text).
|
85 | * @param formatSubjectTokenFieldName For JSON response types, this is the
|
86 | * subject_token field name. For Azure, this is access_token. For text
|
87 | * response types, this is ignored.
|
88 | * @return A promise that resolves with the external subject token.
|
89 | */
|
90 | async getTokenFromFile(filePath, formatType, formatSubjectTokenFieldName) {
|
91 | // Make sure there is a file at the path. lstatSync will throw if there is
|
92 | // nothing there.
|
93 | try {
|
94 | // Resolve path to actual file in case of symlink. Expect a thrown error
|
95 | // if not resolvable.
|
96 | filePath = await realpath(filePath);
|
97 | if (!(await lstat(filePath)).isFile()) {
|
98 | throw new Error();
|
99 | }
|
100 | }
|
101 | catch (err) {
|
102 | err.message = `The file at ${filePath} does not exist, or it is not a file. ${err.message}`;
|
103 | throw err;
|
104 | }
|
105 | let subjectToken;
|
106 | const rawText = await readFile(filePath, { encoding: 'utf8' });
|
107 | if (formatType === 'text') {
|
108 | subjectToken = rawText;
|
109 | }
|
110 | else if (formatType === 'json' && formatSubjectTokenFieldName) {
|
111 | const json = JSON.parse(rawText);
|
112 | subjectToken = json[formatSubjectTokenFieldName];
|
113 | }
|
114 | if (!subjectToken) {
|
115 | throw new Error('Unable to parse the subject_token from the credential_source file');
|
116 | }
|
117 | return subjectToken;
|
118 | }
|
119 | /**
|
120 | * Sends a GET request to the URL provided and resolves with the returned
|
121 | * external subject token.
|
122 | * @param url The URL to call to retrieve the subject token. This is typically
|
123 | * a local metadata server.
|
124 | * @param formatType The token file or URL response type (JSON or text).
|
125 | * @param formatSubjectTokenFieldName For JSON response types, this is the
|
126 | * subject_token field name. For Azure, this is access_token. For text
|
127 | * response types, this is ignored.
|
128 | * @param headers The optional additional headers to send with the request to
|
129 | * the metadata server url.
|
130 | * @return A promise that resolves with the external subject token.
|
131 | */
|
132 | async getTokenFromUrl(url, formatType, formatSubjectTokenFieldName, headers) {
|
133 | const opts = {
|
134 | url,
|
135 | method: 'GET',
|
136 | headers,
|
137 | responseType: formatType,
|
138 | };
|
139 | let subjectToken;
|
140 | if (formatType === 'text') {
|
141 | const response = await this.transporter.request(opts);
|
142 | subjectToken = response.data;
|
143 | }
|
144 | else if (formatType === 'json' && formatSubjectTokenFieldName) {
|
145 | const response = await this.transporter.request(opts);
|
146 | subjectToken = response.data[formatSubjectTokenFieldName];
|
147 | }
|
148 | if (!subjectToken) {
|
149 | throw new Error('Unable to parse the subject_token from the credential_source URL');
|
150 | }
|
151 | return subjectToken;
|
152 | }
|
153 | }
|
154 | exports.IdentityPoolClient = IdentityPoolClient;
|
155 | //# sourceMappingURL=identitypoolclient.js.map |
\ | No newline at end of file |