Skip to content

Commit

Permalink
Fix issue with loading admin from VC (#302)
Browse files Browse the repository at this point in the history
* Use organization identifier for admin white list
* Fix issue loading stats
  • Loading branch information
fdelavega authored Oct 30, 2024
1 parent 29589c8 commit f298e28
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 40 deletions.
52 changes: 33 additions & 19 deletions controllers/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,53 @@ const logger = require('./../lib/logger').logger.getLogger('TMF')

function stats() {

const pageData = async function(baseUrl, mapper) {
let start = 0
let limit = 50
let complete = false
let data = []

while (!complete) {
let productUrl = baseUrl + `&offset=${start}&limit=${limit}`
const response = await axios.request({
method: 'GET',
url: productUrl
})

if (response.data.length == 0) {
complete = true
}

data = data.concat(response.data.map(mapper))
start += limit
}
return data
}

const loadStats = async function() {
// Get the list of launched offering
const productUrl = utils.getAPIProtocol('catalog') + '://' + utils.getAPIHost('catalog') + ':' + utils.getAPIPort('catalog') + '/productOffering?lifecycleStatus=Launched&fields=name'
const offers = await axios.request({
method: 'GET',
url: productUrl
const productBaseUrl = utils.getAPIProtocol('catalog') + '://' + utils.getAPIHost('catalog') + ':' + utils.getAPIPort('catalog') + '/productOffering?lifecycleStatus=Launched&fields=name'
const offers = await pageData(productBaseUrl, (off) => {
return off.name
})

// Get the list of organizations
const partyUrl = utils.getAPIProtocol('party') + '://' + utils.getAPIHost('party') + ':' + utils.getAPIPort('party') + '/organization?fields=tradingName'
const parties = await axios.request({
method: 'GET',
url: partyUrl
const partyBaseUrl = utils.getAPIProtocol('party') + '://' + utils.getAPIHost('party') + ':' + utils.getAPIPort('party') + '/organization?fields=tradingName'
const parties = await pageData(partyBaseUrl, (part) => {
return part.tradingName
})

// Save data in MongoDB
const res = await statsSchema.findOne()
const services = offers.data.map((off) => {
return off.name
})

const organizations = parties.data.map((part) => {
return part.tradingName
})

if (res) {
res.services = services
res.organizations = organizations
res.services = offers
res.organizations = parties
await res.save()
} else {
const newStat = new statsSchema()
newStat.services = services
newStat.organizations = organizations
newStat.services = offers
newStat.organizations = parties
await newStat.save()
}
}
Expand Down
24 changes: 4 additions & 20 deletions lib/strategies/passport-vc.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ class LEARCredSubject {
this.firstName = individual.first_name
this.roles = []

if (config.operators.indexOf(issuer) > -1) {
const organization = mandate.mandator
const orgRoles = this.mapRoles(mandate)

if (config.operators.indexOf(issuer) > -1 || config.operators.indexOf(organization.organizationIdentifier) > -1) {
this.roles.push({
id: 'admin',
name: 'admin'
})
}

const organization = mandate.mandator
const orgRoles = this.mapRoles(mandate)

this.organization = {
id: organization.organizationIdentifier,
name: organization.organization,
Expand Down Expand Up @@ -293,15 +293,12 @@ class VCStrategy extends Strategy {
}

try {
console.log('Building eliptic')

const ec = new EC('p256'); // P-256 curve (also known as secp256r1)
const key = ec.keyFromPrivate(this.privateKey);

// Get the public key in uncompressed format (includes both x and y coordinates)
const publicKey = key.getPublic();

console.log('Building public key')
// Extract x and y coordinates and encode them in Base64url format
const x = publicKey.getX().toString('hex'); // Hex representation of x
const y = publicKey.getY().toString('hex'); // Hex representation of y
Expand All @@ -316,7 +313,6 @@ class VCStrategy extends Strategy {

const keyObject = crypto.createPrivateKey({ format: 'jwk', key: jwk })

console.log('Signing token')
const token = jwt.sign(tokenInfo, keyObject, {
keyid: this.clientID,
algorithm: 'ES256'
Expand All @@ -330,7 +326,6 @@ class VCStrategy extends Strategy {
'client_assertion': token
}

console.log('Calling token')
this.makeTokenRequest(params)
} catch (e) {
console.log(e)
Expand Down Expand Up @@ -358,22 +353,17 @@ class VCStrategy extends Strategy {

authenticatePolling(req, options) {
if (req.query && req.query.state && req.query.code) {
console.log("________ AUTH CODE ______________")
console.log("State: " + req.query.state)
console.log("Code: " + req.query.code)

this._stateCache.set(req.query.state, req.query.code);
return this.pass();
} else if (options.poll && options.state) {
console.log("________ GETTIMG CREDS ______________")
const authCode = this._stateCache.get(options.state);

if (authCode === null || authCode === undefined) {
return this.fail('No auth code received yet');
}
this._stateCache.del(options.state);

console.log(authCode)
this.requestToken(req, authCode);
} else {
return this.fail('Authentication failed');
Expand All @@ -385,10 +375,6 @@ class VCStrategy extends Strategy {
return this.fail('Authentication failed');
}

console.log("________ AUTH CODE REDIR____________")
console.log("State: " + req.query.state)
console.log("Code: " + req.query.code)

const authCode = req.query.code
this.requestTokenWithClientSign(req, authCode);
}
Expand All @@ -406,7 +392,6 @@ class VCStrategy extends Strategy {
}

loadUserProfile(accessToken) {
console.log(accessToken)
const refreshToken = accessToken;

this.userProfile(accessToken, (err, profile) => {
Expand Down Expand Up @@ -450,7 +435,6 @@ class VCStrategy extends Strategy {
}

verifyToken(accessToken, callback) {
console.log("______ TOKEN VERIFICATION ___________")
const decodedToken = jwt.decode(accessToken, {'complete': true});
const header = decodedToken.header;
const payload = decodedToken.payload;
Expand Down
2 changes: 1 addition & 1 deletion test/controllers/admin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2023 Future Internet Consulting and Development Solutions S.L.
/* Copyright (c) 2024 Future Internet Consulting and Development Solutions S.L.
*
* This file belongs to the business-ecosystem-logic-proxy of the
* Business API Ecosystem
Expand Down
122 changes: 122 additions & 0 deletions test/controllers/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* Copyright (c) 2024 Future Internet Consulting and Development Solutions S.L.
*
* This file belongs to the business-ecosystem-logic-proxy of the
* Business API Ecosystem
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

const proxyquire = require('proxyquire');

describe('Stats Controller', () => {

const utils = {
getAPIProtocol: function() {
return 'https';
},
getAPIPort: function() {
return 1234;
},
getAPIHost: function() {
return 'example.com';
}
}

const getStatsInstance = function (axios, cron, schema) {
let mocks = {
'axios': axios,
'node-cron': cron,
'../lib/utils': utils,
'../db/schemas/stats': schema
}

return proxyquire('../../controllers/stats', mocks).stats()
}

it('should load stats when init is called', (done) => {
// Mock axios
const axios = jasmine.createSpyObj('axios', ['request'])
axios.request.and.returnValues(Promise.resolve({
data: [{
'name': 'offering1'
}, {
'name': 'offering2'
}, {
'name': 'offering3'
}]
}), Promise.resolve({
data: [{
'name': 'offering4'
}]
}), Promise.resolve({
data: []
}), Promise.resolve({
data: [{
'tradingName': 'party1'
}]
}), Promise.resolve({
data: []
}))

const cron = jasmine.createSpyObj('node-cron', ['schedule'])
const schema = jasmine.createSpyObj('node-cron', ['findOne'])
const dbObject = {
services: [],
organizations: [],
save: () => {}
}

spyOn(dbObject, 'save')

schema.findOne.and.returnValue(Promise.resolve(dbObject))

const instance = getStatsInstance(axios, cron, schema)

instance.init().then(() => {
// Check the calls
expect(axios.request).toHaveBeenCalledTimes(5); // Check the number of calls

expect(axios.request.calls.argsFor(0)).toEqual([{
url: 'https://example.com:1234/productOffering?lifecycleStatus=Launched&fields=name&offset=0&limit=50',
method: 'GET'
}]);

expect(axios.request.calls.argsFor(1)).toEqual([{
url: 'https://example.com:1234/productOffering?lifecycleStatus=Launched&fields=name&offset=50&limit=50',
method: 'GET'
}]);

expect(axios.request.calls.argsFor(2)).toEqual([{
url: 'https://example.com:1234/productOffering?lifecycleStatus=Launched&fields=name&offset=100&limit=50',
method: 'GET'
}]);

expect(axios.request.calls.argsFor(3)).toEqual([{
url: 'https://example.com:1234/organization?fields=tradingName&offset=0&limit=50',
method: 'GET'
}]);

expect(axios.request.calls.argsFor(4)).toEqual([{
url: 'https://example.com:1234/organization?fields=tradingName&offset=50&limit=50',
method: 'GET'
}]);

expect(dbObject.services).toEqual(['offering1', 'offering2', 'offering3', 'offering4'])
expect(dbObject.organizations).toEqual(['party1'])

expect(dbObject.save).toHaveBeenCalled()
done()
})
})
})

0 comments on commit f298e28

Please sign in to comment.