Created
November 13, 2016 19:34
-
-
Save sjohnr/c84e2713391fdd326e193e6e16239600 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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