happyDomain/checkers/domain_lock.go
Pierre-Olivier Mercier 5d304b6495 internal: rename packages colliding with usecase counterparts
Rename internal/{checker,service,provider,notification} to
internal/{dnschecker,serviceanalyzer,providerregistry,notifier} so
imports no longer collide with the same-named usecase subpackages and
no longer require import aliases to disambiguate.
2026-05-10 20:17:54 +08:00

165 lines
4.7 KiB
Go

// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package checkers
import (
"context"
"fmt"
"strings"
"time"
"git.happydns.org/happyDomain/internal/dnschecker"
"git.happydns.org/happyDomain/model"
)
const defaultRequiredLockStatuses = "clientTransferProhibited"
// domainLockRule verifies that a domain carries the expected EPP lock
// statuses (e.g. clientTransferProhibited) as reported by RDAP/WHOIS.
type domainLockRule struct{}
func (r *domainLockRule) Name() string {
return "domain_lock_check"
}
func (r *domainLockRule) Description() string {
return "Verifies that a domain carries the expected EPP lock statuses (e.g. clientTransferProhibited)"
}
func (r *domainLockRule) ValidateOptions(opts happydns.CheckerOptions) error {
v, ok := opts["requiredStatuses"]
if !ok {
return nil
}
s, ok := v.(string)
if !ok {
return fmt.Errorf("requiredStatuses must be a string")
}
for _, p := range strings.Split(s, ",") {
if strings.TrimSpace(p) != "" {
return nil
}
}
return fmt.Errorf("requiredStatuses must contain at least one EPP status code")
}
func (r *domainLockRule) Evaluate(ctx context.Context, obs happydns.ObservationGetter, opts happydns.CheckerOptions) []happydns.CheckState {
var whois WHOISData
if err := obs.Get(ctx, ObservationKeyWhois, &whois); err != nil {
return []happydns.CheckState{{
Status: happydns.StatusError,
Message: fmt.Sprintf("Failed to get WHOIS data: %v", err),
Code: "lock_error",
}}
}
requiredStr := defaultRequiredLockStatuses
if v, ok := opts["requiredStatuses"].(string); ok && v != "" {
requiredStr = v
}
var required []string
for _, s := range strings.Split(requiredStr, ",") {
s = strings.TrimSpace(s)
if s != "" {
required = append(required, s)
}
}
if len(required) == 0 {
return []happydns.CheckState{{
Status: happydns.StatusUnknown,
Message: "No required lock statuses configured",
Code: "lock_skipped",
}}
}
present := make(map[string]bool, len(whois.Status))
for _, s := range whois.Status {
present[strings.ToLower(s)] = true
}
var missing []string
for _, req := range required {
if !present[strings.ToLower(req)] {
missing = append(missing, req)
}
}
if len(missing) > 0 {
return []happydns.CheckState{{
Status: happydns.StatusCrit,
Message: fmt.Sprintf("Missing lock status: %s", strings.Join(missing, ", ")),
Code: "lock_missing",
Meta: map[string]any{
"missing": missing,
"present": whois.Status,
},
}}
}
return []happydns.CheckState{{
Status: happydns.StatusOK,
Message: fmt.Sprintf("All required statuses present: %s", strings.Join(required, ", ")),
Code: "lock_ok",
Meta: map[string]any{
"required": required,
},
}}
}
func init() {
dnschecker.RegisterChecker(&happydns.CheckerDefinition{
ID: "domain_lock",
Name: "Domain Lock Status",
Availability: happydns.CheckerAvailability{
ApplyToDomain: true,
},
ObservationKeys: []happydns.ObservationKey{ObservationKeyWhois},
Options: happydns.CheckerOptionsDocumentation{
DomainOpts: []happydns.CheckerOptionDocumentation{
{
Id: "domainName",
Type: "string",
AutoFill: happydns.AutoFillDomainName,
Hide: true,
},
{
Id: "requiredStatuses",
Type: "string",
Label: "Required lock statuses",
Description: "Comma-separated list of EPP status codes that must be present on the domain (e.g. clientTransferProhibited, clientUpdateProhibited, clientDeleteProhibited).",
Default: defaultRequiredLockStatuses,
Placeholder: defaultRequiredLockStatuses,
},
},
},
Rules: []happydns.CheckRule{
&domainLockRule{},
},
Interval: &happydns.CheckIntervalSpec{
Min: 1 * time.Hour,
Max: 7 * 24 * time.Hour,
Default: 24 * time.Hour,
},
})
}