Mercurial > gemma
view pkg/soap/soap.go @ 4723:baabc2b2f094
Avoid creating user profiles without matching role
The INSTEAD OF triggers on users.list_users did that already, but
profile data coming e.g. via restoring a dump had been added also
if there was no matching database role in the cluster.
This also unifies the errors occuring on creation of users with existing
role names that differed between roles with and without profile before.
Note this is no referential integrity. A dropped role still leaves an
orphaned profile behind.
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Thu, 17 Oct 2019 18:56:59 +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 }