Skip to content

Commit 86cb5d6

Browse files
authored
error-reporting: Skip authentication for error-reporting when API key is provided (#2554)
PR-URL: #2554
1 parent 46f68c7 commit 86cb5d6

File tree

4 files changed

+128
-25
lines changed

4 files changed

+128
-25
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ Alternatively, you can also follow the instructions for using a service account
116116

117117
## Running Elsewhere
118118

119-
If your application is running outside of Google Cloud Platform, such as locally, on-premise, or on another cloud provider, you can still use Stackdriver Errors.
119+
If your application is running outside of Google Cloud Platform, such as locally, on-premise, or on another cloud provider, you can still use Stackdriver Errors either with locally-stored credentials or with an API Key.
120+
121+
### Using Locally-Stored Credentials
120122

121123
1. You will need to specify your project ID when starting the errors agent.
122124

@@ -145,6 +147,25 @@ If your application is running outside of Google Cloud Platform, such as locally
145147

146148
When running on Google Cloud Platform, we handle these for you automatically.
147149

150+
### Using an API Key
151+
152+
You may use an API key in lieu of locally-stored credentials. Please see [this document][api-key] on how to set up an API key if you do not already have one.
153+
154+
Once you have obtained an API key, you may provide it as part of the Error Reporting instance configuration:
155+
156+
```js
157+
var errors = require('@google-cloud/error-reporting')({
158+
projectId: '{your project ID}',
159+
key: '{your api key}'
160+
});
161+
```
162+
163+
If a key is provided, the module will not attempt to authenticate using the methods associated with locally-stored credentials as mentioned in the previous section.
164+
165+
We recommend using a file, environment variable, or another mechanism to store the API key rather than hard-coding it into your application's source.
166+
167+
**Note:** The Error Reporting instance will check if the provided API key is invalid shortly after it is instantiated. If the key is invalid, an error-level message will be logged to stdout.
168+
148169
## Configuration
149170
150171
The following code snippet lists all available configuration options. All configuration options are optional.
@@ -286,6 +307,7 @@ server.head('/hello/:name', respond);
286307
server.listen(3000);
287308
```
288309
310+
[api-key]: https://support.google.com/cloud/answer/6158862
289311
[app-default-credentials]: https://developers.google.com/identity/protocols/application-default-credentials
290312
[express-error-docs]: https://expressjs.com/en/guide/error-handling.html
291313
[gcloud-sdk]: https://cloud.google.com/sdk/gcloud/

src/google-apis/auth-client.js

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var isString = is.string;
2727
var SCOPES = ['https://www.googleapis.com/auth/cloud-platform'];
2828

2929
/* @const {String} Base Error Reporting API */
30-
var API = 'https://clouderrorreporting.googleapis.com/v1beta1/projects';
30+
var API = 'https://clouderrorreporting.googleapis.com/v1beta1';
3131

3232
/**
3333
* The RequestHandler constructor initializes several properties on the
@@ -70,6 +70,7 @@ class RequestHandler extends common.Service {
7070
}
7171
return null;
7272
}
73+
7374
/**
7475
* No-operation stub function for user callback substitution
7576
* @param {Error|Null} err - the error
@@ -88,26 +89,47 @@ class RequestHandler extends common.Service {
8889
*/
8990
constructor(config, logger) {
9091
var pid = config.getProjectId();
92+
// If an API key is provided, do not try to authenticate.
93+
var tryAuthenticate = !config.getKey();
9194
super({
9295
packageJson: pkg,
93-
baseUrl: 'https://clouderrorreporting.googleapis.com/v1beta1/',
96+
baseUrl: API,
9497
scopes: SCOPES,
9598
projectId: pid !== null ? pid : undefined,
96-
projectIdRequired: true
99+
projectIdRequired: true,
100+
customEndpoint: !tryAuthenticate
97101
}, config);
98102
this._config = config;
99103
this._logger = logger;
100104

101105
var that = this;
102-
this.authClient.getToken(function(err, token) {
103-
if (err) {
104-
that._logger.error([
105-
'Unable to find credential information on instance. This library',
106-
'will be unable to communicate with the Stackdriver API to save',
107-
'errors. Message: ' + err.message
108-
].join(' '));
109-
}
110-
});
106+
if (tryAuthenticate) {
107+
this.authClient.getToken(function(err, token) {
108+
if (err) {
109+
that._logger.error([
110+
'Unable to find credential information on instance. This library',
111+
'will be unable to communicate with the Stackdriver API to save',
112+
'errors. Message: ' + err.message
113+
].join(' '));
114+
}
115+
});
116+
} else {
117+
this.request({
118+
uri: 'events:report',
119+
qs: RequestHandler.manufactureQueryString(this._config.getKey()),
120+
method: 'POST',
121+
json: {}
122+
}, (err, body, response) => {
123+
if (err && err.message !== 'Message cannot be empty.' &&
124+
response.statusCode === 400) {
125+
this._logger.error([
126+
'Encountered an error while attempting to validate the provided',
127+
'API key'
128+
].join(' '), err);
129+
}
130+
});
131+
that._logger.info('API key provided; skipping OAuth2 token request.');
132+
}
111133
}
112134
/**
113135
* Creates a request options object given the value of the error message and
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var forEach = require('lodash.foreach');
3131
var assign = require('lodash.assign');
3232
var pick = require('lodash.pick');
3333
var omitBy = require('lodash.omitby');
34+
var request = require('request');
3435
var util = require('util');
3536
var path = require('path');
3637

@@ -362,6 +363,41 @@ describe('Expected Behavior', function() {
362363
});
363364
});
364365

366+
describe('Error Reporting API', function() {
367+
[
368+
{
369+
name: 'when a valid API key is given',
370+
getKey: () => env.apiKey,
371+
message: 'Message cannot be empty.'
372+
},
373+
{
374+
name: 'when an empty API key is given',
375+
getKey: () => '',
376+
message: 'The request is missing a valid API key.'
377+
},
378+
{
379+
name: 'when an invalid API key is given',
380+
getKey: () => env.apiKey.slice(1) + env.apiKey[0],
381+
message: 'API key not valid. Please pass a valid API key.'
382+
}
383+
].forEach(function(testCase) {
384+
it(`should return an expected message ${testCase.name}`, function(done) {
385+
this.timeout(30000);
386+
const API = 'https://clouderrorreporting.googleapis.com/v1beta1';
387+
const key = testCase.getKey();
388+
request.post({
389+
url: `${API}/projects/${env.projectId}/events:report?key=${key}`,
390+
json: {},
391+
}, (err, response, body) => {
392+
assert.ok(!err && body.error);
393+
assert.strictEqual(response.statusCode, 400);
394+
assert.strictEqual(body.error.message, testCase.message);
395+
done();
396+
});
397+
});
398+
});
399+
});
400+
365401
describe('error-reporting', function() {
366402
const SRC_ROOT = path.join(__dirname, '..', 'src');
367403
const TIMESTAMP = Date.now();

test/unit/google-apis/auth-client.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ var proxyquire = require('proxyquire');
2020

2121
var Configuration = require('../../../src/configuration.js');
2222

23-
function verifyReportedMessage(errToReturn, expectedMessage) {
23+
function verifyReportedMessage(config, errToReturn, expectedLogs) {
2424
class ServiceStub {
2525
constructor() {
2626
this.authClient = {
2727
getToken: function(cb) {
2828
cb(errToReturn);
2929
}
3030
};
31+
this.request = function() {};
3132
}
3233
}
3334

@@ -37,28 +38,50 @@ function verifyReportedMessage(errToReturn, expectedMessage) {
3738
}
3839
});
3940

40-
var message = '';
41+
var logs = {};
4142
var logger = {
4243
error: function(text) {
43-
message += text;
44+
if (!logs.error) {
45+
logs.error = '';
46+
}
47+
logs.error += text;
48+
},
49+
info: function(text) {
50+
if (!logs.info) {
51+
logs.info = '';
52+
}
53+
logs.info += text;
4454
}
4555
};
46-
var config = new Configuration({ ignoreEnvironmentCheck: true }, logger);
56+
var config = new Configuration(config, logger);
4757
new RequestHandler(config, logger);
48-
assert.strictEqual(message, expectedMessage);
58+
assert.deepStrictEqual(logs, expectedLogs);
4959
}
50-
5160
describe('RequestHandler', function() {
61+
it('should not request OAuth2 token if key is provided', function() {
62+
var config = {
63+
ignoreEnvironmentCheck: true,
64+
key: 'key'
65+
};
66+
var message = 'Made OAuth2 Token Request';
67+
verifyReportedMessage(config, new Error(message), {
68+
info: 'API key provided; skipping OAuth2 token request.'
69+
});
70+
});
71+
5272
it('should issue a warning if it cannot communicate with the API', function() {
73+
var config = { ignoreEnvironmentCheck: true };
5374
var message = 'Test Error';
54-
verifyReportedMessage(new Error(message),
55-
'Unable to find credential information on instance. This library ' +
56-
'will be unable to communicate with the Stackdriver API to save ' +
57-
'errors. Message: ' + message);
75+
verifyReportedMessage(config, new Error(message), {
76+
error: 'Unable to find credential information on instance. This ' +
77+
'library will be unable to communicate with the Stackdriver API to ' +
78+
'save errors. Message: ' + message
79+
});
5880
});
5981

6082
it('should not issue a warning if it can communicate with the API', function() {
61-
verifyReportedMessage(null, '');
62-
verifyReportedMessage(undefined, '');
83+
var config = { ignoreEnvironmentCheck: true };
84+
verifyReportedMessage(config, null, {});
85+
verifyReportedMessage(config, undefined, {});
6386
});
6487
});

0 commit comments

Comments
 (0)