russellhaering / gosaml2 Goto Github PK
View Code? Open in Web Editor NEWPure Go implementation of SAML 2.0
License: Apache License 2.0
Pure Go implementation of SAML 2.0
License: Apache License 2.0
Hi, there,
I had an issue while importing this lib for our current SAML token validation. Our current lib also check SessionNotOnOrAfter for expiration. If NotOnOrAfter or SessionNotOnOrAfter is not expired, the SAML token is valid.
Do you think this is a valid request to make the changes to the following code? If not, can we add a flag to skip the following code? So we can handle the expiration logic properly in application layer.
now := sp.Clock.Now()
if now.After(notOnOrAfter) {
return ErrInvalidValue{
Reason: ReasonExpired,
Key: NotOnOrAfterAttr,
Expected: now.Format(time.RFC3339),
Actual: subjectConfirmationData.NotOnOrAfter,
}
}
Hi, I'm back with another request, although this one is less cut and dry. The WarningInfo struct has a warning InvalidTime that's used while processing Assertions, which was going to be very useful for me. But I found out that the InvalidTime warning is only thrown when the Assertion Conditions NotOnOrAfter is violated (validate.go line 79). If, on the other hand, the Assertion SubjectConfirmation NotOnOrAfter is violated, the library returns an error (validate.go line 206). These two seem like the same class of problem to me, so I was wondering if there was a reason why they were handled differently, and if it was possible for both of them to be warnings?
Thanks!
Is this library vulnerable? Perhaps a test case like crewjam/saml#140 could be added here?
The SAML assertion includes the NameID format along with the NameID. For example, a SAML assertion sent back from the IdP could have:
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">
3f7b3dcf-1674-4ecd-92c8-1544f346baf8
</saml:NameID>
Having the NameID format available in AssertionInfo
could be useful for service providers if they only accept certain kinds of NameID formats and want to validate based off this field.
First, thanks for the great library. My issue: The struct to store metadata is
type EntityDescriptor struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
// SAML 2.0 8.3.6 Entity Identifier could be used to represent issuer
EntityID string `xml:"entityID,attr"`
IDPSSODescriptor IDPSSODescriptor `xml:"IDPSSODescriptor"`
}
and then the IDPSSODescriptor struct is
type IDPSSODescriptor struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor"`
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
SingleSignOnService SingleSignOnService `xml:"SingleSignOnService"`
Attributes []Attribute `xml:"Attribute"`
}
That means that there can only be one instance of a SingleSignOnService struct. However, IDPs are allowed to provide multiple endpoints with different bindings. For example, this is (scrubbed) metadata I got from OneLogin:
<EntityDescriptor entityID="https://app.onelogin.com/saml/metadata/000">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo>
<X509Data>
<X509Certificate>...</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://site.onelogin.com/trust/saml2/http-redirect/slo/00000"/>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://site.onelogin.com/trust/saml2/http-redirect/sso/000"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://site.onelogin.com/trust/saml2/http-post/sso/000"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://site.onelogin.com/trust/saml2/soap/sso/000"/>
</IDPSSODescriptor>
</EntityDescriptor>
As it stands, unmarshalling picks one of the endpoints, and I don't get the information for the rest. Adding [] to the struct so that it can store multiple SingleSignOn instances would essentially fix the issue I think. Thoughts?
This would have prevented the recent vulnerabilities.
I'm trying to understand why we need to provide a certificate to create AuthnRequests. So far, I've generated a random one using dsig.RandomKeyStoreForTest()
, and haven't imported this anywhere into my IDP, but everything is working smoothly - so I'm wondering what purpose it serves.
I'm assuming its for a more complex use case?
Hello, Is there any way I can get metadata XML string from the library? It is usually used in order to integrate with IdP and contains information like entity name, public key, assertion consumer endpoint etc.
Hello,
I found your library and have some trouble setting it up to work with the AWS SSO SAML provider.
I followed the s2sample and adjusted some things. First I adjusted the http.Get() call to the metadata of the AWS SSO Portal link of my application. Which I configured like this:
Application start URL: http://localhost:8080/v1/_saml_callback
Application ACS URL: http://localhost:8080/v1/_saml_callback
Application SAML Audience: http://localhost:8080/
I had to create my own tls keypair and configured it for the SPKeyStore field in the SAMLServiceProvider{} struct as dsig.TLSCertKeyStore(keyPair).
As I am new to SAML Im not fully convinced I have set the correct URLs, but as the example application only exposes the /v1/_saml_callback handler, I guess this should be more or less correct.
When I start the application it prints out the sp.BuildAuthURL and if I open this in my browser I will get an error message from the AWS with "Issuer of request does not match our record".
Suitable to this, when I login to the application page in the AWS SSO and activate the configured application to start the process of logging in from the IdP page, I get an 403 not authorized error from the /v1/_saml_callback page.
Any hints what I may be doing wrong, as I suspect the error on my side and not your library.
Marcus
Hello thank you for this Library!
I need to use this library with PingOne and with signed authentication requests. There is currently no exported function to build an Auth URL with the HTTP Redirect Binding.
The current function BuildAuthURL
calls the function BuildAuthURLFromDocument
which has this singular line of code:
return sp.buildAuthURLFromDocument(relayState, BindingHttpPost, doc)
Unless this is a bug and it's supposed to call BindingHttpRedirect
, the library appears to be missing the exported function BuildAuthURLWithRedirect
I have a PR in my personal Git with the requested changes: https://github.com/TheGreatAbyss/gosaml2/pull/1/files
If you would grant me access to the repo I'd be happy to push a branch and open a MR.
Thank You
-- Eric
I have a Okta Application that has some people configured as part of multiple groups. In the SAMLResponse I do see the data: (Groups field has "QA", "Dev" and "Everyone".
Groups: {XMLName:{Space:urn:oasis:names:tc:SAML:2.0:assertion Local:Attribute} FriendlyName: Name:Groups NameFormat:urn:oasis:names:tc:SAML:2.0:attrname-format:basic Values:[{XMLName:{Space:urn:oasis:names:tc:SAML:2.0:assertion Local:AttributeValue} Type: Value:QA} {XMLName:{Space:urn:oasis:names:tc:SAML:2.0:assertion Local:AttributeValue} Type: Value:Everyone} {XMLName:{Space:urn:oasis:names:tc:SAML:2.0:assertion Local:AttributeValue} Type: Value:Dev}]}
However, since the library is treating everything like a key / value strings, only the last group gets attached. i.e. attributeStatement["Groups"] is "QA" (one of them)
This is probably a generic problem for handling attribute Statements where we could expect a list and not just a string.
Solution: We need to support arrays if the attribute statement is a Array. I haven't thought the solution through yet but will give it shot and send a PR :)
I've been doing a bit of fuzzing of this package with go-fuzz. I captured a valid SAML response from Okta and then had go-fuzz mutate it. I have fuzzing implemented on this branch https://github.com/stevenjohnstone/gosaml2/tree/sjj/fuzzing.
Here's the panic in a simple test program:
package main
import (
"crypto/x509"
"encoding/base64"
"encoding/xml"
"time"
saml2 "github.com/russellhaering/gosaml2"
"github.com/russellhaering/gosaml2/types"
dsig "github.com/russellhaering/goxmldsig"
)
const (
timeString = "2019-08-12T12:00:52.718Z"
oktaMetadata = `<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor entityID="http://www.okta.com/exk133onomIuOW98z357" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"><md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAWxzAwX1MA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05MDUyNTExHDAaBgkqhkiG9w0BCQEW
DWluZm9Ab2t0YS5jb20wHhcNMTkwODA4MjA1MzMzWhcNMjkwODA4MjA1NDMzWjCBkjELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTA1MjUxMRwwGgYJ
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
m+ZZF6aEG6ehLLIV6RPA+i1z6ss3HBG2bZD3efwKCDDXYUkp59AE7JsjVHMtpJPHhzHuScuHDMlu
HmkBQTW7j9XpnaRn8SfZXkwlCUHTo+HAC9lwbQxO4d4wnwgnm6FAjm1I/gbfFAobd8BR9pDxHuXE
MQ0DtQu/W3WbDUrz/bhSxPJAoVy2koQn9G0y3unm7eRwYWHeuW6GdPWV2szTtDS0c3qtUXVF5Ugg
iQYlwQu6xkfy4l8iGJL7ETa2BmJzwCFecMIct87SqNhYQwCBH54MBaHcaSsCKyimNvMY9B7RmC+H
4+awePPA1q3R/UQ3Pfom8mx6yDdKIWqlkG3MsQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAiURCZ
P4oJWcf1o5nm4yG15UH01g/S6Y4OUWMi6BFJy9fCrJ0h/2BZKi68SQ0uMAbdK6anxCzq3Rr5MSzW
OWPQ1Zljn3LGPsiTFdFca/GVRen5IYQ7Dr2Mvhtm+QVscEY9TDjtETbTAHEVEjwXmB21wtdIhizv
sQS7wz0A8LV+Atpbev45RiV6COmB6T6vJuFQ7ZsDZMSHZriTYiETTJvHBGd7PtbCxYNc6LRB2JDb
wlekRhVEjR0UhnM+nn2sqqbv7tDEPs63lZSDXCnR1PhscHrEuQ04rHI3OL0gCULVQFvJrj85IAZF
1QQuGUK8ozfOyFpQWAJUW71INnF/SLWv</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://dev-905251.okta.com/app/medev905251_test_1/exk133onomIuOW98z357/sso/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://dev-905251.okta.com/app/medev905251_test_1/exk133onomIuOW98z357/sso/saml"/></md:IDPSSODescriptor></md:EntityDescriptor>`
badInput = `<saml2:Assertion ID="id1684056077776386493060641"IssueInstant="2019-08-12T12:00:52.718Z"Version="2.0"xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"xmlns:xs="http://www.w3.org/2001/XMLSchema"><saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"xmlns="">http://www.okta.com/exk133onomIuOW98z357</l><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#id1684056077776386493060641"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="xs"xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></m></s><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>dC1cm0pLLjIWZC6G2Pmf0JogmqHztp9W1euXPd/TUHo=</e></e></o><ds:SignatureValue>YRSCFLIkIgjbbYLyfCIc8jsP2MUJPjn+nYWRdlVIDdXtYXXxklYqdBXQsxDwNcsOAIGS75PeVGryml3oBkUDg/MfK7z/fFPLXX7c7xgh7/DBAFlSXbwlJQxuXQ5eZcGesgG6nYRwU1hpW+yN7C2ODN9KHi5TUdiEhvy8vdlFSfxdy4Mn68nG/UZBqmHHIZdRG2/Hpcs29YyaVVZUCZ0w22b7zsPuOXHuStOSTQ6isxI2R268+ZNKERYaNMCAGX4zNlT3mHBV0NnZkbO3wmlOfKksL+Qx7L64xFc3PaervxWuPqh2FoWpTCqFdliLdvUfFDszKXJKhO0bj1U0aSrdzg==</e><s><s><s></X></X></o></e><saml2:Subject xmlns=""><saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]</l><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml2:SubjectConfirmationData InResponseTo="_40a419f5-5c1c-43d0-5834-5caf268a5f01"NotOnOrAfter="2019-08-12T12:05:52.718Z"Recipient="https://127.0.0.1/login"/></l></l><saml2:Conditions NotBefore="2019-08-12T11:55:52.718Z"NotOnOrAfter="2019-08-12T12:05:52.718Z"xmlns=""><saml2:AudienceRestriction><saml2:Audience>37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f.jazznetworks.com</l></l></l><saml2:AuthnStatement AuthnInstant="2019-08-12T12:00:52.718Z"SessionIndex="_40a419f5-5c1c-43d0-5834-5caf268a5f01"xmlns=""><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</l></l></l><saml2:AttributeStatement xmlns=""><saml2:Attribute Name="FirstName"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns=""xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:type="xs:string">Steven</l></l><saml2:Attribute Name="LastName"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns=""xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:type="xs:string">Johnstone</l></l><saml2:Attribute Name="Email"NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns=""xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:type="xs:string">[email protected]`
)
func newServiceProvider() *saml2.SAMLServiceProvider {
metadata := &types.EntityDescriptor{} // may need to support EntityDescriptors (plural)
if err := xml.Unmarshal([]byte(oktaMetadata), metadata); err != nil {
panic(err)
}
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors {
for _, xcert := range kd.KeyInfo.X509Data.X509Certificates {
if xcert.Data == "" {
panic("nope")
}
certData, err := base64.StdEncoding.DecodeString(xcert.Data)
if err != nil {
panic(err)
}
idpCert, err := x509.ParseCertificate(certData)
if err != nil {
panic(err)
}
certStore.Roots = append(certStore.Roots, idpCert)
}
}
SSOs := metadata.IDPSSODescriptor.SingleSignOnServices
tenantURI := "test.example.com"
fakeTime, err := time.Parse(time.RFC3339, timeString)
if err != nil {
panic(err)
}
clock := dsig.NewFakeClockAt(fakeTime)
return &saml2.SAMLServiceProvider{
Clock: clock,
IdentityProviderSSOURL: SSOs[0].Location,
IdentityProviderIssuer: metadata.EntityID,
ServiceProviderIssuer: tenantURI,
AssertionConsumerServiceURL: "https://127.0.0.1/login",
SignAuthnRequests: true,
AudienceURI: tenantURI,
IDPCertificateStore: &certStore,
ValidateEncryptionCert: true,
}
}
func main() {
base64Input := base64.StdEncoding.EncodeToString([]byte(badInput))
if _, err := newServiceProvider().RetrieveAssertionInfo(base64Input); err != nil {
_ = err
}
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x6727e6]
goroutine 1 [running]:
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).validateAssertionSignatures.func1(0xc000179f20, 0xc000182ea0, 0x9, 0x1)
/goroot/go/src/github.com/russellhaering/gosaml2/decode_response.go:170 +0x56
github.com/russellhaering/goxmldsig/etreeutils.NSFindIterateCtx.func1(0xc000179f20, 0xc000182ea0, 0xc000179f20, 0x0)
/goroot/go/src/github.com/russellhaering/goxmldsig/etreeutils/namespace.go:281 +0x137
github.com/russellhaering/goxmldsig/etreeutils.NSTraverse(0xc0000ccc60, 0xc000182ea0, 0xc000179ef0, 0x30, 0x6ebda0)
/goroot/go/src/github.com/russellhaering/goxmldsig/etreeutils/namespace.go:148 +0x6e
github.com/russellhaering/goxmldsig/etreeutils.NSFindIterateCtx(0xc0000ccc60, 0xc000182ea0, 0x71c4db, 0x25, 0x712a6e, 0x9, 0xc000179ec0, 0x7152bf, 0x12)
/goroot/go/src/github.com/russellhaering/goxmldsig/etreeutils/namespace.go:268 +0xa4
github.com/russellhaering/goxmldsig/etreeutils.NSFindIterate(...)
/goroot/go/src/github.com/russellhaering/goxmldsig/etreeutils/namespace.go:257
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).validateAssertionSignatures(0xc000166000, 0xc000182ea0, 0x0, 0x0)
/goroot/go/src/github.com/russellhaering/gosaml2/decode_response.go:200 +0xed
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).ValidateEncodedResponse(0xc000166000, 0xc000145000, 0xf80, 0xc000013510, 0xc000160000, 0xc0000d8b40)
/goroot/go/src/github.com/russellhaering/gosaml2/decode_response.go:248 +0x30e
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).RetrieveAssertionInfo(0xc000166000, 0xc000145000, 0xf80, 0xba0, 0xc000145000, 0xf80)
/goroot/go/src/github.com/russellhaering/gosaml2/retrieve_assertion.go:40 +0x9b
main.main()
/goroot/go/src/github.com/russellhaering/gosaml2/crasher/main.go:92 +0xaa
exit status 2
Appears to be unverified assertions can have nil parents. Appears to be fixed with this patch:
index a40e4b1..0804962 100644
--- a/decode_response.go
+++ b/decode_response.go
@@ -6,6 +6,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
+ "errors"
"fmt"
"io/ioutil"
@@ -166,7 +167,11 @@ func (sp *SAMLServiceProvider) validateAssertionSignatures(el *etree.Element) er
signedAssertions := 0
unsignedAssertions := 0
validateAssertion := func(ctx etreeutils.NSContext, unverifiedAssertion *etree.Element) error {
- if unverifiedAssertion.Parent() != el {
+ parent := unverifiedAssertion.Parent()
+ if parent == nil {
+ return errors.New("parent is nil")
+ }
+ if parent != el {
return fmt.Errorf("found assertion with unexpected parent element: %s", unverifiedAssertion.Parent().Tag)
}
Hi, we tried switching a python implementation of our SAML client with this go library, and one of our users managed to trigger a panic:
runtime.gopanic
/usr/local/go/src/runtime/panic.go:770
crypto/cipher.(*cbcDecrypter).CryptBlocks
/usr/local/go/src/crypto/cipher/cbc.go:145
github.com/russellhaering/gosaml2/types.(*EncryptedAssertion).DecryptBytes
/gomod-cache/github.com/russellhaering/[email protected]/types/encrypted_assertion.go:68
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).decryptAssertions.func1
/gomod-cache/github.com/russellhaering/[email protected]/decode_response.go:176
github.com/russellhaering/goxmldsig/etreeutils.NSFindIterateCtx.func1
/gomod-cache/github.com/russellhaering/[email protected]/etreeutils/namespace.go:299
github.com/russellhaering/goxmldsig/etreeutils.NSTraverse
/gomod-cache/github.com/russellhaering/[email protected]/etreeutils/namespace.go:167
github.com/russellhaering/goxmldsig/etreeutils.NSTraverse
/gomod-cache/github.com/russellhaering/[email protected]/etreeutils/namespace.go:174
github.com/russellhaering/goxmldsig/etreeutils.NSFindIterateCtx
/gomod-cache/github.com/russellhaering/[email protected]/etreeutils/namespace.go:286
github.com/russellhaering/goxmldsig/etreeutils.NSFindIterate
/gomod-cache/github.com/russellhaering/[email protected]/etreeutils/namespace.go:275
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).decryptAssertions
/gomod-cache/github.com/russellhaering/[email protected]/decode_response.go:196
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).ValidateEncodedResponse
/gomod-cache/github.com/russellhaering/[email protected]/decode_response.go:287
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).RetrieveAssertionInfo
/gomod-cache/github.com/russellhaering/[email protected]/retrieve_assertion.go:54
We didn't log the SAML response, unfortunately, but could the condition be anticipated in order to fail gracefully and avoid a panic?
This is not requesting a feature or reporting a defect.
I have wasted so much time trying to get SAML working for Go web services. Other libraries haven't seemed to work or worked properly with one IdP but not with others, providing no insight as to why.
This package worked with minimal fuss. I'm grateful to have it.
In VerifyAssertionConditions
in validate.go
, notBefore
and notOnOrAfter
are doing time comparison finer than milliseconds.
According to SAML Core spec
1.3.3 Time Values
All SAML time values have the type xs:dateTime, which is built in to the W3C XML Schema Datatypes
specification [Schema2], and MUST be expressed in UTC form, with no time zone component.
SAML system entities SHOULD NOT rely on time resolution finer than milliseconds. Implementations
MUST NOT generate time instants that specify leap seconds.
TIme comparisons SHOULD NOT rely on time resolution finer than milliseconds.
In our case, the now
clock returned 20:08:46.518691
and the notBefore
SAML assertion had 20:08:46.55
which triggered the warning. The library should at most compare one significant digit after the seconds dot.
This is a major issue that causes intermittent login failure for users.
This affects all versions of package github.com/russellhaering/gosaml2. There is a crash on nil-pointer dereference caused by sending malformed XML signatures.
The document mentions #59 which was fixed with #90
Maybe https://ossindex.sonatype.org/vulnerability/CVE-2020-7731?component-type=golang&component-name=github.com%2Frussellhaering%2Fgosaml2 is not up-to-date.
In decode_response.go post decryption if there is an error in deflating the xml post decryption then the handler returns the error from decryption which will always be nil. It should return the error in parsing the decrypted XML
raw, derr := encryptedAssertion.DecryptBytes(decryptCert)
if derr != nil {
return fmt.Errorf("unable to decrypt encrypted assertion: %v", derr)
}
doc, _, err := parseResponse(raw)
if err != nil {
return fmt.Errorf("unable to create element from decrypted assertion bytes: %v", derr)
}
After parseResponse, it returns derr which will always be nil whereas it should be returning err. Is this a wrong expectation or am I missing something here?
Hi,
I've noticed that some structs and funcs exist to manage SLO but I haven't found how to use them. Could you please share an example ?
First of all, thanks for creating this library--we've found it incredibly useful for providing SAML support to enterprise customers of Sourcegraph!
The SAML 2.0 errata specifies an order to the query parameters in the build signature (https://www.oasis-open.org/committees/download.php/56779/sstc-saml-bindings-errata-2.0-wd-06.pdf):
This library uses the Go stdlib url.Values.Encode
method, which orders query parameters alphabetically. Apparently this relied upon by some SAML implementations: sourcegraph/sourcegraph#121.
Happy to submit a PR for this!
Is there any actual examples of SPKeyStore being set to a valid keystore? It's really confusing how to make this field consume some sort of keystore, preferably a JKS file.
I'm using the latest gosaml2 and noticed that my identity provider (Keycloak) does not accept the signed AuthNRequest.
The reason is:
But it seems that the Redirect binding authUrl does not have SigAlg and Signature parameters, even though setting SignAuthnRequests to true and SPKeyStore to my own Keystore.
reference: spring-projects/spring-security#7711
The link in SECURITY.md is broken, it returns a 404 Not Found when I click the link. I think you may have accidentally disabled private reporting in Settings (docs), as I don't see the button present in the Security tab for this repository but can see it on other repositories.
Azure AD does not support request compression.
All requests are compressed using flate.DefaultCompression.
Please add optional support for specifying no compression.
I just checked your demo on Microsoft Azure AD and it works fine to me. The only think I touched is
configuration of ServiceProviderIssuer and IdentityProviderIssuer. I put my example here.
sp := &saml2.SAMLServiceProvider{
IdentityProviderSSOURL: metadata.IDPSSODescriptor.SingleSignOnServices[0].Location,
ServiceProviderIssuer: "myApp",
IdentityProviderIssuer: "https://sts.windows.net/some-guid/",
AssertionConsumerServiceURL: "https://localhost:8080/api/sso-reply",
SignAuthnRequests: true,
AudienceURI: "http://localhost:8080/api/sso",
IDPCertificateStore: &certStore,
SPKeyStore: randomKeyStore,
}
Thank you! Hope it might help.
How to use this package for okta sso saml2.0 app? Please provide refference.. im not able to do it
So, I've been doing a bit of work with your library after seeing that you got much further than I ever did on the signature verification front. I'm currently working on integrating with Shibboleth and will likely have a few pull requests at least to enable better integration with Shib.
Eventually, though, I got to the point where I realized that you haven't yet added any decryption capability, which is essentially all I got working in my library (though it only implements a few specific decryption schemes for the Shib version I'm using).
I had gotten through the decryption piece and, excited about my progress, was promptly stopped in my tracks by the complexity of xmldsig, xmlc14n, etc. Looks like you got that hard part done where I gave up, though! :-)
I wanted to touch base and see what you would think about integrating efforts since it seems like there's very little overlap -- almost as if it was planned. I figure it'd mostly look like me merging my code into your codebase to support decrypting encrypted assertions, since you've got a little more traction (7 stars to my 0).
Let me know what you think! :-)
Hello,
We have a compatibility problem when switching to gosaml2 (from pysaml2) because we don't keep the public encryption certificate. But gosaml2 will fail when decrypting an EncryptedAssertion that contains this certificate as it tries to validate it against the client provided one (which we don't keep): "The EncryptedKey may or may not include X509Data (certificate)", current line
gosaml2/types/encrypted_key.go
Line 109 in 9517aa5
Thank you,
I have tried to use https://github.com/russellhaering/gosaml2/blob/master/s2example/demo.go to set up an integration test with Okta but I am not making much progress.
After tracing through the example and imported libs I noticed I was getting the following error (via adding my own debug):
>>>> assertionInfo: (*saml2.AssertionInfo)(nil), err: saml2.ErrVerification{Cause:saml2.ErrInvalidValue{Key:"Issuer", Expected:"urn:example:idp", Actual:"http://example.com/saml/acs/example", Reason:""}}
From main.go:
assertionInfo, err := sp.RetrieveAssertionInfo(req.FormValue("SAMLResponse"))
fmt.Printf(">>>> assertionInfo: %#v, err: %#v\n", assertionInfo, err)
if err != nil {
rw.WriteHeader(http.StatusForbidden)
return
}
Diving into this further it leads me to:
File: retrieve_assertion.go
40: response, err := sp.ValidateEncodedResponse(encodedResponse)
41: if err != nil {
42: return nil, ErrVerification{Cause: err}
43: }
And:
File: decode_response.go
228: var responseSignatureValidated bool
229: if !sp.SkipSignatureValidation {
230: el, err = sp.validateElementSignature(el)
231: fmt.Printf(">>>> el: %#v, err: %#v\n", el, err)
232: if err == dsig.ErrMissingSignature {
That prints:
>>>> el: (*etree.Element)(nil), err: &errors.errorString{s:"Missing signature referencing the top-level element"}
Can anyone confirm the Okta IDP integration still works? What could I possibly be doing wrong using the provided example?
The signature in the auth request in signed using the service providers private key and the validation is to be done with public certificate which should be exclusively with the Idp but this certificate is sent in the Auth Request.
Is this behavior correct as we are in the understanding that the public certificate sent in the request should be that of the Identity Provider for Identification purposes and not the one of the Service Provider which would be used for signature validation as all the required information for deciphering the auth request would then be in it.
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
AssertionConsumerServiceURL="https://localhost:10443/v1/samlacsendpoint"
Destination="https://auth.pingone.asia/793adfb9-e7c9-4e80-a1a2-335f27066ffe/saml20/idp/sso"
ID="_c46eb447-c2e0-4dcf-b2a3-ba54961e6bcf"
IssueInstant="2023-11-16T13:43:35Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0"
>
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://example.com/saml/acs/example</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#_c46eb447-c2e0-4dcf-b2a3-ba54961e6bcf">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>hUi2n9LKrdfhgOPawzk7SMMxCC3Pm1Ayusup34CQ5pw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>eaV6UNp+M6e0yifSPTqE3x5RAt3Mp14DuEqatLzkU2kA9tfjEJJkZsFQkjiSr7G+dIfqg5yzbT2fVIMToLEqrFJRU1zhFPXtz8+OwEZ2xzpW3k6DhDxwcVRRZbW7JhOD/qieYw9kDUDhAWbpmhU4QeFhnqMkrKCF5tqVtK0HOk/Z6wFEDopJGJ7OnPJAp57sfv+Lg6knw5qyz7lkx6WuPLJh6DygUYRWcZD2izV9iqinAAVI2pqWuE4uZIXvvF6vkyj1OfAV7b2d0RKrMt9ZuC3TpzxFEau3r9xxQ26iHrYRxrLE6I9nQT0V7GbNMAQzH9+x0x0pmfbWq2CWby9SSQ==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIID1zCCAr+gAwIBAgIUGLf0PTJTRxdcPtHTRCksRL0b9IswDQYJKoZIhvcNAQELBQAwezELMAkGA1UEBhMCSU4xDDAKBgNVBAgMA0dvYTEOMAwGA1UEBwwFVmVybmExEzARBgNVBAoMClBlcnNpc3RlbnQxDDAKBgNVBAsMA0libTErMCkGCSqGSIb3DQEJARYcY3lydXNfZnVydGFkb0BwZXJzaXN0ZW50LmNvbTAeFw0yMzEwMDkxMTQ5NDZaFw0yNDA0MDYxMTQ5NDZaMHsxCzAJBgNVBAYTAklOMQwwCgYDVQQIDANHb2ExDjAMBgNVBAcMBVZlcm5hMRMwEQYDVQQKDApQZXJzaXN0ZW50MQwwCgYDVQQLDANJYm0xKzApBgkqhkiG9w0BCQEWHGN5cnVzX2Z1cnRhZG9AcGVyc2lzdGVudC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2H1meXHs2/bc91+AD2QDiXN6MW4T3EkdT9O4m2xYyifd19ufKjU+nqeCp0j6S7q1RiG3L5MkgJLL4I6ar26Mpv9fHQlG6jhnVzh7Ks21xzhem+SjokxgbyFYDOVPD3B/yyZ7uWmQS9bsmwWeDym1Zsgb4jURl0OuWauu93mPyrUwHXZlUQBF8tcs1/uD+IyAGWeurBm9WEpoPRb9ur7YNlAQxqxMSfh8nIYpywHOPUrRXEX6yDAGsju85UANlbSwEBr8CTF7OkEF7NLxT/gESN0D8M8ZBwAEGxA5J6d//SXpqiLDt1ZfsBB2f27wO3c4s+OOGNvdq57yiYc7+mx3tAgMBAAGjUzBRMB0GA1UdDgQWBBQ5+yobfvbsYodF42pwYG5WQKiyRzAfBgNVHSMEGDAWgBQ5+yobfvbsYodF42pwYG5WQKiyRzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBe+TuprCYWSd/0SV910Gylg6OkPdMmh7Iuum87cwQSvL0PCpe2V531mvqXcWmeeetB1ABAX38TUdG9watNxJyZPELXGqc29YwHZQ28iOpWH3GXtm73mjXfF9L2up9tgc9AbVsG7WlZp8eTE/HdLLVKsFCG02cww57mCl+Ob2OLwCm+cfr2K5aceSk/6yvP+N5W1fajFtf0PLrt7RyQ2M8k+ySvSCHPED4VEZjJo5xUCH3CS+ooHkIHBHq+Ef6WRXA+DNLxvXppko7EKTIurG6gm5Q6zwaBfExbgWHSobs2BVlZBLvUuMhY6HAbXlan0T+lZvXC2kvgy+BULIPKziX3</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<samlp:NameIDPolicy AllowCreate="true" />
</samlp:AuthnRequest>
I got the following error from RetrieveAssertionInfo when testing with testshib.org:
error validating response: Missing signature referencing the top-level element
Howdy,
I noticed that currently assertions are stored as a map[string]string
โ meaning that two assertions with the same key (i.e. a multi-valued LDAP property) will get overwritten. I thought of two ways to fix this:
map[string][]string
, possibly wrapped like a url.Form
, so that the common case is simple.AssertionInfo
map, but add a supplementary list of Assertion
objects. (This would also allow some implementations to use the friendly attribute name.)Thoughts?
Setup from the example in README but used samltest.id instead of Okta for the IDP. Get the following message on samltest.id after the redirect to IDP:
Web Login Service - Message Security Error
The request cannot be fulfilled because the message received does not meet the security requirements of the login service...
In the error logs, I see some messages about invalid / unsupported envelope transforms:
2020-11-08 04:32:09,953 - DEBUG [org.opensaml.saml.common.binding.impl.SAMLAddAttributeConsumingServiceHandler:?] - Message Handler: Selecting default AttributeConsumingService, if any
2020-11-08 04:32:09,953 - DEBUG [org.opensaml.saml.common.binding.impl.SAMLAddAttributeConsumingServiceHandler:?] - Message Handler: No AttributeConsumingService selected
2020-11-08 04:32:09,954 - DEBUG [net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer:?] - Profile Action InitializeRelyingPartyContextFromSAMLPeer: Attaching RelyingPartyContext based on SAML peer boogie-woogie
2020-11-08 04:32:09,954 - DEBUG [net.shibboleth.idp.profile.interceptor.impl.FilterFlowsByNonBrowserSupport:?] - Profile Action FilterFlowsByNonBrowserSupport: Request does not have non-browser requirement, nothing to do
2020-11-08 04:32:09,955 - DEBUG [org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler:?] - Message Handler: Checking SAML message intended destination endpoint against receiver endpoint
2020-11-08 04:32:09,955 - DEBUG [org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler:?] - Message Handler: Intended message destination endpoint: https://samltest.id/idp/profile/SAML2/Redirect/SSO
2020-11-08 04:32:09,955 - DEBUG [org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler:?] - Message Handler: Actual message receiver endpoint: https://samltest.id/idp/profile/SAML2/Redirect/SSO
2020-11-08 04:32:09,955 - DEBUG [org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler:?] - Message Handler: SAML message intended destination endpoint matched recipient endpoint
2020-11-08 04:32:09,955 - DEBUG [org.opensaml.saml.common.binding.security.impl.MessageReplaySecurityHandler:?] - Message Handler: Evaluating message replay for message ID '_14c6baf5-d444-46af-96e6-53d288164010', issue instant '2020-11-08T04:32:09.000Z', entityID 'boogie-woogie'
2020-11-08 04:32:09,955 - DEBUG [org.opensaml.saml.security.impl.SAMLSignatureProfileValidator:?] - Saw Enveloped signature transform
2020-11-08 04:32:09,955 - ERROR [org.opensaml.saml.security.impl.SAMLSignatureProfileValidator:?] - Saw invalid signature transform: http://www.w3.org/2006/12/xml-c14n11
2020-11-08 04:32:09,956 - DEBUG [org.opensaml.saml.common.binding.security.impl.SAMLProtocolMessageXMLSignatureSecurityHandler:?] - Message Handler: Protocol message signature failed signature pre-validation
org.opensaml.xmlsec.signature.support.SignatureException: Signature contained an invalid transform
at org.opensaml.saml.security.impl.SAMLSignatureProfileValidator.validateTransforms(SAMLSignatureProfileValidator.java:243)
2020-11-08 04:32:09,956 - WARN [org.opensaml.profile.action.impl.LogEvent:?] - A non-proceed event occurred while processing the request: MessageAuthenticationError
2020-11-08 04:32:09,956 - DEBUG [org.opensaml.saml.common.profile.logic.DefaultLocalErrorPredicate:?] - No SAMLBindingContext or binding URI available, error must be handled locally
Not my area of expertise, so need some guidance here on what's going on.
There is a string member IdentityProviderSSOBinding in the SAMLServiceProvider struct, which doesn't seem to be used anywhere.
And in buildAuthnRequest there is no way to set ProtocolBinding to anything other than HTTP-POST. Was the IdentityProviderSSOBinding field expected to be used here?
Following up on Andrew's comment on #10. Russell, stylistic preferences between etree
and encoding/xml
? (I too like keeping with the stdlib, but it's not my library.)
Anyway, if you do have an opinion one way or another, I'd be happy to start poking at things.
Thank you for your continued support of this package over the years!
This package will show up with a vulnerability because the latest tag v0.6.0
has a dependency on an unpatched version of goxmldsig <v1.1.0
.
For anyone curious, there was a patch upstream russellhaering/goxmldsig#48 against GHSA-q547-gmf8-8jr7
To solve this we pinned the latest commit explicitly in our go.mod
github.com/russellhaering/gosaml2 v0.6.1-0.20210916051624-757d23f1bc28
But it'll be nice to avoid special casing this and get a version bump, say v.0.6.1
or v0.7.0
. What do you think @russellhaering ?
Thank you for this package.
I was using this package and realized that it lacked proper documentation. For a start, there could at least be a list of functions in the package. I had to dig through the code to find the functions that I wanted to use. It would be really helpful to have a list of functions available in README.
Having an example of the implementation of each of the functions would immensely help developers who use this package.
I started a similar project, https://github.com/amdonov/lite-idp, in 2015 including XML digital signatures. I came across yours when looking at the SAML code in https://github.com/coreos/dex.
Your libraries are much more mature, and I'd like to switch to them. However, I need Artifact Binding support. Would you accept a pull request if I write it?
If the IdP and SP times are out of sync, then the SP may end up mistaking a valid SAML assertion as invalid. Therefore, we should be able to specify a clock skew to use for the service provider so that values within clock skew range of the NotBefore
, NotOnOrAfter
, or SessionNotOnOrAfter
constraints are treated as valid.
The NameIDPolicy
element and Format
attribute are optional as per SAML spec. However, build_request.go
always creates them, even if no value has been specified in SAMLServiceProvider.NameIdFormat
. This results in a SAML request to the IdP containing:
<samlp:NameIDPolicy AllowCreate="true" Format="" />
Azure AD appears to have problems with this, replying with an InvalidNameIDPolicy
error and saying
Cannot provide requested name identifier with format for the given subject
(note two spaces between "format" and "for").
I think this is arguably a bug on Azure's part, since the standard says "if no Format value is provided, then the value urn:oasis:names:tc:SAML:1.0:nameid-format:unspecified (see Section 8.3.1) is in effect", and "" isn't a Format value. However, gosaml2's behavior is also a bug, and probably much easier to fix.
There's a PR which attempts to fix this -- #67 -- but I think it would be better to omit only the Format attribute when unspecified, and still allow the IdP to create new IDs.
I'm trying to use gosaml2 with JBoss Keycloak.
I'm consuming the descriptor metadata but I'm getting this error when running xml.Unmarshal
:
expected element type <EntityDescriptor> but have <EntitiesDescriptor>
Originally reported at russellhaering/goxmldsig#25
I'm working on improving validation, should have a fix within a few days.
Hi,
how do i generate the service provider metadata file that needs to be shared with the IDP
Is there is way that i can generate below meta data file using gosaml2 that needs to share with the IDP
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2021-07-27T11:19:13.559Z" entityID="http://localhost:8001/saml/metadata"> <SPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2021-07-27T11:19:13.55852Z" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="false" WantAssertionsSigned="true"> <KeyDescriptor use="encryption"> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <X509Data> <X509Certificate>MIICvDCCAaQCCQCujMRKGshPODANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wHhcNMjEwNzI1MTExODExWhcNMjIwNzI1MTExODExWjAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDruv/Q6k4TZFnG/vDd060iuRviN2yM0J92nXNn2g3c/g3oqiB9eom/1mRUKOeHOhOFjC18m/nOeNss6RXjkWezIuhzYNKP0ABfhCT1pdVOTI8uBag9aXFPYsyRt+mV6qrJ6pNTzYkWpub0dfMuY1XDkW46Zp6YvAf/UVyfGTq0ZkX0WjrrZJOjaYcWajJ73xm5ZZuoX4KTQmbx7ctVkHsKk9SXFnSkaj1iEdqyg2pwGiG89FGhX731O178V5duLzUAh1bLyEKZKAkofpDWQptrdbP9ztbvVYHKj3dtqnYLjQadfMiX3EDV4RKo86CKbFC7NSy/H0gOtkkYhL9R/VtHAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABw9BVBsJpcGGf7W9i+xaFsebb798TomoD2k5vNqJFi3ShpUAsqR4Zc1I9M4lPUPxFBBE+tpD1cVS6QQIfY62ql7ycd50Ma3CejkCW9DuOQ56X6hFIZl6TeZ0vv7T37m3F+URt7SXflRcn8VEQ1UidEc9VOD/E/1zgBkeniXkBXKgh5MYmUMfTF+flacNl1lBVFiibaD32aaVC97EqfdItcnlKvh7fYGiPAWFRoRrcJfKM1nurAt4kNEdpd/QncGQ2etUbkq3tBLczCRUdFKq1YYAmXVnZUZC6PM4+k/IPzR7xEdjZk1FEFNHTwOXLyxo3ZDlHmMcTvWlT93D7DXaqY=</X509Certificate> </X509Data> </KeyInfo> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"></EncryptionMethod> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"></EncryptionMethod> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"></EncryptionMethod> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"></EncryptionMethod> </KeyDescriptor> <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8001/saml/slo" ResponseLocation="http://localhost:8001/saml/slo"></SingleLogoutService> <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8001/saml/acs" index="1"></AssertionConsumerService> </SPSSODescriptor> </EntityDescriptor>
Line 228 in 2506a07
line 221 parseResponse(raw)
can return nil
for its etree values. This nil then can get used as a parameter in the function call on line 228 el, err = sp.validateElementSignature(el)
That calls the Validate function in goxmldsig/validate.go
, which includes el = el.Copy()
.
If el
is nil, it won't have a Copy
function
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.