Skip to content

Commit

Permalink
auto convert .p12 to .pem so user doesn't have to
Browse files Browse the repository at this point in the history
  • Loading branch information
rayshan committed Nov 23, 2014
1 parent 3ab8467 commit ae9f81f
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
node_modules

# below contain secrets
*.pem
*.p12
options.json
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ node_js:
before_install:
- openssl aes-256-cbc
-k $encrypt_file_password
-in test/fixtures/gaServiceAcctKeyDev.pem.enc
-out test/fixtures/gaServiceAcctKeyDev.pem
-in test/fixtures/gaServiceAcctKeyDev.p12.enc
-out test/fixtures/gaServiceAcctKeyDev.p12
-d
- openssl aes-256-cbc
-k $encrypt_file_password
Expand Down
37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,27 @@ npm install --save ga-extractor

## Usage

Sign up for a GA [Service Account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount), download your `.pem` private key.
Sign up for a GA [Service Account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount), download your `.p12` private key.

```js
var gaExtractor = require('ga-extractor');

var options = {
profileId: "xxxxxxxx", // optional, define once here or define a different one in every queryObj
clientEmail: "[email protected]",
keyPath: "test/fixtures/xxx.pem",
keyContent: "Bag Attributes...", // need either keyPath or keyContent
// need either keyPath or keyContent
keyPath: "test/fixtures/xxx.p12",
keyContent: "Bag Attributes...",
// below are optional
profileId: "xxxxxxxx", // define once here or define a different one in every queryObj
impersonatedUser: "[email protected]"
proxy: "http://proxy.example.com"
};

var gaExtractor = new GaExtractor(options);

// To build your query, see
// https://developers.google.com/analytics/devguides/reporting/core/v3/reference#q_summary
// and https://developers.google.com/analytics/devguides/reporting/core/v3/reference
var queryObj = {
'start-date': '31daysAgo',
'end-date': 'yesterday',
Expand All @@ -50,16 +54,19 @@ var queryObj = {
// no need to define 'max-results', always extracts all data
};

// no need to call .auth, extraction only happen after successful auth
gaExtractor.extract(queryObj)
.then(function (data) {
// do something with data returned, e.g. transform & load into database
})
.catch(function (err) {console.error(err)})
data = [
["United States", "5471"],
["United Kingdom", "1084"],
["France", "801"]
// ...
];
})
.catch(function (err) {console.error(err)});
```

To construct your `queryObj`, see [here](https://developers.google.com/analytics/devguides/reporting/core/v3/reference#q_summary) and [here](https://developers.google.com/analytics/devguides/reporting/core/v3/reference).

To try your query without writing any code, use the [Query Explorer Tool](https://ga-dev-tools.appspot.com/explorer/).

Example of original data returned by GA API:
Expand Down Expand Up @@ -102,22 +109,16 @@ Example of original data returned by GA API:
}
```

`.extract` returns only what's in the `rows` object above:
```json
[
["United States", "5471"],
["United Kingdom", "1084"],
["France", "801"]
]
```
`.extract` returns only the content of `rows` object above.


## Contribution

Install dependencies: `npm install`

Ensure tests pass: `npm test`

TODO: all tests are integration tests and go against live GA API server. Docs on fixtures needed for testing coming soon. In the mean time you can submit a PR and owner will manually test.
TODO: all tests are integration tests and execute against live GA API server. Docs on fixtures needed for testing coming soon. In the mean time you can submit a PR and owner will manually test.

Build .js for distribution: `npm build`

Expand Down
29 changes: 21 additions & 8 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(function() {
var GaExtractor, Promise, RateLimiter, gaMaxRowsPerRequest, gaRateLimiter, p;
var GaExtractor, Promise, RateLimiter, gaMaxRowsPerRequest, gaRateLimiter, gp12, p;

p = require('path');

Expand All @@ -9,6 +9,8 @@

gaRateLimiter = Promise.promisifyAll(new RateLimiter(1, 500));

gp12 = Promise.promisify(require('google-p12-pem'));

gaMaxRowsPerRequest = 10000;

GaExtractor = (function() {
Expand All @@ -29,22 +31,33 @@
},
proxy: this.config.proxy
}).data.ga);
this.authClient = new this.gApi.auth.JWT(this.config.clientEmail, this.config.keyPath, this.config.keyContent, "https://www.googleapis.com/auth/analytics.readonly", this.impersonatedUser);
return;
}

GaExtractor.prototype.auth = function() {
return new Promise((function(_this) {
return function(resolve, reject) {
_this.authClient.authorize(function(err, token) {
if (err) {
reject(new Error("OAuth error; err = " + err.error));
var _convertKey;
_convertKey = new Promise(function(_resolve) {
if (_this.config.keyPath) {
return _resolve(gp12(_this.config.keyPath));
} else {
resolve(token);
return _resolve(null);
}
});
_this.gApi.options({
auth: _this.authClient
return _convertKey.then(function(keyContent) {
var _authClient;
_authClient = new _this.gApi.auth.JWT(_this.config.clientEmail, null, keyContent, "https://www.googleapis.com/auth/analytics.readonly", _this.impersonatedUser);
_authClient.authorize(function(err, token) {
if (err) {
return reject(new Error("OAuth error; err = " + err.error));
} else {
return resolve(token);
}
});
return _this.gApi.options({
auth: _authClient
});
});
};
})(this));
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"homepage": "https://github.com/rayshan/ga-extractor",
"dependencies": {
"bluebird": "~2.3",
"google-p12-pem": "0.0.1",
"googleapis": "~1.0",
"limiter": "~1.0.5"
},
Expand All @@ -43,7 +44,7 @@
"coffee-script": "~1.8",
"gulp": "~3.8",
"gulp-coffee": "^2.2.0",
"gulp-mocha": "~1.1",
"gulp-mocha": "^2.x",
"mocha": "~2.0"
},
"license": "MIT"
Expand Down
44 changes: 25 additions & 19 deletions src/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ p = require 'path'
Promise = require 'bluebird'
RateLimiter = require('limiter').RateLimiter
# promisify for better control flow
# limit # of concurrent requests to 1 in 1/2 sec to not hammer GA server
gaRateLimiter = Promise.promisifyAll new RateLimiter 1, 500
gp12 = Promise.promisify require 'google-p12-pem'

# ==========

Expand All @@ -24,34 +26,38 @@ class GaExtractor
proxy: @config.proxy
}).data.ga

# define auth obj; needed for initial auth & extractions
@authClient = new @gApi.auth.JWT(
@config.clientEmail,
@config.keyPath, # key as .pem file
@config.keyContent,
"https://www.googleapis.com/auth/analytics.readonly", # scope uri
@impersonatedUser
)
return

auth: -> new Promise (resolve, reject) =>
@authClient.authorize (err, token) ->
# returns expiry_date: 1406182540 (16 days) and refresh_token: 'jwt-placeholder'
if err
reject new Error "OAuth error; err = #{ err.error }"
# convert .p12 key to .pem
_convertKey = new Promise (_resolve) =>
if @config.keyPath
_resolve gp12 @config.keyPath
else
resolve token
return
@gApi.options auth: @authClient
return
_resolve null

_convertKey.then (keyContent) =>
_authClient = new @gApi.auth.JWT(
@config.clientEmail
null
keyContent
"https://www.googleapis.com/auth/analytics.readonly" # scope uri
@impersonatedUser
)

_authClient.authorize (err, token) =>
# returns .expiry_date in 1 hr
if err
reject new Error "OAuth error; err = #{ err.error }"
else
resolve token
@gApi.options auth: _authClient

extract: (queryObj) ->
# limit # of concurrent requests to not hammer GA server
@auth().delay 500
.bind @
.then gaRateLimiter.removeTokensAsync 1
# .get(0) - for some reason data[1] is whole object returned by request again
.then -> @ga.getAsync(queryObj).get 0
.then -> @ga.getAsync(queryObj).get 0 # [1] is whole object returned by request again
.then (results) ->
data = results.rows
return data unless results.totalResults > gaMaxRowsPerRequest
Expand Down
Binary file added test/fixtures/gaServiceAcctKeyDev.p12.enc
Binary file not shown.
Binary file removed test/fixtures/gaServiceAcctKeyDev.pem.enc
Binary file not shown.
Binary file modified test/fixtures/options.json.enc
Binary file not shown.
4 changes: 2 additions & 2 deletions test/test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ describe 'GaExtractor', ->
clientEmail: options.clientEmail
}).should.throw Error

it 'should instantiate with a .pem key file', ->
it 'should instantiate with a .p12 key file', ->
(-> new GaExtractor {
profileId: options.profileId
clientEmail: options.clientEmail
keyPath: options.keyPath
}).should.not.throw Error

it 'should instantiate with keyContent as string read from .pem file', ->
it 'should instantiate with keyContent as string read from .p12 file', ->
(-> new GaExtractor {
profileId: options.profileId
clientEmail: options.clientEmail
Expand Down

0 comments on commit ae9f81f

Please sign in to comment.