diff --git a/header_tmpl.go b/header_tmpl.go index a667187..0072f94 100644 --- a/header_tmpl.go +++ b/header_tmpl.go @@ -8,15 +8,15 @@ var headerTmpl = ` package {{.}} import ( + "bytes" + "crypto/tls" "encoding/xml" - "time" - "bytes" - "crypto/tls" - "encoding/xml" - "io/ioutil" - "net/http" + "io/ioutil" "log" + "math/rand" "net" + "net/http" + "time" {{/*range .Imports*/}} {{/*.*/}} diff --git a/operations_tmpl.go b/operations_tmpl.go index f1f48dd..b8503ad 100644 --- a/operations_tmpl.go +++ b/operations_tmpl.go @@ -22,8 +22,13 @@ var opsTmpl = ` } } + func (service *{{$portType}}) AddHeader(header interface{}) { + service.client.AddHeader(header) + } + + // Backwards-compatible function: use AddHeader instead func (service *{{$portType}}) SetHeader(header interface{}) { - service.client.SetHeader(header) + service.client.AddHeader(header) } {{range .Operations}} diff --git a/soap_tmpl.go b/soap_tmpl.go index 63e349e..f28d4fd 100644 --- a/soap_tmpl.go +++ b/soap_tmpl.go @@ -19,8 +19,8 @@ type SOAPEnvelope struct { type SOAPHeader struct { XMLName xml.Name ` + "`" + `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` + "`" + ` - - Header interface{} + + Items []interface{} ` + "`" + `xml:",omitempty"` + "`" + ` } type SOAPBody struct { @@ -39,16 +39,97 @@ type SOAPFault struct { 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 + Login string Password string } type SOAPClient struct { - url string - tls bool - auth *BasicAuth - header interface{} + url string + tls bool + 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<= 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 { @@ -113,15 +194,17 @@ func NewSOAPClient(url string, tls bool, auth *BasicAuth) *SOAPClient { } } -func (s *SOAPClient) SetHeader(header interface{}) { - s.header = header +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.header != nil { - envelope.Header = &SOAPHeader{Header: s.header} + 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 @@ -149,10 +232,8 @@ func (s *SOAPClient) Call(soapAction string, request, response interface{}) erro } req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"") - if soapAction != "" { - req.Header.Add("SOAPAction", soapAction) - } - + req.Header.Add("SOAPAction", soapAction) + req.Header.Set("User-Agent", "gowsdl/0.1") req.Close = true