2023-12-24 10:18:08 +00:00
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
2020-06-08 23:25:15 +00:00
//
2023-12-24 10:18:08 +00:00
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
2020-06-08 23:25:15 +00:00
//
2023-12-24 10:18:08 +00:00
// 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.
2020-06-08 23:25:15 +00:00
//
2023-12-24 10:18:08 +00:00
// 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.
2020-06-08 23:25:15 +00:00
//
2023-12-24 10:18:08 +00:00
// 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/>.
2020-06-08 23:25:15 +00:00
package api
import (
2020-06-10 23:18:33 +00:00
"fmt"
2021-05-05 01:48:16 +00:00
"log"
2020-06-08 23:25:15 +00:00
"net/http"
2020-06-20 21:45:02 +00:00
"strings"
2020-06-24 15:33:34 +00:00
"time"
2020-06-08 23:25:15 +00:00
2023-05-20 10:24:37 +00:00
"github.com/StackExchange/dnscontrol/v4/models"
2023-11-27 13:49:51 +00:00
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
2021-05-05 01:48:16 +00:00
"github.com/gin-gonic/gin"
2020-06-25 21:47:50 +00:00
"github.com/miekg/dns"
2020-06-08 23:25:15 +00:00
2023-09-07 09:37:18 +00:00
"git.happydns.org/happyDomain/config"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/services"
"git.happydns.org/happyDomain/storage"
2024-05-16 16:09:29 +00:00
"git.happydns.org/happyDomain/utils"
2020-06-08 23:25:15 +00:00
)
2021-05-05 01:48:16 +00:00
func declareZonesRoutes ( cfg * config . Options , router * gin . RouterGroup ) {
2023-02-27 22:19:43 +00:00
router . POST ( "/retrieve_zone" , retrieveZone )
2021-05-05 01:48:16 +00:00
router . POST ( "/diff_zones/:zoneid1/:zoneid2" , diffZones )
2020-06-25 21:47:50 +00:00
2021-05-05 01:48:16 +00:00
apiZonesRoutes := router . Group ( "/zone/:zoneid" )
apiZonesRoutes . Use ( ZoneHandler )
2020-06-08 23:25:15 +00:00
2023-02-28 00:50:53 +00:00
apiZonesRoutes . POST ( "/import" , importZone )
2021-05-05 01:48:16 +00:00
apiZonesRoutes . POST ( "/view" , viewZone )
apiZonesRoutes . POST ( "/apply_changes" , applyZone )
apiZonesRoutes . GET ( "" , GetZone )
apiZonesRoutes . PATCH ( "" , UpdateZoneService )
apiZonesSubdomainRoutes := apiZonesRoutes . Group ( "/:subdomain" )
apiZonesSubdomainRoutes . Use ( subdomainHandler )
apiZonesSubdomainRoutes . GET ( "" , getZoneSubdomain )
apiZonesSubdomainRoutes . POST ( "/services" , addZoneService )
declareServiceSettingsRoutes ( cfg , apiZonesSubdomainRoutes )
apiZonesSubdomainServiceIdRoutes := apiZonesSubdomainRoutes . Group ( "/services/:serviceid" )
apiZonesSubdomainServiceIdRoutes . Use ( serviceIdHandler )
apiZonesSubdomainServiceIdRoutes . GET ( "" , getZoneService )
apiZonesSubdomainServiceIdRoutes . DELETE ( "" , deleteZoneService )
apiZonesSubdomainServiceIdRoutes . GET ( "/records" , getServiceRecords )
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
func loadZoneFromId ( domain * happydns . Domain , id string ) ( * happydns . Zone , int , error ) {
2022-10-25 16:16:34 +00:00
zoneid , err := happydns . NewIdentifierFromString ( id )
2021-05-05 01:48:16 +00:00
if err != nil {
return nil , http . StatusBadRequest , fmt . Errorf ( "Invalid zoneid: %q" , id )
}
2020-06-08 23:25:15 +00:00
2021-05-05 01:48:16 +00:00
// Check that the zoneid exists in the domain history
if ! domain . HasZone ( zoneid ) {
return nil , http . StatusNotFound , fmt . Errorf ( "Zone not found: %q" , id )
}
2020-06-08 23:25:15 +00:00
2021-05-05 01:48:16 +00:00
zone , err := storage . MainStore . GetZone ( zoneid )
if err != nil {
return nil , http . StatusNotFound , fmt . Errorf ( "Zone not found: %q" , id )
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
return zone , http . StatusOK , nil
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
func ZoneHandler ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone , statuscode , err := loadZoneFromId ( domain , c . Param ( "zoneid" ) )
if err != nil {
c . AbortWithStatusJSON ( statuscode , gin . H { "errmsg" : err . Error ( ) } )
return
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
c . Set ( "zone" , zone )
c . Next ( )
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
func GetZone ( c * gin . Context ) {
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
c . JSON ( http . StatusOK , zone )
}
func subdomainHandler ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
subdomain := strings . TrimSuffix ( strings . TrimSuffix ( strings . TrimSuffix ( c . Param ( "subdomain" ) , "." + domain . DomainName ) , "@" ) , domain . DomainName )
c . Set ( "subdomain" , subdomain )
c . Next ( )
}
2023-08-05 16:15:52 +00:00
type zoneServices struct {
Services [ ] * happydns . ServiceCombined ` json:"services" `
}
// getZoneSubdomain returns the services associated with a given subdomain.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary List services
// @Schemes
// @Description Returns the services associated with the given subdomain.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)"
// @Success 200 {object} zoneServices
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId}/{subdomain} [get]
2021-05-05 01:48:16 +00:00
func getZoneSubdomain ( c * gin . Context ) {
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
subdomain := c . MustGet ( "subdomain" ) . ( string )
2023-08-05 16:15:52 +00:00
c . JSON ( http . StatusOK , zoneServices {
Services : zone . Services [ subdomain ] ,
} )
2020-06-14 20:58:28 +00:00
}
2023-08-05 16:15:52 +00:00
// addZoneService adds a Service to the given subdomain of the Zone.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Add a Service.
// @Schemes
// @Description Add a Service to the given subdomain of the Zone.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)"
// @Success 200 {object} happydns.Zone
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services [post]
2021-05-05 01:48:16 +00:00
func addZoneService ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
subdomain := c . MustGet ( "subdomain" ) . ( string )
2020-06-20 21:45:02 +00:00
usc := & happydns . ServiceCombined { }
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & usc )
2020-06-20 21:45:02 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid service JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-20 21:45:02 +00:00
}
if usc . Service == nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "Unable to parse the given service." } )
return
2020-06-20 21:45:02 +00:00
}
2021-05-05 01:48:16 +00:00
err = zone . AppendService ( subdomain , domain . DomainName , usc )
2020-10-10 16:47:41 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to add service: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-20 21:45:02 +00:00
}
2021-05-05 01:48:16 +00:00
err = storage . MainStore . UpdateZone ( zone )
2020-06-20 21:45:02 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: Unable to UpdateZone in updateZoneService: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your zone. Please retry later." } )
return
2020-06-20 21:45:02 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , zone )
2020-06-20 21:45:02 +00:00
}
2021-05-05 01:48:16 +00:00
func serviceIdHandler ( c * gin . Context ) {
2022-10-25 16:16:34 +00:00
serviceid , err := happydns . NewIdentifierFromString ( c . Param ( "serviceid" ) )
2020-06-14 20:58:28 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Bad service identifier: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-14 20:58:28 +00:00
}
2021-05-05 01:48:16 +00:00
c . Set ( "serviceid" , serviceid )
c . Next ( )
2020-06-14 20:58:28 +00:00
}
2023-08-05 16:15:52 +00:00
// getServiceService retrieves the designated Service.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Get the Service.
// @Schemes
// @Description Retrieve the designated Service.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)"
// @Param serviceId path string true "Service identifier"
// @Success 200 {object} happydns.ServiceCombined
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services/{serviceId} [get]
2021-05-05 01:48:16 +00:00
func getZoneService ( c * gin . Context ) {
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
2024-01-19 15:23:03 +00:00
serviceid := c . MustGet ( "serviceid" ) . ( happydns . Identifier )
2021-05-05 01:48:16 +00:00
subdomain := c . MustGet ( "subdomain" ) . ( string )
c . JSON ( http . StatusOK , zone . FindSubdomainService ( subdomain , serviceid ) )
}
2023-08-05 16:15:52 +00:00
// retrieveZone retrieves the current zone deployed on the NS Provider.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Retrieve the zone on the Provider.
// @Schemes
// @Description Retrieve the current zone deployed on the NS Provider.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Success 200 {object} happydns.ZoneMeta "The new zone metadata"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain not found"
// @Router /domains/{domainId}/retrieve_zone [post]
2023-02-27 22:19:43 +00:00
func retrieveZone ( c * gin . Context ) {
2021-05-05 01:48:16 +00:00
user := c . MustGet ( "LoggedUser" ) . ( * happydns . User )
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
2021-05-21 11:08:18 +00:00
provider , err := storage . MainStore . GetProvider ( user , domain . IdProvider )
2020-06-08 23:25:15 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : fmt . Sprintf ( "Unable to find your provider: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-08 23:25:15 +00:00
}
2021-05-21 11:08:18 +00:00
zone , err := provider . ImportZone ( domain )
2020-06-08 23:25:15 +00:00
if err != nil {
2023-05-20 16:44:43 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to synchronize your zone: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
services , defaultTTL , err := svcs . AnalyzeZone ( domain . DomainName , zone )
2020-06-08 23:25:15 +00:00
if err != nil {
2023-05-20 16:44:43 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to perform the analysis of your zone: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-08 23:25:15 +00:00
}
myZone := & happydns . Zone {
2020-06-24 15:33:34 +00:00
ZoneMeta : happydns . ZoneMeta {
2021-05-05 01:48:16 +00:00
IdAuthor : domain . IdUser ,
2020-06-24 15:33:34 +00:00
DefaultTTL : defaultTTL ,
LastModified : time . Now ( ) ,
} ,
Services : services ,
2020-06-08 23:25:15 +00:00
}
2020-12-20 11:01:33 +00:00
// Create history zone
2020-06-08 23:25:15 +00:00
err = storage . MainStore . CreateZone ( myZone )
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to CreateZone in importZone: %s\n" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create your zone." } )
return
2020-06-08 23:25:15 +00:00
}
2021-05-05 01:48:16 +00:00
domain . ZoneHistory = append (
2022-10-25 16:16:34 +00:00
[ ] happydns . Identifier { myZone . Id } , domain . ZoneHistory ... )
2020-06-08 23:25:15 +00:00
2020-12-20 11:01:33 +00:00
// Create wip zone
err = storage . MainStore . CreateZone ( myZone )
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to CreateZone2 in importZone: %s\n" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create your zone." } )
return
2020-12-20 11:01:33 +00:00
}
2021-05-05 01:48:16 +00:00
domain . ZoneHistory = append (
2022-10-25 16:16:34 +00:00
[ ] happydns . Identifier { myZone . Id } , domain . ZoneHistory ... )
2020-06-08 23:25:15 +00:00
2021-05-05 01:48:16 +00:00
err = storage . MainStore . UpdateDomain ( domain )
2020-06-08 23:25:15 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: unable to UpdateDomain in importZone: %s\n" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create your zone." } )
return
2020-06-08 23:25:15 +00:00
}
2023-11-24 03:18:36 +00:00
storage . MainStore . CreateDomainLog ( domain , happydns . NewDomainLog ( user , happydns . LOG_INFO , fmt . Sprintf ( "Zone imported from provider API: %s" , myZone . Id . String ( ) ) ) )
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , & myZone . ZoneMeta )
2020-06-08 23:25:15 +00:00
}
2020-06-10 23:18:33 +00:00
2023-02-28 00:50:53 +00:00
// importZone takes a bind style file
func importZone ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
fd , _ , err := c . Request . FormFile ( "zone" )
if err != nil {
log . Printf ( "Error when retrieving zone file from %s: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "Unable to read your zone file: something is wrong in your request" } )
return
}
defer fd . Close ( )
zp := dns . NewZoneParser ( fd , domain . DomainName , "" )
var rrs [ ] dns . RR
for rr , ok := zp . Next ( ) ; ok ; rr , ok = zp . Next ( ) {
rrs = append ( rrs , rr )
}
2024-05-16 16:09:29 +00:00
rcs , err := utils . RRstoRCs ( rrs , strings . TrimSuffix ( domain . DomainName , "." ) )
2023-09-15 18:25:31 +00:00
if err != nil {
log . Printf ( "Error when converting RRs to RCs of %s: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to read your zone file: %s" , err . Error ( ) ) } )
return
}
zone . Services , _ , err = svcs . AnalyzeZone ( domain . DomainName , rcs )
2023-02-28 00:50:53 +00:00
if err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
}
zone . LastModified = time . Now ( )
err = storage . MainStore . UpdateZone ( zone )
if err != nil {
log . Printf ( "%s: Unable to UpdateZone in updateZoneService: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your zone. Please retry later." } )
return
}
2023-11-24 03:18:36 +00:00
storage . MainStore . CreateDomainLog ( domain , happydns . NewDomainLog ( c . MustGet ( "LoggedUser" ) . ( * happydns . User ) , happydns . LOG_INFO , fmt . Sprintf ( "Zone imported from Bind-style file: %s" , zone . Id . String ( ) ) ) )
2023-02-28 00:50:53 +00:00
c . JSON ( http . StatusOK , zone )
}
2023-08-05 16:15:52 +00:00
// diffZones computes the difference between the two zone identifiers given.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Compute differences between zones.
// @Schemes
// @Description Compute the difference between the two zone identifiers given.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId1 path string true "Zone identifier to use as the old one. Currently only @ are expected, to use the currently deployed zone."
// @Param zoneId2 path string true "Zone identifier to use as the new one"
// @Success 200 {object} []string "Differences, reported as text, one diff per item"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain not found"
// @Failure 500 {object} happydns.Error
// @Failure 501 {object} happydns.Error "Diff between to zone identifier, currently not supported"
// @Router /domains/{domainId}/diff_zones/{zoneId1}/{zoneId2} [post]
2021-05-05 01:48:16 +00:00
func diffZones ( c * gin . Context ) {
user := c . MustGet ( "LoggedUser" ) . ( * happydns . User )
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
2023-11-27 13:49:51 +00:00
zone2 , statuscode , err := loadZoneFromId ( domain , c . Param ( "zoneid2" ) )
2021-05-05 01:48:16 +00:00
if err != nil {
c . AbortWithStatusJSON ( statuscode , gin . H { "errmsg" : err . Error ( ) } )
return
2020-06-25 21:47:50 +00:00
}
2023-11-27 13:49:51 +00:00
records2 := zone2 . GenerateRRs ( domain . DomainName )
2021-07-05 08:54:46 +00:00
2023-11-27 13:49:51 +00:00
dc2 := & models . DomainConfig {
2021-05-21 14:23:26 +00:00
Name : strings . TrimSuffix ( domain . DomainName , "." ) ,
2023-11-27 13:49:51 +00:00
Records : records2 ,
2020-06-25 21:47:50 +00:00
}
2023-11-27 13:49:51 +00:00
if c . Param ( "zoneid1" ) == "@" {
provider , err := storage . MainStore . GetProvider ( user , domain . IdProvider )
if err != nil {
c . AbortWithStatusJSON ( http . StatusNotFound , fmt . Errorf ( "Unable to find the given provider: %q for %q" , domain . IdProvider , domain . DomainName ) )
return
}
corrections , err := provider . GetDomainCorrections ( domain , dc2 )
if err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
}
var rrCorected [ ] string
for _ , c := range corrections {
rrCorected = append ( rrCorected , c . Msg )
}
c . JSON ( http . StatusOK , rrCorected )
} else {
zone1 , statuscode , err := loadZoneFromId ( domain , c . Param ( "zoneid1" ) )
if err != nil {
c . AbortWithStatusJSON ( statuscode , gin . H { "errmsg" : err . Error ( ) } )
return
}
records1 := zone1 . GenerateRRs ( domain . DomainName )
dc1 := & models . DomainConfig {
Name : strings . TrimSuffix ( domain . DomainName , "." ) ,
Records : records1 ,
}
corrections , err := diff2 . ByRecord ( records2 , dc1 , nil )
if err != nil {
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : err . Error ( ) } )
return
}
var rrCorected [ ] string
for _ , c := range corrections {
rrCorected = append ( rrCorected , c . Msgs ... )
}
c . JSON ( http . StatusOK , rrCorected )
2022-07-26 13:06:19 +00:00
}
2020-06-25 21:47:50 +00:00
}
2023-11-19 11:15:50 +00:00
type applyZoneForm struct {
WantedCorrections [ ] string ` json:"wantedCorrections" `
CommitMsg string ` json:"commitMessage" `
}
2023-08-05 16:15:52 +00:00
// applyZone performs the requested changes with the provider.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Performs requested changes to the real zone.
// @Schemes
// @Description Perform the requested changes with the provider.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param body body []string true "Differences (from /diff_zones) to apply"
// @Success 200 {object} happydns.ZoneMeta "The new Zone metadata containing the current zone"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Failure 500 {object} happydns.Error
// @Router /domains/{domainId}/zone/{zoneId}/apply_changes [post]
2021-05-05 01:48:16 +00:00
func applyZone ( c * gin . Context ) {
user := c . MustGet ( "LoggedUser" ) . ( * happydns . User )
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
2021-05-21 14:23:26 +00:00
provider , err := storage . MainStore . GetProvider ( user , domain . IdProvider )
2020-06-25 21:47:50 +00:00
if err != nil {
2021-05-21 14:23:26 +00:00
c . AbortWithStatusJSON ( http . StatusNotFound , fmt . Errorf ( "Unable to find the given provider: %q for %q" , domain . IdProvider , domain . DomainName ) )
2021-05-05 01:48:16 +00:00
return
2020-06-25 21:47:50 +00:00
}
2023-09-15 18:25:31 +00:00
records := zone . GenerateRRs ( domain . DomainName )
2021-07-05 08:54:46 +00:00
2021-05-21 14:23:26 +00:00
dc := & models . DomainConfig {
Name : strings . TrimSuffix ( domain . DomainName , "." ) ,
2021-07-05 08:54:46 +00:00
Records : records ,
2020-06-25 21:47:50 +00:00
}
2023-11-19 11:15:50 +00:00
var form applyZoneForm
err = c . ShouldBindJSON ( & form )
2021-07-11 09:23:52 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid string array JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-07-11 09:23:52 +00:00
return
}
2023-11-24 03:18:36 +00:00
nbcorrections := len ( form . WantedCorrections )
2023-05-18 17:34:54 +00:00
corrections , err := provider . GetDomainCorrections ( domain , dc )
2021-05-21 14:23:26 +00:00
for _ , cr := range corrections {
2023-11-19 11:15:50 +00:00
for ic , wc := range form . WantedCorrections {
2021-07-11 09:23:52 +00:00
if wc == cr . Msg {
log . Printf ( "%s: apply correction: %s" , domain . DomainName , cr . Msg )
err := cr . F ( )
if err != nil {
2023-11-24 03:18:36 +00:00
storage . MainStore . CreateDomainLog ( domain , happydns . NewDomainLog ( user , happydns . LOG_ERR , fmt . Sprintf ( "Failed zone publishing (%s): %s" , zone . Id . String ( ) , err . Error ( ) ) ) )
2021-07-11 09:23:52 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to update the zone: %s" , err . Error ( ) ) } )
2022-12-22 11:55:19 +00:00
return
2021-07-11 09:23:52 +00:00
}
2023-11-19 11:15:50 +00:00
form . WantedCorrections = append ( form . WantedCorrections [ : ic ] , form . WantedCorrections [ ic + 1 : ] ... )
2021-07-11 09:23:52 +00:00
break
}
2020-06-25 21:47:50 +00:00
}
}
2023-11-19 11:15:50 +00:00
if len ( form . WantedCorrections ) > 0 {
2023-11-24 03:18:36 +00:00
storage . MainStore . CreateDomainLog ( domain , happydns . NewDomainLog ( user , happydns . LOG_ERR , fmt . Sprintf ( "Failed zone publishing (%s): %d corrections were not applied." , zone . Id . String ( ) , nbcorrections ) ) )
2023-11-19 11:15:50 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to perform the following changes: %s" , form . WantedCorrections ) } )
2022-12-22 11:55:19 +00:00
return
2021-07-11 09:23:52 +00:00
}
2023-11-24 03:18:36 +00:00
storage . MainStore . CreateDomainLog ( domain , happydns . NewDomainLog ( user , happydns . LOG_ACK , fmt . Sprintf ( "Zone published (%s), %d corrections applied with success" , zone . Id . String ( ) , nbcorrections ) ) )
2020-06-25 21:47:50 +00:00
// Create a new zone in history for futher updates
2021-05-05 01:48:16 +00:00
newZone := zone . DerivateNew ( )
2020-06-25 21:47:50 +00:00
err = storage . MainStore . CreateZone ( newZone )
if err != nil {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s was unable to CreateZone: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create the zone now." } )
return
2020-06-25 21:47:50 +00:00
}
2021-05-05 01:48:16 +00:00
domain . ZoneHistory = append (
2022-10-25 16:16:34 +00:00
[ ] happydns . Identifier { newZone . Id } , domain . ZoneHistory ... )
2020-06-25 21:47:50 +00:00
2021-05-05 01:48:16 +00:00
err = storage . MainStore . UpdateDomain ( domain )
2020-06-25 21:47:50 +00:00
if err != nil {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s was unable to UpdateDomain: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create the zone now." } )
return
2020-06-25 21:47:50 +00:00
}
// Commit changes in previous zone
now := time . Now ( )
2023-11-19 11:15:50 +00:00
zone . ZoneMeta . IdAuthor = user . Id
zone . CommitMsg = & form . CommitMsg
zone . ZoneMeta . CommitDate = & now
2021-05-05 01:48:16 +00:00
zone . ZoneMeta . Published = & now
2020-06-25 21:47:50 +00:00
2021-05-05 01:48:16 +00:00
zone . LastModified = time . Now ( )
2020-06-25 21:47:50 +00:00
2021-05-05 01:48:16 +00:00
err = storage . MainStore . UpdateZone ( zone )
2020-06-25 21:47:50 +00:00
if err != nil {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s was unable to UpdateZone: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create the zone now." } )
return
2020-06-25 21:47:50 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , newZone . ZoneMeta )
2020-06-25 21:47:50 +00:00
}
2023-08-05 16:15:52 +00:00
// viewZone creates a flatten export of the zone.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Get flatten zone file.
// @Schemes
// @Description Create a flatten export of the zone that can be read as a BIND-like file.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Success 200 {object} string "The exported zone file (with initial and leading JSON quote)"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId}/view [post]
2021-05-05 01:48:16 +00:00
func viewZone ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
2020-06-25 21:47:50 +00:00
var ret string
2023-09-15 18:25:31 +00:00
for _ , rc := range zone . GenerateRRs ( domain . DomainName ) {
if _ , ok := dns . StringToType [ rc . Type ] ; ok {
ret += rc . ToRR ( ) . String ( ) + "\n"
} else {
ret += fmt . Sprintf ( "%s %d IN %s %s\n" , rc . NameFQDN , rc . TTL , rc . Type , rc . String ( ) )
}
2020-06-25 21:47:50 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , ret )
2020-06-25 21:47:50 +00:00
}
2023-08-05 16:15:52 +00:00
// UpdateZoneService adds or updates a service inside the given Zone.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Add or update a Service.
// @Schemes
// @Description Add or update a Service inside the given Zone.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param body body happydns.ServiceCombined true "Service to update"
// @Success 200 {object} happydns.Zone
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId} [patch]
2021-05-05 01:48:16 +00:00
func UpdateZoneService ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
2020-06-10 23:18:33 +00:00
usc := & happydns . ServiceCombined { }
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & usc )
2020-06-10 23:18:33 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid domain JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-10 23:18:33 +00:00
}
2021-05-05 01:48:16 +00:00
err = zone . EraseService ( usc . Domain , domain . DomainName , usc . Id , usc )
2020-06-14 20:58:28 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to delete service: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-14 20:58:28 +00:00
}
2021-05-05 01:48:16 +00:00
zone . LastModified = time . Now ( )
2020-06-24 15:33:34 +00:00
2021-05-05 01:48:16 +00:00
err = storage . MainStore . UpdateZone ( zone )
2020-06-14 20:58:28 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: Unable to UpdateZone in updateZoneService: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your zone. Please retry later." } )
return
2020-06-14 20:58:28 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , zone )
2020-06-14 20:58:28 +00:00
}
2023-08-05 16:15:52 +00:00
// deleteZoneService drops the given Service.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Drop the given Service.
// @Schemes
// @Description Drop the given Service.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)"
// @Param serviceId path string true "Service identifier"
// @Success 200 {object} happydns.Zone
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services/{serviceId} [delete]
2021-05-05 01:48:16 +00:00
func deleteZoneService ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
2022-10-25 16:16:34 +00:00
serviceid := c . MustGet ( "serviceid" ) . ( happydns . Identifier )
2021-05-05 01:48:16 +00:00
subdomain := c . MustGet ( "subdomain" ) . ( string )
2021-01-09 18:56:20 +00:00
2021-05-05 01:48:16 +00:00
err := zone . EraseService ( subdomain , domain . DomainName , serviceid , nil )
2020-06-10 23:18:33 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to delete service: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-06-10 23:18:33 +00:00
}
2021-05-05 01:48:16 +00:00
zone . LastModified = time . Now ( )
2020-06-24 15:33:34 +00:00
2021-05-05 01:48:16 +00:00
err = storage . MainStore . UpdateZone ( zone )
2020-06-10 23:18:33 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: Unable to UpdateZone in deleteZoneService: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your zone. Please retry later." } )
return
2020-06-10 23:18:33 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , zone )
2020-06-10 23:18:33 +00:00
}
2020-09-29 21:34:28 +00:00
2020-08-21 11:52:59 +00:00
type serviceRecord struct {
2023-09-15 18:25:31 +00:00
Type string ` json:"type" `
String string ` json:"str" `
Fields * models . RecordConfig ` json:"fields,omitempty" `
RR dns . RR ` json:"rr,omitempty" `
2020-08-21 11:52:59 +00:00
}
2023-08-05 16:15:52 +00:00
// getServiceRecords retrieves the records that will be generated by a Service.
2023-09-07 09:37:18 +00:00
//
2023-08-05 16:15:52 +00:00
// @Summary Get the records for a Service.
// @Schemes
// @Description Retrieve the records that will be generated by a Service.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier"
// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)"
// @Param serviceId path string true "Service identifier"
// @Success 200 {object} happydns.Zone
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain or Zone not found"
// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services/{serviceId}/records [get]
2021-05-05 01:48:16 +00:00
func getServiceRecords ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
zone := c . MustGet ( "zone" ) . ( * happydns . Zone )
2022-10-25 16:16:34 +00:00
serviceid := c . MustGet ( "serviceid" ) . ( happydns . Identifier )
2021-05-05 01:48:16 +00:00
subdomain := c . MustGet ( "subdomain" ) . ( string )
2021-01-22 13:49:39 +00:00
2021-05-05 01:48:16 +00:00
svc := zone . FindSubdomainService ( subdomain , serviceid )
2020-09-29 21:34:28 +00:00
if svc == nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : "Service not found." } )
return
2020-09-29 21:34:28 +00:00
}
2020-08-21 11:52:59 +00:00
var ret [ ] serviceRecord
2023-09-15 18:25:31 +00:00
for _ , rc := range svc . GenRRs ( subdomain , 3600 , domain . DomainName ) {
var rr dns . RR
if _ , ok := dns . StringToType [ rc . Type ] ; ok {
rr = rc . ToRR ( )
}
2020-08-21 11:52:59 +00:00
ret = append ( ret , serviceRecord {
2023-09-15 18:25:31 +00:00
Type : rc . Type ,
String : rc . String ( ) ,
Fields : rc ,
RR : rr ,
2020-08-21 11:52:59 +00:00
} )
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , ret )
2020-09-29 21:34:28 +00:00
}