open-learning-exchange/planet

View on GitHub
design/users/_auth.json

Summary

Maintainability
Test Coverage
{"validate_doc_update":"function(newDoc, oldDoc, userCtx, secObj) {\n    if (newDoc._deleted === true) {\n      // allow deletes by admins and matching users\n      // without checking the other fields\n      // and forbid deleting admin's user doc\n      if (((userCtx.roles.indexOf('_admin') !== -1) ||\n        (userCtx.name == oldDoc.name)) &&\n        (oldDoc.isUserAdmin !== true || oldDoc.roles.length > 0)) {\n        return;\n      } else {\n        throw({forbidden: oldDoc.isUserAdmin === true ? 'Deleting admin is not permitted' : 'Only admins may delete other user docs.'});\n      }\n    }\n\n    if (newDoc.type !== 'user') {\n      throw({forbidden : 'doc.type must be user'});\n    } // we only allow user docs for now\n\n    if (!newDoc.name) {\n      throw({forbidden: 'doc.name is required'});\n    }\n\n    if (!newDoc.roles) {\n      throw({forbidden: 'doc.roles must exist'});\n    }\n\n    if (!isArray(newDoc.roles)) {\n      throw({forbidden: 'doc.roles must be an array'});\n    }\n\n    if (newDoc.roles.indexOf('openlearner') > -1) {\n      throw({forbidden: 'Role not allowed'});\n    }\n\n    for (var idx = 0; idx < newDoc.roles.length; idx++) {\n      if (typeof newDoc.roles[idx] !== 'string') {\n        throw({forbidden: 'doc.roles can only contain strings'});\n      }\n    }\n\n    if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) {\n      throw({\n        forbidden: 'Doc ID must be of the form org.couchdb.user:name'\n      });\n    }\n\n    if (oldDoc) { // validate all updates\n      if (oldDoc.name !== newDoc.name) {\n        throw({forbidden: 'Usernames can not be changed.'});\n      }\n    }\n\n    if (newDoc.password_sha && !newDoc.salt) {\n      throw({\n        forbidden: 'Users with password_sha must have a salt.' +\n          'See /_utils/script/couch.js for example code.'\n      });\n    }\n\n    if (newDoc.password_scheme === 'pbkdf2') {\n      if (typeof(newDoc.iterations) !== 'number') {\n         throw({forbidden: 'iterations must be a number.'});\n      }\n      if (typeof(newDoc.derived_key) !== 'string') {\n         throw({forbidden: 'derived_key must be a string.'});\n      }\n    }\n\n    var is_server_or_database_admin = function(userCtx, secObj) {\n      // see if the user is a server admin\n      if(userCtx.roles.indexOf('_admin') !== -1) {\n        return true; // a server admin\n      }\n\n      // see if the user a database admin specified by name\n      if(secObj && secObj.admins && secObj.admins.names) {\n        if(secObj.admins.names.indexOf(userCtx.name) !== -1) {\n          return true; // database admin\n        }\n      }\n\n      // see if the user a database admin specified by role\n      if(secObj && secObj.admins && secObj.admins.roles) {\n        var db_roles = secObj.admins.roles;\n        for(var idx = 0; idx < userCtx.roles.length; idx++) {\n          var user_role = userCtx.roles[idx];\n          if(db_roles.indexOf(user_role) !== -1) {\n            return true; // role matches!\n          }\n        }\n      }\n\n      return false; // default to no admin\n    }\n\n    var filterDifferentRoles = function(array) {\n      return function(role) { return array.indexOf(role) === -1 };\n    }\n\n    var combineArrays = function(array1, array2) {\n      return array1.concat(array2.filter(filterDifferentRoles(array1)));\n    }\n\n    var onlyLearnerChange = function(oldRoles, newRoles) {\n      var removedRoles = oldRoles.filter(filterDifferentRoles(newRoles));\n      var addedRoles = newRoles.filter(filterDifferentRoles(oldRoles));\n      var changedRoles = combineArrays(removedRoles, addedRoles);\n      return changedRoles.length === 1 && changedRoles[0] === 'learner';\n    }\n\n    if (!is_server_or_database_admin(userCtx, secObj)) {\n      var openLearnerRole = secObj.admins.roles.indexOf('openlearner') > -1;\n      if (oldDoc) { // validate non-admin updates\n        if (userCtx.name !== newDoc.name) {\n          throw({\n            forbidden: 'You may only update your own user document.'\n          });\n        }\n        // validate role updates\n        var oldRoles = (oldDoc.roles || []).sort();\n        var newRoles = newDoc.roles.sort();\n\n        if (openLearnerRole && onlyLearnerChange(oldRoles, newRoles)) {\n          return;\n        }\n\n        if (oldRoles.length !== newRoles.length) {\n          throw({forbidden: 'Only _admin may edit roles'});\n        }\n\n        for (var i = 0; i < oldRoles.length; i++) {\n          if (oldRoles[i] !== newRoles[i]) {\n            throw({forbidden: 'Only _admin may edit roles'});\n          }\n        }\n      } else if (newDoc.roles.length > 0) {\n        if (!openLearnerRole || newDoc.roles.length > 1 || newDoc.roles[0] !== 'learner') {\n          throw({forbidden: 'Only _admin may set roles'});\n        }\n      }\n    }\n\n    // no system roles in users db\n    for (var i = 0; i < newDoc.roles.length; i++) {\n      if (newDoc.roles[i][0] === '_') {\n        throw({\n          forbidden:\n          'No system roles (starting with underscore) in users db.'\n        });\n      }\n    }\n\n    // no system names as names\n    if (newDoc.name[0] === '_') {\n      throw({forbidden: 'Username may not start with underscore.'});\n    }\n\n    var badUserNameChars = [':'];\n\n    for (var i = 0; i < badUserNameChars.length; i++) {\n      if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) {\n        throw({forbidden: 'Character `' + badUserNameChars[i] +\n            '` is not allowed in usernames.'});\n      }\n    }\n  }"}