Skip to content

Commit 4e3b0f8

Browse files
authored
Add support for oauth authorization (#489)
1 parent 4920487 commit 4e3b0f8

File tree

10 files changed

+189
-16
lines changed

10 files changed

+189
-16
lines changed

lib-es5/api_client/call_api.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ var ensurePresenceOf = utils.ensurePresenceOf;
1212
function call_api(method, uri, params, callback, options) {
1313
ensurePresenceOf({ method, uri });
1414
var api_url = utils.base_api_url(uri, options);
15-
var auth = {
16-
key: ensureOption(options, "api_key"),
17-
secret: ensureOption(options, "api_secret")
18-
};
19-
15+
var auth = {};
16+
if (options.oauth_token || config().oauth_token) {
17+
auth = {
18+
oauth_token: ensureOption(options, "oauth_token")
19+
};
20+
} else {
21+
auth = {
22+
key: ensureOption(options, "api_key"),
23+
secret: ensureOption(options, "api_secret")
24+
};
25+
}
2026
return execute_request(method, params, auth, api_url, callback, options);
2127
}
2228

lib-es5/api_client/execute_request.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function execute_request(method, params, auth, api_url, callback) {
2323
handle_response = void 0; // declare to user later
2424
var key = auth.key;
2525
var secret = auth.secret;
26+
var oauth_token = auth.oauth_token;
2627
var content_type = 'application/x-www-form-urlencoded';
2728

2829
if (options.content_type === 'json') {
@@ -43,9 +44,15 @@ function execute_request(method, params, auth, api_url, callback) {
4344
headers: {
4445
'Content-Type': content_type,
4546
'User-Agent': utils.getUserAgent()
46-
},
47-
auth: key + ":" + secret
47+
}
4848
});
49+
50+
if (oauth_token) {
51+
request_options.headers.Authorization = `Bearer ${oauth_token}`;
52+
} else {
53+
request_options.auth = key + ":" + secret;
54+
}
55+
4956
if (options.agent != null) {
5057
request_options.agent = options.agent;
5158
}

lib-es5/uploader.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var https = isSecure ? require('https') : require('http');
3333
var Cache = require('./cache');
3434
var utils = require("./utils");
3535
var UploadStream = require('./upload_stream');
36+
var config = require("./config");
3637

3738
var build_upload_params = utils.build_upload_params,
3839
extend = utils.extend,
@@ -558,7 +559,6 @@ function call_api(action, callback, options, get_params) {
558559

559560
return Buffer.from(encodeFieldPart(boundary, key, value), 'utf8');
560561
});
561-
562562
var result = post(api_url, post_data, boundary, file, handle_response, options);
563563
if (isObject(result)) {
564564
return result;
@@ -572,6 +572,7 @@ function call_api(action, callback, options, get_params) {
572572
function post(url, post_data, boundary, file, callback, options) {
573573
var file_header = void 0;
574574
var finish_buffer = Buffer.from("--" + boundary + "--", 'ascii');
575+
var oauth_token = options.oauth_token || config().oauth_token;
575576
if (file != null || options.stream) {
576577
// eslint-disable-next-line no-nested-ternary
577578
var filename = options.stream ? options.filename ? options.filename : "file" : basename(file);
@@ -588,6 +589,10 @@ function post(url, post_data, boundary, file, callback, options) {
588589
if (options.x_unique_upload_id != null) {
589590
headers['X-Unique-Upload-Id'] = options.x_unique_upload_id;
590591
}
592+
if (oauth_token != null) {
593+
headers.Authorization = `Bearer ${oauth_token}`;
594+
}
595+
591596
post_options = extend(post_options, {
592597
method: 'POST',
593598
headers: headers

lib-es5/utils/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,11 +1106,14 @@ function process_request_params(params, options) {
11061106
if (options.unsigned != null && options.unsigned) {
11071107
params = exports.clear_blank(params);
11081108
delete params.timestamp;
1109+
} else if (options.oauth_token || config().oauth_token) {
1110+
params = exports.clear_blank(options);
11091111
} else if (options.signature) {
11101112
params = exports.clear_blank(options);
11111113
} else {
11121114
params = exports.sign_request(params, options);
11131115
}
1116+
11141117
return params;
11151118
}
11161119

lib/api_client/call_api.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ const { ensurePresenceOf } = utils;
99
function call_api(method, uri, params, callback, options) {
1010
ensurePresenceOf({ method, uri });
1111
const api_url = utils.base_api_url(uri, options);
12-
const auth = {
13-
key: ensureOption(options, "api_key"),
14-
secret: ensureOption(options, "api_secret")
15-
};
16-
12+
let auth = {};
13+
if (options.oauth_token || config().oauth_token){
14+
auth = {
15+
oauth_token: ensureOption(options, "oauth_token")
16+
};
17+
} else {
18+
auth = {
19+
key: ensureOption(options, "api_key"),
20+
secret: ensureOption(options, "api_secret")
21+
};
22+
}
1723
return execute_request(method, params, auth, api_url, callback, options);
1824
}
1925

lib/api_client/execute_request.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ function execute_request(method, params, auth, api_url, callback, options = {})
1616
let query_params, handle_response; // declare to user later
1717
let key = auth.key;
1818
let secret = auth.secret;
19+
let oauth_token = auth.oauth_token;
1920
let content_type = 'application/x-www-form-urlencoded';
2021

2122
if (options.content_type === 'json') {
@@ -36,9 +37,15 @@ function execute_request(method, params, auth, api_url, callback, options = {})
3637
headers: {
3738
'Content-Type': content_type,
3839
'User-Agent': utils.getUserAgent()
39-
},
40-
auth: key + ":" + secret
40+
}
4141
});
42+
43+
if (oauth_token) {
44+
request_options.headers.Authorization = `Bearer ${oauth_token}`;
45+
} else {
46+
request_options.auth = key + ":" + secret
47+
}
48+
4249
if (options.agent != null) {
4350
request_options.agent = options.agent;
4451
}

lib/uploader.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const https = isSecure ? require('https') : require('http');
1313
const Cache = require('./cache');
1414
const utils = require("./utils");
1515
const UploadStream = require('./upload_stream');
16+
const config = require("./config");
1617

1718
const {
1819
build_upload_params,
@@ -459,7 +460,6 @@ function call_api(action, callback, options, get_params) {
459460
.map(
460461
([key, value]) => Buffer.from(encodeFieldPart(boundary, key, value), 'utf8')
461462
);
462-
463463
let result = post(api_url, post_data, boundary, file, handle_response, options);
464464
if (isObject(result)) {
465465
return result;
@@ -473,6 +473,7 @@ function call_api(action, callback, options, get_params) {
473473
function post(url, post_data, boundary, file, callback, options) {
474474
let file_header;
475475
let finish_buffer = Buffer.from("--" + boundary + "--", 'ascii');
476+
let oauth_token = options.oauth_token || config().oauth_token;
476477
if ((file != null) || options.stream) {
477478
// eslint-disable-next-line no-nested-ternary
478479
let filename = options.stream ? options.filename ? options.filename : "file" : basename(file);
@@ -489,6 +490,10 @@ function post(url, post_data, boundary, file, callback, options) {
489490
if (options.x_unique_upload_id != null) {
490491
headers['X-Unique-Upload-Id'] = options.x_unique_upload_id;
491492
}
493+
if (oauth_token != null) {
494+
headers.Authorization = `Bearer ${oauth_token}`;
495+
}
496+
492497
post_options = extend(post_options, {
493498
method: 'POST',
494499
headers: headers

lib/utils/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,11 +1017,14 @@ function process_request_params(params, options) {
10171017
if ((options.unsigned != null) && options.unsigned) {
10181018
params = exports.clear_blank(params);
10191019
delete params.timestamp;
1020+
} else if (options.oauth_token || config().oauth_token) {
1021+
params = exports.clear_blank(options);
10201022
} else if (options.signature) {
10211023
params = exports.clear_blank(options);
10221024
} else {
10231025
params = exports.sign_request(params, options);
10241026
}
1027+
10251028
return params;
10261029
}
10271030

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
const sinon = require('sinon');
2+
const cloudinary = require("../../../../cloudinary");
3+
const helper = require("../../../spechelper");
4+
const describe = require('../../../testUtils/suite');
5+
const testConstants = require('../../../testUtils/testConstants');
6+
const { PUBLIC_IDS } = testConstants;
7+
const { PUBLIC_ID } = PUBLIC_IDS;
8+
9+
describe("oauth_token", function(){
10+
it("should send the oauth_token option to the server (admin_api)", function() {
11+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
12+
cloudinary.v2.api.resource(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' });
13+
return sinon.assert.calledWith(requestSpy,
14+
sinon.match.has("headers",
15+
sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4")
16+
));
17+
});
18+
});
19+
20+
it("should send the oauth_token config to the server (admin_api)", function() {
21+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
22+
cloudinary.config({
23+
api_key: undefined,
24+
api_secret: undefined,
25+
oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4'
26+
});
27+
cloudinary.v2.api.resource(PUBLIC_ID);
28+
return sinon.assert.calledWith(requestSpy,
29+
sinon.match.has("headers",
30+
sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4")
31+
));
32+
});
33+
});
34+
35+
it("should not fail when only providing api_key and secret (admin_api)", function() {
36+
cloudinary.config({
37+
api_key: "1234",
38+
api_secret: "1234",
39+
oauth_token: undefined
40+
});
41+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
42+
cloudinary.v2.api.resource(PUBLIC_ID);
43+
return sinon.assert.calledWith(requestSpy, sinon.match({ auth: "1234:1234" }));
44+
});
45+
});
46+
47+
it("should fail when missing all credentials (admin_api)", function() {
48+
cloudinary.config({
49+
api_key: undefined,
50+
api_secret: undefined,
51+
oauth_token: undefined
52+
});
53+
expect(() => {
54+
cloudinary.v2.api.resource(PUBLIC_ID)
55+
}).to.throwError(/Must supply api_key/);
56+
});
57+
58+
it("oauth_token as option should take priority with secret and key (admin_api)", function() {
59+
cloudinary.config({
60+
api_key: '1234',
61+
api_secret: '1234'
62+
});
63+
return cloudinary.v2.api.resource(PUBLIC_ID, {oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4'})
64+
.then(
65+
() => expect().fail()
66+
).catch(({ error }) => expect(error.message).to.contain("Invalid token"));
67+
});
68+
69+
it("should send the oauth_token option to the server (upload_api)", function() {
70+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
71+
cloudinary.v2.uploader.upload(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' });
72+
return sinon.assert.calledWith(requestSpy,
73+
sinon.match.has("headers",
74+
sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4")
75+
));
76+
});
77+
});
78+
79+
it("should send the oauth_token config to the server (upload_api)", function() {
80+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
81+
cloudinary.config({
82+
api_key: undefined,
83+
api_secret: undefined,
84+
oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4'
85+
});
86+
cloudinary.v2.uploader.upload(PUBLIC_ID);
87+
return sinon.assert.calledWith(requestSpy,
88+
sinon.match.has("headers",
89+
sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4")
90+
));
91+
});
92+
});
93+
94+
it("should not fail when only providing api_key and secret (upload_api)", function() {
95+
cloudinary.config({
96+
api_key: "1234",
97+
api_secret: "1234",
98+
oauth_token: undefined
99+
});
100+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
101+
cloudinary.v2.uploader.upload(PUBLIC_ID)
102+
return sinon.assert.calledWith(requestSpy, sinon.match({ auth: null }));
103+
});
104+
});
105+
106+
it("should fail when missing all credentials (upload_api)", function() {
107+
cloudinary.config({
108+
api_key: undefined,
109+
api_secret: undefined,
110+
oauth_token: undefined
111+
});
112+
expect(() => {
113+
cloudinary.v2.uploader.upload(PUBLIC_ID)
114+
}).to.throwError(/Must supply api_key/);
115+
});
116+
117+
it("should not need credentials for unsigned upload", function() {
118+
cloudinary.config({
119+
api_key: undefined,
120+
api_secret: undefined,
121+
oauth_token: undefined
122+
});
123+
return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
124+
cloudinary.v2.uploader.unsigned_upload(PUBLIC_ID, 'preset')
125+
return sinon.assert.calledWith(requestSpy, sinon.match({ auth: null }));
126+
});
127+
});
128+
});

types/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ declare module 'cloudinary' {
342342
account_id?: string;
343343
provisioning_api_key?: string;
344344
provisioning_api_secret?: string;
345+
oauth_token?: string;
345346

346347
[futureKey: string]: any;
347348
}
@@ -387,6 +388,7 @@ declare module 'cloudinary' {
387388
export interface AdminApiOptions {
388389
agent?: object;
389390
content_type?: string;
391+
oauth_token?: string;
390392

391393
[futureKey: string]: any;
392394
}
@@ -509,6 +511,7 @@ declare module 'cloudinary' {
509511
use_filename?: boolean;
510512
chunk_size?: number;
511513
disable_promises?: boolean;
514+
oauth_token?: string;
512515

513516
[futureKey: string]: any;
514517
}

0 commit comments

Comments
 (0)