Mercurial > gemma
view pkg/soap/soap.go @ 5422:ad8e3fffb868 marking-single-beam
schema: geography supports MULTIPOINTZ as modifier.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Thu, 08 Jul 2021 10:53:26 +0200 |
parents | f8e684108425 |
children | 5f47eeea988d |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package soap import ( "bytes" "context" "crypto/tls" "encoding/xml" "fmt" "io/ioutil" "log" "math/rand" "net" "net/http" "sync" "time" "gemma.intevation.de/gemma/pkg/config" ) type SOAPEnvelope struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` Header *SOAPHeader Body SOAPBody } type SOAPHeader struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` Items []interface{} `xml:",omitempty"` } type SOAPBody struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` Fault *SOAPFault `xml:",omitempty"` Content interface{} `xml:",omitempty"` } type SOAPFault struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"` Code string `xml:"faultcode,omitempty"` String string `xml:"faultstring,omitempty"` Actor string `xml:"faultactor,omitempty"` Detail string `xml:"detail,omitempty"` } const ( // Predefined WSS namespaces to be used in WssNsWSSE string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" WssNsWSU string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" WssNsType string = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" ) type WSSSecurityHeader struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ wsse:Security"` XmlNSWsse string `xml:"xmlns:wsse,attr"` MustUnderstand string `xml:"mustUnderstand,attr,omitempty"` Token *WSSUsernameToken `xml:",omitempty"` } type WSSUsernameToken struct { XMLName xml.Name `xml:"wsse:UsernameToken"` XmlNSWsu string `xml:"xmlns:wsu,attr"` XmlNSWsse string `xml:"xmlns:wsse,attr"` Id string `xml:"wsu:Id,attr,omitempty"` Username *WSSUsername `xml:",omitempty"` Password *WSSPassword `xml:",omitempty"` } type WSSUsername struct { XMLName xml.Name `xml:"wsse:Username"` XmlNSWsse string `xml:"xmlns:wsse,attr"` Data string `xml:",chardata"` } type WSSPassword struct { XMLName xml.Name `xml:"wsse:Password"` XmlNSWsse string `xml:"xmlns:wsse,attr"` XmlNSType string `xml:"Type,attr"` Data string `xml:",chardata"` } type BasicAuth struct { Login string Password string } type SOAPClient struct { url string tlsCfg *tls.Config auth *BasicAuth headers []interface{} } // ********** // Accepted solution from http://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang // Author: Icza - http://stackoverflow.com/users/1705598/icza const ( letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) func randStringBytesMaskImprSrc(n int) string { src := rand.NewSource(time.Now().UnixNano()) b := make([]byte, n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i-- } cache >>= letterIdxBits remain-- } return string(b) } // ********** func NewWSSSecurityHeader(user, pass, mustUnderstand string) *WSSSecurityHeader { hdr := &WSSSecurityHeader{XmlNSWsse: WssNsWSSE, MustUnderstand: mustUnderstand} hdr.Token = &WSSUsernameToken{XmlNSWsu: WssNsWSU, XmlNSWsse: WssNsWSSE, Id: "UsernameToken-" + randStringBytesMaskImprSrc(9)} hdr.Token.Username = &WSSUsername{XmlNSWsse: WssNsWSSE, Data: user} hdr.Token.Password = &WSSPassword{XmlNSWsse: WssNsWSSE, XmlNSType: WssNsType, Data: pass} return hdr } func (b *SOAPBody) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { if b.Content == nil { return xml.UnmarshalError("Content must be a pointer to a struct") } var ( token xml.Token err error consumed bool ) Loop: for { if token, err = d.Token(); err != nil { return err } if token == nil { break } switch se := token.(type) { case xml.StartElement: if consumed { return xml.UnmarshalError("Found multiple elements inside SOAP body; not wrapped-document/literal WS-I compliant") } else if se.Name.Space == "http://schemas.xmlsoap.org/soap/envelope/" && se.Name.Local == "Fault" { b.Fault = &SOAPFault{} b.Content = nil err = d.DecodeElement(b.Fault, &se) if err != nil { return err } consumed = true } else { if err = d.DecodeElement(b.Content, &se); err != nil { return err } consumed = true } case xml.EndElement: break Loop } } return nil } func (f *SOAPFault) Error() string { return f.String } func NewSOAPClient(url string, insecureSkipVerify bool, auth *BasicAuth) *SOAPClient { tlsCfg := &tls.Config{ InsecureSkipVerify: insecureSkipVerify, } return NewSOAPClientWithTLSConfig(url, tlsCfg, auth) } func NewSOAPClientWithTLSConfig(url string, tlsCfg *tls.Config, auth *BasicAuth) *SOAPClient { return &SOAPClient{ url: url, tlsCfg: tlsCfg, auth: auth, } } func (s *SOAPClient) AddHeader(header interface{}) { s.headers = append(s.headers, header) } func (s *SOAPClient) Call(soapAction string, request, response interface{}) error { envelope := SOAPEnvelope{} if s.headers != nil && len(s.headers) > 0 { soapHeader := &SOAPHeader{Items: make([]interface{}, len(s.headers))} copy(soapHeader.Items, s.headers) envelope.Header = soapHeader } envelope.Body.Content = request buffer := new(bytes.Buffer) encoder := xml.NewEncoder(buffer) //encoder.Indent("", " ") if err := encoder.Encode(envelope); err != nil { return err } if err := encoder.Flush(); err != nil { return err } req, err := http.NewRequest("POST", s.url, buffer) if err != nil { return err } if s.auth != nil { req.SetBasicAuth(s.auth.Login, s.auth.Password) } req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"") req.Header.Add("SOAPAction", soapAction) req.Header.Set("User-Agent", "gowsdl/0.1") req.Close = true timeout := config.SOAPTimeout() tr := &http.Transport{ TLSClientConfig: s.tlsCfg, Dial: func(network, addr string) (net.Conn, error) { return net.DialTimeout(network, addr, timeout) }, } client := &http.Client{Transport: tr} ctx, cancel := context.WithTimeout(context.Background(), timeout) var once sync.Once defer once.Do(cancel) req = req.WithContext(ctx) go func() { defer once.Do(cancel) <-ctx.Done() }() res, err := client.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode < http.StatusOK || res.StatusCode > 299 { rawbody, err := ioutil.ReadAll(res.Body) var body string if err == nil { if len(rawbody) > 1024 { body = fmt.Sprintf("\nbody: %.1024q...", rawbody) } else { body = fmt.Sprintf("\nbody: %q", rawbody) } } return fmt.Errorf( "HTTP error: %d (%s)%s", res.StatusCode, http.StatusText(res.StatusCode), body) } rawbody, err := ioutil.ReadAll(res.Body) if err != nil { return err } if len(rawbody) == 0 { log.Println("warn: empty response") return nil } //log.Println(string(rawbody)) respEnvelope := new(SOAPEnvelope) respEnvelope.Body = SOAPBody{Content: response} err = xml.Unmarshal(rawbody, respEnvelope) if err != nil { return err } fault := respEnvelope.Body.Fault if fault != nil { return fault } return nil }