taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

commit e7f2072ae60b28c8465fd8dd84f56fd54f5b24a6
parent 6a0c1a45f3a41c62720daccefe2eb761813454b2
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Fri,  8 Aug 2025 12:31:25 +0200

refactor valiadtors to support future OIDC et al

Diffstat:
Apkg/taldir/command_validator.go | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/taldir/taldir.go | 83++++++++++++++-----------------------------------------------------------------
Apkg/taldir/validator.go | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 167 insertions(+), 69 deletions(-)

diff --git a/pkg/taldir/command_validator.go b/pkg/taldir/command_validator.go @@ -0,0 +1,100 @@ +// This file is part of tdir, the Taler Directory implementation. +// Copyright (C) 2025 Martin Schanzenbach +// +// Taldir 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. +// +// Taldir 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 <http://www.gnu.org/licenses/>. +// +// SPDX-License-Identifier: AGPL3.0-or-later + +package taldir + +import ( + "html/template" + "regexp" + "fmt" + "os/exec" +) + +type CommandValidator struct { + + // Name + name string + + // Config + config *TaldirConfig + + // Validator alias regex + validAliasRegex string + + // The command to call for validation + command string + + // registration/lookup page + landingPageTpl *template.Template +} + +func (t *CommandValidator) LandingPageTpl() *template.Template { + return t.landingPageTpl +} + +func (t *CommandValidator) Type() ValidatorType { + return ValidatorTypeCommand +} + +func (t *CommandValidator) Name() string { + return t.name +} + +func (t *CommandValidator) IsEnabled() bool { + return t.config.Ini.Section("taldir-validator-" + t.name).Key("enabled").MustBool(false) +} + +func (t *CommandValidator) ChallengeFee() string { + return t.config.Ini.Section("taldir-validator-" + t.name).Key("challenge_fee").MustString("KUDOS:0") +} + +func (t *CommandValidator) IsAliasValid(alias string) (err error) { + if t.validAliasRegex != "" { + matched, _ := regexp.MatchString(t.validAliasRegex, alias) + if !matched { + return fmt.Errorf("alias `%s' invalid", alias) // TODO i18n + } + } + return +} + +func (t *CommandValidator) RegistrationStart(topic string, link string, message string, address string, challenge string) error { + path, err := exec.LookPath(t.command) + if err != nil { + return err + } + out, err := exec.Command(path, address, challenge, topic, message).Output() + // FIXME logger + fmt.Printf("Executing `%s %s %s %s %s`\n", path, address, challenge, topic, message) + if err != nil { + fmt.Printf("%s, %v\n", out, err) + return err + } + return nil +} + +func make_command_validator (cfg *TaldirConfig, name string, landingPageTpl *template.Template) CommandValidator { + sec := cfg.Ini.Section("taldir-validator-" + name) + return CommandValidator{ + name: name, + config: cfg, + landingPageTpl: landingPageTpl, + validAliasRegex: sec.Key("valid_alias_regex").MustString(""), + command: sec.Key("command").MustString(""), + } +} diff --git a/pkg/taldir/taldir.go b/pkg/taldir/taldir.go @@ -34,7 +34,6 @@ import ( "net/http" "net/url" "os" - "os/exec" "regexp" "strings" "time" @@ -128,34 +127,6 @@ type Taldir struct { Logger *log.Logger } -type ValidatorType string - -const ( - ValidatorTypeCommand ValidatorType = "command" - ValidatorTypeOIDC ValidatorType = "oidc" -) - -type Validator struct { - - // Validator name - Name string - - // Validator alias regex - ValidAliasRegex string - - // Validator type - Type ValidatorType - - // Amount of payment required - ChallengeFee string - - // The command to call for validation - Command string - - // registration/lookup page - LandingPageTpl *template.Template -} - // VersionResponse is the JSON response of the /config endpoint type VersionResponse struct { // libtool-style representation of the Merchant protocol version, see @@ -312,16 +283,6 @@ func (t *Taldir) isPMSValid(pms string) (err error) { return } -func (v *Validator) isAliasValid(alias string) (err error) { - if v.ValidAliasRegex != "" { - matched, _ := regexp.MatchString(v.ValidAliasRegex, alias) - if !matched { - return fmt.Errorf("alias `%s' invalid", alias) // TODO i18n - } - } - return -} - // Primary lookup function. // Allows the caller to query a wallet key using the hash(!) of the // identity, e.g. SHA512(<email address>) @@ -344,7 +305,7 @@ func (t *Taldir) disseminateStop(e Entry) error { for _, d := range t.Disseminators { err := d.DisseminateStop(&e) if err != nil { - t.Logf(LogWarning, "Dissemination stop failed for disseminator `%s' and entry `%s'", d.Name, e.HsAddress) + t.Logf(LogWarning, "Dissemination stop failed for disseminator `%s' and entry `%s'", d.Name(), e.HsAddress) } } return nil @@ -355,7 +316,7 @@ func (t *Taldir) disseminateStart(e Entry) { for _, d := range t.Disseminators { err := d.DisseminateStart(&e) if err != nil { - t.Logf(LogWarning, "Dissemination start failed for disseminator `%s' and entry `%s': %v", d.Name, e.HsAddress, err) + t.Logf(LogWarning, "Dissemination start failed for disseminator `%s' and entry `%s': %v", d.Name(), e.HsAddress, err) } } } @@ -572,7 +533,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { sliceDuration := time.Duration(validation.Duration * 1000) cost, err := util.CalculateCost(t.MonthlyFee, - validator.ChallengeFee, + validator.ChallengeFee(), sliceDuration, monthDuration) if err != nil { @@ -618,21 +579,12 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } - path, err := exec.LookPath(validator.Command) - if err != nil { - t.Logf(LogError, err.Error()+"\n") - t.Db.Delete(&validation) - w.WriteHeader(500) - return - } - t.Logf(LogDebug, "Found `%s' in path as `%s'\n", validator.Command, path) topic := t.I18n.GetLocale(r).GetMessage("taldirRegTopic") link := t.Host + "/register/" + url.QueryEscape(validation.HAddress) + "/" + url.QueryEscape(validation.Challenge) + "?address=" + url.QueryEscape(req.Address) message := t.I18n.GetLocale(r).GetMessage("taldirRegMessage", link) - out, err := exec.Command(path, req.Address, validation.Challenge, topic, message).Output() - t.Logf(LogDebug, "Executing `%s %s %s %s %s`\n", path, req.Address, validation.Challenge, topic, message) + err = validator.RegistrationStart(topic, link, message, req.Address, validation.Challenge) if err != nil { - fmt.Printf("%s, %v\n", out, err) + t.Logf(LogError, err.Error()+"\n") t.Db.Delete(&validation) w.WriteHeader(500) return @@ -648,7 +600,7 @@ func (t *Taldir) configResponse(w http.ResponseWriter, r *http.Request) { for key := range t.Validators { var meth Method meth.Name = key - meth.ChallengeFee = t.Validators[key].ChallengeFee + meth.ChallengeFee = t.Validators[key].ChallengeFee() i++ meths = append(meths, meth) } @@ -833,13 +785,13 @@ func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) // Check if alias is valid alias := r.URL.Query().Get("address") - err := val.isAliasValid(alias) + err := val.IsAliasValid(alias) emsg := "" found := false if nil != err { t.Logf(LogWarning, "Not a valid alias\n") emsg = t.I18n.GetLocale(r).GetMessage("aliasInvalid", alias) - http.Redirect(w, r, fmt.Sprintf("/landing/"+val.Name+"?error=%s", emsg), http.StatusSeeOther) + http.Redirect(w, r, fmt.Sprintf("/landing/"+val.Name()+"?error=%s", emsg), http.StatusSeeOther) return } else { hAddressBin := sha512.Sum512([]byte(r.URL.Query().Get("address"))) @@ -855,7 +807,7 @@ func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) fullData := map[string]any{ "version": t.Cfg.Version, "available": !found, - "method": val.Name, + "method": val.Name(), "address": r.URL.Query().Get("address"), "result": entry.TargetUri, "error": emsg, @@ -884,7 +836,7 @@ func (t *Taldir) methodLandingPage(w http.ResponseWriter, r *http.Request) { "productDisclaimerShort": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimerShort")), "tr": t.I18n.GetLocale(r).GetMessage, } - err := val.LandingPageTpl.Execute(w, fullData) + err := val.LandingPageTpl().Execute(w, fullData) if err != nil { fmt.Println(err) } @@ -985,23 +937,16 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { continue } vname := strings.TrimPrefix(sec.Name(), "taldir-validator-") - if !sec.Key("enabled").MustBool(false) { - t.Logger.Printf("`%s` validator disabled.\n", vname) - continue - } vlandingPageTplFile := sec.Key("registration_page").MustString(t.getFileName("web/templates/landing_" + vname + ".html")) vlandingPageTpl, err := template.ParseFiles(vlandingPageTplFile, navTplFile, footerTplFile) if err != nil { t.Logger.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname) continue } - t.Validators[vname] = Validator{ - Name: vname, - LandingPageTpl: vlandingPageTpl, - ChallengeFee: sec.Key("challenge_fee").MustString("KUDOS:0"), - Command: sec.Key("command").MustString(""), - Type: ValidatorType(sec.Key("type").MustString("")), - ValidAliasRegex: sec.Key("valid_alias_regex").MustString(""), + v := make_command_validator(&cfg, vname, vlandingPageTpl) + if v.IsEnabled() { + t.Logger.Printf("`%s` validator disabled.\n", vname) + t.Validators[vname] = &v } } t.Disseminators = make(map[string]Disseminator) diff --git a/pkg/taldir/validator.go b/pkg/taldir/validator.go @@ -0,0 +1,53 @@ +// This file is part of tdir, the Taler Directory implementation. +// Copyright (C) 2025 Martin Schanzenbach +// +// Taldir 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. +// +// Taldir 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 <http://www.gnu.org/licenses/>. +// +// SPDX-License-Identifier: AGPL3.0-or-later + +package taldir + +import ( + "html/template" +) + +type ValidatorType string + +const ( + ValidatorTypeCommand ValidatorType = "command" + ValidatorTypeOIDC ValidatorType = "oidc" +) + +type Validator interface { + + // Validator name + Name() string + + // Validator type + Type() ValidatorType + + // Amount of payment required + ChallengeFee() string + + // registration/lookup page + LandingPageTpl() *template.Template + + // Is alias valid + IsAliasValid(alias string) error + + // Start registration + RegistrationStart(topic string, link string, message string, address string, challenge string) error +} + +