github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/app/saml.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"crypto/x509"
     8  	"encoding/pem"
     9  	"encoding/xml"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"mime/multipart"
    13  	"net/http"
    14  	"strings"
    15  
    16  	"github.com/vnforks/kid/v5/model"
    17  )
    18  
    19  const (
    20  	SamlPublicCertificateName = "saml-public.crt"
    21  	SamlPrivateKeyName        = "saml-private.key"
    22  	SamlIdpCertificateName    = "saml-idp.crt"
    23  )
    24  
    25  func (a *App) GetSamlMetadata() (string, *model.AppError) {
    26  	if a.Saml() == nil {
    27  		err := model.NewAppError("GetSamlMetadata", "api.admin.saml.not_available.app_error", nil, "", http.StatusNotImplemented)
    28  		return "", err
    29  	}
    30  
    31  	result, err := a.Saml().GetMetadata()
    32  	if err != nil {
    33  		return "", model.NewAppError("GetSamlMetadata", "api.admin.saml.metadata.app_error", nil, "err="+err.Message, err.StatusCode)
    34  	}
    35  	return result, nil
    36  }
    37  
    38  func (a *App) writeSamlFile(filename string, fileData *multipart.FileHeader) *model.AppError {
    39  	file, err := fileData.Open()
    40  	if err != nil {
    41  		return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.open.app_error", nil, err.Error(), http.StatusInternalServerError)
    42  	}
    43  	defer file.Close()
    44  
    45  	data, err := ioutil.ReadAll(file)
    46  	if err != nil {
    47  		return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
    48  	}
    49  
    50  	err = a.Srv().configStore.SetFile(filename, data)
    51  	if err != nil {
    52  		return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
    53  	}
    54  
    55  	return nil
    56  }
    57  
    58  func (a *App) AddSamlPublicCertificate(fileData *multipart.FileHeader) *model.AppError {
    59  	if err := a.writeSamlFile(SamlPublicCertificateName, fileData); err != nil {
    60  		return err
    61  	}
    62  
    63  	cfg := a.Config().Clone()
    64  	*cfg.SamlSettings.PublicCertificateFile = SamlPublicCertificateName
    65  
    66  	if err := cfg.IsValid(); err != nil {
    67  		return err
    68  	}
    69  
    70  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
    71  
    72  	return nil
    73  }
    74  
    75  func (a *App) AddSamlPrivateCertificate(fileData *multipart.FileHeader) *model.AppError {
    76  	if err := a.writeSamlFile(SamlPrivateKeyName, fileData); err != nil {
    77  		return err
    78  	}
    79  
    80  	cfg := a.Config().Clone()
    81  	*cfg.SamlSettings.PrivateKeyFile = SamlPrivateKeyName
    82  
    83  	if err := cfg.IsValid(); err != nil {
    84  		return err
    85  	}
    86  
    87  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
    88  
    89  	return nil
    90  }
    91  
    92  func (a *App) AddSamlIdpCertificate(fileData *multipart.FileHeader) *model.AppError {
    93  	if err := a.writeSamlFile(SamlIdpCertificateName, fileData); err != nil {
    94  		return err
    95  	}
    96  
    97  	cfg := a.Config().Clone()
    98  	*cfg.SamlSettings.IdpCertificateFile = SamlIdpCertificateName
    99  
   100  	if err := cfg.IsValid(); err != nil {
   101  		return err
   102  	}
   103  
   104  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
   105  
   106  	return nil
   107  }
   108  
   109  func (a *App) removeSamlFile(filename string) *model.AppError {
   110  	if err := a.Srv().configStore.RemoveFile(filename); err != nil {
   111  		return model.NewAppError("RemoveSamlFile", "api.admin.remove_certificate.delete.app_error", map[string]interface{}{"Filename": filename}, err.Error(), http.StatusInternalServerError)
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func (a *App) RemoveSamlPublicCertificate() *model.AppError {
   118  	if err := a.removeSamlFile(*a.Config().SamlSettings.PublicCertificateFile); err != nil {
   119  		return err
   120  	}
   121  
   122  	cfg := a.Config().Clone()
   123  	*cfg.SamlSettings.PublicCertificateFile = ""
   124  	*cfg.SamlSettings.Encrypt = false
   125  
   126  	if err := cfg.IsValid(); err != nil {
   127  		return err
   128  	}
   129  
   130  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
   131  
   132  	return nil
   133  }
   134  
   135  func (a *App) RemoveSamlPrivateCertificate() *model.AppError {
   136  	if err := a.removeSamlFile(*a.Config().SamlSettings.PrivateKeyFile); err != nil {
   137  		return err
   138  	}
   139  
   140  	cfg := a.Config().Clone()
   141  	*cfg.SamlSettings.PrivateKeyFile = ""
   142  	*cfg.SamlSettings.Encrypt = false
   143  
   144  	if err := cfg.IsValid(); err != nil {
   145  		return err
   146  	}
   147  
   148  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
   149  
   150  	return nil
   151  }
   152  
   153  func (a *App) RemoveSamlIdpCertificate() *model.AppError {
   154  	if err := a.removeSamlFile(*a.Config().SamlSettings.IdpCertificateFile); err != nil {
   155  		return err
   156  	}
   157  
   158  	cfg := a.Config().Clone()
   159  	*cfg.SamlSettings.IdpCertificateFile = ""
   160  	*cfg.SamlSettings.Enable = false
   161  
   162  	if err := cfg.IsValid(); err != nil {
   163  		return err
   164  	}
   165  
   166  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
   167  
   168  	return nil
   169  }
   170  
   171  func (a *App) GetSamlCertificateStatus() *model.SamlCertificateStatus {
   172  	status := &model.SamlCertificateStatus{}
   173  
   174  	status.IdpCertificateFile, _ = a.Srv().configStore.HasFile(*a.Config().SamlSettings.IdpCertificateFile)
   175  	status.PrivateKeyFile, _ = a.Srv().configStore.HasFile(*a.Config().SamlSettings.PrivateKeyFile)
   176  	status.PublicCertificateFile, _ = a.Srv().configStore.HasFile(*a.Config().SamlSettings.PublicCertificateFile)
   177  
   178  	return status
   179  }
   180  
   181  func (a *App) GetSamlMetadataFromIdp(idpMetadataUrl string) (*model.SamlMetadataResponse, *model.AppError) {
   182  	if a.Saml() == nil {
   183  		err := model.NewAppError("GetSamlMetadataFromIdp", "api.admin.saml.not_available.app_error", nil, "", http.StatusNotImplemented)
   184  		return nil, err
   185  	}
   186  
   187  	if !strings.HasPrefix(idpMetadataUrl, "http://") && !strings.HasPrefix(idpMetadataUrl, "https://") {
   188  		idpMetadataUrl = "https://" + idpMetadataUrl
   189  	}
   190  
   191  	idpMetadataRaw, err := a.FetchSamlMetadataFromIdp(idpMetadataUrl)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	data, err := a.BuildSamlMetadataObject(idpMetadataRaw)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	return data, nil
   202  }
   203  
   204  func (a *App) FetchSamlMetadataFromIdp(url string) ([]byte, *model.AppError) {
   205  	resp, err := a.HTTPService().MakeClient(false).Get(url)
   206  	if err != nil {
   207  		return nil, model.NewAppError("FetchSamlMetadataFromIdp", "app.admin.saml.invalid_response_from_idp.app_error", nil, err.Error(), http.StatusBadRequest)
   208  	}
   209  
   210  	if resp.StatusCode != http.StatusOK {
   211  		return nil, model.NewAppError("FetchSamlMetadataFromIdp", "app.admin.saml.invalid_response_from_idp.app_error", nil, fmt.Sprintf("status_code=%d", resp.StatusCode), http.StatusBadRequest)
   212  	}
   213  	defer resp.Body.Close()
   214  
   215  	bodyXML, err := ioutil.ReadAll(resp.Body)
   216  	if err != nil {
   217  		return nil, model.NewAppError("FetchSamlMetadataFromIdp", "app.admin.saml.failure_read_response_body_from_idp.app_error", nil, err.Error(), http.StatusInternalServerError)
   218  	}
   219  
   220  	return bodyXML, nil
   221  }
   222  
   223  func (a *App) BuildSamlMetadataObject(idpMetadata []byte) (*model.SamlMetadataResponse, *model.AppError) {
   224  	entityDescriptor := model.EntityDescriptor{}
   225  	err := xml.Unmarshal(idpMetadata, &entityDescriptor)
   226  	if err != nil {
   227  		return nil, model.NewAppError("BuildSamlMetadataObject", "app.admin.saml.failure_decode_metadata_xml_from_idp.app_error", nil, err.Error(), http.StatusInternalServerError)
   228  	}
   229  
   230  	data := &model.SamlMetadataResponse{}
   231  	data.IdpDescriptorUrl = entityDescriptor.EntityID
   232  
   233  	if entityDescriptor.IDPSSODescriptors == nil || len(entityDescriptor.IDPSSODescriptors) == 0 {
   234  		err := model.NewAppError("BuildSamlMetadataObject", "api.admin.saml.invalid_xml_missing_idpssodescriptors.app_error", nil, "", http.StatusInternalServerError)
   235  		return nil, err
   236  	}
   237  
   238  	idpSSODescriptor := entityDescriptor.IDPSSODescriptors[0]
   239  	if idpSSODescriptor.SingleSignOnServices == nil || len(idpSSODescriptor.SingleSignOnServices) == 0 {
   240  		err := model.NewAppError("BuildSamlMetadataObject", "api.admin.saml.invalid_xml_missing_ssoservices.app_error", nil, "", http.StatusInternalServerError)
   241  		return nil, err
   242  	}
   243  
   244  	data.IdpUrl = idpSSODescriptor.SingleSignOnServices[0].Location
   245  	if idpSSODescriptor.SSODescriptor.RoleDescriptor.KeyDescriptors == nil || len(idpSSODescriptor.SSODescriptor.RoleDescriptor.KeyDescriptors) == 0 {
   246  		err := model.NewAppError("BuildSamlMetadataObject", "api.admin.saml.invalid_xml_missing_keydescriptor.app_error", nil, "", http.StatusInternalServerError)
   247  		return nil, err
   248  	}
   249  	keyDescriptor := idpSSODescriptor.SSODescriptor.RoleDescriptor.KeyDescriptors[0]
   250  	data.IdpPublicCertificate = keyDescriptor.KeyInfo.X509Data.X509Certificate.Cert
   251  
   252  	return data, nil
   253  }
   254  
   255  func (a *App) SetSamlIdpCertificateFromMetadata(data []byte) *model.AppError {
   256  	const certPrefix = "-----BEGIN CERTIFICATE-----\n"
   257  	const certSuffix = "\n-----END CERTIFICATE-----"
   258  	fixedCertTxt := certPrefix + string(data) + certSuffix
   259  
   260  	block, _ := pem.Decode([]byte(fixedCertTxt))
   261  	if _, e := x509.ParseCertificate(block.Bytes); e != nil {
   262  		return model.NewAppError("SetSamlIdpCertificateFromMetadata", "api.admin.saml.failure_parse_idp_certificate.app_error", nil, e.Error(), http.StatusInternalServerError)
   263  	}
   264  
   265  	data = pem.EncodeToMemory(&pem.Block{
   266  		Type:  "CERTIFICATE",
   267  		Bytes: block.Bytes,
   268  	})
   269  
   270  	if err := a.Srv().configStore.SetFile(SamlIdpCertificateName, data); err != nil {
   271  		return model.NewAppError("SetSamlIdpCertificateFromMetadata", "api.admin.saml.failure_save_idp_certificate_file.app_error", nil, err.Error(), http.StatusInternalServerError)
   272  	}
   273  
   274  	cfg := a.Config().Clone()
   275  	*cfg.SamlSettings.IdpCertificateFile = SamlIdpCertificateName
   276  
   277  	if err := cfg.IsValid(); err != nil {
   278  		return err
   279  	}
   280  
   281  	a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
   282  
   283  	return nil
   284  }