PageRenderTime 25ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/DAV/plugins/auth/abstractDigest.js

https://github.com/mikedeboer/jsDAV
JavaScript | 281 lines | 248 code | 6 blank | 27 comment | 3 complexity | 25c8a2ce3699bc97dfcf07521da9d0f6 MD5 | raw file
Possible License(s): JSON
  1. /*
  2. * @package jsDAV
  3. * @subpackage DAV
  4. * @copyright Copyright(c) 2011 Ajax.org B.V. <info AT ajax DOT org>
  5. * @author Mike de Boer <info AT mikedeboer DOT nl>
  6. * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
  7. */
  8. "use strict";
  9. var jsDAV_Auth_iBackend = require("./iBackend");
  10. var Exc = require("./../../../shared/exceptions");
  11. var Util = require("./../../../shared/util");
  12. /**
  13. * These constants are used in setQOP();
  14. */
  15. var QOP_AUTH = 1;
  16. var QOP_AUTHINT = 2;
  17. /**
  18. * This method returns the full digest string.
  19. *
  20. * If the header could not be found, null will be returned
  21. *
  22. * @return mixed
  23. */
  24. function getDigest(req) {
  25. // most other servers
  26. var digest = req.headers["authorization"];
  27. if (digest && digest.toLowerCase().indexOf("digest") === 0)
  28. return digest.substr(7);
  29. else
  30. return null;
  31. }
  32. /**
  33. * Parses the different pieces of the digest string into an array.
  34. *
  35. * This method returns false if an incomplete digest was supplied
  36. *
  37. * @param {String} digest
  38. * @return mixed
  39. */
  40. function parseDigest(digest) {
  41. if (!digest)
  42. return false;
  43. // protect against missing data
  44. var needed_parts = {"nonce": 1, "nc": 1, "cnonce": 1, "qop": 1,
  45. "username": 1, "uri": 1, "response": 1};
  46. var data = {};
  47. digest.replace(/(\w+)=(?:(?:")([^"]+)"|([^\s,]+))/g, function(m, m1, m2, m3) {
  48. data[m1] = m2 ? m2 : m3;
  49. delete needed_parts[m1];
  50. return m;
  51. });
  52. return Object.keys(needed_parts).length ? false : data;
  53. }
  54. /**
  55. * HTTP Digest authentication backend class
  56. *
  57. * This class can be used by authentication objects wishing to use HTTP Digest
  58. * Most of the digest logic is handled, implementors just need to worry about
  59. * the getDigestHash method
  60. */
  61. var jsDAV_Auth_Backend_AbstractDigest = module.exports = jsDAV_Auth_iBackend.extend({
  62. initialize: function() {
  63. this.nonce = Util.uniqid();
  64. },
  65. /**
  66. * This variable holds the currently logged in username.
  67. *
  68. * @var array|null
  69. */
  70. currentUser: null,
  71. digestParts: null,
  72. A1: null,
  73. qop: QOP_AUTH,
  74. /**
  75. * Gathers all information from the headers
  76. *
  77. * This method needs to be called prior to anything else.
  78. *
  79. * @return void
  80. */
  81. init: function(realm, req) {
  82. this.realm = realm;
  83. this.opaque = Util.createHash(this.realm);
  84. this.digest = getDigest(req);
  85. this.digestParts = parseDigest(this.digest);
  86. },
  87. /**
  88. * Sets the quality of protection value.
  89. *
  90. * Possible values are:
  91. * QOP_AUTH
  92. * QOP_AUTHINT
  93. *
  94. * Multiple values can be specified using logical OR.
  95. *
  96. * QOP_AUTHINT ensures integrity of the request body, but this is not
  97. * supported by most HTTP clients. QOP_AUTHINT also requires the entire
  98. * request body to be md5'ed, which can put strains on CPU and memory.
  99. *
  100. * @param {Number} qop
  101. * @return void
  102. */
  103. setQOP: function(qop) {
  104. this.qop = qop;
  105. },
  106. /**
  107. * Validates the user.
  108. *
  109. * The A1 parameter should be Util.createHash(username + ':' + realm + ':' + password);
  110. *
  111. * @param {String} A1
  112. * @return bool
  113. */
  114. validateA1: function(handler, newA1, cbvalida1) {
  115. this.A1 = newA1;
  116. this.validate(handler, cbvalida1);
  117. },
  118. /**
  119. * Validates authentication through a password. The actual password must be provided here.
  120. * It is strongly recommended not store the password in plain-text and use validateA1 instead.
  121. *
  122. * @param {String} password
  123. * @return bool
  124. */
  125. validatePassword: function(handler, password, cbvalidpass) {
  126. this.A1 = Util.createHash(this.digestParts["username"] + ":" + this.realm + ":" + password);
  127. this.validate(handler, cbvalidpass);
  128. },
  129. /**
  130. * Returns the username for the request
  131. *
  132. * @return string
  133. */
  134. getUsername: function() {
  135. return this.digestParts["username"];
  136. },
  137. /**
  138. * Validates the digest challenge
  139. *
  140. * @return bool
  141. */
  142. validate: function(handler, cbvalidate) {
  143. var req = handler.httpRequest;
  144. var A2 = req.method + ":" + this.digestParts["uri"];
  145. var self = this;
  146. if (this.digestParts["qop"] == "auth-int") {
  147. // Making sure we support this qop value
  148. if (!(this.qop & QOP_AUTHINT))
  149. return cbvalidate(false);
  150. // We need to add an md5 of the entire request body to the A2 part of the hash
  151. handler.getRequestBody("utf8", null, false, function(noop, body) {
  152. A2 += ":" + Util.createHash(body);
  153. afterBody();
  154. });
  155. }
  156. else {
  157. // We need to make sure we support this qop value
  158. if (!(this.qop & QOP_AUTH))
  159. return cbvalidate(false);
  160. afterBody();
  161. }
  162. function afterBody() {
  163. A2 = Util.createHash(A2);
  164. var validResponse = Util.createHash(self.A1 + ":" + self.digestParts["nonce"] + ":"
  165. + self.digestParts["nc"] + ":" + self.digestParts["cnonce"] + ":"
  166. + self.digestParts["qop"] + ":" + A2);
  167. cbvalidate(self.digestParts["response"] == validResponse);
  168. }
  169. },
  170. /**
  171. * Returns a users digest hash based on the username and realm.
  172. *
  173. * If the user was not known, null must be returned.
  174. *
  175. * @param {String} realm
  176. * @param {String} username
  177. * @return string|null
  178. */
  179. getDigestHash: function(realm, username, cbdighash) {},
  180. /**
  181. * Returns an HTTP 401 header, forcing login
  182. *
  183. * This should be called when username and password are incorrect, or not supplied at all
  184. *
  185. * @return void
  186. */
  187. requireAuth: function(realm, err, cbreqauth) {
  188. if (!(err instanceof Exc.jsDAV_Exception))
  189. err = new Exc.NotAuthenticated(err);
  190. var currQop = "";
  191. switch (this.qop) {
  192. case QOP_AUTH:
  193. currQop = "auth";
  194. break;
  195. case QOP_AUTHINT:
  196. currQop = "auth-int";
  197. break;
  198. case QOP_AUTH | QOP_AUTHINT:
  199. currQop = "auth,auth-int";
  200. break;
  201. }
  202. err.addHeader("WWW-Authenticate", "Digest realm=\"" + realm +
  203. "\",qop=\"" + currQop + "\",nonce=\"" + this.nonce +
  204. "\",opaque=\"" + this.opaque + "\"");
  205. cbreqauth(err, false);
  206. },
  207. /**
  208. * Authenticates the user based on the current request.
  209. *
  210. * If authentication is succesful, true must be returned.
  211. * If authentication fails, an exception must be thrown.
  212. *
  213. * @throws Sabre_DAV_Exception_NotAuthenticated
  214. * @return bool
  215. */
  216. authenticate: function(handler, realm, cbauth) {
  217. var req = handler.httpRequest;
  218. this.init(realm, req);
  219. var username = this.digestParts["username"];
  220. // No username was given
  221. if (!username)
  222. return this.requireAuth(realm, "No digest authentication headers were found", cbauth);
  223. var self = this;
  224. this.getDigestHash(realm, username, function(err, hash) {
  225. // If this was false, the user account didn't exist
  226. if (err || !hash)
  227. return self.requireAuth(realm, err || "The supplied username was not on file", cbauth);
  228. if (typeof hash != "string") {
  229. handler.handleError(new Exc.jsDAV_Exception(
  230. "The returned value from getDigestHash must be a string or null"));
  231. return cbauth(null, false);
  232. }
  233. // If this was false, the password or part of the hash was incorrect.
  234. self.validateA1(handler, hash, function(isValid) {
  235. if (!isValid)
  236. return self.requireAuth(realm, "Incorrect username", cbauth);
  237. self.currentUser = username;
  238. cbauth(null, true);
  239. });
  240. });
  241. },
  242. /**
  243. * Returns the currently logged in username.
  244. *
  245. * @return string|null
  246. */
  247. getCurrentUser: function(callback) {
  248. callback(null, this.currentUser);
  249. }
  250. });