2013-11-10 06:22:16 +00:00
|
|
|
var assert = require('assert');
|
|
|
|
var loopback = require('../index');
|
2014-10-09 15:32:03 +00:00
|
|
|
var Scope = loopback.Scope;
|
|
|
|
var ACL = loopback.ACL;
|
|
|
|
var Role = loopback.Role;
|
|
|
|
var RoleMapping = loopback.RoleMapping;
|
2013-11-10 06:22:16 +00:00
|
|
|
var User = loopback.User;
|
2013-12-18 05:10:05 +00:00
|
|
|
var testModel;
|
2013-11-10 06:22:16 +00:00
|
|
|
|
2013-11-12 18:10:32 +00:00
|
|
|
function checkResult(err, result) {
|
|
|
|
// console.log(err, result);
|
|
|
|
assert(!err);
|
|
|
|
}
|
|
|
|
|
2014-05-20 23:17:52 +00:00
|
|
|
var ds = null;
|
|
|
|
before(function() {
|
|
|
|
ds = loopback.createDataSource({connector: loopback.Memory});
|
|
|
|
});
|
2013-11-10 06:22:16 +00:00
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
describe('security scopes', function() {
|
2013-12-18 05:10:05 +00:00
|
|
|
beforeEach(function() {
|
|
|
|
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
|
2014-06-05 07:45:09 +00:00
|
|
|
testModel = loopback.PersistedModel.extend('testModel');
|
2013-12-18 05:10:05 +00:00
|
|
|
ACL.attachTo(ds);
|
|
|
|
Role.attachTo(ds);
|
|
|
|
RoleMapping.attachTo(ds);
|
|
|
|
User.attachTo(ds);
|
|
|
|
Scope.attachTo(ds);
|
|
|
|
testModel.attachTo(ds);
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should allow access to models for the given scope by wildcard', function() {
|
|
|
|
Scope.create({name: 'userScope', description: 'access user information'}, function(err, scope) {
|
2013-11-15 17:41:26 +00:00
|
|
|
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
|
2013-11-13 18:02:59 +00:00
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW},
|
2014-11-21 02:35:36 +00:00
|
|
|
function(err, resource) {
|
2013-11-15 17:41:26 +00:00
|
|
|
Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
|
|
|
|
Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
|
|
|
|
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
|
2013-11-10 06:22:16 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should allow access to models for the given scope', function() {
|
|
|
|
Scope.create({name: 'testModelScope', description: 'access testModel information'}, function(err, scope) {
|
2013-11-13 18:02:59 +00:00
|
|
|
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
|
2013-12-18 05:10:05 +00:00
|
|
|
model: 'testModel', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
|
2014-11-21 02:35:36 +00:00
|
|
|
function(err, resource) {
|
2013-11-14 01:07:43 +00:00
|
|
|
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
|
2013-12-18 05:10:05 +00:00
|
|
|
model: 'testModel', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
|
2014-11-21 02:35:36 +00:00
|
|
|
function(err, resource) {
|
2013-11-14 01:07:43 +00:00
|
|
|
// console.log(resource);
|
2014-11-21 02:35:36 +00:00
|
|
|
Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, function(err, perm) {
|
2013-11-14 01:24:42 +00:00
|
|
|
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
|
2013-11-14 01:07:43 +00:00
|
|
|
});
|
2014-11-21 02:35:36 +00:00
|
|
|
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, function(err, perm) {
|
2013-11-14 01:24:42 +00:00
|
|
|
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
|
2013-11-14 01:07:43 +00:00
|
|
|
});
|
2014-11-21 02:35:36 +00:00
|
|
|
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, function(err, perm) {
|
2013-11-14 01:07:43 +00:00
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
2014-11-21 02:35:36 +00:00
|
|
|
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, function(err, perm) {
|
2013-11-14 01:07:43 +00:00
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-11-12 06:16:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
describe('security ACLs', function() {
|
2014-03-19 22:09:20 +00:00
|
|
|
it('should order ACL entries based on the matching score', function() {
|
|
|
|
var acls = [
|
|
|
|
{
|
2014-11-21 01:46:21 +00:00
|
|
|
'model': 'account',
|
|
|
|
'accessType': '*',
|
|
|
|
'permission': 'DENY',
|
|
|
|
'principalType': 'ROLE',
|
|
|
|
'principalId': '$everyone'
|
2014-03-19 22:09:20 +00:00
|
|
|
},
|
|
|
|
{
|
2014-11-21 01:46:21 +00:00
|
|
|
'model': 'account',
|
|
|
|
'accessType': '*',
|
|
|
|
'permission': 'ALLOW',
|
|
|
|
'principalType': 'ROLE',
|
|
|
|
'principalId': '$owner'
|
2014-03-19 22:09:20 +00:00
|
|
|
},
|
|
|
|
{
|
2014-11-21 01:46:21 +00:00
|
|
|
'model': 'account',
|
|
|
|
'accessType': 'READ',
|
|
|
|
'permission': 'ALLOW',
|
|
|
|
'principalType': 'ROLE',
|
|
|
|
'principalId': '$everyone'
|
2014-03-19 22:09:20 +00:00
|
|
|
}];
|
|
|
|
var req = {
|
|
|
|
model: 'account',
|
|
|
|
property: 'find',
|
|
|
|
accessType: 'WRITE'
|
|
|
|
};
|
2014-06-02 20:41:14 +00:00
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
acls = acls.map(function(a) { return new ACL(a); });
|
2014-06-02 20:41:14 +00:00
|
|
|
|
2014-03-19 22:09:20 +00:00
|
|
|
var perm = ACL.resolvePermission(acls, req);
|
|
|
|
assert.deepEqual(perm, { model: 'account',
|
|
|
|
property: 'find',
|
|
|
|
accessType: 'WRITE',
|
2014-06-02 20:41:14 +00:00
|
|
|
permission: 'ALLOW',
|
|
|
|
methodNames: []});
|
2014-03-19 22:09:20 +00:00
|
|
|
});
|
2013-11-12 06:16:51 +00:00
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should allow access to models for the given principal by wildcard', function() {
|
2015-02-20 14:31:15 +00:00
|
|
|
// jscs:disable validateIndentation
|
2013-11-15 17:41:26 +00:00
|
|
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
|
2014-11-21 02:35:36 +00:00
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) {
|
2013-11-12 06:16:51 +00:00
|
|
|
|
2013-11-15 17:41:26 +00:00
|
|
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
|
2014-11-21 02:35:36 +00:00
|
|
|
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) {
|
2013-11-14 01:24:42 +00:00
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function(err, perm) {
|
2013-11-14 01:24:42 +00:00
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function(err, perm) {
|
2013-11-14 01:24:42 +00:00
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
2013-11-12 06:16:51 +00:00
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should allow access to models by exception', function() {
|
2013-12-18 05:10:05 +00:00
|
|
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
|
2014-11-21 02:35:36 +00:00
|
|
|
accessType: ACL.ALL, permission: ACL.DENY}, function(err, acl) {
|
2013-12-09 23:26:53 +00:00
|
|
|
|
2013-12-18 05:10:05 +00:00
|
|
|
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
|
2014-11-21 02:35:36 +00:00
|
|
|
accessType: ACL.READ, permission: ACL.ALLOW}, function(err, acl) {
|
2013-12-09 23:26:53 +00:00
|
|
|
|
2015-01-14 21:38:44 +00:00
|
|
|
ACL.create({principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,
|
|
|
|
accessType: ACL.EXECUTE, permission: ACL.ALLOW}, function(err, acl) {
|
2013-12-09 23:26:53 +00:00
|
|
|
|
2015-01-14 21:38:44 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
2013-12-09 23:26:53 +00:00
|
|
|
|
2015-01-14 21:38:44 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
2013-12-09 23:26:53 +00:00
|
|
|
|
2015-01-14 21:38:44 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
|
|
|
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
|
|
|
|
|
|
|
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
2013-12-09 23:26:53 +00:00
|
|
|
|
2015-01-14 21:38:44 +00:00
|
|
|
});
|
2013-12-09 23:26:53 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should honor defaultPermission from the model', function() {
|
2013-11-20 21:31:30 +00:00
|
|
|
var Customer = ds.createModel('Customer', {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
acls: [
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
acls: [
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
Customer.settings.defaultPermission = ACL.DENY;
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function(err, perm) {
|
2013-11-20 21:31:30 +00:00
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) {
|
2013-11-20 21:31:30 +00:00
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) {
|
2013-11-20 21:31:30 +00:00
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should honor static ACLs from the model', function() {
|
2013-11-15 17:41:26 +00:00
|
|
|
var Customer = ds.createModel('Customer', {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
acls: [
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
acls: [
|
2015-01-14 21:38:44 +00:00
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW},
|
|
|
|
{principalType: ACL.USER, principalId: 'u002', accessType: ACL.EXECUTE, permission: ACL.ALLOW},
|
|
|
|
{principalType: ACL.USER, principalId: 'u003', accessType: ACL.EXECUTE, permission: ACL.DENY}
|
2013-11-15 17:41:26 +00:00
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
/*
|
|
|
|
Customer.settings.acls = [
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
|
|
];
|
|
|
|
*/
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function(err, perm) {
|
2013-11-15 17:41:26 +00:00
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) {
|
2013-11-15 17:41:26 +00:00
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, function(err, perm) {
|
2013-12-09 23:26:53 +00:00
|
|
|
assert(perm.permission === ACL.ALLOW);
|
2013-11-15 17:41:26 +00:00
|
|
|
});
|
|
|
|
|
2015-01-14 21:38:44 +00:00
|
|
|
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.ALLOW);
|
|
|
|
});
|
|
|
|
|
|
|
|
ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE, function(err, perm) {
|
|
|
|
assert(perm.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
2013-11-15 17:41:26 +00:00
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('should filter static ACLs by model/property', function() {
|
2014-10-08 23:30:34 +00:00
|
|
|
var Model1 = ds.createModel('Model1', {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
acls: [
|
|
|
|
{principalType: ACL.USER, principalId: 'u001',
|
|
|
|
accessType: ACL.WRITE, permission: ACL.DENY},
|
|
|
|
{principalType: ACL.USER, principalId: 'u001',
|
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
acls: [
|
|
|
|
{principalType: ACL.USER, principalId: 'u001', property: 'name',
|
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW},
|
|
|
|
{principalType: ACL.USER, principalId: 'u002', property: 'findOne',
|
2015-03-05 10:35:18 +00:00
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW},
|
|
|
|
{principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'],
|
2014-10-08 23:30:34 +00:00
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
var staticACLs = ACL.getStaticACLs('Model1', 'name');
|
|
|
|
assert(staticACLs.length === 3);
|
|
|
|
|
|
|
|
staticACLs = ACL.getStaticACLs('Model1', 'findOne');
|
2015-03-05 10:35:18 +00:00
|
|
|
assert(staticACLs.length === 2);
|
|
|
|
|
|
|
|
staticACLs = ACL.getStaticACLs('Model1', 'findById');
|
2014-10-08 23:30:34 +00:00
|
|
|
assert(staticACLs.length === 1);
|
2015-03-05 10:35:18 +00:00
|
|
|
assert(staticACLs[0].property === 'findById');
|
2014-10-08 23:30:34 +00:00
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
it('should check access against LDL, ACL, and Role', function() {
|
2013-11-20 21:31:30 +00:00
|
|
|
// var log = console.log;
|
|
|
|
var log = function() {};
|
|
|
|
|
|
|
|
// Create
|
2014-11-21 02:35:36 +00:00
|
|
|
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {
|
2013-11-20 21:31:30 +00:00
|
|
|
|
|
|
|
log('User: ', user.toObject());
|
|
|
|
|
2013-12-11 07:33:57 +00:00
|
|
|
var userId = user.id;
|
|
|
|
|
2013-11-20 21:31:30 +00:00
|
|
|
// Define a model with static ACLs
|
|
|
|
var Customer = ds.createModel('Customer', {
|
|
|
|
name: {
|
|
|
|
type: String,
|
|
|
|
acls: [
|
2013-12-11 07:33:57 +00:00
|
|
|
{principalType: ACL.USER, principalId: userId, accessType: ACL.WRITE, permission: ACL.DENY},
|
|
|
|
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
|
2013-11-20 21:31:30 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
acls: [
|
2013-12-11 07:33:57 +00:00
|
|
|
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
|
2014-01-16 23:05:10 +00:00
|
|
|
],
|
|
|
|
defaultPermission: 'DENY'
|
2013-11-20 21:31:30 +00:00
|
|
|
});
|
|
|
|
|
2013-12-11 07:33:57 +00:00
|
|
|
ACL.create({principalType: ACL.USER, principalId: userId, model: 'Customer', property: ACL.ALL,
|
2014-11-21 02:35:36 +00:00
|
|
|
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) {
|
2013-11-20 21:31:30 +00:00
|
|
|
|
|
|
|
log('ACL 1: ', acl.toObject());
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
Role.create({name: 'MyRole'}, function(err, myRole) {
|
2013-11-20 21:31:30 +00:00
|
|
|
log('Role: ', myRole.toObject());
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
myRole.principals.create({principalType: RoleMapping.USER, principalId: userId}, function(err, p) {
|
2013-11-20 21:31:30 +00:00
|
|
|
|
|
|
|
log('Principal added to role: ', p.toObject());
|
|
|
|
|
2013-12-11 07:33:57 +00:00
|
|
|
ACL.create({principalType: ACL.ROLE, principalId: 'MyRole', model: 'Customer', property: ACL.ALL,
|
2014-11-21 02:35:36 +00:00
|
|
|
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) {
|
2013-11-20 21:31:30 +00:00
|
|
|
|
|
|
|
log('ACL 2: ', acl.toObject());
|
|
|
|
|
2014-04-21 18:13:03 +00:00
|
|
|
ACL.checkAccessForContext({
|
2013-11-20 21:31:30 +00:00
|
|
|
principals: [
|
2013-12-12 00:03:48 +00:00
|
|
|
{type: ACL.USER, id: userId}
|
2013-11-20 21:31:30 +00:00
|
|
|
],
|
|
|
|
model: 'Customer',
|
|
|
|
property: 'name',
|
|
|
|
accessType: ACL.READ
|
|
|
|
}, function(err, access) {
|
|
|
|
assert(!err && access.permission === ACL.ALLOW);
|
|
|
|
});
|
2014-01-16 23:05:10 +00:00
|
|
|
|
2014-04-21 18:13:03 +00:00
|
|
|
ACL.checkAccessForContext({
|
2014-01-16 23:05:10 +00:00
|
|
|
principals: [
|
|
|
|
{type: ACL.ROLE, id: Role.EVERYONE}
|
|
|
|
],
|
|
|
|
model: 'Customer',
|
|
|
|
property: 'name',
|
|
|
|
accessType: ACL.READ
|
|
|
|
}, function(err, access) {
|
|
|
|
assert(!err && access.permission === ACL.DENY);
|
|
|
|
});
|
|
|
|
|
2013-11-20 21:31:30 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-11-10 06:22:16 +00:00
|
|
|
});
|