MM-20698: Add server support for getting SAML metadata and updating S… (#13311)
* MM-20698: Add server support for getting SAMl metadata and updating SAMl settings
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c41e9d970a
Коммит
37ce413b7d
62
api4/saml.go
62
api4/saml.go
@@ -4,6 +4,8 @@
|
|||||||
package api4
|
package api4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -22,6 +24,8 @@ func (api *API) InitSaml() {
|
|||||||
api.BaseRoutes.SAML.Handle("/certificate/idp", api.ApiSessionRequired(removeSamlIdpCertificate)).Methods("DELETE")
|
api.BaseRoutes.SAML.Handle("/certificate/idp", api.ApiSessionRequired(removeSamlIdpCertificate)).Methods("DELETE")
|
||||||
|
|
||||||
api.BaseRoutes.SAML.Handle("/certificate/status", api.ApiSessionRequired(getSamlCertificateStatus)).Methods("GET")
|
api.BaseRoutes.SAML.Handle("/certificate/status", api.ApiSessionRequired(getSamlCertificateStatus)).Methods("GET")
|
||||||
|
|
||||||
|
api.BaseRoutes.SAML.Handle("/metadatafromidp", api.ApiHandler(getSamlMetadataFromIdp)).Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSamlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getSamlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -100,16 +104,44 @@ func addSamlIdpCertificate(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileData, err := parseSamlCertificateRequest(r, *c.App.Config().FileSettings.MaxFileSize)
|
v := r.Header.Get("Content-Type")
|
||||||
|
if v == "" {
|
||||||
|
c.Err = model.NewAppError("addSamlIdpCertificate", "api.admin.saml.set_certificate_from_metadata.missing_content_type.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d, _, err := mime.ParseMediaType(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = model.NewAppError("addSamlIdpCertificate", "api.admin.saml.set_certificate_from_metadata.invalid_content_type.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.App.AddSamlIdpCertificate(fileData); err != nil {
|
if d == "application/x-pem-file" {
|
||||||
c.Err = err
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("addSamlIdpCertificate", "api.admin.saml.set_certificate_from_metadata.invalid_body.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.App.SetSamlIdpCertificateFromMetadata(body); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if d == "multipart/form-data" {
|
||||||
|
fileData, err := parseSamlCertificateRequest(r, *c.App.Config().FileSettings.MaxFileSize)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.App.AddSamlIdpCertificate(fileData); err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.Err = model.NewAppError("addSamlIdpCertificate", "api.admin.saml.set_certificate_from_metadata.invalid_content_type.app_error", nil, "", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ReturnStatusOK(w)
|
ReturnStatusOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,3 +196,25 @@ func getSamlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request
|
|||||||
status := c.App.GetSamlCertificateStatus()
|
status := c.App.GetSamlCertificateStatus()
|
||||||
w.Write([]byte(status.ToJson()))
|
w.Write([]byte(status.ToJson()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSamlMetadataFromIdp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
url := props["saml_metadata_url"]
|
||||||
|
if url == "" {
|
||||||
|
c.SetInvalidParam("saml_metadata_url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := c.App.GetSamlMetadataFromIdp(url)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("getSamlMetadataFromIdp", "api.admin.saml.failure_get_metadata_from_idp.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(metadata.ToJson()))
|
||||||
|
}
|
||||||
|
|||||||
@@ -4282,6 +4282,7 @@ func TestLoginErrorMessage(t *testing.T) {
|
|||||||
*cfg.SamlSettings.Encrypt = false
|
*cfg.SamlSettings.Encrypt = false
|
||||||
*cfg.SamlSettings.IdpUrl = "https://localhost/adfs/ls"
|
*cfg.SamlSettings.IdpUrl = "https://localhost/adfs/ls"
|
||||||
*cfg.SamlSettings.IdpDescriptorUrl = "https://localhost/adfs/services/trust"
|
*cfg.SamlSettings.IdpDescriptorUrl = "https://localhost/adfs/services/trust"
|
||||||
|
*cfg.SamlSettings.IdpMetadataUrl = "https://localhost/adfs/metadata"
|
||||||
*cfg.SamlSettings.AssertionConsumerServiceURL = "https://localhost/login/sso/saml"
|
*cfg.SamlSettings.AssertionConsumerServiceURL = "https://localhost/login/sso/saml"
|
||||||
*cfg.SamlSettings.IdpCertificateFile = app.SamlIdpCertificateName
|
*cfg.SamlSettings.IdpCertificateFile = app.SamlIdpCertificateName
|
||||||
*cfg.SamlSettings.PrivateKeyFile = app.SamlPrivateKeyName
|
*cfg.SamlSettings.PrivateKeyFile = app.SamlPrivateKeyName
|
||||||
|
|||||||
110
app/saml.go
110
app/saml.go
@@ -4,9 +4,14 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
@@ -172,3 +177,108 @@ func (a *App) GetSamlCertificateStatus() *model.SamlCertificateStatus {
|
|||||||
|
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetSamlMetadataFromIdp(idpMetadataUrl string) (*model.SamlMetadataResponse, *model.AppError) {
|
||||||
|
if a.Saml == nil {
|
||||||
|
err := model.NewAppError("GetSamlMetadataFromIdp", "api.admin.saml.not_available.app_error", nil, "", http.StatusNotImplemented)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(idpMetadataUrl, "http://") && !strings.HasPrefix(idpMetadataUrl, "https://") {
|
||||||
|
idpMetadataUrl = "https://" + idpMetadataUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
idpMetadataRaw, err := a.FetchSamlMetadataFromIdp(idpMetadataUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := a.BuildSamlMetadataObject(idpMetadataRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) FetchSamlMetadataFromIdp(url string) ([]byte, *model.AppError) {
|
||||||
|
resp, err := a.HTTPService.MakeClient(false).Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("FetchSamlMetadataFromIdp", "app.admin.saml.invalid_response_from_idp.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, model.NewAppError("FetchSamlMetadataFromIdp", "app.admin.saml.invalid_response_from_idp.app_error", nil, fmt.Sprintf("status_code=%d", resp.StatusCode), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyXML, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("FetchSamlMetadataFromIdp", "app.admin.saml.failure_read_response_body_from_idp.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bodyXML, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) BuildSamlMetadataObject(idpMetadata []byte) (*model.SamlMetadataResponse, *model.AppError) {
|
||||||
|
entityDescriptor := model.EntityDescriptor{}
|
||||||
|
err := xml.Unmarshal(idpMetadata, &entityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("BuildSamlMetadataObject", "app.admin.saml.failure_decode_metadata_xml_from_idp.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &model.SamlMetadataResponse{}
|
||||||
|
data.IdpDescriptorUrl = entityDescriptor.EntityID
|
||||||
|
|
||||||
|
if entityDescriptor.IDPSSODescriptors == nil || len(entityDescriptor.IDPSSODescriptors) == 0 {
|
||||||
|
err := model.NewAppError("BuildSamlMetadataObject", "api.admin.saml.invalid_xml_missing_idpssodescriptors.app_error", nil, "", http.StatusInternalServerError)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idpSSODescriptor := entityDescriptor.IDPSSODescriptors[0]
|
||||||
|
if idpSSODescriptor.SingleSignOnServices == nil || len(idpSSODescriptor.SingleSignOnServices) == 0 {
|
||||||
|
err := model.NewAppError("BuildSamlMetadataObject", "api.admin.saml.invalid_xml_missing_ssoservices.app_error", nil, "", http.StatusInternalServerError)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data.IdpUrl = idpSSODescriptor.SingleSignOnServices[0].Location
|
||||||
|
if idpSSODescriptor.SSODescriptor.RoleDescriptor.KeyDescriptors == nil || len(idpSSODescriptor.SSODescriptor.RoleDescriptor.KeyDescriptors) == 0 {
|
||||||
|
err := model.NewAppError("BuildSamlMetadataObject", "api.admin.saml.invalid_xml_missing_keydescriptor.app_error", nil, "", http.StatusInternalServerError)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyDescriptor := idpSSODescriptor.SSODescriptor.RoleDescriptor.KeyDescriptors[0]
|
||||||
|
data.IdpPublicCertificate = keyDescriptor.KeyInfo.X509Data.X509Certificate.Cert
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SetSamlIdpCertificateFromMetadata(data []byte) *model.AppError {
|
||||||
|
const certPrefix = "-----BEGIN CERTIFICATE-----\n"
|
||||||
|
const certSuffix = "\n-----END CERTIFICATE-----"
|
||||||
|
fixedCertTxt := certPrefix + string(data) + certSuffix
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(fixedCertTxt))
|
||||||
|
if _, e := x509.ParseCertificate(block.Bytes); e != nil {
|
||||||
|
return model.NewAppError("SetSamlIdpCertificateFromMetadata", "api.admin.saml.failure_parse_idp_certificate.app_error", nil, e.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: block.Bytes,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := a.Srv.configStore.SetFile(SamlIdpCertificateName, data); err != nil {
|
||||||
|
return model.NewAppError("SetSamlIdpCertificateFromMetadata", "api.admin.saml.failure_save_idp_certificate_file.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := a.Config().Clone()
|
||||||
|
*cfg.SamlSettings.IdpCertificateFile = SamlIdpCertificateName
|
||||||
|
|
||||||
|
if err := cfg.IsValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
48
i18n/en.json
48
i18n/en.json
@@ -83,6 +83,30 @@
|
|||||||
"id": "api.admin.remove_certificate.delete.app_error",
|
"id": "api.admin.remove_certificate.delete.app_error",
|
||||||
"translation": "An error occurred while deleting the certificate."
|
"translation": "An error occurred while deleting the certificate."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.failure_get_metadata_from_idp.app_error",
|
||||||
|
"translation": "Failed to obtain metadata from Identity Provider URL."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.failure_parse_idp_certificate.app_error",
|
||||||
|
"translation": "Failure encountered while parsing the metadata information received from the Identity Provider to a certificate."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.failure_save_idp_certificate_file.app_error",
|
||||||
|
"translation": "Could not save certificate file."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.invalid_xml_missing_idpssodescriptors.app_error",
|
||||||
|
"translation": "Missing Identity Provider SSO Descriptors node in the XML."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.invalid_xml_missing_keydescriptor.app_error",
|
||||||
|
"translation": "Missing Identity Provider Key Descriptors node in the XML."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.invalid_xml_missing_ssoservices.app_error",
|
||||||
|
"translation": "Missing Identity Provider SSO Services node in the XML."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.admin.saml.metadata.app_error",
|
"id": "api.admin.saml.metadata.app_error",
|
||||||
"translation": "An error occurred while building Service Provider Metadata."
|
"translation": "An error occurred while building Service Provider Metadata."
|
||||||
@@ -91,6 +115,18 @@
|
|||||||
"id": "api.admin.saml.not_available.app_error",
|
"id": "api.admin.saml.not_available.app_error",
|
||||||
"translation": "SAML 2.0 is not configured or supported on this server."
|
"translation": "SAML 2.0 is not configured or supported on this server."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.set_certificate_from_metadata.invalid_body.app_error",
|
||||||
|
"translation": "Invalid certificate text."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.set_certificate_from_metadata.invalid_content_type.app_error",
|
||||||
|
"translation": "Invalid content type."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.set_certificate_from_metadata.missing_content_type.app_error",
|
||||||
|
"translation": "Missing content type."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.admin.test_email.body",
|
"id": "api.admin.test_email.body",
|
||||||
"translation": "It appears your Mattermost email is setup correctly!"
|
"translation": "It appears your Mattermost email is setup correctly!"
|
||||||
@@ -2838,6 +2874,18 @@
|
|||||||
"id": "api.websocket_handler.server_busy.app_error",
|
"id": "api.websocket_handler.server_busy.app_error",
|
||||||
"translation": "Server is busy, non-critical services are temporarily unavailable"
|
"translation": "Server is busy, non-critical services are temporarily unavailable"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "app.admin.saml.failure_decode_metadata_xml_from_idp.app_error",
|
||||||
|
"translation": "Could not decode the XML metadata information received from the Identity Provider."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.admin.saml.failure_read_response_body_from_idp.app_error",
|
||||||
|
"translation": "Failure encountered when reading the response payload received from the Identity Provider."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.admin.saml.invalid_response_from_idp.app_error",
|
||||||
|
"translation": "Could not read the response received from the Identity Provider."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.admin.test_email.failure",
|
"id": "app.admin.test_email.failure",
|
||||||
"translation": "Connection unsuccessful: {{.Error}}"
|
"translation": "Connection unsuccessful: {{.Error}}"
|
||||||
|
|||||||
@@ -3463,6 +3463,18 @@ func (c *Client4) GetSamlCertificateStatus() (*SamlCertificateStatus, *Response)
|
|||||||
return SamlCertificateStatusFromJson(r.Body), BuildResponse(r)
|
return SamlCertificateStatusFromJson(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client4) GetSamlMetadataFromIdp(samlMetadataURL string) (*SamlMetadataResponse, *Response) {
|
||||||
|
requestBody := make(map[string]string)
|
||||||
|
requestBody["saml_metadata_url"] = samlMetadataURL
|
||||||
|
r, err := c.DoApiPost(c.GetSamlRoute()+"/metadatafromidp", MapToJson(requestBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
return SamlMetadataResponseFromJson(r.Body), BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
// Compliance Section
|
// Compliance Section
|
||||||
|
|
||||||
// CreateComplianceReport creates an incoming webhook for a channel.
|
// CreateComplianceReport creates an incoming webhook for a channel.
|
||||||
|
|||||||
@@ -1913,6 +1913,7 @@ type SamlSettings struct {
|
|||||||
|
|
||||||
IdpUrl *string
|
IdpUrl *string
|
||||||
IdpDescriptorUrl *string
|
IdpDescriptorUrl *string
|
||||||
|
IdpMetadataUrl *string
|
||||||
AssertionConsumerServiceURL *string
|
AssertionConsumerServiceURL *string
|
||||||
|
|
||||||
SignatureAlgorithm *string
|
SignatureAlgorithm *string
|
||||||
@@ -1984,6 +1985,10 @@ func (s *SamlSettings) SetDefaults() {
|
|||||||
s.IdpDescriptorUrl = NewString("")
|
s.IdpDescriptorUrl = NewString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.IdpMetadataUrl == nil {
|
||||||
|
s.IdpMetadataUrl = NewString("")
|
||||||
|
}
|
||||||
|
|
||||||
if s.IdpCertificateFile == nil {
|
if s.IdpCertificateFile == nil {
|
||||||
s.IdpCertificateFile = NewString("")
|
s.IdpCertificateFile = NewString("")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ func TestConfigIsValidFakeAlgorithm(t *testing.T) {
|
|||||||
|
|
||||||
*c1.SamlSettings.IdpUrl = "http://test.url.com"
|
*c1.SamlSettings.IdpUrl = "http://test.url.com"
|
||||||
*c1.SamlSettings.IdpDescriptorUrl = "http://test.url.com"
|
*c1.SamlSettings.IdpDescriptorUrl = "http://test.url.com"
|
||||||
|
*c1.SamlSettings.IdpMetadataUrl = "http://test.url.com"
|
||||||
*c1.SamlSettings.IdpCertificateFile = "certificatefile"
|
*c1.SamlSettings.IdpCertificateFile = "certificatefile"
|
||||||
*c1.SamlSettings.EmailAttribute = "Email"
|
*c1.SamlSettings.EmailAttribute = "Email"
|
||||||
*c1.SamlSettings.UsernameAttribute = "Username"
|
*c1.SamlSettings.UsernameAttribute = "Username"
|
||||||
|
|||||||
160
model/saml.go
160
model/saml.go
@@ -5,7 +5,9 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,6 +27,153 @@ type SamlCertificateStatus struct {
|
|||||||
PublicCertificateFile bool `json:"public_certificate_file"`
|
PublicCertificateFile bool `json:"public_certificate_file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SamlMetadataResponse struct {
|
||||||
|
IdpDescriptorUrl string `json:"idp_descriptor_url"`
|
||||||
|
IdpUrl string `json:"idp_url"`
|
||||||
|
IdpPublicCertificate string `json:"idp_public_certificate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameIDFormat struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Format string `xml:",attr,omitempty"`
|
||||||
|
Value string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameID struct {
|
||||||
|
NameQualifier string `xml:",attr"`
|
||||||
|
SPNameQualifier string `xml:",attr"`
|
||||||
|
Format string `xml:",attr,omitempty"`
|
||||||
|
SPProvidedID string `xml:",attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttributeValue struct {
|
||||||
|
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
NameID *NameID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attribute struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
FriendlyName string `xml:",attr"`
|
||||||
|
Name string `xml:",attr"`
|
||||||
|
NameFormat string `xml:",attr"`
|
||||||
|
Values []AttributeValue `xml:"AttributeValue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Binding string `xml:"Binding,attr"`
|
||||||
|
Location string `xml:"Location,attr"`
|
||||||
|
ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndexedEndpoint struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Binding string `xml:"Binding,attr"`
|
||||||
|
Location string `xml:"Location,attr"`
|
||||||
|
ResponseLocation *string `xml:"ResponseLocation,attr,omitempty"`
|
||||||
|
Index int `xml:"index,attr"`
|
||||||
|
IsDefault *bool `xml:"isDefault,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IDPSSODescriptor struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
|
||||||
|
SSODescriptor
|
||||||
|
WantAuthnRequestsSigned *bool `xml:",attr"`
|
||||||
|
|
||||||
|
SingleSignOnServices []Endpoint `xml:"SingleSignOnService"`
|
||||||
|
NameIDMappingServices []Endpoint `xml:"NameIDMappingService"`
|
||||||
|
AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
|
||||||
|
AttributeProfiles []string `xml:"AttributeProfile"`
|
||||||
|
Attributes []Attribute `xml:"Attribute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSODescriptor struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
RoleDescriptor
|
||||||
|
ArtifactResolutionServices []IndexedEndpoint `xml:"ArtifactResolutionService"`
|
||||||
|
SingleLogoutServices []Endpoint `xml:"SingleLogoutService"`
|
||||||
|
ManageNameIDServices []Endpoint `xml:"ManageNameIDService"`
|
||||||
|
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type X509Certificate struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Cert string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type X509Data struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
X509Certificate X509Certificate `xml:"X509Certificate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyInfo struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
DS string `xml:"xmlns:ds,attr"`
|
||||||
|
X509Data X509Data `xml:"X509Data"`
|
||||||
|
}
|
||||||
|
type EncryptionMethod struct {
|
||||||
|
Algorithm string `xml:"Algorithm,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyDescriptor struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Use string `xml:"use,attr,omitempty"`
|
||||||
|
KeyInfo KeyInfo `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleDescriptor struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
ID string `xml:",attr,omitempty"`
|
||||||
|
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
|
||||||
|
CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
|
||||||
|
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
|
||||||
|
ErrorURL string `xml:"errorURL,attr,omitempty"`
|
||||||
|
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor,omitempty"`
|
||||||
|
Organization *Organization `xml:"Organization,omitempty"`
|
||||||
|
ContactPersons []ContactPerson `xml:"ContactPerson,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContactPerson struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
ContactType string `xml:"contactType,attr"`
|
||||||
|
Company string
|
||||||
|
GivenName string
|
||||||
|
SurName string
|
||||||
|
EmailAddresses []string `xml:"EmailAddress"`
|
||||||
|
TelephoneNumbers []string `xml:"TelephoneNumber"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalizedName struct {
|
||||||
|
Lang string `xml:"xml lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalizedURI struct {
|
||||||
|
Lang string `xml:"xml lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Organization struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
OrganizationNames []LocalizedName `xml:"OrganizationName"`
|
||||||
|
OrganizationDisplayNames []LocalizedName `xml:"OrganizationDisplayName"`
|
||||||
|
OrganizationURLs []LocalizedURI `xml:"OrganizationURL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityDescriptor struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
|
||||||
|
EntityID string `xml:"entityID,attr"`
|
||||||
|
ID string `xml:",attr,omitempty"`
|
||||||
|
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
|
||||||
|
CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
|
||||||
|
RoleDescriptors []RoleDescriptor `xml:"RoleDescriptor"`
|
||||||
|
IDPSSODescriptors []IDPSSODescriptor `xml:"IDPSSODescriptor"`
|
||||||
|
Organization Organization `xml:"Organization"`
|
||||||
|
ContactPerson ContactPerson `xml:"ContactPerson"`
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SamlCertificateStatus) ToJson() string {
|
func (s *SamlCertificateStatus) ToJson() string {
|
||||||
b, _ := json.Marshal(s)
|
b, _ := json.Marshal(s)
|
||||||
return string(b)
|
return string(b)
|
||||||
@@ -35,3 +184,14 @@ func SamlCertificateStatusFromJson(data io.Reader) *SamlCertificateStatus {
|
|||||||
json.NewDecoder(data).Decode(&status)
|
json.NewDecoder(data).Decode(&status)
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SamlMetadataResponse) ToJson() string {
|
||||||
|
b, _ := json.Marshal(s)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SamlMetadataResponseFromJson(data io.Reader) *SamlMetadataResponse {
|
||||||
|
var status *SamlMetadataResponse
|
||||||
|
json.NewDecoder(data).Decode(&status)
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|||||||
@@ -289,6 +289,7 @@
|
|||||||
"Encrypt": true,
|
"Encrypt": true,
|
||||||
"IdpUrl": "",
|
"IdpUrl": "",
|
||||||
"IdpDescriptorUrl": "",
|
"IdpDescriptorUrl": "",
|
||||||
|
"IdpMetadataUrl": "",
|
||||||
"AssertionConsumerServiceURL": "",
|
"AssertionConsumerServiceURL": "",
|
||||||
"ScopingIDPProviderId": "",
|
"ScopingIDPProviderId": "",
|
||||||
"ScopingIDPName": "",
|
"ScopingIDPName": "",
|
||||||
@@ -401,4 +402,4 @@
|
|||||||
"CustomUrlSchemes": [],
|
"CustomUrlSchemes": [],
|
||||||
"ExperimentalTimezone": false
|
"ExperimentalTimezone": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ссылка в новой задаче
Block a user