github.com/vmware/govmomi@v0.51.0/sts/internal/types.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package internal 6 7 // The sts/internal package provides the types for invoking the sts.Issue method. 8 // The sts.Issue and SessionManager LoginByToken methods require an XML signature. 9 // Unlike the JRE and .NET runtimes, the Go stdlib does not support XML signing. 10 // We should considering contributing to the goxmldsig package and gosaml2 to meet 11 // the needs of sts.Issue rather than maintaining this package long term. 12 // The tricky part of xmldig is the XML canonicalization (C14N), which is responsible 13 // for most of the make-your-eyes bleed XML formatting in this package. 14 // C14N is also why some structures use xml.Name without a field tag and methods modify the xml.Name directly, 15 // though also working around Go's handling of XML namespace prefixes. 16 // Most of the types in this package were originally generated from the wsdl and hacked up gen/ scripts, 17 // but have since been modified by hand. 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/sha256" 23 "encoding/base64" 24 "fmt" 25 "log" 26 "path" 27 "reflect" 28 "strings" 29 30 "github.com/vmware/govmomi/vim25/soap" 31 "github.com/vmware/govmomi/vim25/types" 32 "github.com/vmware/govmomi/vim25/xml" 33 ) 34 35 const ( 36 XSI = "http://www.w3.org/2001/XMLSchema-instance" 37 WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 38 DSIG = "http://www.w3.org/2000/09/xmldsig#" 39 SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" 40 Time = "2006-01-02T15:04:05.000Z" 41 ) 42 43 // Security is used as soap.Envelope.Header.Security when signing requests. 44 type Security struct { 45 XMLName xml.Name `xml:"wsse:Security"` 46 WSSE string `xml:"xmlns:wsse,attr"` 47 WSU string `xml:"xmlns:wsu,attr"` 48 Timestamp Timestamp 49 BinarySecurityToken *BinarySecurityToken `xml:",omitempty"` 50 UsernameToken *UsernameToken `xml:",omitempty"` 51 Assertion string `xml:",innerxml"` 52 Signature *Signature `xml:",omitempty"` 53 } 54 55 type Timestamp struct { 56 XMLName xml.Name `xml:"wsu:Timestamp"` 57 NS string `xml:"xmlns:wsu,attr"` 58 ID string `xml:"wsu:Id,attr"` 59 Created string `xml:"wsu:Created"` 60 Expires string `xml:"wsu:Expires"` 61 } 62 63 func (t *Timestamp) C14N() string { 64 return Marshal(t) 65 } 66 67 type BinarySecurityToken struct { 68 XMLName xml.Name `xml:"wsse:BinarySecurityToken"` 69 EncodingType string `xml:"EncodingType,attr"` 70 ValueType string `xml:"ValueType,attr"` 71 ID string `xml:"wsu:Id,attr"` 72 Value string `xml:",chardata"` 73 } 74 75 type UsernameToken struct { 76 XMLName xml.Name `xml:"wsse:UsernameToken"` 77 Username string `xml:"wsse:Username"` 78 Password string `xml:"wsse:Password"` 79 } 80 81 type Signature struct { 82 XMLName xml.Name 83 NS string `xml:"xmlns:ds,attr"` 84 ID string `xml:"Id,attr"` 85 SignedInfo SignedInfo 86 SignatureValue Value 87 KeyInfo KeyInfo 88 } 89 90 func (s *Signature) C14N() string { 91 return fmt.Sprintf(`<ds:Signature xmlns:ds="%s">%s%s%s</ds:Signature>`, 92 DSIG, s.SignedInfo.C14N(), s.SignatureValue.C14N(), s.KeyInfo.C14N()) 93 } 94 95 type SignedInfo struct { 96 XMLName xml.Name 97 NS string `xml:"xmlns:ds,attr,omitempty"` 98 CanonicalizationMethod Method 99 SignatureMethod Method 100 Reference []Reference 101 } 102 103 func (s SignedInfo) C14N() string { 104 ns := "" // empty in ActAs c14n form for example 105 if s.NS != "" { 106 ns = fmt.Sprintf(` xmlns:ds="%s"`, s.NS) 107 } 108 109 c14n := []string{fmt.Sprintf("<ds:SignedInfo%s>", ns)} 110 c14n = append(c14n, s.CanonicalizationMethod.C14N(), s.SignatureMethod.C14N()) 111 for i := range s.Reference { 112 c14n = append(c14n, s.Reference[i].C14N()) 113 } 114 c14n = append(c14n, "</ds:SignedInfo>") 115 116 return strings.Join(c14n, "") 117 } 118 119 type Method struct { 120 XMLName xml.Name 121 Algorithm string `xml:",attr"` 122 } 123 124 func (m *Method) C14N() string { 125 return mkns("ds", m, &m.XMLName) 126 } 127 128 type Value struct { 129 XMLName xml.Name 130 Value string `xml:",innerxml"` 131 } 132 133 func (v *Value) C14N() string { 134 return mkns("ds", v, &v.XMLName) 135 } 136 137 type Reference struct { 138 XMLName xml.Name 139 URI string `xml:",attr"` 140 Transforms Transforms 141 DigestMethod Method 142 DigestValue Value 143 } 144 145 func (r Reference) C14N() string { 146 for i := range r.Transforms.Transform { 147 t := &r.Transforms.Transform[i] 148 t.XMLName.Local = "ds:Transform" 149 t.XMLName.Space = "" 150 151 if t.InclusiveNamespaces != nil { 152 name := &t.InclusiveNamespaces.XMLName 153 if !strings.HasPrefix(name.Local, "ec:") { 154 name.Local = "ec:" + name.Local 155 name.Space = "" 156 } 157 t.InclusiveNamespaces.NS = t.Algorithm 158 } 159 } 160 161 c14n := []string{ 162 fmt.Sprintf(`<ds:Reference URI="%s">`, r.URI), 163 r.Transforms.C14N(), 164 r.DigestMethod.C14N(), 165 r.DigestValue.C14N(), 166 "</ds:Reference>", 167 } 168 169 return strings.Join(c14n, "") 170 } 171 172 func NewReference(id string, val string) Reference { 173 sum := sha256.Sum256([]byte(val)) 174 175 return Reference{ 176 XMLName: xml.Name{Local: "ds:Reference"}, 177 URI: "#" + id, 178 Transforms: Transforms{ 179 XMLName: xml.Name{Local: "ds:Transforms"}, 180 Transform: []Transform{ 181 Transform{ 182 XMLName: xml.Name{Local: "ds:Transform"}, 183 Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", 184 }, 185 }, 186 }, 187 DigestMethod: Method{ 188 XMLName: xml.Name{Local: "ds:DigestMethod"}, 189 Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256", 190 }, 191 DigestValue: Value{ 192 XMLName: xml.Name{Local: "ds:DigestValue"}, 193 Value: base64.StdEncoding.EncodeToString(sum[:]), 194 }, 195 } 196 } 197 198 type Transforms struct { 199 XMLName xml.Name 200 Transform []Transform 201 } 202 203 func (t *Transforms) C14N() string { 204 return mkns("ds", t, &t.XMLName) 205 } 206 207 type Transform struct { 208 XMLName xml.Name 209 Algorithm string `xml:",attr"` 210 InclusiveNamespaces *InclusiveNamespaces `xml:",omitempty"` 211 } 212 213 type InclusiveNamespaces struct { 214 XMLName xml.Name 215 NS string `xml:"xmlns:ec,attr,omitempty"` 216 PrefixList string `xml:",attr"` 217 } 218 219 type X509Data struct { 220 XMLName xml.Name 221 X509Certificate string `xml:",innerxml"` 222 } 223 224 type KeyInfo struct { 225 XMLName xml.Name 226 NS string `xml:"xmlns:ds,attr,omitempty"` 227 SecurityTokenReference *SecurityTokenReference `xml:",omitempty"` 228 X509Data *X509Data `xml:",omitempty"` 229 } 230 231 func (o *KeyInfo) C14N() string { 232 names := []*xml.Name{ 233 &o.XMLName, 234 } 235 236 if o.SecurityTokenReference != nil { 237 names = append(names, &o.SecurityTokenReference.XMLName) 238 } 239 if o.X509Data != nil { 240 names = append(names, &o.X509Data.XMLName) 241 } 242 243 return mkns("ds", o, names...) 244 } 245 246 type SecurityTokenReference struct { 247 XMLName xml.Name `xml:"wsse:SecurityTokenReference"` 248 WSSE11 string `xml:"xmlns:wsse11,attr,omitempty"` 249 TokenType string `xml:"wsse11:TokenType,attr,omitempty"` 250 Reference *SecurityReference `xml:",omitempty"` 251 KeyIdentifier *KeyIdentifier `xml:",omitempty"` 252 } 253 254 type SecurityReference struct { 255 XMLName xml.Name `xml:"wsse:Reference"` 256 URI string `xml:",attr"` 257 ValueType string `xml:",attr"` 258 } 259 260 type KeyIdentifier struct { 261 XMLName xml.Name `xml:"wsse:KeyIdentifier"` 262 ID string `xml:",innerxml"` 263 ValueType string `xml:",attr"` 264 } 265 266 type Issuer struct { 267 XMLName xml.Name 268 Format string `xml:",attr"` 269 Value string `xml:",innerxml"` 270 } 271 272 func (i *Issuer) C14N() string { 273 return mkns("saml2", i, &i.XMLName) 274 } 275 276 type Assertion struct { 277 XMLName xml.Name 278 ID string `xml:",attr"` 279 IssueInstant string `xml:",attr"` 280 Version string `xml:",attr"` 281 Issuer Issuer 282 Signature Signature 283 Subject Subject 284 Conditions Conditions 285 AuthnStatement AuthnStatement 286 AttributeStatement AttributeStatement 287 } 288 289 func (a *Assertion) C14N() string { 290 start := `<saml2:Assertion xmlns:saml2="%s" ID="%s" IssueInstant="%s" Version="%s">` 291 c14n := []string{ 292 fmt.Sprintf(start, a.XMLName.Space, a.ID, a.IssueInstant, a.Version), 293 a.Issuer.C14N(), 294 a.Signature.C14N(), 295 a.Subject.C14N(), 296 a.Conditions.C14N(), 297 a.AuthnStatement.C14N(), 298 a.AttributeStatement.C14N(), 299 `</saml2:Assertion>`, 300 } 301 302 return strings.Join(c14n, "") 303 } 304 305 type NameID struct { 306 XMLName xml.Name 307 Format string `xml:",attr"` 308 ID string `xml:",innerxml"` 309 } 310 311 type Subject struct { 312 XMLName xml.Name 313 NameID NameID 314 SubjectConfirmation SubjectConfirmation 315 } 316 317 func (s *Subject) C14N() string { 318 data := &s.SubjectConfirmation.SubjectConfirmationData 319 names := []*xml.Name{ 320 &s.XMLName, 321 &s.NameID.XMLName, 322 &s.SubjectConfirmation.XMLName, 323 &data.XMLName, 324 } 325 if s.SubjectConfirmation.NameID != nil { 326 names = append(names, &s.SubjectConfirmation.NameID.XMLName) 327 } 328 if data.KeyInfo != nil { 329 data.NS = XSI 330 data.Type = "saml2:KeyInfoConfirmationDataType" 331 data.KeyInfo.XMLName = xml.Name{Local: "ds:KeyInfo"} 332 data.KeyInfo.X509Data.XMLName = xml.Name{Local: "ds:X509Data"} 333 data.KeyInfo.NS = DSIG 334 } 335 return mkns("saml2", s, names...) 336 } 337 338 type SubjectConfirmationData struct { 339 XMLName xml.Name 340 NS string `xml:"xmlns:xsi,attr,omitempty"` 341 Type string `xml:"xsi:type,attr,omitempty"` 342 NotOnOrAfter string `xml:",attr,omitempty"` 343 KeyInfo *KeyInfo 344 } 345 346 type SubjectConfirmation struct { 347 XMLName xml.Name 348 Method string `xml:",attr"` 349 NameID *NameID 350 SubjectConfirmationData SubjectConfirmationData 351 } 352 353 type Condition struct { 354 Type string `xml:"xsi:type,attr,omitempty"` 355 } 356 357 func (c *Condition) GetCondition() *Condition { 358 return c 359 } 360 361 type BaseCondition interface { 362 GetCondition() *Condition 363 } 364 365 func init() { 366 types.Add("BaseCondition", reflect.TypeOf((*Condition)(nil)).Elem()) 367 types.Add("del:DelegationRestrictionType", reflect.TypeOf((*DelegateRestriction)(nil)).Elem()) 368 types.Add("rsa:RenewRestrictionType", reflect.TypeOf((*RenewRestriction)(nil)).Elem()) 369 } 370 371 type Conditions struct { 372 XMLName xml.Name 373 NotBefore string `xml:",attr"` 374 NotOnOrAfter string `xml:",attr"` 375 ProxyRestriction *ProxyRestriction `xml:",omitempty"` 376 Condition []BaseCondition `xml:",omitempty"` 377 } 378 379 func (c *Conditions) C14N() string { 380 names := []*xml.Name{ 381 &c.XMLName, 382 } 383 384 if c.ProxyRestriction != nil { 385 names = append(names, &c.ProxyRestriction.XMLName) 386 } 387 388 for i := range c.Condition { 389 switch r := c.Condition[i].(type) { 390 case *DelegateRestriction: 391 names = append(names, &r.XMLName, &r.Delegate.NameID.XMLName) 392 r.NS = XSI 393 r.Type = "del:DelegationRestrictionType" 394 r.Delegate.NS = r.Delegate.XMLName.Space 395 r.Delegate.XMLName = xml.Name{Local: "del:Delegate"} 396 case *RenewRestriction: 397 names = append(names, &r.XMLName) 398 r.NS = XSI 399 r.Type = "rsa:RenewRestrictionType" 400 } 401 } 402 403 return mkns("saml2", c, names...) 404 } 405 406 type ProxyRestriction struct { 407 XMLName xml.Name 408 Count int32 `xml:",attr"` 409 } 410 411 type RenewRestriction struct { 412 XMLName xml.Name 413 NS string `xml:"xmlns:xsi,attr,omitempty"` 414 Count int32 `xml:",attr"` 415 Condition 416 } 417 418 type Delegate struct { 419 XMLName xml.Name 420 NS string `xml:"xmlns:del,attr,omitempty"` 421 DelegationInstant string `xml:",attr"` 422 NameID NameID 423 } 424 425 type DelegateRestriction struct { 426 XMLName xml.Name 427 NS string `xml:"xmlns:xsi,attr,omitempty"` 428 Condition 429 Delegate Delegate 430 } 431 432 type AuthnStatement struct { 433 XMLName xml.Name 434 AuthnInstant string `xml:",attr"` 435 AuthnContext struct { 436 XMLName xml.Name 437 AuthnContextClassRef struct { 438 XMLName xml.Name 439 Value string `xml:",innerxml"` 440 } 441 } 442 } 443 444 func (a *AuthnStatement) C14N() string { 445 return mkns("saml2", a, &a.XMLName, &a.AuthnContext.XMLName, &a.AuthnContext.AuthnContextClassRef.XMLName) 446 } 447 448 type AttributeStatement struct { 449 XMLName xml.Name 450 Attribute []Attribute 451 } 452 453 func (a *AttributeStatement) C14N() string { 454 c14n := []string{"<saml2:AttributeStatement>"} 455 for i := range a.Attribute { 456 c14n = append(c14n, a.Attribute[i].C14N()) 457 } 458 c14n = append(c14n, "</saml2:AttributeStatement>") 459 return strings.Join(c14n, "") 460 } 461 462 type AttributeValue struct { 463 XMLName xml.Name 464 Type string `xml:"type,attr,typeattr"` 465 Value string `xml:",innerxml"` 466 } 467 468 func (a *AttributeValue) C14N() string { 469 return fmt.Sprintf(`<saml2:AttributeValue xmlns:xsi="%s" xsi:type="%s">%s</saml2:AttributeValue>`, XSI, a.Type, a.Value) 470 } 471 472 type Attribute struct { 473 XMLName xml.Name 474 FriendlyName string `xml:",attr"` 475 Name string `xml:",attr"` 476 NameFormat string `xml:",attr"` 477 AttributeValue []AttributeValue 478 } 479 480 func (a *Attribute) C14N() string { 481 c14n := []string{ 482 fmt.Sprintf(`<saml2:Attribute FriendlyName="%s" Name="%s" NameFormat="%s">`, a.FriendlyName, a.Name, a.NameFormat), 483 } 484 485 for i := range a.AttributeValue { 486 c14n = append(c14n, a.AttributeValue[i].C14N()) 487 } 488 489 c14n = append(c14n, `</saml2:Attribute>`) 490 491 return strings.Join(c14n, "") 492 } 493 494 type Lifetime struct { 495 Created string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"` 496 Expires string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Expires"` 497 } 498 499 func (t *Lifetime) C14N() string { 500 return fmt.Sprintf(`<Lifetime><wsu:Created>%s</wsu:Created><wsu:Expires>%s</wsu:Expires></Lifetime>`, t.Created, t.Expires) 501 } 502 503 type Renewing struct { 504 Allow bool `xml:",attr"` 505 OK bool `xml:",attr"` 506 } 507 508 type UseKey struct { 509 Sig string `xml:",attr"` 510 } 511 512 type Target struct { 513 Token string `xml:",innerxml"` 514 } 515 516 type RequestSecurityToken struct { 517 TokenType string `xml:",omitempty"` 518 RequestType string `xml:",omitempty"` 519 Lifetime *Lifetime `xml:",omitempty"` 520 Renewing *Renewing `xml:",omitempty"` 521 Delegatable bool `xml:",omitempty"` 522 KeyType string `xml:",omitempty"` 523 SignatureAlgorithm string `xml:",omitempty"` 524 UseKey *UseKey `xml:",omitempty"` 525 ActAs *Target `xml:",omitempty"` 526 ValidateTarget *Target `xml:",omitempty"` 527 RenewTarget *Target `xml:",omitempty"` 528 } 529 530 func Unmarshal(data []byte, v any) error { 531 dec := xml.NewDecoder(bytes.NewReader(data)) 532 dec.TypeFunc = types.TypeFunc() 533 return dec.Decode(v) 534 } 535 536 // toString returns an XML encoded RequestSecurityToken. 537 // When c14n is true, returns the canonicalized ActAs.Assertion which is required to sign the Issue request. 538 // When c14n is false, returns the original content of the ActAs.Assertion. 539 // The original content must be used within the request Body, as it has its own signature. 540 func (r *RequestSecurityToken) toString(c14n bool) string { 541 actas := "" 542 if r.ActAs != nil { 543 token := r.ActAs.Token 544 if c14n { 545 var a Assertion 546 err := Unmarshal([]byte(r.ActAs.Token), &a) 547 if err != nil { 548 log.Printf("decode ActAs: %s", err) 549 } 550 token = a.C14N() 551 } 552 553 actas = fmt.Sprintf(`<wst:ActAs xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200802">%s</wst:ActAs>`, token) 554 } 555 556 body := []string{ 557 fmt.Sprintf(`<RequestSecurityToken xmlns="http://docs.oasis-open.org/ws-sx/ws-trust/200512">`), 558 fmt.Sprintf(`<TokenType>%s</TokenType>`, r.TokenType), 559 fmt.Sprintf(`<RequestType>%s</RequestType>`, r.RequestType), 560 r.Lifetime.C14N(), 561 } 562 563 if r.RenewTarget == nil { 564 body = append(body, 565 fmt.Sprintf(`<Renewing Allow="%t" OK="%t"></Renewing>`, r.Renewing.Allow, r.Renewing.OK), 566 fmt.Sprintf(`<Delegatable>%t</Delegatable>`, r.Delegatable), 567 actas, 568 fmt.Sprintf(`<KeyType>%s</KeyType>`, r.KeyType), 569 fmt.Sprintf(`<SignatureAlgorithm>%s</SignatureAlgorithm>`, r.SignatureAlgorithm), 570 fmt.Sprintf(`<UseKey Sig="%s"></UseKey>`, r.UseKey.Sig)) 571 } else { 572 token := r.RenewTarget.Token 573 if c14n { 574 var a Assertion 575 err := Unmarshal([]byte(r.RenewTarget.Token), &a) 576 if err != nil { 577 log.Printf("decode Renew: %s", err) 578 } 579 token = a.C14N() 580 } 581 582 body = append(body, 583 fmt.Sprintf(`<UseKey Sig="%s"></UseKey>`, r.UseKey.Sig), 584 fmt.Sprintf(`<RenewTarget>%s</RenewTarget>`, token)) 585 } 586 587 return strings.Join(append(body, `</RequestSecurityToken>`), "") 588 } 589 590 func (r *RequestSecurityToken) C14N() string { 591 return r.toString(true) 592 } 593 594 func (r *RequestSecurityToken) String() string { 595 return r.toString(false) 596 } 597 598 type RequestSecurityTokenResponseCollection struct { 599 RequestSecurityTokenResponse RequestSecurityTokenResponse 600 } 601 602 type RequestSecurityTokenResponse struct { 603 RequestedSecurityToken RequestedSecurityToken 604 Lifetime *Lifetime `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 Lifetime"` 605 } 606 607 type RequestedSecurityToken struct { 608 Assertion string `xml:",innerxml"` 609 } 610 611 type RequestSecurityTokenBody struct { 612 Req *RequestSecurityToken `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityToken,omitempty"` 613 Res *RequestSecurityTokenResponseCollection `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityTokenResponseCollection,omitempty"` 614 Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"` 615 } 616 617 func (b *RequestSecurityTokenBody) Fault() *soap.Fault { return b.Fault_ } 618 619 func (b *RequestSecurityTokenBody) RequestSecurityToken() *RequestSecurityToken { return b.Req } 620 621 func (r *RequestSecurityToken) Action() string { 622 kind := path.Base(r.RequestType) 623 return "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/" + kind 624 } 625 626 func Issue(ctx context.Context, r soap.RoundTripper, req *RequestSecurityToken) (*RequestSecurityTokenResponseCollection, error) { 627 var reqBody, resBody RequestSecurityTokenBody 628 629 reqBody.Req = req 630 631 if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil { 632 return nil, err 633 } 634 635 return resBody.Res, nil 636 } 637 638 type RenewSecurityTokenBody struct { 639 Req *RequestSecurityToken `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityToken,omitempty"` 640 Res *RequestSecurityTokenResponse `xml:"http://docs.oasis-open.org/ws-sx/ws-trust/200512 RequestSecurityTokenResponse,omitempty"` 641 Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"` 642 } 643 644 func (b *RenewSecurityTokenBody) Fault() *soap.Fault { return b.Fault_ } 645 646 func (b *RenewSecurityTokenBody) RequestSecurityToken() *RequestSecurityToken { return b.Req } 647 648 func Renew(ctx context.Context, r soap.RoundTripper, req *RequestSecurityToken) (*RequestSecurityTokenResponse, error) { 649 var reqBody, resBody RenewSecurityTokenBody 650 651 reqBody.Req = req 652 653 if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil { 654 return nil, err 655 } 656 657 return resBody.Res, nil 658 } 659 660 // Marshal panics if xml.Marshal returns an error 661 func Marshal(val any) string { 662 b, err := xml.Marshal(val) 663 if err != nil { 664 panic(err) 665 } 666 return string(b) 667 } 668 669 // mkns prepends the given namespace to xml.Name.Local and returns obj encoded as xml. 670 // Note that the namespace is required when encoding, but the namespace prefix must not be 671 // present when decoding as Go's decoding does not handle namespace prefix. 672 func mkns(ns string, obj any, name ...*xml.Name) string { 673 ns += ":" 674 for i := range name { 675 name[i].Space = "" 676 if !strings.HasPrefix(name[i].Local, ns) { 677 name[i].Local = ns + name[i].Local 678 } 679 } 680 681 return Marshal(obj) 682 }