mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-16 06:57:44 +08:00
feat(cloudid): add azure saml
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport" ID="_1e55eaf7-5cb9-4601-b557-e322fdfb2baf" entityID="urn:federation:MicrosoftOnline">
|
||||
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<SignedInfo>
|
||||
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||
<Reference URI="#_1e55eaf7-5cb9-4601-b557-e322fdfb2baf">
|
||||
<Transforms>
|
||||
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
|
||||
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||
</Transforms>
|
||||
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
|
||||
<DigestValue>fA33hUaj24/8F8bArDIkH6CpX0k=</DigestValue>
|
||||
</Reference>
|
||||
</SignedInfo>
|
||||
<SignatureValue>p/rlVWxLRIYiHozzWUgsjmQ5ItMZ/ITeEOg/edeo50l7qU8wLM9JD55EHoLpVvxImBcCfY1CxKdq3s1o9Up1jvEcH9KjfYZea1q8hzTkQdt7F2XvxoqDFRLvVwyHHJjygaEvFRXXxml7IDgDx9ScSRhQeGUlTGhI9eAFSaGkChM/FPnmekZ3ngSwbVf4FsTLIArxMqEnV0lXNMTr5NHQ6VL5wZ/cHLNCkkBoY0qWyv2hnYiZiXa4TKjpAN+8QF5GDPv/0ExJwe+aoRAQhdBzhBUICQdQd+v3Rb5SuX++iVfmf4IPMqj8CK4uK7iYXNLCGEO8oFR2KlK/zBpHzw6yCQ==</SignatureValue>
|
||||
<KeyInfo>
|
||||
<X509Data>
|
||||
<X509Certificate>MIIC/TCCAeWgAwIBAgIQF9xAVqfHt7dNSOnsMKYLDjANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwHhcNMTkwNjE4MDAwMDAwWhcNMjEwNjE4MDAwMDAwWjApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLwL+mOk+zhgjHPFiZuDaU7coCOMkYyJyWS2B2KWYx7BMmy+SBSlFvvDHIiMXeLnv9DiUnQXtjClZXNqEo2amM/KdXfnCUlwdnqGa89TbB8kM6aBQKCh5Up7vejD+KRpPcOK7DqxWnm037zNKH+6liwRMUD/+lkBODoG1/ThuBVM0Kr1Oowfnkb4JYeF5xZk+2+ThR2dcQzdsz8dBW1GpGopdoM5G1amVhoRPBeNlYUqf0JUPE36Dx0iCmeXX00HTR2gd+/DJFVe34l/VxL4cqufmRcqWHy7axonbpt8svjAervGgueF5W/BVGcO4eVwFzFfnAz7bSaH/zbsNnQkFtAgMBAAGjITAfMB0GA1UdDgQWBBQlxmu/Hj1qB2A306p0Sz9up711AjANBgkqhkiG9w0BAQsFAAOCAQEAEhb+i+J69DvzCTfmC0Mn+uSGzBaW0ksMZNjM8/p/RFueytNo8/DY6TUb9W8l/HSVON782iGG7hIsG68RUIZ5q+/WAB9iBvKkKv5aGZ0bK/eD5boHwBwMa3udTjU68Vkai0A7naISEh22h+lQPvKiftCEzK1x9j2FoAXEMhACnpTwRrrj3SsJXCsMaOKcVw7sCmM4/4wD5I1+dfMSe85rSEzzUCDcCMEg67sBKdO2CFK1GoH21ttrz1CV+xt8Pn9a488kgsp2ZyU+yixS59CrM7onEzt1R4miXWAJAtydNHAmmbgKrTxqf67fEXFcrlFAHexi6vvINZWZPD6+J+q6xg==</X509Certificate>
|
||||
</X509Data>
|
||||
</KeyInfo>
|
||||
</Signature>
|
||||
<SPSSODescriptor WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<KeyDescriptor use="signing">
|
||||
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<X509Data>
|
||||
<X509Certificate>MIIC/TCCAeWgAwIBAgIQF9xAVqfHt7dNSOnsMKYLDjANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwHhcNMTkwNjE4MDAwMDAwWhcNMjEwNjE4MDAwMDAwWjApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLwL+mOk+zhgjHPFiZuDaU7coCOMkYyJyWS2B2KWYx7BMmy+SBSlFvvDHIiMXeLnv9DiUnQXtjClZXNqEo2amM/KdXfnCUlwdnqGa89TbB8kM6aBQKCh5Up7vejD+KRpPcOK7DqxWnm037zNKH+6liwRMUD/+lkBODoG1/ThuBVM0Kr1Oowfnkb4JYeF5xZk+2+ThR2dcQzdsz8dBW1GpGopdoM5G1amVhoRPBeNlYUqf0JUPE36Dx0iCmeXX00HTR2gd+/DJFVe34l/VxL4cqufmRcqWHy7axonbpt8svjAervGgueF5W/BVGcO4eVwFzFfnAz7bSaH/zbsNnQkFtAgMBAAGjITAfMB0GA1UdDgQWBBQlxmu/Hj1qB2A306p0Sz9up711AjANBgkqhkiG9w0BAQsFAAOCAQEAEhb+i+J69DvzCTfmC0Mn+uSGzBaW0ksMZNjM8/p/RFueytNo8/DY6TUb9W8l/HSVON782iGG7hIsG68RUIZ5q+/WAB9iBvKkKv5aGZ0bK/eD5boHwBwMa3udTjU68Vkai0A7naISEh22h+lQPvKiftCEzK1x9j2FoAXEMhACnpTwRrrj3SsJXCsMaOKcVw7sCmM4/4wD5I1+dfMSe85rSEzzUCDcCMEg67sBKdO2CFK1GoH21ttrz1CV+xt8Pn9a488kgsp2ZyU+yixS59CrM7onEzt1R4miXWAJAtydNHAmmbgKrTxqf67fEXFcrlFAHexi6vvINZWZPD6+J+q6xg==</X509Certificate>
|
||||
</X509Data>
|
||||
</KeyInfo>
|
||||
</KeyDescriptor>
|
||||
<KeyDescriptor use="signing">
|
||||
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<X509Data>
|
||||
<X509Certificate>MIIC/TCCAeWgAwIBAgIQbgDHfi3t1JNGVqwD5/7lmjANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwHhcNMjAxMjIxMDAwMDAwWhcNMjUxMjIxMDAwMDAwWjApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFT0/0/2qQurnYa0LbJHF9YYozhEH6r9mCxVDBYbewSG4tGgrWpsewQ/96pcczGMQctMvU+h2eX38Hx/f9JAIDbuRQzQlsPhQS7DDZ6WlTXU+t8d/g2C7fpSoLs4KVdJih4xyjLUWj+BK/ijsRjBt4Riw9VbJH/DdWKyoSMbECEiE+s1RtLP/eYoMmNfxyQGqWirCNqVNBTlqzYQp4dgF0foYy4ktoxwmQOVoTcIMFYp1I4pFPI7CxuMLkfK0X7aTbM7YGphvMfJxJkjrQdyI7G5d1t4DNi3zkEbBT7FGAr6qPt3Kn9ralpqJKHdpEBA9N0vNwQo5XTYIhUbPQ16IRAgMBAAGjITAfMB0GA1UdDgQWBBRs7tPmfkksSr67KtElHjYZbeaCTjANBgkqhkiG9w0BAQsFAAOCAQEAJqwMZSjQJ36x+1sty6EeLKQLQewQwPaEC47Zut+8bXed6Q8jMZ0bfa/MM7XquEcabaMZLQuKLft44YXwXXQOfQrI2qjQr3eToJFlDT9hR0rfp9wQqttDxd6Aa6RWwDTgo5oKUQCTKLHhEy8uWzScK0eGt2d7TWTaDXjRSwNq6tM7fRhZs07tKBV3xfi9EQy/mlavAMFRBVm86NSo7AsOG1IOMq03U3ooCWAXh9PdvvHNfHhH19futAnC/HeOjwRF1Qc527aBMphYFQLdiThfmfmiE/AhQqCwZ2oE7uCJhBtR+Kb1ZGhjI35pHfsSqGiFa7Kr+5ave822PDcke89Mvg==</X509Certificate>
|
||||
</X509Data>
|
||||
</KeyInfo>
|
||||
</KeyDescriptor>
|
||||
<KeyDescriptor use="signing">
|
||||
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<X509Data>
|
||||
<X509Certificate>MIIC/TCCAeWgAwIBAgIQbgDHfi3t1JNGVqwD5/7lmjANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwHhcNMjAxMjIxMDAwMDAwWhcNMjUxMjIxMDAwMDAwWjApMScwJQYDVQQDEx5MaXZlIElEIFNUUyBTaWduaW5nIFB1YmxpYyBLZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFT0/0/2qQurnYa0LbJHF9YYozhEH6r9mCxVDBYbewSG4tGgrWpsewQ/96pcczGMQctMvU+h2eX38Hx/f9JAIDbuRQzQlsPhQS7DDZ6WlTXU+t8d/g2C7fpSoLs4KVdJih4xyjLUWj+BK/ijsRjBt4Riw9VbJH/DdWKyoSMbECEiE+s1RtLP/eYoMmNfxyQGqWirCNqVNBTlqzYQp4dgF0foYy4ktoxwmQOVoTcIMFYp1I4pFPI7CxuMLkfK0X7aTbM7YGphvMfJxJkjrQdyI7G5d1t4DNi3zkEbBT7FGAr6qPt3Kn9ralpqJKHdpEBA9N0vNwQo5XTYIhUbPQ16IRAgMBAAGjITAfMB0GA1UdDgQWBBRs7tPmfkksSr67KtElHjYZbeaCTjANBgkqhkiG9w0BAQsFAAOCAQEAJqwMZSjQJ36x+1sty6EeLKQLQewQwPaEC47Zut+8bXed6Q8jMZ0bfa/MM7XquEcabaMZLQuKLft44YXwXXQOfQrI2qjQr3eToJFlDT9hR0rfp9wQqttDxd6Aa6RWwDTgo5oKUQCTKLHhEy8uWzScK0eGt2d7TWTaDXjRSwNq6tM7fRhZs07tKBV3xfi9EQy/mlavAMFRBVm86NSo7AsOG1IOMq03U3ooCWAXh9PdvvHNfHhH19futAnC/HeOjwRF1Qc527aBMphYFQLdiThfmfmiE/AhQqCwZ2oE7uCJhBtR+Kb1ZGhjI35pHfsSqGiFa7Kr+5ave822PDcke89Mvg==</X509Certificate>
|
||||
</X509Data>
|
||||
</KeyInfo>
|
||||
</KeyDescriptor>
|
||||
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://login.microsoftonline.com/login.srf"/>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
||||
<NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
|
||||
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://login.microsoftonline.com/login.srf" index="0" isDefault="true"/>
|
||||
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://login.microsoftonline.com/login.srf" index="1"/>
|
||||
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://login.microsoftonline.com/login.srf" index="2"/>
|
||||
</SPSSODescriptor>
|
||||
<Extensions>
|
||||
<alg:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
|
||||
<alg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||
</Extensions>
|
||||
</EntityDescriptor>
|
||||
@@ -141,11 +141,11 @@ require (
|
||||
k8s.io/apimachinery v0.19.3
|
||||
k8s.io/client-go v0.19.3
|
||||
k8s.io/cluster-bootstrap v0.19.3
|
||||
yunion.io/x/executor v0.0.0-20201201131200-44fa553abd9e
|
||||
yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9
|
||||
yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769
|
||||
yunion.io/x/log v0.0.0-20201210064738-43181789dc74
|
||||
yunion.io/x/ovsdb v0.0.0-20200526071744-27bf0940cbc7
|
||||
yunion.io/x/pkg v0.0.0-20201123083159-ca3aea986ff2
|
||||
yunion.io/x/pkg v0.0.0-20210109071527-7e72daf56747
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e
|
||||
yunion.io/x/sqlchemy v0.0.0-20201219153152-2d901261898c
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce
|
||||
|
||||
@@ -918,8 +918,8 @@ sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
yunion.io/x/executor v0.0.0-20201201131200-44fa553abd9e h1:NRtqiegW4NME0v10rpChORZnJnuAUbpv31R5DsqqczQ=
|
||||
yunion.io/x/executor v0.0.0-20201201131200-44fa553abd9e/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9 h1:TyKy58HHgjkTJYrZgD0haz4bItoC+QwcOQ+GtMuGTjw=
|
||||
yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
|
||||
yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769 h1:LIQ4hhLGQuQK+XxlV+8JrKBuL37WUT+5ZTVxBwHOTD4=
|
||||
yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769/go.mod h1:p0nyMqGA/apTxxyLIU/o1k4V7Vujl2O6ey30L594sYE=
|
||||
@@ -933,8 +933,8 @@ yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZV
|
||||
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v0.0.0-20200814072949-4f1b541857d6 h1:UarEDTBGkgcgc+nc+PZ75uo9M9+jiOGd5P2B90TxDNw=
|
||||
yunion.io/x/pkg v0.0.0-20200814072949-4f1b541857d6/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v0.0.0-20201123083159-ca3aea986ff2 h1:NeCr2J8HjcIuJvEhP0rwWA1UKP8ReOv6HVf5k9YPtyA=
|
||||
yunion.io/x/pkg v0.0.0-20201123083159-ca3aea986ff2/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v0.0.0-20210109071527-7e72daf56747 h1:sTr6mjWW8aAMKkrnnU2SG8EqhLRl/D3rGnBhaP2ksTI=
|
||||
yunion.io/x/pkg v0.0.0-20210109071527-7e72daf56747/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e h1:v+EzIadodSwkdZ/7bremd7J8J50Cise/HCylsOJngmo=
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
|
||||
yunion.io/x/sqlchemy v0.0.0-20201219153152-2d901261898c h1:71nVDQq1oUjvZknEUNfetdiOB1jZMEfmoQlMUaoPIJs=
|
||||
|
||||
+58
-5
@@ -505,18 +505,71 @@ func isJsonContentType(r *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isFormContentType(r *http.Request) bool {
|
||||
contType := strings.ToLower(r.Header.Get("Content-Type"))
|
||||
if strings.HasPrefix(contType, "application/json") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type TContentType string
|
||||
|
||||
const (
|
||||
ContentTypeJson = TContentType("Json")
|
||||
ContentTypeForm = TContentType("Form")
|
||||
ContentTypeUnknown = TContentType("Unknown")
|
||||
)
|
||||
|
||||
func getContentType(r *http.Request) TContentType {
|
||||
contType := func() string {
|
||||
for _, k := range []string{"Content-Type", "content-type"} {
|
||||
contentType := r.Header.Get(k)
|
||||
if len(contentType) > 0 {
|
||||
return strings.ToLower(contentType)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
for k, v := range map[string]TContentType{
|
||||
"application/json": ContentTypeJson,
|
||||
"application/x-www-form-urlencoded": ContentTypeForm,
|
||||
} {
|
||||
if strings.HasPrefix(contType, k) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ContentTypeUnknown
|
||||
}
|
||||
|
||||
func FetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (params map[string]string, query jsonutils.JSONObject, body jsonutils.JSONObject) {
|
||||
var err error
|
||||
params = appctx.AppContextParams(ctx)
|
||||
query, err = jsonutils.ParseQueryString(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
log.Errorf("Parse query string %s failed: %s", r.URL.RawQuery, err)
|
||||
log.Errorf("Parse query string %s failed: %v", r.URL.RawQuery, err)
|
||||
}
|
||||
//var body jsonutils.JSONObject = nil
|
||||
if (r.Method == "PUT" || r.Method == "POST" || r.Method == "DELETE" || r.Method == "PATCH") && r.ContentLength > 0 && isJsonContentType(r) {
|
||||
body, err = FetchJSON(r)
|
||||
if err != nil {
|
||||
log.Errorf("Fail to decode JSON request body: %s", err)
|
||||
if r.Method == "PUT" || r.Method == "POST" || r.Method == "DELETE" || r.Method == "PATCH" {
|
||||
switch getContentType(r) {
|
||||
case ContentTypeJson:
|
||||
if r.ContentLength > 0 {
|
||||
body, err = FetchJSON(r)
|
||||
if err != nil {
|
||||
log.Warningf("Fail to decode JSON request body: %v", err)
|
||||
}
|
||||
}
|
||||
case ContentTypeForm:
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
log.Warningf("ParseForm %s error: %v", r.URL.String(), err)
|
||||
}
|
||||
query, err = jsonutils.ParseQueryString(r.PostForm.Encode())
|
||||
if err != nil {
|
||||
log.Warningf("Parse query string %s failed: %v", r.PostForm.Encode(), err)
|
||||
}
|
||||
default:
|
||||
log.Warningf("%s invalid contentType with header %v", r.URL.String(), r.Header)
|
||||
}
|
||||
}
|
||||
return params, query, body
|
||||
|
||||
@@ -557,6 +557,9 @@ func (self *SCloudaccount) GetCloudusers() ([]SClouduser, error) {
|
||||
}
|
||||
|
||||
func (self *SCloudaccount) SyncCloudusers(ctx context.Context, userCred mcclient.TokenCredential, iUsers []cloudprovider.IClouduser) ([]SClouduser, []cloudprovider.IClouduser, compare.SyncResult) {
|
||||
lockman.LockRawObject(ctx, "cloudusers", self.Id)
|
||||
defer lockman.ReleaseRawObject(ctx, "cloudusers", self.Id)
|
||||
|
||||
result := compare.SyncResult{}
|
||||
dbUsers, err := self.GetCloudusers()
|
||||
if err != nil {
|
||||
@@ -1147,7 +1150,10 @@ func (self *SCloudaccount) StartSAMLProviderCreateTask(ctx context.Context, user
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return sp.StartSAMLProviderCreateTask(ctx, userCred, "")
|
||||
if sp != nil {
|
||||
return sp.StartSAMLProviderCreateTask(ctx, userCred, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SCloudaccountManager) SyncSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
||||
@@ -1805,3 +1811,39 @@ func (self *SCloudaccount) GetUserCloudgroups(userId string) ([]string, error) {
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *SCloudaccount) InviteAzureUser(ctx context.Context, userCred mcclient.TokenCredential, domain string) (string, error) {
|
||||
samlUsers, err := self.GetSamlusers()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "GetSamlusers")
|
||||
}
|
||||
for i := range samlUsers {
|
||||
if samlUsers[i].OwnerId == userCred.GetUserId() {
|
||||
if len(samlUsers[i].Email) == 0 {
|
||||
_, err := db.Update(&samlUsers[i], func() error {
|
||||
samlUsers[i].Email = fmt.Sprintf("%s@%s", userCred.GetUserName(), domain)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "db.Update")
|
||||
}
|
||||
}
|
||||
provider, err := self.GetProvider()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "self.GetProvider")
|
||||
}
|
||||
conf := cloudprovider.SClouduserCreateConfig{
|
||||
Name: userCred.GetUserName(),
|
||||
Email: samlUsers[i].Email,
|
||||
UserType: "Guest",
|
||||
}
|
||||
iUser, err := provider.CreateIClouduser(&conf)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "CreateIClouduser")
|
||||
}
|
||||
db.SetExternalId(&samlUsers[i], userCred, iUser.GetName())
|
||||
return iUser.GetInviteUrl(), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("not found any saml user for %s", userCred.GetUserName())
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ type SCloudgroupResourceBase struct {
|
||||
CloudgroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"`
|
||||
}
|
||||
|
||||
func (self *SCloudgroupJointsBase) GetCloudgroup() (*SCloudgroup, error) {
|
||||
func (self *SCloudgroupResourceBase) GetCloudgroup() (*SCloudgroup, error) {
|
||||
group, err := CloudgroupManager.FetchById(self.CloudgroupId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "FetchById")
|
||||
|
||||
@@ -24,13 +24,17 @@ import (
|
||||
api "yunion.io/x/onecloud/pkg/apis/cloudid"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/cloudid/options"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
)
|
||||
|
||||
type SSamluserManager struct {
|
||||
db.SStatusDomainLevelUserResourceBaseManager
|
||||
db.SExternalizedResourceBaseManager
|
||||
|
||||
SCloudgroupResourceBaseManager
|
||||
SCloudaccountResourceBaseManager
|
||||
}
|
||||
@@ -51,8 +55,12 @@ func init() {
|
||||
|
||||
type SSamluser struct {
|
||||
db.SStatusDomainLevelUserResourceBase
|
||||
db.SExternalizedResourceBase
|
||||
SCloudgroupResourceBase
|
||||
SCloudaccountResourceBase
|
||||
|
||||
// 邮箱地址
|
||||
Email string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"domain_optional"`
|
||||
}
|
||||
|
||||
func (manager *SSamluserManager) GetResourceCount() ([]db.SScopeResourceCount, error) {
|
||||
@@ -173,3 +181,38 @@ func (manager *SSamluserManager) FetchCustomizeColumns(
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (self *SSamluser) SyncAzureGroup() error {
|
||||
group, err := self.GetCloudgroup()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetCloudgroup")
|
||||
}
|
||||
account, err := self.GetCloudaccount()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetCloudaccount")
|
||||
}
|
||||
cache, err := CloudgroupcacheManager.Register(group, account)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "group cache Register")
|
||||
}
|
||||
if len(cache.ExternalId) == 0 {
|
||||
s := auth.GetAdminSession(context.TODO(), options.Options.Region, "")
|
||||
_, err = cache.GetOrCreateICloudgroup(context.TODO(), s.GetToken())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetOrCreateICloudgroup")
|
||||
}
|
||||
cache, err = CloudgroupcacheManager.Register(group, account)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "group cache Register")
|
||||
}
|
||||
}
|
||||
iGroup, err := cache.GetICloudgroup()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetICloudgroup")
|
||||
}
|
||||
err = iGroup.AddUser(self.ExternalId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "iGroup.AddUser")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/aliyun"
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/aws"
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/awscn"
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/azure"
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/google"
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/huawei"
|
||||
_ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/qcloud"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package azure // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/azure"
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudid/models"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/samlutils"
|
||||
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
|
||||
)
|
||||
|
||||
func (d *SAzureSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
|
||||
data := samlutils.SSAMLIdpInitiatedLoginData{}
|
||||
|
||||
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
return data, httperrors.NewResourceNotFoundError2("cloudaccount", cloudAccountId)
|
||||
}
|
||||
return data, httperrors.NewGeneralError(err)
|
||||
}
|
||||
account := _account.(*models.SCloudaccount)
|
||||
if account.Provider != api.CLOUD_PROVIDER_AZURE {
|
||||
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_AWS)
|
||||
}
|
||||
if account.SAMLAuth.IsFalse() {
|
||||
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
|
||||
}
|
||||
|
||||
samlProvider, valid := account.IsSAMLProviderValid()
|
||||
if !valid {
|
||||
return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id)
|
||||
}
|
||||
|
||||
inviteUrl, err := account.InviteAzureUser(ctx, userCred, samlProvider.ExternalId)
|
||||
if err != nil {
|
||||
return data, httperrors.NewGeneralError(errors.Wrapf(err, "InviteAzureUser"))
|
||||
}
|
||||
|
||||
data.Form = fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0; url=%s">
|
||||
<title>waiting...</title>
|
||||
</head>
|
||||
<body>
|
||||
waiting...
|
||||
</body>
|
||||
</html>`, inviteUrl)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (d *SAzureSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
|
||||
data := samlutils.SSAMLSpInitiatedLoginData{}
|
||||
|
||||
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == sql.ErrNoRows {
|
||||
return data, httperrors.NewResourceNotFoundError2("cloudaccount", cloudAccountId)
|
||||
}
|
||||
return data, httperrors.NewGeneralError(err)
|
||||
}
|
||||
account := _account.(*models.SCloudaccount)
|
||||
if account.Provider != api.CLOUD_PROVIDER_AZURE {
|
||||
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_AWS)
|
||||
}
|
||||
if account.SAMLAuth.IsFalse() {
|
||||
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
|
||||
}
|
||||
|
||||
samlUsers, err := account.GetSamlusers()
|
||||
if err != nil {
|
||||
return data, httperrors.NewGeneralError(errors.Wrapf(err, "GetSamlusers"))
|
||||
}
|
||||
|
||||
for i := range samlUsers {
|
||||
if samlUsers[i].OwnerId == userCred.GetUserId() && samlUsers[i].Email == sp.Username {
|
||||
|
||||
err := samlUsers[i].SyncAzureGroup()
|
||||
if err != nil {
|
||||
return data, errors.Wrapf(err, "SyncAzureGroup")
|
||||
}
|
||||
|
||||
data.NameId = sp.Username
|
||||
|
||||
data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
|
||||
data.AudienceRestriction = sp.GetEntityId()
|
||||
for _, v := range []struct {
|
||||
name string
|
||||
friendlyName string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
|
||||
value: data.NameId,
|
||||
},
|
||||
} {
|
||||
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
|
||||
Name: v.name,
|
||||
FriendlyName: v.friendlyName,
|
||||
Values: []string{v.value},
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return data, httperrors.NewResourceNotFoundError("not found any saml user for %s -> %s", userCred.GetUserName(), sp.Username)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudid/models"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
type SAzureSAMLDriver struct{}
|
||||
|
||||
func (d *SAzureSAMLDriver) GetEntityID() string {
|
||||
return cloudprovider.SAML_ENTITY_ID_AZURE
|
||||
}
|
||||
|
||||
func (d *SAzureSAMLDriver) GetMetadataFilename() string {
|
||||
return "azure.xml"
|
||||
}
|
||||
|
||||
func (d *SAzureSAMLDriver) GetMetadataUrl() string {
|
||||
return "https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml"
|
||||
}
|
||||
|
||||
func init() {
|
||||
models.Register(&SAzureSAMLDriver{})
|
||||
}
|
||||
@@ -24,6 +24,7 @@ type SClouduserCreateConfig struct {
|
||||
Email string
|
||||
MobilePhone string
|
||||
ExternalPolicyIds []string
|
||||
UserType string
|
||||
}
|
||||
|
||||
type SCloudpolicyPermission struct {
|
||||
|
||||
@@ -1072,6 +1072,9 @@ type IClouduser interface {
|
||||
GetGlobalId() string
|
||||
GetName() string
|
||||
|
||||
GetEmailAddr() string
|
||||
GetInviteUrl() string
|
||||
|
||||
GetICloudgroups() ([]ICloudgroup, error)
|
||||
|
||||
GetISystemCloudpolicies() ([]ICloudpolicy, error)
|
||||
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
SAML_ENTITY_ID_QCLOUD = "cloud.tencent.com"
|
||||
SAML_ENTITY_ID_HUAWEI_CLOUD = "https://auth.huaweicloud.com/"
|
||||
SAML_ENTITY_ID_GOOGLE = "google.com"
|
||||
SAML_ENTITY_ID_AZURE = "urn:federation:MicrosoftOnline"
|
||||
)
|
||||
|
||||
type SAMLProviderCreateOptions struct {
|
||||
|
||||
@@ -34,7 +34,7 @@ func (opts *CloudgroupListOptions) Params() (jsonutils.JSONObject, error) {
|
||||
|
||||
type CloudgroupCreateOptions struct {
|
||||
NAME string `json:"name"`
|
||||
PROVIDER string `json:"provider" choices:"Google|Aliyun|Aws|Huawei|Qcloud"`
|
||||
PROVIDER string `json:"provider" choices:"Google|Aliyun|Aws|Huawei|Qcloud|Azure"`
|
||||
CloudpolicyIds []string `json:"cloudpolicy_ids"`
|
||||
Desc string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -61,6 +61,14 @@ func (user *SUser) GetName() string {
|
||||
return user.UserName
|
||||
}
|
||||
|
||||
func (user *SUser) GetEmailAddr() string {
|
||||
return user.Email
|
||||
}
|
||||
|
||||
func (user *SUser) GetInviteUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SUser) Delete() error {
|
||||
groups, err := user.client.ListGroupsForUser(user.UserName)
|
||||
if err != nil {
|
||||
|
||||
@@ -46,6 +46,14 @@ type SUser struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
func (user *SUser) GetEmailAddr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SUser) GetInviteUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SUser) GetGlobalId() string {
|
||||
if len(user.UserId) > 0 {
|
||||
return user.UserId
|
||||
|
||||
@@ -41,6 +41,14 @@ type SUser struct {
|
||||
PasswordLastUsed time.Time `xml:"PasswordLastUsed"`
|
||||
}
|
||||
|
||||
func (user *SUser) GetEmailAddr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SUser) GetInviteUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SUser) AttachSystemPolicy(policyArn string) error {
|
||||
return user.client.AttachUserPolicy(user.UserName, user.client.getIamArn(policyArn))
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
azureenv "github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
@@ -658,8 +659,8 @@ func (ae *AzureResponseError) ParseErrorFromJsonResponse(statusCode int, body js
|
||||
}
|
||||
|
||||
func _jsonRequest(client *autorest.Client, method, domain, path string, body jsonutils.JSONObject, params url.Values, debug bool) (jsonutils.JSONObject, error) {
|
||||
url := fmt.Sprintf("%s/%s?%s", strings.TrimSuffix(domain, "/"), strings.TrimPrefix(path, "/"), params.Encode())
|
||||
req := httputils.NewJsonRequest(httputils.THttpMethod(method), url, body)
|
||||
uri := fmt.Sprintf("%s/%s?%s", strings.TrimSuffix(domain, "/"), strings.TrimPrefix(path, "/"), params.Encode())
|
||||
req := httputils.NewJsonRequest(httputils.THttpMethod(method), uri, body)
|
||||
ae := AzureResponseError{}
|
||||
cli := httputils.NewJsonClient(client)
|
||||
header, body, err := cli.Send(context.TODO(), req, &ae, debug)
|
||||
@@ -678,7 +679,16 @@ func _jsonRequest(client *autorest.Client, method, domain, path string, body jso
|
||||
location := locationFunc(header)
|
||||
if len(location) > 0 && (body == nil || body.IsZero() || !body.Contains("id")) {
|
||||
err = cloudprovider.Wait(time.Second*10, time.Minute*30, func() (bool, error) {
|
||||
req := httputils.NewJsonRequest(httputils.GET, location, nil)
|
||||
locationUrl, err := url.Parse(location)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "url.Parse(%s)", location)
|
||||
}
|
||||
if len(locationUrl.Query().Get("api-version")) == 0 {
|
||||
q, _ := url.ParseQuery(locationUrl.RawQuery)
|
||||
q.Set("api-version", params.Get("api-version"))
|
||||
locationUrl.RawQuery = q.Encode()
|
||||
}
|
||||
req := httputils.NewJsonRequest(httputils.GET, locationUrl.String(), nil)
|
||||
lae := AzureResponseError{}
|
||||
_header, _body, _err := cli.Send(context.TODO(), req, &lae, debug)
|
||||
if _err != nil {
|
||||
@@ -724,7 +734,7 @@ func _jsonRequest(client *autorest.Client, method, domain, path string, body jso
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "time out for waiting %s %s", method, url)
|
||||
return nil, errors.Wrapf(err, "time out for waiting %s %s", method, uri)
|
||||
}
|
||||
}
|
||||
return body, nil
|
||||
@@ -922,6 +932,7 @@ func (self *SAzureClient) GetCapabilities() []string {
|
||||
// cloudprovider.CLOUD_CAPABILITY_CACHE,
|
||||
cloudprovider.CLOUD_CAPABILITY_EVENT,
|
||||
cloudprovider.CLOUD_CAPABILITY_CLOUDID,
|
||||
cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
|
||||
}
|
||||
return caps
|
||||
}
|
||||
@@ -956,3 +967,40 @@ func (self *SAzureClient) SetTags(resourceId string, tags map[string]string) (js
|
||||
input.Properties.Tags = tags
|
||||
return self.put(path, jsonutils.Marshal(input))
|
||||
}
|
||||
|
||||
func (self *SAzureClient) msGraphClient() *http.Client {
|
||||
conf := clientcredentials.Config{
|
||||
ClientID: self.clientId,
|
||||
ClientSecret: self.clientSecret,
|
||||
|
||||
TokenURL: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", self.tenantId),
|
||||
Scopes: []string{"https://graph.microsoft.com/.default"},
|
||||
}
|
||||
return conf.Client(context.TODO())
|
||||
}
|
||||
|
||||
func (self *SAzureClient) ListGraphUsers() ([]SClouduser, error) {
|
||||
resp, err := self.msGraphRequest("GET", "users", nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "msGraphRequest.users")
|
||||
}
|
||||
users := []SClouduser{}
|
||||
err = resp.Unmarshal(&users, "value")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "resp.Unmarshal")
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (self *SAzureClient) msGraphRequest(method string, resource string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
client := self.msGraphClient()
|
||||
url := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s", resource)
|
||||
req := httputils.NewJsonRequest(httputils.THttpMethod(method), url, body)
|
||||
ae := AzureResponseError{}
|
||||
cli := httputils.NewJsonClient(client)
|
||||
_, body, err := cli.Send(context.TODO(), req, &ae, self.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
@@ -134,9 +134,8 @@ func (cli *SAzureClient) AssignPolicy(objectId, roleName, subscriptionId string)
|
||||
"principalId": objectId,
|
||||
},
|
||||
}
|
||||
subscriptionIds := []string{subscriptionId}
|
||||
subscriptionIds := []string{}
|
||||
if len(subscriptionId) == 0 {
|
||||
subscriptionIds = []string{}
|
||||
for _, subscription := range cli.subscriptions {
|
||||
subscriptionIds = append(subscriptionIds, subscription.SubscriptionId)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ type SClouduser struct {
|
||||
UserState string
|
||||
UserStateChangedOn string
|
||||
UserType string
|
||||
|
||||
inviteRedeemUrl string
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetName() string {
|
||||
@@ -90,6 +92,14 @@ func (user *SClouduser) GetGlobalId() string {
|
||||
return user.ObjectId
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetEmailAddr() string {
|
||||
return user.Mail
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetInviteUrl() string {
|
||||
return user.inviteRedeemUrl
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
|
||||
policies, err := user.client.GetCloudpolicies(user.ObjectId)
|
||||
if err != nil {
|
||||
@@ -250,6 +260,9 @@ func (self *SAzureClient) GetIClouduserByName(name string) (cloudprovider.ICloud
|
||||
}
|
||||
|
||||
func (self *SAzureClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) {
|
||||
if conf.UserType == "Guest" {
|
||||
return self.InviteUser(conf.Email)
|
||||
}
|
||||
return self.CreateClouduser(conf.Name, conf.Password)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ func (self *SAzureProviderFactory) IsCloudeventRegional() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SAzureProviderFactory) IsSupportSAMLAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SAzureProviderFactory) ValidateChangeBandwidth(instanceId string, bandwidth int64) error {
|
||||
return fmt.Errorf("Changing %s bandwidth is not supported", azure.CLOUD_PROVIDER_AZURE)
|
||||
}
|
||||
@@ -271,3 +275,12 @@ func (self *SAzureProvider) GetICustomCloudpolicies() ([]cloudprovider.ICloudpol
|
||||
func (self *SAzureProvider) CreateSubscription(input cloudprovider.SubscriptionCreateInput) error {
|
||||
return self.client.CreateSubscription(input.Name, input.EnrollmentAccountId, input.OfferType)
|
||||
}
|
||||
|
||||
// fake func
|
||||
func (self *SAzureProvider) CreateICloudSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (cloudprovider.ICloudSAMLProvider, error) {
|
||||
return self.client.CreateSAMLProvider(opts)
|
||||
}
|
||||
|
||||
func (self *SAzureProvider) GetSamlEntityId() string {
|
||||
return cloudprovider.SAML_ENTITY_ID_AZURE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/cloudid"
|
||||
api "yunion.io/x/onecloud/pkg/apis/cloudid"
|
||||
compute_api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudid/options"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud"
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
"yunion.io/x/onecloud/pkg/util/samlutils"
|
||||
)
|
||||
|
||||
type SAMLProvider struct {
|
||||
multicloud.SResourceBase
|
||||
client *SAzureClient
|
||||
|
||||
Name string
|
||||
Metadata samlutils.EntityDescriptor
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) Delete() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) GetGlobalId() string {
|
||||
return strings.TrimPrefix(options.Options.ApiServer, "https://")
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) GetId() string {
|
||||
return options.Options.ApiServer
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) GetName() string {
|
||||
return self.Name
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) GetStatus() string {
|
||||
return api.SAML_PROVIDER_STATUS_AVAILABLE
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) UpdateMetadata(metadata samlutils.EntityDescriptor) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) {
|
||||
return &self.Metadata, nil
|
||||
}
|
||||
|
||||
func (self *SAMLProvider) GetAuthUrl() string {
|
||||
input := samlutils.SIdpInitiatedLoginInput{
|
||||
EntityID: cloudprovider.SAML_ENTITY_ID_AZURE,
|
||||
IdpId: self.client.cpcfg.AccountId,
|
||||
}
|
||||
if self.client.GetAccessEnv() != compute_api.CLOUD_ACCESS_ENV_AZURE_GLOBAL {
|
||||
return ""
|
||||
}
|
||||
return httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, fmt.Sprintf("sso?%s", jsonutils.Marshal(input).QueryString()))
|
||||
}
|
||||
|
||||
func (self *SAzureClient) ListSAMLProviders() ([]SAMLProvider, error) {
|
||||
_, err := self.msGraphRequest("GET", "identityProviders", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []SAMLProvider{}, nil
|
||||
}
|
||||
|
||||
func (self *SAzureClient) InviteUser(email string) (*SClouduser, error) {
|
||||
body := jsonutils.Marshal(map[string]string{
|
||||
"invitedUserEmailAddress": email,
|
||||
"inviteRedirectUrl": fmt.Sprintf("https://portal.azure.com/%s", self.tenantId),
|
||||
})
|
||||
resp, err := self.msGraphRequest("POST", "invitations", body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "msGraphRequest.invitations")
|
||||
}
|
||||
inviteUrl, _ := resp.GetString("inviteRedeemUrl")
|
||||
err = cloudprovider.Wait(time.Second*2, time.Minute, func() (bool, error) {
|
||||
users, err := self.ListGraphUsers()
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "GetCloudusers")
|
||||
}
|
||||
for i := range users {
|
||||
users[i].inviteRedeemUrl = inviteUrl
|
||||
if users[i].GetEmailAddr() == email {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after invite %s", email)
|
||||
}
|
||||
users, err := self.ListGraphUsers()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetCloudusers")
|
||||
}
|
||||
for i := range users {
|
||||
users[i].inviteRedeemUrl = inviteUrl
|
||||
if users[i].GetEmailAddr() == email {
|
||||
return &users[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after invite %s", email)
|
||||
}
|
||||
|
||||
func (self *SAzureClient) CreateSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (*SAMLProvider, error) {
|
||||
return &SAMLProvider{
|
||||
client: self,
|
||||
Name: opts.Name,
|
||||
Metadata: opts.Metadata,
|
||||
}, nil
|
||||
}
|
||||
@@ -84,4 +84,16 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type GroupUserList struct {
|
||||
}
|
||||
|
||||
shellutils.R(&GroupUserList{}, "graph-user-list", "List graph users", func(cli *azure.SRegion, args *GroupUserList) error {
|
||||
users, err := cli.GetClient().ListGraphUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(users)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package shell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/multicloud/azure"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
type SAMLProviderListOptions struct {
|
||||
}
|
||||
shellutils.R(&SAMLProviderListOptions{}, "saml-provider-list", "List regions", func(cli *azure.SRegion, args *SAMLProviderListOptions) error {
|
||||
sps, err := cli.GetClient().ListSAMLProviders()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(sps, 0, 0, 0, nil)
|
||||
return nil
|
||||
})
|
||||
|
||||
type SInvitateUser struct {
|
||||
EMAIL string
|
||||
}
|
||||
|
||||
shellutils.R(&SInvitateUser{}, "invite-user", "Invitate user", func(cli *azure.SRegion, args *SInvitateUser) error {
|
||||
user, err := cli.GetClient().InviteUser(args.EMAIL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(user)
|
||||
fmt.Println("invite url: ", user.GetInviteUrl())
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -140,6 +140,14 @@ type SClouduser struct {
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (self *SClouduser) GetEmailAddr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SClouduser) GetInviteUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SGoogleClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
|
||||
roles, err := self.GetRoles("")
|
||||
if err != nil {
|
||||
|
||||
@@ -51,6 +51,14 @@ func (user *SClouduser) GetName() string {
|
||||
return user.Name
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetEmailAddr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetInviteUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
|
||||
return []cloudprovider.ICloudpolicy{}, nil
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ func (user *SUser) GetGlobalId() string {
|
||||
return fmt.Sprintf("%d", user.Uin)
|
||||
}
|
||||
|
||||
func (self *SUser) GetEmailAddr() string {
|
||||
return self.Email
|
||||
}
|
||||
|
||||
func (self *SUser) GetInviteUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *SUser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
|
||||
policies := []SPolicy{}
|
||||
offset := 1
|
||||
|
||||
@@ -194,6 +194,27 @@ func prepareServer() error {
|
||||
Values: []string{v.value},
|
||||
})
|
||||
}
|
||||
case "urn:federation:MicrosoftOnline":
|
||||
data.NameId = sp.Username
|
||||
data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
|
||||
data.AudienceRestriction = sp.GetEntityId()
|
||||
for _, v := range []struct {
|
||||
name string
|
||||
friendlyName string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
|
||||
value: data.NameId,
|
||||
},
|
||||
} {
|
||||
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
|
||||
Name: v.name,
|
||||
FriendlyName: v.friendlyName,
|
||||
Values: []string{v.value},
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
case "google.com/a/yunion-hk.com":
|
||||
data.NameId = "qiujian"
|
||||
data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
|
||||
@@ -406,6 +427,10 @@ func prepareServer() error {
|
||||
name: "Google cloud SSO",
|
||||
url: "https://www.google.com/a/yunion-hk.com/ServiceLogin?continue=https://console.cloud.google.com",
|
||||
},
|
||||
{
|
||||
name: "Azure cloud SSO",
|
||||
url: "https://login.microsoftonline.com/redeem?rd=https%3a%2f%2finvitations.microsoft.com%2fredeem%2f%3ftenant%3d17493ddf-fa90-4f95-8576-5df011c126e5%26user%3d3bc1c055-aa14-4795-aef0-5970b00d03c7%26ticket%3d0GDu%252bZ7nLbg01rYL5u%252b401%252bOLyZjxPewSBJIAZZ7E0U%253d%26ver%3d2.0",
|
||||
},
|
||||
} {
|
||||
htmlBuf.WriteString(fmt.Sprintf(`<li><a href="%s">%s (SP-Initiated)</a></li>`, v.url, v.name))
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ func (idp *SSAMLIdpInstance) AddHandlers(app *appsrv.Application, prefix string,
|
||||
if middleware != nil {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
app.AddHandler("POST", idp.redirectLoginPath, handler)
|
||||
app.AddHandler("GET", idp.redirectLoginPath, handler)
|
||||
handler = idp.redirectLogoutHandler
|
||||
if middleware != nil {
|
||||
@@ -224,16 +225,17 @@ func (idp *SSAMLIdpInstance) processLoginRequest(ctx context.Context, idpId stri
|
||||
return "", errors.Wrapf(httperrors.ErrInputParameter, "Destination not match: get %s want %s", authReq.Destination, idp.getRedirectLoginUrl(idpId))
|
||||
}
|
||||
|
||||
if authReq.AssertionConsumerServiceURL != sp.GetPostAssertionConsumerServiceUrl() {
|
||||
if len(authReq.AssertionConsumerServiceURL) > 0 && authReq.AssertionConsumerServiceURL != sp.GetPostAssertionConsumerServiceUrl() {
|
||||
return "", errors.Wrapf(httperrors.ErrInputParameter, "AssertionConsumerServiceURL not match: get %s want %s", authReq.AssertionConsumerServiceURL, sp.GetPostAssertionConsumerServiceUrl())
|
||||
}
|
||||
|
||||
sp.Username = input.Username
|
||||
resp, err := idp.getLoginResponse(ctx, authReq, idpId, sp)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "getLoginResponse")
|
||||
}
|
||||
|
||||
form, err := idp.samlResponse2Form(authReq.AssertionConsumerServiceURL, resp, input.RelayState)
|
||||
form, err := idp.samlResponse2Form(sp.GetPostAssertionConsumerServiceUrl(), resp, input.RelayState)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "samlResponse2Form")
|
||||
}
|
||||
@@ -246,7 +248,6 @@ func (idp *SSAMLIdpInstance) samlResponse2Form(url string, resp *samlutils.Respo
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "xml.Marshal")
|
||||
}
|
||||
|
||||
signed, err := idp.saml.SignXML(string(respXml))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "saml.SignXML")
|
||||
@@ -287,7 +288,7 @@ func (idp *SSAMLIdpInstance) getLoginResponse(ctx context.Context, req samlutils
|
||||
IssuerEntityId: idp.saml.GetEntityId(),
|
||||
RequestID: req.ID,
|
||||
RequestEntityId: req.Issuer.Issuer,
|
||||
AssertionConsumerServiceURL: req.AssertionConsumerServiceURL,
|
||||
AssertionConsumerServiceURL: sp.GetPostAssertionConsumerServiceUrl(),
|
||||
SSAMLSpInitiatedLoginData: data,
|
||||
}
|
||||
resp := samlutils.NewResponse(input)
|
||||
@@ -303,6 +304,9 @@ func (idp *SSAMLIdpInstance) processIdpInitiatedLogin(ctx context.Context, input
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "idp.onIdpInitiatedLogin")
|
||||
}
|
||||
if len(data.Form) > 0 {
|
||||
return data.Form, nil
|
||||
}
|
||||
respInput := samlutils.SSAMLResponseInput{
|
||||
IssuerCertString: idp.saml.GetCertString(),
|
||||
IssuerEntityId: idp.saml.GetEntityId(),
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
|
||||
type SSAMLServiceProvider struct {
|
||||
desc samlutils.EntityDescriptor
|
||||
|
||||
Username string
|
||||
}
|
||||
|
||||
func (sp *SSAMLServiceProvider) GetEntityId() string {
|
||||
|
||||
@@ -36,6 +36,8 @@ type SSAMLSpInitiatedLoginData struct {
|
||||
AudienceRestriction string
|
||||
|
||||
Attributes []SSAMLResponseAttribute
|
||||
|
||||
Form string
|
||||
}
|
||||
|
||||
type SSAMLIdpInitiatedLoginData struct {
|
||||
@@ -59,7 +61,7 @@ type SSAMLResponseInput struct {
|
||||
|
||||
func NewResponse(input SSAMLResponseInput) Response {
|
||||
// since := timeutils.IsoTime(time.Now().UTC().Add(-time.Minute * 60 * 24))
|
||||
until := timeutils.IsoTime(time.Now().UTC().Add(time.Minute * 60 * 24))
|
||||
until := timeutils.IsoTime(time.Now().UTC().Add(time.Minute * 5))
|
||||
|
||||
respId := GenerateSAMLId()
|
||||
assertId := GenerateSAMLId()
|
||||
@@ -251,6 +253,7 @@ func NewResponse(input SSAMLResponseInput) Response {
|
||||
Space: XMLNS_ASSERT,
|
||||
Local: "Conditions",
|
||||
},
|
||||
NotBefore: &now,
|
||||
NotOnOrAfter: until,
|
||||
AudienceRestrictions: []AudienceRestriction{},
|
||||
},
|
||||
|
||||
@@ -250,6 +250,8 @@ type SIdpRedirectLoginInput struct {
|
||||
RelayState string `json:"RelayState,ignoreempty"`
|
||||
SigAlg string `json:"SigAlg,ignoreempty"`
|
||||
Signature string `json:"Signature,ignoreempty"`
|
||||
|
||||
Username string `json:"username,ignoreempty"`
|
||||
}
|
||||
|
||||
type SIdpInitiatedLoginInput struct {
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/ma314smith/signedxml"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/utils"
|
||||
)
|
||||
@@ -72,11 +73,15 @@ func SAMLDecode(input string) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "base64.StdEncoding.DecodeString")
|
||||
}
|
||||
plainText, err := decompress(reqBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decompress")
|
||||
}
|
||||
return plainText, nil
|
||||
return func() []byte {
|
||||
// Azure no need to decompress
|
||||
plainText, err := decompress(reqBytes)
|
||||
if err != nil {
|
||||
log.Warningf("decompress %s error: %v", string(reqBytes), err)
|
||||
return reqBytes
|
||||
}
|
||||
return plainText
|
||||
}(), nil
|
||||
}
|
||||
|
||||
func SAMLEncode(input []byte) (string, error) {
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package clientcredentials implements the OAuth2.0 "client credentials" token flow,
|
||||
// also known as the "two-legged OAuth 2.0".
|
||||
//
|
||||
// This should be used when the client is acting on its own behalf or when the client
|
||||
// is the resource owner. It may also be used when requesting access to protected
|
||||
// resources based on an authorization previously arranged with the authorization
|
||||
// server.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.4
|
||||
package clientcredentials // import "golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
// Config describes a 2-legged OAuth2 flow, with both the
|
||||
// client application information and the server's endpoint URLs.
|
||||
type Config struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the application's secret.
|
||||
ClientSecret string
|
||||
|
||||
// TokenURL is the resource server's token endpoint
|
||||
// URL. This is a constant specific to each server.
|
||||
TokenURL string
|
||||
|
||||
// Scope specifies optional requested permissions.
|
||||
Scopes []string
|
||||
|
||||
// EndpointParams specifies additional parameters for requests to the token endpoint.
|
||||
EndpointParams url.Values
|
||||
|
||||
// AuthStyle optionally specifies how the endpoint wants the
|
||||
// client ID & client secret sent. The zero value means to
|
||||
// auto-detect.
|
||||
AuthStyle oauth2.AuthStyle
|
||||
}
|
||||
|
||||
// Token uses client credentials to retrieve a token.
|
||||
//
|
||||
// The provided context optionally controls which HTTP client is used. See the oauth2.HTTPClient variable.
|
||||
func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) {
|
||||
return c.TokenSource(ctx).Token()
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using the provided token.
|
||||
// The token will auto-refresh as necessary.
|
||||
//
|
||||
// The provided context optionally controls which HTTP client
|
||||
// is returned. See the oauth2.HTTPClient variable.
|
||||
//
|
||||
// The returned Client and its Transport should not be modified.
|
||||
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||
}
|
||||
|
||||
// TokenSource returns a TokenSource that returns t until t expires,
|
||||
// automatically refreshing it as necessary using the provided context and the
|
||||
// client ID and client secret.
|
||||
//
|
||||
// Most users will use Config.Client instead.
|
||||
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||
source := &tokenSource{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
}
|
||||
return oauth2.ReuseTokenSource(nil, source)
|
||||
}
|
||||
|
||||
type tokenSource struct {
|
||||
ctx context.Context
|
||||
conf *Config
|
||||
}
|
||||
|
||||
// Token refreshes the token by using a new client credentials request.
|
||||
// tokens received this way do not include a refresh token
|
||||
func (c *tokenSource) Token() (*oauth2.Token, error) {
|
||||
v := url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
}
|
||||
if len(c.conf.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(c.conf.Scopes, " "))
|
||||
}
|
||||
for k, p := range c.conf.EndpointParams {
|
||||
// Allow grant_type to be overridden to allow interoperability with
|
||||
// non-compliant implementations.
|
||||
if _, ok := v[k]; ok && k != "grant_type" {
|
||||
return nil, fmt.Errorf("oauth2: cannot overwrite parameter %q", k)
|
||||
}
|
||||
v[k] = p
|
||||
}
|
||||
|
||||
tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, v, internal.AuthStyle(c.conf.AuthStyle))
|
||||
if err != nil {
|
||||
if rErr, ok := err.(*internal.RetrieveError); ok {
|
||||
return nil, (*oauth2.RetrieveError)(rErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t := &oauth2.Token{
|
||||
AccessToken: tk.AccessToken,
|
||||
TokenType: tk.TokenType,
|
||||
RefreshToken: tk.RefreshToken,
|
||||
Expiry: tk.Expiry,
|
||||
}
|
||||
return t.WithExtra(tk.Raw), nil
|
||||
}
|
||||
Vendored
+3
-2
@@ -725,6 +725,7 @@ golang.org/x/net/publicsuffix
|
||||
golang.org/x/net/trace
|
||||
# golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
|
||||
golang.org/x/oauth2
|
||||
golang.org/x/oauth2/clientcredentials
|
||||
golang.org/x/oauth2/google
|
||||
golang.org/x/oauth2/internal
|
||||
golang.org/x/oauth2/jws
|
||||
@@ -1105,7 +1106,7 @@ k8s.io/utils/integer
|
||||
sigs.k8s.io/structured-merge-diff/v4/value
|
||||
# sigs.k8s.io/yaml v1.2.0
|
||||
sigs.k8s.io/yaml
|
||||
# yunion.io/x/executor v0.0.0-20201201131200-44fa553abd9e
|
||||
# yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9
|
||||
yunion.io/x/executor/apis
|
||||
yunion.io/x/executor/client
|
||||
yunion.io/x/executor/server
|
||||
@@ -1118,7 +1119,7 @@ yunion.io/x/log/hooks
|
||||
yunion.io/x/ovsdb/cli_util
|
||||
yunion.io/x/ovsdb/schema/ovn_nb
|
||||
yunion.io/x/ovsdb/types
|
||||
# yunion.io/x/pkg v0.0.0-20201123083159-ca3aea986ff2
|
||||
# yunion.io/x/pkg v0.0.0-20210109071527-7e72daf56747
|
||||
yunion.io/x/pkg/errors
|
||||
yunion.io/x/pkg/gotypes
|
||||
yunion.io/x/pkg/prettytable
|
||||
|
||||
+15
@@ -19,6 +19,8 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
type valueElement struct {
|
||||
@@ -79,6 +81,19 @@ func CompareSets(dbSet interface{}, extSet interface{}, removed interface{}, com
|
||||
sort.Sort(valueSet(dbSetArray))
|
||||
sort.Sort(valueSet(extSetArray))
|
||||
|
||||
dupCheck := map[string][]reflect.Value{}
|
||||
for i := range extSetArray {
|
||||
_, ok := dupCheck[extSetArray[i].key]
|
||||
if !ok {
|
||||
dupCheck[extSetArray[i].key] = []reflect.Value{}
|
||||
}
|
||||
dupCheck[extSetArray[i].key] = append(dupCheck[extSetArray[i].key], extSetArray[i].value)
|
||||
|
||||
if len(dupCheck[extSetArray[i].key]) > 1 {
|
||||
return errors.Wrapf(errors.ErrDuplicateId, "duplicated id: %s", extSetArray[i].key)
|
||||
}
|
||||
}
|
||||
|
||||
removedValue := reflect.Indirect(reflect.ValueOf(removed))
|
||||
commonDBValue := reflect.Indirect(reflect.ValueOf(commonDB))
|
||||
commonExtValue := reflect.Indirect(reflect.ValueOf(commonExt))
|
||||
|
||||
Reference in New Issue
Block a user