/
BaseConstraints.scala
143 lines (125 loc) · 4.42 KB
/
BaseConstraints.scala
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
141
142
143
package controllers
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation._
import scala.Predef._
import validation.ValidationError
import play.api.data.validation.Valid
import play.api.{Play, Logger}
/**
* Adds some decent password checking constraints.
*
* There are two password checkers.
*
* The weak one, 'weakPassword' uses regular expressions and is mostly useless.
*
* The strong one, 'strongPassword' calls out to "pwqcheck", part of the Openwall
* <a href="http://www.openwall.com/passwdqc/">passwdqc</a> package. pwqcheck
* is a unix command line utility, so you must install passwdqc first and
* make sure that it is available. This is easier on Linux based systems,
* but you can use a Mac variant by installing
* <a href="https://github.com/iphoting/passwdqc-mac">passwdqc-mac</a>. You can
* change the pwqcheck parameters by setting "authentication.pwqcheck" in application.conf.
*
* Note that this has NOT been tried to scale in a production environment.
*
* @author wsargent
*/
trait BaseConstraints
{
def logger: Logger
val MIN_PASSWORD_LENGTH = 8
val internalPasswordConstraint: Constraint[String] = Constraint("constraints.internalPassword")({
plaintext =>
val errors = internalPasswordChecks(plaintext)
if (errors.isEmpty) {
Valid
} else {
Invalid(errors)
}
}
)
val externalPasswordConstraint: Constraint[String] = Constraint("constraints.externalPassword")({
plaintext =>
val errors = externalPasswordCheck(plaintext)
if (errors.isEmpty) {
Valid
} else {
Invalid(errors)
}
}
)
/**
* Uses a weak password checker for validation.
*/
val weakPassword: Mapping[String] = nonEmptyText(minLength = MIN_PASSWORD_LENGTH).verifying(internalPasswordConstraint)
/**
* Uses a strong password checker for validation.
*/
val strongPassword: Mapping[String] = nonEmptyText(minLength = MIN_PASSWORD_LENGTH).verifying(externalPasswordConstraint)
/**
* An example of an internal password check, for when you don't want a dependency on passwdqc.
*
* @param plainText the plaintext password.
* @return the validation result.
*/
def internalPasswordChecks(plainText: String): Seq[ValidationError] = {
// XXX want to abstract this out to run through a chain, but this shows the idea.
val allNumbers = """\d*""".r
val allWords = """[A-Za-z]*""".r
plainText match {
case allNumbers() => Seq(ValidationError("Password is all numbers"))
case allWords() => Seq(ValidationError("Password is all letters"))
case _ => Seq()
}
}
/**
* Calls out to an external process "pwqcheck" (assumed to be on the path) and reads from any errors.
* Taken from <a href="http://www.openwall.com/articles/PHP-Users-Passwords#enforcing-password-policy">enforcing password policy</a>.
*
* @param plainText The plaintext password to check.
* @return validation errors returned from the stdout of pwqcheck.
*/
def externalPasswordCheck(plainText: String): Seq[ValidationError] = {
import java.io.{OutputStreamWriter, PrintWriter}
import scala.sys.process._
import collection.mutable.ArrayBuffer
// only check one password.
val pwqcheckExec = Play.maybeApplication.flatMap(_.configuration.getString("authentication.pwqcheck")).getOrElse("pwqcheck -1")
val pwqcheck = Process(pwqcheckExec)
// Really don't like using a mutable data structure here.
var errorList = ArrayBuffer[ValidationError]()
def readFromStdout(input: java.io.InputStream) {
try {
scala.io.Source.fromInputStream(input).getLines().foreach(line => {
if (line != "OK") {
errorList += ValidationError(line)
}
logger.debug(line)
})
} finally {
input.close()
}
}
def writeToStdin(output: java.io.OutputStream) {
val writer = new PrintWriter(new OutputStreamWriter(output))
try {
writer.println(plainText)
} finally {
writer.close()
}
}
def readFromStderr(stderr: java.io.InputStream) {
try {
scala.io.Source.fromInputStream(stderr).getLines().foreach(line => {
logger.error(line)
})
} finally {
stderr.close()
}
}
val exitValue = pwqcheck.run(new ProcessIO(writeToStdin, readFromStdout, readFromStderr)).exitValue()
logger.debug("exitValue = " + exitValue)
errorList.toSeq
}
}