app/models/cracker_binary.rb
# frozen_string_literal: true
# SPDX-FileCopyrightText: 2024 UncleSp1d3r
# SPDX-License-Identifier: MPL-2.0
# The CrackerBinary class represents a cracker binary in the application.
# It includes associations with operating systems and an attached archive file.
# The class also includes validations for the version and archive file.
#
# Associations:
# - has_and_belongs_to_many :operating_systems
# - has_one_attached :archive_file, dependent: :destroy
#
# Validations:
# - version: presence: true
# - validates_with VersionValidator
# - archive_file: attached: true, content_type: "application/x-7z-compressed"
# - operating_systems: presence: true
#
# Instance Methods:
# - semantic_version: Returns the semantic version of the cracker binary.
# - set_semantic_version: Sets the semantic version components based on the given version string.
# - version=(value): Sets the version attribute after validating and formatting the input value.
#
# Class Methods:
# - check_for_newer(operating_system_name, version_string): Checks for a newer version of a cracker binary for a given operating system.
# - latest_versions(operating_system_name): Returns the latest versions of cracker binaries for a specific operating system.
# - to_semantic_version(ver): Converts a version string to a semantic version object.
# - version_regex: Returns a regular expression pattern for matching version numbers.
# == Schema Information
#
# Table name: cracker_binaries
#
# id :bigint not null, primary key
# active(Is the cracker binary active?) :boolean default(TRUE), not null
# major_version(The major version of the cracker binary.) :integer
# minor_version(The minor version of the cracker binary.) :integer
# patch_version(The patch version of the cracker binary.) :integer
# prerelease_version(The prerelease version of the cracker binary.) :string default("")
# version(Version of the cracker binary, e.g. 6.0.0 or 6.0.0-rc1) :string not null, indexed
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_cracker_binaries_on_version (version)
#
class CrackerBinary < ApplicationRecord
has_and_belongs_to_many :operating_systems # The operating systems that the cracker binary supports.
has_one_attached :archive_file, dependent: :destroy # The archive file containing the cracker binary. Should be a 7zip file.
validates :version, presence: true
validates_with VersionValidator # Validates the version format is a semantic version. (e.g. 1.2.3)
validates :archive_file, attached: true,
content_type: "application/x-7z-compressed"
validates :operating_systems, presence: true
# Returns the semantic version of the cracker binary.
#
# The semantic version is represented by an instance of the SemVersion class,
# which encapsulates the major version, minor version, patch version, and prerelease version.
#
# @return [SemVersion] The semantic version of the cracker binary.
def semantic_version
SemVersion.new([major_version, minor_version, patch_version, prerelease_version])
end
# Sets the semantic version components based on the given version string.
#
# Parameters:
# - version: A string representing the semantic version.
#
# Returns: None
def set_semantic_version
sem = SemVersion.new(version)
self.major_version = sem.major
self.minor_version = sem.minor
self.patch_version = sem.patch
self.prerelease_version = sem.prerelease
end
# Sets the version attribute after validating and formatting the input value.
# If the value starts with "v", it removes the "v" prefix.
# Validates the version using SemVersion. If invalid, adds an error to the version attribute.
# If valid, sets the version attribute and updates the semantic version.
#
# @param value [String] the version string to be set
# @return [void]
def version=(value)
value = value.delete("v") if value.start_with?("v")
unless SemVersion.valid?(value)
errors.add(:version, "is not a valid version")
return
end
super(value)
set_semantic_version
end
class << self
# Public: Checks for a newer version of a cracker binary for a given operating system.
#
# operating_system_name - The name of the operating system (String).
# version_string - The version string of the current cracker binary (String).
#
# Returns the latest version of the cracker binary that is greater than the current version, or nil if no newer version is found.
def check_for_newer(operating_system_name, version_string)
# Convert the version string to a semantic version.
sem_version = CrackerBinary.to_semantic_version(version_string)
# Return nil if the version string is invalid.
return nil if sem_version.nil?
# Find the latest version that is greater than or equal to the current version.
major_match = latest_versions(operating_system_name)
.where(major_version: sem_version.major..)
# If there are no major versions that are greater than or equal to the current version, return nil.
return nil if major_match.empty?
minor_match = major_match.where(minor_version: sem_version.minor..)
# If there are no minor versions that are greater than or equal to the current version, return nil.
return nil if minor_match.empty?
# If there are minor versions that are greater than the current version, return the latest one.
return minor_match.first if minor_match.first.minor_version > sem_version.minor
patch_match = minor_match.where(patch_version: sem_version.patch..)
# If there are no patch versions that are greater than or equal to the current version, return nil.
return nil if patch_match.empty?
# If there are patch versions that are greater than the current version (where the major and minor match), return the latest one.
return patch_match.first if patch_match.first.patch_version > sem_version.patch
# Check for any prerelease versions that are greater than or equal to the current version, where the major, minor, and patch match.
latest = patch_match.where("prerelease_version >= ? OR prerelease_version IS NULL", sem_version.prerelease)
.order(created_at: :desc).first
# Return nil if no latest version is found.
return nil if latest.nil?
# If everything matches, return the latest version.
# Return the latest version if it is greater than the current version. Otherwise, return nil.
latest.semantic_version > sem_version ? latest : nil
end
# Returns the latest versions of cracker binaries for a specific operating system.
#
# @param operating_system_name [String] The name of the operating system.
# @return [ActiveRecord::Relation] A collection of cracker binaries ordered by creation date in descending order.
def latest_versions(operating_system_name)
CrackerBinary.includes(:operating_systems)
.where(operating_systems: { name: operating_system_name }).order(created_at: :desc)
end
# Converts a version string to a semantic version object.
#
# Parameters:
# - ver: A string representing the version.
#
# Returns:
# - A semantic version object if the input is a valid version string, otherwise nil.
def to_semantic_version(ver)
return ver unless ver.is_a? String
ver = ver.delete("v") if ver.start_with?("v")
return nil unless SemVersion.valid?(ver)
SemVersion.from_loose_version(ver)
end
# Returns a regular expression pattern for matching version numbers.
#
# The pattern matches version numbers in the format:
# - Major.Minor.Patch-PreRelease+BuildMetadata
#
# Examples:
# - 1.0.0
# - 2.3.1-alpha.1+build.123
#
# Returns:
# - A regular expression pattern for matching version numbers.
def version_regex
/^(\d+)(?:\.(\d+)(?:\.(\d+)(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?)?)?$/
end
end
end