Skip to content

Commit

Permalink
Manage service and resource specs lifecycle (#308)
Browse files Browse the repository at this point in the history
* Update portal
* Fix issue creating productOffering bundles
* Update service specification controller to validate lifecycle updates
* Validate lifecycle status of resource specs
* Fix certifier power to upload certificates
  • Loading branch information
fdelavega authored Dec 4, 2024
1 parent 8659359 commit 2a6d399
Show file tree
Hide file tree
Showing 16 changed files with 701 additions and 232 deletions.
2 changes: 1 addition & 1 deletion controllers/tmf-apis/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ const catalog = (function() {
});
}

var offeringPath = url.parse(offering.href).pathname;
const offeringPath = `/productOffering/${offering.id}`
retrieveAsset(offeringPath, function(err, result) {
if (err) {
var id = offering.id ? offering.id : '';
Expand Down
126 changes: 78 additions & 48 deletions controllers/tmf-apis/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const tmfUtils = require('./../../lib/tmfUtils')
const axios = require('axios')
const config = require('./../../config')

var resource = (function (){
const resource = (function (){

var validateRetrieving = function(req, callback) {
const validateRetrieving = function(req, callback) {
// Check if the request is a list of resources specifications
if (req.path.endsWith('resourceSpecification') && req.user != null) {
return tmfUtils.filterRelatedPartyFields(req, () => tmfUtils.ensureRelatedPartyIncluded(req, callback));
Expand All @@ -35,7 +35,7 @@ var resource = (function (){
// validate if a resource specification is returned only by the owner
};

var getResourceAPIUrl = function(path) {
const getResourceAPIUrl = function(path) {
const resPath = path.replace(`/${config.endpoints.resource.path}/`, '')

return utils.getAPIURL(
Expand All @@ -61,69 +61,99 @@ var resource = (function (){
});
}
}).catch((err) => {
callback({
status: err.response.status
});
let errCb = {
status: err.status
}

if (err.response) {
errCb = {
status: err.response.status
}
}
callback(errCb);
})
};


var validateOwnerSeller = function(req, callback) {
retrieveAsset(req.apiUrl, function(err, response) {
if (err) {
if (err.status === 404) {
callback({
status: 404,
message: 'The required resource does not exist'
});
} else {
callback({
status: 500,
message: 'The required resource cannot be created/updated'
});
}
} else {
if (!tmfUtils.hasPartyRole(req, response.body.relatedParty, 'owner') || !utils.hasRole(req.user, config.oauth2.roles.seller)) {
callback({
status: 403,
message: 'Unauthorized to update non-owned/non-seller resources'
});
}
else{
callback(null)
}
}
});
};

var validateOwnerSellerPost = function(req, callback) {
let body;
try {
body = JSON.parse(req.body);
} catch (e) {
const getPrevVersion = function(req, callback) {
retrieveAsset(req.apiUrl, (err, response) => {
if (err) {
if (err.status === 404) {
callback({
status: 404,
message: 'The required resource does not exist'
});
} else {
callback({
status: 500,
message: 'The required resource cannot be created/updated'
});
}
} else {
req.prevBody = response.body
callback(null)
}
});
}

const parseBody = function (req, callback) {
try {
req.parsedBody = JSON.parse(req.body);
} catch (e) {
callback({
status: 400,
message: 'The provided body is not a valid JSON'
});

return; // EXIT
}
callback(null)
}

const validateOwnerSeller = function(req, callback) {
if (!tmfUtils.hasPartyRole(req, req.prevBody.relatedParty, 'owner') || !utils.hasRole(req.user, config.oauth2.roles.seller)) {
callback({
status: 400,
message: 'The provided body is not a valid JSON'
status: 403,
message: 'Unauthorized to update non-owned/non-seller resource specs'
});

return; // EXIT
} else{
callback(null)
}
};

const validateUpdate = function(req, callback) {
// Check the lifecycle updates
const body = req.parsedBody
const prevBody = req.prevBody

if (body.lifecycleStatus != null && !tmfUtils.isValidStatusTransition(prevBody.lifecycleStatus, body.lifecycleStatus)) {
// The status is being updated
return callback({
status: 400,
message: `Cannot transition from lifecycle status ${prevBody.lifecycleStatus} to ${body.lifecycleStatus}`
})
}

callback(null)
}

const validateOwnerSellerPost = function(req, callback) {
const body = req.parsedBody

if (!tmfUtils.hasPartyRole(req, body.relatedParty, 'owner') || !utils.hasRole(req.user, config.oauth2.roles.seller)) {
callback({
status: 403,
message: 'Unauthorized to create non-owned/non-seller resources'
message: 'Unauthorized to create non-owned/non-seller resource specs'
});
}
else{
callback(null)
}
};

var validators = {
const validators = {
GET: [validateRetrieving],
POST: [utils.validateLoggedIn, validateOwnerSellerPost],
PATCH: [utils.validateLoggedIn, validateOwnerSeller],
POST: [utils.validateLoggedIn, parseBody, validateOwnerSellerPost],
PATCH: [utils.validateLoggedIn, parseBody, getPrevVersion, validateUpdate, validateOwnerSeller],
PUT: [utils.methodNotAllowed],
DELETE: [utils.methodNotAllowed]
};
Expand Down
71 changes: 51 additions & 20 deletions controllers/tmf-apis/serviceCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const serviceCatalog = (function() {
// validate if a service specification is returned only by the owner
};

var getResourceAPIUrl = function(path) {
const getResourceAPIUrl = function(path) {
const resPath = path.replace(`/${config.endpoints.service.path}/`, '')

return utils.getAPIURL(
Expand All @@ -67,14 +67,21 @@ const serviceCatalog = (function() {
});
}
}).catch((err) => {
callback({
status: err.response.status
});
let errCb = {
status: err.status
}

if (err.response) {
errCb = {
status: err.response.status
}
}
callback(errCb);
})
};

const validateOwnerSeller = function(req, callback) {
retrieveAsset(req.apiUrl, function(err, response) {
const getPrevVersion = function(req, callback) {
retrieveAsset(req.apiUrl, (err, response) => {
if (err) {
if (err.status === 404) {
callback({
Expand All @@ -88,22 +95,15 @@ const serviceCatalog = (function() {
});
}
} else {
if (!tmfUtils.hasPartyRole(req, response.body.relatedParty, 'owner') || !utils.hasRole(req.user, config.oauth2.roles.seller)) {
callback({
status: 403,
message: 'Unauthorized to update non-owned/non-seller services'
});
} else {
callback(null)
}
req.prevBody = response.body
callback(null)
}
});
};
}

const validateOwnerSellerPost = function(req, callback) {
let body;
const parseBody = function (req, callback) {
try {
body = JSON.parse(req.body);
req.parsedBody = JSON.parse(req.body);
} catch (e) {
callback({
status: 400,
Expand All @@ -112,7 +112,22 @@ const serviceCatalog = (function() {

return; // EXIT
}
callback(null)
}

const validateOwnerSeller = function(req, callback) {
if (!tmfUtils.hasPartyRole(req, req.prevBody.relatedParty, 'owner') || !utils.hasRole(req.user, config.oauth2.roles.seller)) {
callback({
status: 403,
message: 'Unauthorized to update non-owned/non-seller services'
});
} else {
callback(null)
}
};

const validateOwnerSellerPost = function(req, callback) {
const body = req.parsedBody
if (!tmfUtils.hasPartyRole(req, body.relatedParty, 'owner') || !utils.hasRole(req.user, config.oauth2.roles.seller)) {
callback({
status: 403,
Expand All @@ -123,10 +138,26 @@ const serviceCatalog = (function() {
}
};

const validateUpdate = function(req, callback) {
// Check the lifecycle updates
const body = req.parsedBody
const prevBody = req.prevBody

if (body.lifecycleStatus != null && !tmfUtils.isValidStatusTransition(prevBody.lifecycleStatus, body.lifecycleStatus)) {
// The status is being updated
return callback({
status: 400,
message: `Cannot transition from lifecycle status ${prevBody.lifecycleStatus} to ${body.lifecycleStatus}`
})
}

callback(null)
}

const validators = {
GET: [validateRetrieving],
POST: [utils.validateLoggedIn, validateOwnerSellerPost],
PATCH: [utils.validateLoggedIn, validateOwnerSeller],
POST: [utils.validateLoggedIn, parseBody, validateOwnerSellerPost],
PATCH: [utils.validateLoggedIn, parseBody, getPrevVersion, validateUpdate, validateOwnerSeller],
PUT: [utils.methodNotAllowed],
DELETE: [utils.methodNotAllowed]
};
Expand Down
4 changes: 2 additions & 2 deletions lib/strategies/passport-vc.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class LEARCredentialSubject extends LEARCredSubject {
'id': 'seller',
'name': 'seller'
}
} else if (func == 'certification' && (action.includes('UploadCertificate') || action.includes('post_verifiable_certification'))) {
} else if (func == 'certification' && (action.includes('Upload') || action.includes('post_verifiable_certification'))) {
role = {
'id': 'certifier',
'name': 'certifier'
Expand Down Expand Up @@ -163,7 +163,7 @@ class LEARCredentialMachineSubject extends LEARCredSubject {
'id': 'seller',
'name': 'seller'
}
} else if (power.function.toLowerCase() == 'certification' && (power.action.includes('UploadCertificate') || power.action.includes('post_verifiable_certification'))) {
} else if (power.function.toLowerCase() == 'certification' && (power.action.includes('Upload') || power.action.includes('post_verifiable_certification'))) {
role = {
'id': 'certifier',
'name': 'certifier'
Expand Down
10 changes: 10 additions & 0 deletions lib/tmfUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,13 @@ exports.equalCustomCharacteristics = function(chars1, chars2) {
// Compare resulting characteristics
return equal(filteredChars1, filteredChars2);
};


exports.isValidStatusTransition = function(firstSt, nextSt) {
const lifecycle = ['active', 'launched', 'retired', 'obsolete']

const firstInd = lifecycle.indexOf(firstSt.toLowerCase())
const nextInd = lifecycle.indexOf(nextSt.toLowerCase())

return firstInd > -1 && nextInd > -1 && nextInd == firstInd + 1
}
Loading

0 comments on commit 2a6d399

Please sign in to comment.