1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
odoo.define('auth_password_policy', function (require) {
"use strict";
var core = require('web.core');
var _t = core._t;
var Policy = core.Class.extend({
/**
*
* @param {Object} info
* @param {Number} [info.minlength=0]
* @param {Number} [info.minwords=0]
* @param {Number} [info.minclasses=0]
*/
init: function (info) {
this._minlength = info.minlength || 1;
this._minwords = info.minwords || 1;
this._minclasses = info.minclasses || 1;
},
toString: function () {
var msgs = [];
if (this._minlength > 1) {
msgs.push(_.str.sprintf(_t("at least %d characters"), this._minlength));
}
if (this._minwords > 1) {
msgs.push(_.str.sprintf(_t("at least %d words"), this._minwords));
}
if (this._minclasses > 1) {
msgs.push(_.str.sprintf(_t("at least %d character classes"), this._minclasses));
}
return msgs.join(', ')
},
score: function (password) {
var lengthscore = Math.min(
password.length / this._minlength,
1.0);
// we want the number of "words". Splitting on no-words doesn't work
// because JS will add an empty string when matching a leading or
// trailing pattern e.g. " foo ".split(/\W+/) will return ['', 'foo', '']
// by splitting on the words, we should always get wordscount + 1
var wordscore = Math.min(
// \w includes _ which we don't want, so combine \W and _ then
// invert it to know what "word" is
//
// Sadly JS is absolute garbage, so this splitting is basically
// solely ascii-based unless we want to include cset
// (http://inimino.org/~inimino/blog/javascript_cset) which can
// generate non-trivial character-class-set-based regex patterns
// for us. We could generate the regex statically but they're huge
// and gnarly as hell.
(password.split(/[^\W_]+/).length - 1) / this._minwords,
1.0
);
// See above for issues pertaining to character classification:
// we'll classify using the ascii range because that's basically our
// only option
var classes =
((/[a-z]/.test(password)) ? 1 : 0)
+ ((/[A-Z]/.test(password)) ? 1 : 0)
+ ((/\d/.test(password)) ? 1 : 0)
+ ((/[^A-Za-z\d]/.test(password)) ? 1 : 0);
var classesscore = Math.min(classes / this._minclasses, 1.0);
return lengthscore * wordscore * classesscore;
},
});
return {
/**
* Computes the password's score, should be roughly continuous, under 0.5
* if the requirements don't pass and at 1 if the recommendations are
* exceeded
*/
computeScore: function (password, requirements, recommendations) {
var req = requirements.score(password);
var rec = recommendations.score(password);
return Math.pow(req, 4) * (0.5 + Math.pow(rec, 2) / 2);
},
Policy: Policy,
// Recommendations from Shay (2016):
// Our research has shown that there are other policies that are more
// usable and more secure. We found three policies (2class12, 3class12,
// and 2word16) that we can directly recommend over comp8
//
// Since 2class12 is a superset of 3class12 and 2word16, either pick it or
// pick the other two (and get the highest score of the two). We're
// picking the other two.
recommendations: {
score: function (password) {
return _.max(_.invoke(this.policies, 'score', password));
},
policies: [
new Policy({minlength: 16, minwords: 2}),
new Policy({minlength: 12, minclasses: 3})
]
}
}
});
odoo.define('auth_password_policy.Meter', function (require) {
"use strict";
var core = require('web.core');
var policy = require('auth_password_policy');
var Widget = require('web.Widget');
var _t = core._t;
var PasswordPolicyMeter = Widget.extend({
tagName: 'meter',
className: 'o_password_meter',
attributes: {
min: 0,
low: 0.5,
high: 0.99,
max: 1,
value: 0,
optimum: 1,
},
init: function (parent, required, recommended) {
this._super(parent);
this._required = required;
this._recommended = recommended;
},
start: function () {
var helpMessage = _t("Required: %s.\n\nHint: increase length, use multiple words and use non-letter characters to increase your password's strength.");
this.el.setAttribute(
'title', _.str.sprintf(helpMessage, String(this._required) || _t("no requirements")));
return this._super().then(function () {
});
},
/**
* Updates the meter with the information of the new password: computes
* the (required x recommended) score and sets the widget's value as that
*
* @param {String} password
*/
update: function (password) {
this.el.value = policy.computeScore(password, this._required, this._recommended);
}
});
return PasswordPolicyMeter;
});
|