Skip to content

Instantly share code, notes, and snippets.

@Codelaby
Last active November 12, 2024 15:30
Show Gist options
  • Save Codelaby/beeb3a1438ca2c7990a74256d7408741 to your computer and use it in GitHub Desktop.
Save Codelaby/beeb3a1438ca2c7990a74256d7408741 to your computer and use it in GitHub Desktop.
class PasswordViewModel: ObservableObject {
@Published var password: String = "" {
didSet {
validatePassword()
}
}
@Published var isPasswordValid: Bool = false
@Published var complianceSummary: String = ""
@Published var compliantRules: [PasswordRule] = []
private let rules: [PasswordRule] = [
.allowed(.custom(Set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_"))), // Solo caracteres permitidos
.required(.upper), // Al menos una letra mayúscula
.required(.lower), // Al menos una letra minúscula
.required(.digits), // Al menos un dígito
.required(.special), // Al menos un carácter especial
.requiredLength(8), // Longitud mínima de 8 caracteres
.maxLength(16), // Longitud máxima de 16 caracteres
.maxConsecutive(2) // Máximo de 2 caracteres consecutivos iguales
]
// Generador de descripción para las reglas
var rulesDescription: String {
PasswordRule.descriptor(for: rules)
}
// Valida la contraseña actual y actualiza las propiedades de estado
private func validatePassword() {
let result = PasswordRule.evaluate(password: password, rules: rules)
isPasswordValid = result.allRequiredMet
complianceSummary = "\(result.complianceCount) / \(result.totalRules) reglas cumplidas."
compliantRules = result.compliantRules
}
}
// Vista SwiftUI que interactúa con PasswordViewModel
struct SecureStronghtField: View {
@StateObject private var viewModel = PasswordViewModel()
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Contraseña")
.font(.headline)
TextField("Ingresa tu contraseña", text: $viewModel.password)
.textContentType(.password)
.autocorrectionDisabled()
.autocapitalization(.none)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text(viewModel.complianceSummary)
.foregroundColor(viewModel.isPasswordValid ? .green : .red)
.font(.caption)
if !viewModel.isPasswordValid {
VStack(alignment: .leading) {
Text("Reglas de la contraseña:")
.font(.subheadline)
Text(viewModel.rulesDescription)
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
.padding()
}
}
#Preview {
SecureStronghtField()
}
import Foundation
fileprivate extension String {
func containsConsecutiveCharacters(length: Int) -> Bool {
let characters = Array(self)
for i in 0..<(characters.count - length + 1) {
let substring = String(characters[i..<(i + length)])
if substring.allSatisfy({ $0 == substring.first }) {
return true
}
}
return false
}
}
fileprivate extension PasswordRule.CharacterClass {
func contains(_ character: Character) -> Bool {
switch self {
case .upper: return character.isUppercase
case .lower: return character.isLowercase
case .digits: return character.isNumber
case .special: return "!@#$%^&*()_".contains(character)
case .asciiPrintable: return character.isASCII && character.unicodeScalars.allSatisfy { $0.isASCII && $0.value >= 32 && $0.value <= 126 }
case .unicode: return true
case .custom(let characters): return characters.contains(character)
}
}
}
enum PasswordRule: CustomStringConvertible {
enum CharacterClass {
case upper
case lower
case digits
case special
case asciiPrintable
case unicode
case custom(Set<Character>)
}
case required(CharacterClass)
case allowed(CharacterClass)
case maxConsecutive(UInt)
case requiredLength(UInt)
case maxLength(UInt)
case range(CharacterClass, min: UInt, max: UInt)
static func descriptor(for rules: [PasswordRule]) -> String {
return rules.map { "\($0.description);" }.joined(separator: " ")
}
static func evaluate(password: String, rules: [PasswordRule]) -> (allRequiredMet: Bool, complianceCount: Int, totalRules: Int, compliantRules: [PasswordRule]) {
var complianceCount = 0
var compliantRules: [PasswordRule] = []
var allRequiredMet = true
guard password.count >= 3 else {
return (false, 0, rules.count, compliantRules)
}
for rule in rules {
if rule.isCompliant(password: password) {
complianceCount += 1
compliantRules.append(rule)
} else if rule.isRequired {
allRequiredMet = false
} else if case .allowed = rule {
allRequiredMet = false
}
}
return (allRequiredMet, complianceCount, rules.count, compliantRules)
}
private func isCompliant(password: String) -> Bool {
switch self {
case .required(let characterClass):
return password.contains { characterClass.contains($0) }
case .allowed(let characterClass):
return password.allSatisfy { characterClass.contains($0) }
case .maxConsecutive(let length):
return !password.containsConsecutiveCharacters(length: Int(length))
case .requiredLength(let length):
return password.count >= Int(length)
case .maxLength(let length):
return password.count <= Int(length)
case .range(let characterClass, let min, let max):
let count = password.filter { characterClass.contains($0) }.count
return count >= Int(min) && count <= Int(max)
}
}
var isRequired: Bool {
switch self {
case .required, .requiredLength, .maxLength:
return true
default:
return false
}
}
var description: String {
switch self {
case .required(let characterClass):
return "required: \(characterClass)"
case .allowed(let characterClass):
return "allowed: \(characterClass)"
case .maxConsecutive(let length):
return "max-consecutive: \(length)"
case .requiredLength(let length):
return "required-length: \(length)"
case .maxLength(let length):
return "max-length: \(length)"
case .range(let characterClass, let min, let max):
return "range: \(characterClass) min: \(min) max: \(max)"
}
}
}
extension String {
func containsConsecutiveCharacters(length: Int) -> Bool {
let characters = Array(self)
for i in 0..<(characters.count - length + 1) {
let substring = String(characters[i..<(i + length)])
if substring.allSatisfy({ $0 == substring.first }) {
return true
}
}
return false
}
}
extension PasswordRule.CharacterClass {
func contains(_ character: Character) -> Bool {
switch self {
case .upper: return character.isUppercase
case .lower: return character.isLowercase
case .digits: return character.isNumber
case .special: return "!@#$%^&*()".contains(character)
case .asciiPrintable: return character.isASCII && character.unicodeScalars.allSatisfy { $0.isASCII && $0.value >= 32 && $0.value <= 126 }
case .unicode: return true
case .custom(let characters): return characters.contains(character)
}
}
}
// Definición de las reglas de contraseña
let passwordRules: [PasswordRule] = [
.required(.upper),
.required(.lower),
.required(.digits),
.required(.special),
.allowed(.unicode),
.maxConsecutive(3),
.requiredLength(8),
.maxLength(16),
.required(.custom(Set("!@#$%^&*()"))),
.range(.upper, min: 2, max: 4),
.range(.lower, min: 2, max: 4),
.range(.digits, min: 2, max: 4),
.range(.special, min: 2, max: 4)
]
// Evaluar diferentes contraseñas
let passwords = [
"_",
"aaaabc",
"Aa1!Bb2@",
// "Abc123!",
// "abcdefgh",
// "ABCDEFGH",
// "12345678",
// "Aa1!Aa1!",
// "Aa1!Bb2@Cc3#",
// "Aa1!Bb2@Cc3#Dd4$",
// "Aa1!Bb2@Cc3#Dd4$Ee5%",
// "Aa1!Bb2@Cc3#Dd4$Ee5%Ff6^"
]
for password in passwords {
print("Evaluating password: \(password)")
let evaluation = PasswordRule.evaluate(password: password, rules: passwordRules)
print("Compliance Count: \(evaluation.complianceCount) / Total Rules: \(evaluation.totalRules)")
print("All Required Met: \(evaluation.allRequiredMet)")
print("Compliant Rules: \(evaluation.compliantRules.map { $0.description })")
print("---")
}
import Foundation
struct PasswordRuleSet: OptionSet {
let rawValue: Int
static let requiresUppercase = PasswordRuleSet(rawValue: 1 << 0)
static let requiresLowercase = PasswordRuleSet(rawValue: 1 << 1)
static let requiresDigits = PasswordRuleSet(rawValue: 1 << 2)
static let requiresSpecial = PasswordRuleSet(rawValue: 1 << 3)
static let allowsUnicode = PasswordRuleSet(rawValue: 1 << 4)
static let maxConsecutive = PasswordRuleSet(rawValue: 1 << 5)
static let minLength = PasswordRuleSet(rawValue: 1 << 6)
static let maxLength = PasswordRuleSet(rawValue: 1 << 7)
static let rangeUppercase = PasswordRuleSet(rawValue: 1 << 8)
static let rangeLowercase = PasswordRuleSet(rawValue: 1 << 9)
static let rangeDigits = PasswordRuleSet(rawValue: 1 << 10)
static let rangeSpecial = PasswordRuleSet(rawValue: 1 << 11)
}
struct PasswordConfig {
var options: PasswordRuleSet
var minLength: UInt = 8
var maxLength: UInt = 16
var maxConsecutive: UInt = 3
var rangeUppercase: (min: UInt, max: UInt) = (2, 4)
var rangeLowercase: (min: UInt, max: UInt) = (2, 4)
var rangeDigits: (min: UInt, max: UInt) = (2, 4)
var rangeSpecial: (min: UInt, max: UInt) = (2, 4)
}
// Extensión para detectar caracteres consecutivos
extension String {
func containsConsecutiveCharacters(length: Int) -> Bool {
let characters = Array(self)
for i in 0..<(characters.count - length + 1) {
let substring = String(characters[i..<(i + length)])
if substring.allSatisfy({ $0 == substring.first }) {
return true
}
}
return false
}
}
// Definimos una configuración para las reglas de la contraseña
let passwordConfig = PasswordConfig(
options: [.requiresUppercase, .requiresLowercase, .requiresDigits, .requiresSpecial, .allowsUnicode, .maxConsecutive, .minLength, .maxLength, .rangeUppercase, .rangeLowercase, .rangeDigits, .rangeSpecial],
minLength: 8,
maxLength: 16,
maxConsecutive: 3,
rangeUppercase: (2, 4),
rangeLowercase: (2, 4),
rangeDigits: (2, 4),
rangeSpecial: (2, 4)
)
// Modificación de la función para manejar el caso de contraseñas vacías y sin reglas cumplidas
func evaluatePassword(_ password: String, with config: PasswordConfig) -> (isValid: Bool, complianceSet: PasswordRuleSet, complianceCount: Int) {
// Si la contraseña está vacía, devuelve inmediatamente que no cumple ninguna regla
guard password.count >= 3 else {
return (isValid: false, complianceSet: [], complianceCount: 0)
}
let options = config.options
var complianceSet: PasswordRuleSet = []
var totalRulesSet: PasswordRuleSet = []
// Verificar longitud mínima
if options.contains(.minLength) {
totalRulesSet.insert(.minLength)
if password.count >= config.minLength {
complianceSet.insert(.minLength)
}
}
// Verificar longitud máxima
if options.contains(.maxLength) {
totalRulesSet.insert(.maxLength)
if password.count <= config.maxLength {
complianceSet.insert(.maxLength)
}
}
// Verificar caracteres consecutivos
if options.contains(.maxConsecutive) {
totalRulesSet.insert(.maxConsecutive)
if !password.containsConsecutiveCharacters(length: Int(config.maxConsecutive)) {
complianceSet.insert(.maxConsecutive)
}
}
// Verificar clases de caracteres con rango
if options.contains(.rangeUppercase) {
// Asegurarse de que min <= max para rangeUppercase
guard config.rangeUppercase.min <= config.rangeUppercase.max else {
fatalError("Rango de mayúsculas no válido: min > max")
}
totalRulesSet.insert(.rangeUppercase)
let uppercaseCount = password.filter({ $0.isUppercase }).count
if uppercaseCount >= config.rangeUppercase.min && uppercaseCount <= config.rangeUppercase.max {
complianceSet.insert(.rangeUppercase)
}
}
if options.contains(.rangeLowercase) {
// Asegurarse de que min <= max para rangeLowercase
guard config.rangeLowercase.min <= config.rangeLowercase.max else {
fatalError("Rango de minúsculas no válido: min > max")
}
totalRulesSet.insert(.rangeLowercase)
let lowercaseCount = password.filter({ $0.isLowercase }).count
if lowercaseCount >= config.rangeLowercase.min && lowercaseCount <= config.rangeLowercase.max {
complianceSet.insert(.rangeLowercase)
}
}
if options.contains(.rangeDigits) {
// Asegurarse de que min <= max para rangeDigits
guard config.rangeDigits.min <= config.rangeDigits.max else {
fatalError("Rango de dígitos no válido: min > max")
}
totalRulesSet.insert(.rangeDigits)
let digitCount = password.filter({ $0.isNumber }).count
if digitCount >= config.rangeDigits.min && digitCount <= config.rangeDigits.max {
complianceSet.insert(.rangeDigits)
}
}
if options.contains(.rangeSpecial) {
// Asegurarse de que min <= max para rangeSpecial
guard config.rangeSpecial.min <= config.rangeSpecial.max else {
fatalError("Rango de caracteres especiales no válido: min > max")
}
totalRulesSet.insert(.rangeSpecial)
let specialCount = password.filter({ "!@#$%^&*()".contains($0) }).count
if specialCount >= config.rangeSpecial.min && specialCount <= config.rangeSpecial.max {
complianceSet.insert(.rangeSpecial)
}
}
// Comparar rawValue para determinar si la contraseña cumple todas las reglas
let isValid = complianceSet.rawValue == totalRulesSet.rawValue
let complianceCount = complianceSet.rawValue.nonzeroBitCount // Número de reglas cumplidas
return (isValid, complianceSet, complianceCount)
}
// Ejemplos de contraseñas
let passwords = [
"____",
"aaa",
"A1a@Bc2!",
"Aaaaa123!",
"Aa1!Aa1!",
"Ab1@Def2",
"ABCDEFGH"
]
// Total de reglas definidas en el PasswordConfig
let totalRulesCount = passwordConfig.options.rawValue.nonzeroBitCount
// Evaluar cada contraseña
for password in passwords {
let result = evaluatePassword(password, with: passwordConfig)
print("Password: \(password)")
print("Is valid? \(result.isValid)")
print("Compliance Set: \(result.complianceSet)") // ComplianceValue que indica las reglas cumplidas
print("Compliance Count: \(result.complianceCount) of \(totalRulesCount)") // Número de reglas cumplidas
print("---")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment