Skip to content

Commit

Permalink
Fix awardBadge to bake images
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Nov 25, 2013
1 parent 04b5775 commit 9953aec
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 79 deletions.
3 changes: 1 addition & 2 deletions controllers/demo.js
Expand Up @@ -53,7 +53,7 @@ exports.massAward = function (req, res) {
awardBadge({
assertion: item.assertion,
url: item.assertionUrl,
public_path: [email, item.baseName].join(':'),
publicPath: [email, item.baseName].join(':'),
imagedata: item.imgData,
recipient: email
}, function(err) {
Expand Down Expand Up @@ -104,4 +104,3 @@ function makeDemoAssertion(email, image, title, description) {
}
});
}

50 changes: 35 additions & 15 deletions lib/award.js
Expand Up @@ -29,41 +29,36 @@ function md5obj(obj) {

function award(opts, callback) {
callback = callback||function () {};
const badgeDir = configuration.get('badge_path');
const filename = md5obj(opts.assertion) + '.png';
const filepath = path.join(badgeDir, filename);
const originalImage = opts.imagedata||opts.imageData;

async.parallel({
user: function getUser(callback) {
User.findOrCreate(opts.recipient, callback);
},
imageData: function checkBadgeBaked(callback) {
if (!opts.url)
return callback();
bakery.extract(originalImage, function (err, data) {
if (data)
return callback(null, originalImage);
bakery.bake({
image: originalImage,
data: opts.url
}, callback);
})
const bakeryOptions = createBakeryOptions(opts)
bakery.bake(bakeryOptions, callback);
}
}, function (err, results) {
if (err) return callback(err);
const user = results.user
const bakedImage = results.imageData

const badge = new Badge({
body: opts.assertion,
user_id: results.user.get('id'),
endpoint: opts.url,
signature: opts.signature,
public_path: opts.public_path,
image_path: path.join(badgeDir.replace(/^.*?static/, ''), filename),
public_path: opts.publicPath,
image_path: makeBadgeImagePath(filename),
});

const badgeImage = new BadgeImage({
badge_hash: Badge.createHash(opts.assertion),
image_data: opts.imagedata.toString('base64')
image_data: bakedImage.toString('base64')
})

async.series({
badge: badge.save.bind(badge),
image: badgeImage.save.bind(badgeImage)
Expand All @@ -74,3 +69,28 @@ function award(opts, callback) {
}

module.exports = award;

function createBakeryOptions(opts) {
const result = { image: opts.imagedata || opts.imageData }

if (opts.signature) {
result.signature = opts.signature
return result
}

const assertion = opts.assertion

// pre-1.0.0 assertions did not have a verify structure and we need
// one for baking, so what we're gonna do here is just create one and
// stick it to the assertion so the bakery can do its thing.
if (!assertion.verify)
assertion.verify = { type: 'hosted', url: opts.url }

result.assertion = assertion
return result
}

function makeBadgeImagePath(filename) {
const badgeDir = configuration.get('badge_path');
return path.join(badgeDir.replace(/^.*?static/, ''), filename)
}
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -30,7 +30,7 @@
"underscore": "~1.3.3",
"optimist": "~0.3.0",
"openbadges-validator": "0.0.21",
"openbadges-bakery": "0.2.7",
"openbadges-bakery": "1.0.0",
"newrelic": "~0.10.3",
"async": "~0.2.6",
"messina": "~0.1.1",
Expand Down
148 changes: 87 additions & 61 deletions test/award.test.js
Expand Up @@ -3,8 +3,8 @@ const jws = require('jws');
const test = require('tap').test;
const fs = require('fs');
const path = require('path');
const bakery = require('openbadges-bakery')
const awardBadge = require('../lib/award');
const mysql = require('../lib/mysql');
const Badge = require('../models/badge');
const BadgeImage = require('../models/badge-image');
const normalize = require('../lib/normalize-assertion');
Expand All @@ -13,100 +13,126 @@ const TEST_ASSERTION = $.makeAssertion();
const BADGE_DIRECTORY = path.basename(require('../lib/configuration').get('badge_path'));
const PNG_DATA = fs.readFileSync(path.join(__dirname, '/utils/images/no-badge-data.png'));

const NEW_ASSERTION = {
version: '1.0.0',
structures: {
assertion: {
"uid": "f2c20",
"recipient": {
"type": "email",
"hashed": true,
"salt": "deadsea",
"id": "sha256$c7ef86405ba71b85acd8e2e95166c4b111448089f2e1599f42fe1bba46e865c5"
},
"image": "https://example.org/beths-robot-badge.png",
"evidence": "https://example.org/beths-robot-work.html",
"issuedOn": 1359217910,
"badge": "https://example.org/robotics-badge.json",
"verify": {
"type": "hosted",
"url": "https://example.org/beths-robotics-badge.json"
}
},
badge: {
"name": "Awesome Robotics Badge",
"description": "For doing awesome things with robots that people think is pretty great.",
"image": "https://example.org/robotics-badge.png",
"criteria": "https://example.org/robotics-badge.html",
"tags": ["robots", "awesome"],
"issuer": "https://example.org/organization.json",
"alignment": [
{ "name": "CCSS.ELA-Literacy.RST.11-12.3",
"url": "http://www.corestandards.org/ELA-Literacy/RST/11-12/3",
"description": "Follow precisely a complex multistep procedure when carrying out experiments, taking measurements, or performing technical tasks; analyze the specific results based on explanations in the text."
},
{ "name": "CCSS.ELA-Literacy.RST.11-12.9",
"url": "http://www.corestandards.org/ELA-Literacy/RST/11-12/9",
"description": " Synthesize information from a range of sources (e.g., texts, experiments, simulations) into a coherent understanding of a process, phenomenon, or concept, resolving conflicting information when possible."
}
]
},
issuer: {
"name": "An Example Badge Issuer",
"image": "https://example.org/logo.png",
"url": "https://example.org/some/path",
"email": "steved@example.org",
"revocationList": "https://example.org/revoked.json"
}
}
};

$.prepareDatabase(function (done) {
test('awardBadge: old assertion', function (t) {
const endpoint = 'http://example.com/badge';
const badgeData = {
assertion: TEST_ASSERTION,
url: endpoint,
assertion: TEST_ASSERTION,
imagedata: PNG_DATA,
recipient: TEST_ASSERTION.recipient
}

t.plan(4)

awardBadge(badgeData, function (err, badge) {
t.notOk(err, 'should not have an error');
Badge.find({endpoint: endpoint}, function (err, badges) {
t.notOk(err, 'should not have an error');
t.same(badges.length, 1, 'should have exactly one badge');
const badgePath = badges[0].get('image_path');

const badgeHash = badge.get('body_hash')

BadgeImage.findOne({ badge_hash: badgeHash }, function (err, image) {
const bakedImage = image.toBuffer()

bakery.extract(bakedImage, function (err, result) {
t.notOk(err, 'no error getting baked data')

const debakedAssertion = JSON.parse(result)
t.same(debakedAssertion.verify.url, endpoint, 'has right endpoint')
})
})


Badge.findOne({endpoint: endpoint}, function (err, badge) {
const badgePath = badge.get('image_path');
t.ok(badgePath.match(BADGE_DIRECTORY), 'should match');
t.end();
});
})
});

test('awardBadge: signed new assertion', function (t) {
const newAssertion = createNewAssertion()

const signature = jws.sign({
header: {alg: 'rs256'},
payload: NEW_ASSERTION.structures.assertion,
payload: newAssertion.structures.assertion,
privateKey: fs.readFileSync(__dirname + '/rsa-private.pem')
});

const normalizedAssertion = normalize(NEW_ASSERTION);
const normalizedAssertion = normalize(newAssertion);

awardBadge({
assertion: normalizedAssertion,
imagedata: PNG_DATA,
recipient: 'brian@example.org',
signature: signature
}, function (err, badge) {

t.same(signature, badge.get('signature'));
t.same(normalizedAssertion.uid, badge.getFromAssertion('uid'));

// get the badge image
BadgeImage.findOne({ badge_hash: badge.get('body_hash') }, function (err, image) {
t.same(image.toBuffer(), PNG_DATA);
t.end();
const query = { badge_hash: badge.get('body_hash') }
BadgeImage.findOne(query, function (err, image) {
const imageData = image.toBuffer()
t.ok(imageData != PNG_DATA, 'badge should be different');
bakery.extract(imageData, function (err, result) {
t.notOk(err, 'no errors')
t.same(result, signature, 'has right signature')
t.end();
})
})
});
});

$.finish(test);
});


function createNewAssertion() {
return {
version: '1.0.0',
structures: {
assertion: {
"uid": "f2c20",
"recipient": {
"type": "email",
"hashed": true,
"salt": "deadsea",
"id": "sha256$c7ef86405ba71b85acd8e2e95166c4b111448089f2e1599f42fe1bba46e865c5"
},
"image": "https://example.org/beths-robot-badge.png",
"evidence": "https://example.org/beths-robot-work.html",
"issuedOn": 1359217910,
"badge": "https://example.org/robotics-badge.json",
"verify": {
"type": "hosted",
"url": "https://example.org/beths-robotics-badge.json"
}
},
badge: {
"name": "Awesome Robotics Badge",
"description": "For doing awesome things with robots that people think is pretty great.",
"image": "https://example.org/robotics-badge.png",
"criteria": "https://example.org/robotics-badge.html",
"tags": ["robots", "awesome"],
"issuer": "https://example.org/organization.json",
"alignment": [
{ "name": "CCSS.ELA-Literacy.RST.11-12.3",
"url": "http://www.corestandards.org/ELA-Literacy/RST/11-12/3",
"description": "Follow precisely a complex multistep procedure when carrying out experiments, taking measurements, or performing technical tasks; analyze the specific results based on explanations in the text."
},
{ "name": "CCSS.ELA-Literacy.RST.11-12.9",
"url": "http://www.corestandards.org/ELA-Literacy/RST/11-12/9",
"description": " Synthesize information from a range of sources (e.g., texts, experiments, simulations) into a coherent understanding of a process, phenomenon, or concept, resolving conflicting information when possible."
}
]
},
issuer: {
"name": "An Example Badge Issuer",
"image": "https://example.org/logo.png",
"url": "https://example.org/some/path",
"email": "steved@example.org",
"revocationList": "https://example.org/revoked.json"
}
}
}
}

0 comments on commit 9953aec

Please sign in to comment.