Skip to content

Instantly share code, notes, and snippets.

@sjohnr
Created November 13, 2016 19:34
Show Gist options
  • Save sjohnr/c84e2713391fdd326e193e6e16239600 to your computer and use it in GitHub Desktop.
Save sjohnr/c84e2713391fdd326e193e6e16239600 to your computer and use it in GitHub Desktop.
apply plugin: 'kotlin'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
buildscript {
ext.kotlin_version = '1.0.4'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
enum class CharacterClass(val regex: String) {
Alpha("[A-Za-z]"), UpperAlpha("[A-Z]"), LowerAlpha("[a-z]"), Numeric("[0-9]"), AlphaNumeric("[A-Za-z0-9]"), Symbol("[!@#$%^&*()\\-_+|~=`{}\\[\\]:\";'<>?,.\\/\\\\]"), Character(".")
}
enum class PolicyType(val text: String) {
MustContain("MUST contain"), MustNotContain("MUST NOT contain"), MustMatch("MUST match")
}
enum class RangeType(val text: String) {
AtMost("at most %s"), AtLeast("at least %s"), Exactly("exactly %s"), Between("between %s and %s")
}
abstract class Range(val min: Int = 0, val max: Int = 0) {
fun getRangeType(): RangeType = when {
min == max -> RangeType.Exactly
max == 0 -> RangeType.AtLeast
min == 1 && max > 0 -> RangeType.AtMost
else -> RangeType.Between
}
fun toHumanReadable(): String = getRangeType().let {
when (it) {
RangeType.Exactly -> it.text.format(min)
RangeType.AtLeast -> it.text.format(min)
RangeType.AtMost -> it.text.format(max)
else -> it.text.format(min, max)
}
}
fun toHumanReadableSuffix(): String = when {
min == 1 && max == 0 -> ""
max == 1 && min == 0 -> ""
else -> "s"
}
fun toRegex(): String = when {
min == max -> "{$min}"
max == 0 -> "{$min,}"
else -> "{$min,$max}"
}
}
class Password {
val policies: MutableList<Policy> = arrayListOf()
fun policy(init: Policy.() -> Unit): Policy {
val policy = apply(Policy(), init)
policies.add(policy)
return policy
}
fun toHumanReadable(): String = policies.map(Policy::toHumanReadable).joinToString(", and ")
fun toRegex(): Regex = policies.map(Policy::toRegex).map { "($it)" }.joinToString("").toRegex()
}
class Policy {
var policyType: PolicyType = PolicyType.MustMatch
var characterClass: CharacterClass = CharacterClass.Character
var range: Range = Between(1, 128)
fun toHumanReadable(): String = policyType.text + " " + range.toHumanReadable() + " " + characterClass.name + range.toHumanReadableSuffix()
fun toRegex(): String = when (policyType) {
PolicyType.MustContain -> "?=.*" + characterClass.regex + range.toRegex()
PolicyType.MustNotContain -> "?!.*" + characterClass.regex + range.toRegex()
PolicyType.MustMatch -> characterClass.regex + range.toRegex()
}
}
class AtLeast(min: Int) : Range(min, 0)
class AtMost(max: Int) : Range(1, max)
class Exactly(value: Int) : Range(value, value)
class Between(min: Int, max: Int) : Range(min, max)
private fun <T> apply(t: T, init: T.() -> Unit): T {
t.init()
return t
}
fun password(init: Password.() -> Unit): Password = apply(Password(), init)
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class PasswordPolicyTest {
@Test fun toHumanReadable_Least() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Alpha
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = AtLeast(1)
}
}
assertEquals("MUST contain at least 1 Alpha, and MUST contain at least 1 Numeric", passwordPolicy.toHumanReadable())
}
@Test fun toHumanReadable_Most() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Alpha
range = Between(1, 100)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = Between(1, 8)
}
}
assertEquals("MUST contain at most 100 Alphas, and MUST contain at most 8 Numerics", passwordPolicy.toHumanReadable())
}
@Test fun toHumanReadable_NotMoreThan() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustNotContain
characterClass = CharacterClass.Numeric
range = AtLeast(8)
}
}
assertEquals("MUST NOT contain at least 8 Numerics", passwordPolicy.toHumanReadable())
}
@Test fun toHumanReadable_Between() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = Between(2, 8)
}
}
assertEquals("MUST contain between 2 and 8 Numerics", passwordPolicy.toHumanReadable())
}
@Test fun toHumanReadable_MustMatch() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustMatch
characterClass = CharacterClass.Character
range = AtLeast(8)
}
}
assertEquals("MUST match at least 8 Characters", passwordPolicy.toHumanReadable())
}
@Test fun toRegEx_Least() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Alpha
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustMatch
characterClass = CharacterClass.Character
range = AtLeast(2)
}
}
val regex = passwordPolicy.toRegex()
assertTrue("a1".matches(regex))
assertTrue("A1".matches(regex))
assertFalse("12".matches(regex))
assertFalse("ab".matches(regex))
assertFalse("Aa".matches(regex))
}
@Test fun toRegEx_Most() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Alpha
range = Between(1, 100)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = Between(1, 8)
}
policy {
policyType = PolicyType.MustMatch
characterClass = CharacterClass.Character
range = AtLeast(2)
}
}
val regex = passwordPolicy.toRegex()
assertTrue("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345678".matches(regex))
assertTrue("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789".matches(regex))
assertTrue("abcdefghijklmnopqrstuvwxyz1234567".matches(regex))
}
@Test fun toRegEx_NotMoreThan() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Alpha
range = Between(1, 100)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = Between(1, 8)
}
policy {
policyType = PolicyType.MustNotContain
characterClass = CharacterClass.Numeric
range = AtLeast(8)
}
policy {
policyType = PolicyType.MustMatch
characterClass = CharacterClass.Character
range = AtLeast(2)
}
}
val regex = passwordPolicy.toRegex()
assertTrue("a1".matches(regex))
assertFalse("a12345678".matches(regex))
}
@Test fun toRegEx_All() {
val passwordPolicy = password {
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.UpperAlpha
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.LowerAlpha
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Numeric
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustContain
characterClass = CharacterClass.Symbol
range = AtLeast(1)
}
policy {
policyType = PolicyType.MustNotContain
characterClass = CharacterClass.Numeric
range = AtLeast(8)
}
policy {
policyType = PolicyType.MustMatch
characterClass = CharacterClass.Character
range = AtLeast(8)
}
}
val regex = passwordPolicy.toRegex()
assertTrue("Qwerty1@".matches(regex))
assertFalse("qwerty1@".matches(regex))
assertFalse("Qwerty12".matches(regex))
assertFalse("Qwerty!@".matches(regex))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment