Skip to content

Commit

Permalink
Merge 4a3cbef into 76d7b75
Browse files Browse the repository at this point in the history
  • Loading branch information
Traksewt committed Nov 13, 2017
2 parents 76d7b75 + 4a3cbef commit 74d32b7
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 6 deletions.
8 changes: 6 additions & 2 deletions lib/http-invocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,13 @@ HttpInvocation.prototype.createRequest = function() {
// the body is json
req.json = true;

// add auth if it is set
if (auth) {
if (auth && auth.accessToken) {
// use regular headers to send LoopBack's access token
req.headers = req.headers || {};
req.headers.Authorization = auth.accessToken.id;
} else if (auth) {
req.auth = {};
// add auth if it is set
if (auth.username && auth.password) {
req.auth.username = auth.username;
req.auth.password = auth.password;
Expand Down
51 changes: 49 additions & 2 deletions lib/rest-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function RestAdapter(remotes, options) {

this.remotes = remotes;
this.Context = HttpContext;
this.options = options || (remotes.options || {}).rest;
this.options = options || (remotes.options || {}).rest || {};
this.typeRegistry = remotes._typeRegistry;
}

Expand Down Expand Up @@ -133,6 +133,32 @@ RestAdapter.prototype.connect = function(url) {
this.connection = url;
};

/**
*
* Get the authorization to use when invoking a remote method.
*
* @param {Object} invocationOptions The value of the "options" argument
* of the invoked method
* @private
*/
RestAdapter.prototype._getInvocationAuth = function(invocationOptions) {
const auth = this.remotes.auth;
if (auth || !this.options.passAccessToken) {
// Use the globally-configured authentication credentials
return auth;
}

// Check the `options` argument provided by the caller of the invoked method
// It may have the access token that can be used
const accessToken = invocationOptions && invocationOptions.accessToken;
if (accessToken) {
return {accessToken};
}

// No authentication credentials are configured.
return undefined;
};

RestAdapter.prototype.invoke = function(method, ctorArgs, args, callback) {
assert(this.connection,
g.f('Cannot invoke method without a connection. See {{RemoteObjects#connect().}}'));
Expand All @@ -152,9 +178,11 @@ RestAdapter.prototype.invoke = function(method, ctorArgs, args, callback) {
if (!restMethod) {
return callback(new Error(g.f('Cannot invoke unkown method: %s', method)));
}
const invokeOptions = restMethod.getArgByName('options', args);
const auth = this._getInvocationAuth(invokeOptions);

var invocation = new HttpInvocation(
restMethod, ctorArgs, args, this.connection, remotes.auth, this.typeRegistry
restMethod, ctorArgs, args, this.connection, auth, this.typeRegistry
);
var ctx = invocation.context;
ctx.req = invocation.createRequest();
Expand Down Expand Up @@ -633,6 +661,25 @@ function RestMethod(restClass, sharedMethod) {
}
}

/**
* Get the argument from the invoked arg array by arg name.
* @param argName the name of the arg to lookup
* @param invokedArgs array
* @returns {*} the arg value or undefined if not found
*/
RestMethod.prototype.getArgByName = function(argName, invokedArgs) {
let argValue;
if (!this.accepts || !this.accepts.length) return undefined;
this.accepts.some(function(argProperty, i) {
if (argProperty.arg && argProperty.arg.toLowerCase() === argName.toLowerCase()) {
argValue = invokedArgs[i];
return true;
}
return false;
});
return argValue;
};

RestMethod.prototype.isReturningArray = function() {
return this.returns.length == 1 &&
this.returns[0].root &&
Expand Down
15 changes: 13 additions & 2 deletions test/http-invocation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ describe('HttpInvocation', function() {
expect(inv.createRequest()).to.eql(expectedReq);
});

it('creates a loopback auth request', function() {
const inv = givenInvocationForEndpoint(null, [], null,
{accessToken: {id: 'abc'}});
expect(inv.createRequest().headers).to.have.property('Authorization', 'abc');
});

it('makes primitive type arguments as query params', function() {
var accepts = [
{arg: 'a', type: 'number'},
Expand Down Expand Up @@ -347,14 +353,19 @@ function givenInvocation(method, params) {
params.typeRegistry || new TypeRegistry());
}

function givenInvocationForEndpoint(accepts, args, verb) {
function givenInvocationForEndpoint(accepts, args, verb, auth) {
var method = givenSharedStaticMethod({
accepts: accepts,
});
method.getEndpoints = function() {
return [createEndpoint({verb: verb || 'GET'})];
};
return givenInvocation(method, {ctorArgs: [], args: args, baseUrl: 'http://base'});
return givenInvocation(method, {
ctorArgs: [],
args: args,
baseUrl: 'http://base',
auth: auth,
});
}

function createEndpoint(config) {
Expand Down
56 changes: 56 additions & 0 deletions test/rest-adapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,31 @@ describe('RestAdapter', function() {
});
});

describe('getArgByName()', function() {
const acceptsTwoArgs = [
{arg: 'argName1', type: String},
{arg: 'argName2', type: String},
];

it('should find the first arg', function() {
const method = givenRestStaticMethod({accepts: acceptsTwoArgs});
expect(method.getArgByName('argName1', ['firstArg', 'secondArg']))
.to.equal('firstArg');
});

it('should not find the second arg', function() {
const method = givenRestStaticMethod({accepts: acceptsTwoArgs});
expect(method.getArgByName('argName2', ['firstArg', 'secondArg']))
.to.equal('secondArg');
});

it('should not find argument not defined in metadata', function() {
const method = givenRestStaticMethod({accepts: acceptsTwoArgs});
expect(method.getArgByName('unknown-arg', ['firstArg', 'secondArg']))
.to.equal(undefined);
});
});

describe('acceptsSingleBodyArgument()', function() {
it('returns true when the arg is a single Object from body', function() {
var method = givenRestStaticMethod({
Expand Down Expand Up @@ -474,6 +499,37 @@ describe('RestAdapter', function() {
}
});

describe('_getInvocationAuth()', function() {
let remotes, restAdapter;
beforeEach(() => {
remotes = RemoteObjects.create({cors: false});
restAdapter = new RestAdapter(remotes, {
passAccessToken: true,
});
});

it('should find the access token in the options from the args', () => {
const accessToken = {id: 'def'};
const options = {accessToken: accessToken};
const auth = restAdapter._getInvocationAuth(options);
expect(auth).to.deep.equal(options);
});

it('should find the auth from the remote', () => {
remotes.auth = {bearer: 'zzz'};
const auth = restAdapter._getInvocationAuth(undefined);
expect(auth).to.deep.equal(remotes.auth);
});

it('should prefer global auth over invocation options', () => {
remotes.auth = {bearer: 'zzz'};
const accessToken = {id: 'def'};
const options = {accessToken: accessToken};
const auth = restAdapter._getInvocationAuth(options);
expect(auth).to.deep.equal(remotes.auth);
});
});

describe('getRestMethodByName()', function() {
var SHARED_CLASS_NAME = 'testClass';
var METHOD_NAME = 'testMethod';
Expand Down

0 comments on commit 74d32b7

Please sign in to comment.