mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Implement LDAP Certificate (#15361)
* Implement LDAP Certificate * add diagnostics and translations * update from code review * pass pointer to update pict function * pass object to first function * remove debug log messages * update test to add localmode test * update lint errors Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
112
api4/ldap.go
112
api4/ldap.go
@@ -6,6 +6,7 @@ package api4
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/audit"
|
||||
@@ -32,6 +33,13 @@ func (api *API) InitLdap() {
|
||||
|
||||
// DELETE /api/v4/ldap/groups/:remote_id/link
|
||||
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.ApiSessionRequired(unlinkLdapGroup)).Methods("DELETE")
|
||||
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/public", api.ApiSessionRequired(addLdapPublicCertificate)).Methods("POST")
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/private", api.ApiSessionRequired(addLdapPrivateCertificate)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/public", api.ApiSessionRequired(removeLdapPublicCertificate)).Methods("DELETE")
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/private", api.ApiSessionRequired(removeLdapPrivateCertificate)).Methods("DELETE")
|
||||
|
||||
}
|
||||
|
||||
func syncLdap(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -290,3 +298,107 @@ func migrateIdLdap(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func parseLdapCertificateRequest(r *http.Request, maxFileSize int64) (*multipart.FileHeader, *model.AppError) {
|
||||
err := r.ParseMultipartForm(maxFileSize)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("addLdapCertificate", "api.admin.add_certificate.parseform.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
m := r.MultipartForm
|
||||
|
||||
fileArray, ok := m.File["certificate"]
|
||||
if !ok {
|
||||
return nil, model.NewAppError("addLdapCertificate", "api.admin.add_certificate.no_file.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(fileArray) <= 0 {
|
||||
return nil, model.NewAppError("addLdapCertificate", "api.admin.add_certificate.array.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return fileArray[0], nil
|
||||
}
|
||||
|
||||
func addLdapPublicCertificate(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
|
||||
}
|
||||
|
||||
fileData, err := parseLdapCertificateRequest(r, *c.App.Config().FileSettings.MaxFileSize)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("addLdapPublicCertificate", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
auditRec.AddMeta("filename", fileData.Filename)
|
||||
|
||||
if err := c.App.AddLdapPublicCertificate(fileData); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func addLdapPrivateCertificate(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
|
||||
}
|
||||
|
||||
fileData, err := parseLdapCertificateRequest(r, *c.App.Config().FileSettings.MaxFileSize)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("addLdapPrivateCertificate", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
auditRec.AddMeta("filename", fileData.Filename)
|
||||
|
||||
if err := c.App.AddLdapPrivateCertificate(fileData); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func removeLdapPublicCertificate(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
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("removeLdapPublicCertificate", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
if err := c.App.RemoveLdapPublicCertificate(); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func removeLdapPrivateCertificate(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
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("removeLdapPrivateCertificate", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
if err := c.App.RemoveLdapPrivateCertificate(); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
@@ -8,4 +8,9 @@ func (api *API) InitLdapLocal() {
|
||||
api.BaseRoutes.LDAP.Handle("/sync", api.ApiLocal(syncLdap)).Methods("POST")
|
||||
api.BaseRoutes.LDAP.Handle("/test", api.ApiLocal(testLdap)).Methods("POST")
|
||||
api.BaseRoutes.LDAP.Handle("/groups", api.ApiLocal(getLdapGroups)).Methods("GET")
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/public", api.ApiLocal(addLdapPublicCertificate)).Methods("POST")
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/private", api.ApiLocal(addLdapPrivateCertificate)).Methods("POST")
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/public", api.ApiLocal(removeLdapPublicCertificate)).Methods("DELETE")
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/private", api.ApiLocal(removeLdapPrivateCertificate)).Methods("DELETE")
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,92 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
var spPrivateKey = `-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
kSU80FrXITorOgZogkDk0FVpJA3qvQ==
|
||||
-----END PRIVATE KEY-----`
|
||||
|
||||
var spPublicCertificate = `-----BEGIN CERTIFICATE-----
|
||||
MIIFijCCA3KgAwIBAgIJAIRQ3EwrvOprMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
|
||||
BAYTAlVTMRIwEAYDVQQHDAlQYWxvIEFsdG8xEzARBgNVBAoMCk1hdHRlcm1vc3Qx
|
||||
DzANBgNVBAsMBkRldk9wczETMBEGA1UEAwwKY2xpZW50LmNvbTAeFw0xOTA5MTIx
|
||||
NzM1MzdaFw0yOTA5MDkxNzM1MzdaMFwxCzAJBgNVBAYTAlVTMRIwEAYDVQQHDAlQ
|
||||
YWxvIEFsdG8xEzARBgNVBAoMCk1hdHRlcm1vc3QxDzANBgNVBAsMBkRldk9wczET
|
||||
MBEGA1UEAwwKY2xpZW50LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
|
||||
ggIBANtVtR87yAUOCrHDdnuBfmVrJMopdBPrbeHR5Qdej3mMh0CkzIJ2FFihjP05
|
||||
S+jTVpDRTIStzJYpQkTcTRgQ/8yO8EZMh6aaSP81Ba0uojFlkWeXy5VybmA9ZMmA
|
||||
SHPL2Lj8Pj2qiVhqQk5WQr5+cBZTMiI7kYgzoi3zO4Mgr1ryWcsroIZaFZQNHyzq
|
||||
eXE6aIbfYtEo8A4muXE09sMk7lJX9aS17/VR1UJH1xbkWugnlp79xQSkx42mIUwJ
|
||||
zj2AM8Q4s5MCuhizVs6FLiYYCuU7RZlTPftzJlKj8y1YmjZjtuRV6MC3ql0xya9g
|
||||
3LnJ12uyUD7Q6K15lLh03TW0drhCIRo1Vn4N0pZ+kkViqpU02r9gy95SOTshuecm
|
||||
nP7KU5zzM2d6iLATkIj6aYWnRfT5m3UgkPsFdkTDfsmGT5h4F1BSeQc9YoePh2Qo
|
||||
+l+M9f8rWvYbUJjuIMbdXV8+wgUB8JS8ttYf33iFp7PP1T6Fe7z0uaGcqrrfx8N2
|
||||
4ZgpA7JjPz8GxDUxA/mQKsODkeGepTH90QISUWUcKerdn+AehA6JzEDx/UZ4IOIg
|
||||
TZUVhlFAsMvbWVv2jNAEVstll8ZosaZQ+F5xDRGtD9bhg82EG/JcQs6MKwN+7qW2
|
||||
ehvoTwu7Huw2iEW4tpiDQDMNF97OFTRGpZdREqWmKobSpqhbAgMBAAGjTzBNMBIG
|
||||
A1UdEwEB/wQIMAYBAf8CAQAwNwYDVR0RBDAwLoIOd3d3LmNsaWVudC5jb22CEGFk
|
||||
bWluLmNsaWVudC5jb22HBMCoAQqHBAoAAOowDQYJKoZIhvcNAQELBQADggIBAFEI
|
||||
D1ySRS+lQYVm24PPIUH5OmBEJUsVKI/zUXEQ4hdqEqN4UA3NGKkujajTz2fStaOj
|
||||
LfGDup1ZQRYG6VVvNwbZHX9G9mb8TyZ12XFLVjPTbxoG+NZb3ipue9S6qZcT9WEF
|
||||
sjaXhkVNhhVc1GOMnv/FNiclLPWLMnR8WST+Y+WSsT59wP40kJynaT7wQt2TmImg
|
||||
kQfM69jQNgAkyrFwO8y1YcnH7Avrw9YvzhUWG2FfNCTTVNb+StxNtqGwvDV33iZ2
|
||||
bBUWIy2fsNUA4tUYK31Ye6thJiKmvy/LqVJ415gPsI3zHzTCLU/GBUCNCNnEDnhU
|
||||
KO2K3mk1wK3sshMGcda/Xz2a9TfkIxs0pkenS57bZ8xT7mxBzXsZGm7Mnb2fujmX
|
||||
fBEyxQ2ot0Nl9Lp26WrBjQZojJ10Ic2IRxU3spC/FYK7BenQEAdnNHkyQ3lowAto
|
||||
NpOL+j+1ooksPQbp4DeIBbrZDNKvFot+ja2aDJ738sgXf8ht7kGXA5DPNtPLsmUr
|
||||
wpZrhxKD6pXVPhA6EeG2efdUP1ODslmehl4t2yX+FqHChnl7E012W8Cf0Ugybp1t
|
||||
15IXg8GxCRENSNAwpOvTMkoonHqNvBkaCDZHtxeyJMJWQW1B0Xek1JY3CNHvnY7I
|
||||
MCOV5SHi05kD42JSSbmw190VAa4QRGikaeWRhDsj
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
func TestTestLdap(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
@@ -112,3 +198,45 @@ func TestMigrateIdLdap(t *testing.T) {
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadPublicCertificate(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
_, resp := th.Client.UploadLdapPublicCertificate([]byte(spPublicCertificate))
|
||||
require.NotNil(t, resp.Error, "Should have failed. No System Admin privileges")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
_, resp = client.UploadLdapPublicCertificate([]byte(spPrivateKey))
|
||||
require.Nil(t, resp.Error, "Should have passed. System Admin privileges %v", resp.Error)
|
||||
})
|
||||
|
||||
_, resp = th.Client.DeleteLdapPublicCertificate()
|
||||
require.NotNil(t, resp.Error, "Should have failed. No System Admin privileges")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
_, resp := client.DeleteLdapPublicCertificate()
|
||||
require.Nil(t, resp.Error, "Should have passed. System Admin privileges %v", resp.Error)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadPrivateCertificate(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
_, resp := th.Client.UploadLdapPrivateCertificate([]byte(spPrivateKey))
|
||||
require.NotNil(t, resp.Error, "Should have failed. No System Admin privileges")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
_, resp = client.UploadLdapPrivateCertificate([]byte(spPrivateKey))
|
||||
require.Nil(t, resp.Error, "Should have passed. System Admin privileges %v", resp.Error)
|
||||
})
|
||||
|
||||
_, resp = th.Client.DeleteLdapPrivateCertificate()
|
||||
require.NotNil(t, resp.Error, "Should have failed. No System Admin privileges")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
_, resp := client.DeleteLdapPrivateCertificate()
|
||||
require.Nil(t, resp.Error, "Should have passed. System Admin privileges %v", resp.Error)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -341,6 +341,8 @@ type AppIface interface {
|
||||
AddChannelMember(userId string, channel *model.Channel, userRequestorId string, postRootId string) (*model.ChannelMember, *model.AppError)
|
||||
AddConfigListener(listener func(*model.Config, *model.Config)) string
|
||||
AddDirectChannels(teamId string, user *model.User) *model.AppError
|
||||
AddLdapPrivateCertificate(fileData *multipart.FileHeader) *model.AppError
|
||||
AddLdapPublicCertificate(fileData *multipart.FileHeader) *model.AppError
|
||||
AddSamlIdpCertificate(fileData *multipart.FileHeader) *model.AppError
|
||||
AddSamlPrivateCertificate(fileData *multipart.FileHeader) *model.AppError
|
||||
AddSamlPublicCertificate(fileData *multipart.FileHeader) *model.AppError
|
||||
@@ -807,6 +809,8 @@ type AppIface interface {
|
||||
RemoveAllDeactivatedMembersFromChannel(channel *model.Channel) *model.AppError
|
||||
RemoveConfigListener(id string)
|
||||
RemoveFile(path string) *model.AppError
|
||||
RemoveLdapPrivateCertificate() *model.AppError
|
||||
RemoveLdapPublicCertificate() *model.AppError
|
||||
RemovePlugin(id string) *model.AppError
|
||||
RemovePluginFromData(data model.PluginEventData)
|
||||
RemoveSamlIdpCertificate() *model.AppError
|
||||
|
||||
97
app/ldap.go
97
app/ldap.go
@@ -4,6 +4,8 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
@@ -175,3 +177,98 @@ func (a *App) MigrateIdLDAP(toAttribute string) *model.AppError {
|
||||
}
|
||||
return model.NewAppError("IdMigrateLDAP", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (a *App) writeLdapFile(filename string, fileData *multipart.FileHeader) *model.AppError {
|
||||
file, err := fileData.Open()
|
||||
if err != nil {
|
||||
return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.open.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = a.Srv().configStore.SetFile(filename, data)
|
||||
if err != nil {
|
||||
return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) AddLdapPublicCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
if err := a.writeLdapFile(model.LDAP_PUBIC_CERTIFICATE_NAME, fileData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := a.Config().Clone()
|
||||
*cfg.LdapSettings.PublicCertificateFile = model.LDAP_PUBIC_CERTIFICATE_NAME
|
||||
|
||||
if err := cfg.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) AddLdapPrivateCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
if err := a.writeLdapFile(model.LDAP_PRIVATE_KEY_NAME, fileData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := a.Config().Clone()
|
||||
*cfg.LdapSettings.PrivateKeyFile = model.LDAP_PRIVATE_KEY_NAME
|
||||
|
||||
if err := cfg.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) removeLdapFile(filename string) *model.AppError {
|
||||
if err := a.Srv().configStore.RemoveFile(filename); err != nil {
|
||||
return model.NewAppError("RemoveLdapFile", "api.admin.remove_certificate.delete.app_error", map[string]interface{}{"Filename": filename}, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) RemoveLdapPublicCertificate() *model.AppError {
|
||||
if err := a.removeLdapFile(*a.Config().LdapSettings.PublicCertificateFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := a.Config().Clone()
|
||||
*cfg.LdapSettings.PublicCertificateFile = ""
|
||||
|
||||
if err := cfg.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) RemoveLdapPrivateCertificate() *model.AppError {
|
||||
if err := a.removeLdapFile(*a.Config().LdapSettings.PrivateKeyFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := a.Config().Clone()
|
||||
*cfg.LdapSettings.PrivateKeyFile = ""
|
||||
|
||||
if err := cfg.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
||||
|
||||
if a.Srv().License() != nil && *a.Srv().License().Features.LDAP && a.Ldap() != nil {
|
||||
a.Srv().Go(func() {
|
||||
a.Ldap().UpdateProfilePictureIfNecessary(user, session)
|
||||
a.Ldap().UpdateProfilePictureIfNecessary(*user, session)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,50 @@ func (a *OpenTracingAppLayer) AddDirectChannels(teamId string, user *model.User)
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AddLdapPrivateCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddLdapPrivateCertificate")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.AddLdapPrivateCertificate(fileData)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AddLdapPublicCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddLdapPublicCertificate")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.AddLdapPublicCertificate(fileData)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AddPublicKey(name string, key io.Reader) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddPublicKey")
|
||||
@@ -11340,6 +11384,50 @@ func (a *OpenTracingAppLayer) RemoveFile(path string) *model.AppError {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveLdapPrivateCertificate() *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveLdapPrivateCertificate")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.RemoveLdapPrivateCertificate()
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveLdapPublicCertificate() *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveLdapPublicCertificate")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.RemoveLdapPublicCertificate()
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemovePlugin(id string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemovePlugin")
|
||||
|
||||
@@ -21,6 +21,6 @@ type LdapInterface interface {
|
||||
GetGroup(groupUID string) (*model.Group, *model.AppError)
|
||||
GetAllGroupsPage(page int, perPage int, opts model.LdapGroupSearchOpts) ([]*model.Group, int, *model.AppError)
|
||||
FirstLoginSync(userID, userAuthService, userAuthData, email string) *model.AppError
|
||||
UpdateProfilePictureIfNecessary(*model.User, *model.Session)
|
||||
UpdateProfilePictureIfNecessary(model.User, *model.Session)
|
||||
GetADLdapIdFromSAMLId(authData string) string
|
||||
}
|
||||
|
||||
@@ -305,6 +305,6 @@ func (_m *LdapInterface) SwitchToLdap(userId string, ldapId string, ldapPassword
|
||||
}
|
||||
|
||||
// UpdateProfilePictureIfNecessary provides a mock function with given fields: _a0, _a1
|
||||
func (_m *LdapInterface) UpdateProfilePictureIfNecessary(_a0 *model.User, _a1 *model.Session) {
|
||||
func (_m *LdapInterface) UpdateProfilePictureIfNecessary(_a0 model.User, _a1 *model.Session) {
|
||||
_m.Called(_a0, _a1)
|
||||
}
|
||||
|
||||
16
i18n/en.json
16
i18n/en.json
@@ -59,6 +59,10 @@
|
||||
"id": "api.admin.add_certificate.open.app_error",
|
||||
"translation": "Could not open certificate file."
|
||||
},
|
||||
{
|
||||
"id": "api.admin.add_certificate.parseform.app_error",
|
||||
"translation": "Error parsing multiform request"
|
||||
},
|
||||
{
|
||||
"id": "api.admin.add_certificate.saving.app_error",
|
||||
"translation": "Could not save certificate file."
|
||||
@@ -5490,10 +5494,18 @@
|
||||
"id": "ent.ldap.do_login.bind_admin_user.app_error",
|
||||
"translation": "Unable to bind to AD/LDAP server. Check BindUsername and BindPassword."
|
||||
},
|
||||
{
|
||||
"id": "ent.ldap.do_login.certificate.app_error",
|
||||
"translation": "Error loading LDAP TLS Certificate file."
|
||||
},
|
||||
{
|
||||
"id": "ent.ldap.do_login.invalid_password.app_error",
|
||||
"translation": "Invalid Password."
|
||||
},
|
||||
{
|
||||
"id": "ent.ldap.do_login.key.app_error",
|
||||
"translation": "Error loading LDAP TLS Key file."
|
||||
},
|
||||
{
|
||||
"id": "ent.ldap.do_login.licence_disable.app_error",
|
||||
"translation": "AD/LDAP functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license."
|
||||
@@ -5518,6 +5530,10 @@
|
||||
"id": "ent.ldap.do_login.user_not_registered.app_error",
|
||||
"translation": "User not registered on AD/LDAP server."
|
||||
},
|
||||
{
|
||||
"id": "ent.ldap.do_login.x509.app_error",
|
||||
"translation": "Error creating key pair"
|
||||
},
|
||||
{
|
||||
"id": "ent.ldap.save_user.email_exists.ldap_app_error",
|
||||
"translation": "This account does not use AD/LDAP authentication. Please sign in using email and password."
|
||||
|
||||
@@ -3671,7 +3671,7 @@ func (c *Client4) GetSamlMetadata() (string, *Response) {
|
||||
return buf.String(), BuildResponse(r)
|
||||
}
|
||||
|
||||
func samlFileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, error) {
|
||||
func fileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, error) {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
@@ -3694,7 +3694,7 @@ func samlFileToMultipart(data []byte, filename string) ([]byte, *multipart.Write
|
||||
// UploadSamlIdpCertificate will upload an IDP certificate for SAML and set the config to use it.
|
||||
// The filename parameter is deprecated and ignored: the server will pick a hard-coded filename when writing to disk.
|
||||
func (c *Client4) UploadSamlIdpCertificate(data []byte, filename string) (bool, *Response) {
|
||||
body, writer, err := samlFileToMultipart(data, filename)
|
||||
body, writer, err := fileToMultipart(data, filename)
|
||||
if err != nil {
|
||||
return false, &Response{Error: NewAppError("UploadSamlIdpCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
@@ -3706,7 +3706,7 @@ func (c *Client4) UploadSamlIdpCertificate(data []byte, filename string) (bool,
|
||||
// UploadSamlPublicCertificate will upload a public certificate for SAML and set the config to use it.
|
||||
// The filename parameter is deprecated and ignored: the server will pick a hard-coded filename when writing to disk.
|
||||
func (c *Client4) UploadSamlPublicCertificate(data []byte, filename string) (bool, *Response) {
|
||||
body, writer, err := samlFileToMultipart(data, filename)
|
||||
body, writer, err := fileToMultipart(data, filename)
|
||||
if err != nil {
|
||||
return false, &Response{Error: NewAppError("UploadSamlPublicCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
@@ -3718,7 +3718,7 @@ func (c *Client4) UploadSamlPublicCertificate(data []byte, filename string) (boo
|
||||
// UploadSamlPrivateCertificate will upload a private key for SAML and set the config to use it.
|
||||
// The filename parameter is deprecated and ignored: the server will pick a hard-coded filename when writing to disk.
|
||||
func (c *Client4) UploadSamlPrivateCertificate(data []byte, filename string) (bool, *Response) {
|
||||
body, writer, err := samlFileToMultipart(data, filename)
|
||||
body, writer, err := fileToMultipart(data, filename)
|
||||
if err != nil {
|
||||
return false, &Response{Error: NewAppError("UploadSamlPrivateCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
@@ -4079,6 +4079,48 @@ func (c *Client4) MigrateAuthToSaml(fromAuthService string, usersMap map[string]
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// UploadLdapPublicCertificate will upload a public certificate for LDAP and set the config to use it.
|
||||
func (c *Client4) UploadLdapPublicCertificate(data []byte) (bool, *Response) {
|
||||
body, writer, err := fileToMultipart(data, LDAP_PUBIC_CERTIFICATE_NAME)
|
||||
if err != nil {
|
||||
return false, &Response{Error: NewAppError("UploadLdapPublicCertificate", "model.client.upload_ldap_cert.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
|
||||
_, resp := c.DoUploadFile(c.GetLdapRoute()+"/certificate/public", body, writer.FormDataContentType())
|
||||
return resp.Error == nil, resp
|
||||
}
|
||||
|
||||
// UploadLdapPrivateCertificate will upload a private key for LDAP and set the config to use it.
|
||||
func (c *Client4) UploadLdapPrivateCertificate(data []byte) (bool, *Response) {
|
||||
body, writer, err := fileToMultipart(data, LDAP_PRIVATE_KEY_NAME)
|
||||
if err != nil {
|
||||
return false, &Response{Error: NewAppError("UploadLdapPrivateCertificate", "model.client.upload_Ldap_cert.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
|
||||
_, resp := c.DoUploadFile(c.GetLdapRoute()+"/certificate/private", body, writer.FormDataContentType())
|
||||
return resp.Error == nil, resp
|
||||
}
|
||||
|
||||
// DeleteLdapPublicCertificate deletes the LDAP IDP certificate from the server and updates the config to not use it and disable LDAP.
|
||||
func (c *Client4) DeleteLdapPublicCertificate() (bool, *Response) {
|
||||
r, err := c.DoApiDelete(c.GetLdapRoute() + "/certificate/public")
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// DeleteLDAPPrivateCertificate deletes the LDAP IDP certificate from the server and updates the config to not use it and disable LDAP.
|
||||
func (c *Client4) DeleteLdapPrivateCertificate() (bool, *Response) {
|
||||
r, err := c.DoApiDelete(c.GetLdapRoute() + "/certificate/private")
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// Audits Section
|
||||
|
||||
// GetAudits returns a list of audits for the whole system.
|
||||
|
||||
@@ -1943,9 +1943,11 @@ type LdapSettings struct {
|
||||
SyncIntervalMinutes *int `access:"authentication"`
|
||||
|
||||
// Advanced
|
||||
SkipCertificateVerification *bool `access:"authentication"`
|
||||
QueryTimeout *int `access:"authentication"`
|
||||
MaxPageSize *int `access:"authentication"`
|
||||
SkipCertificateVerification *bool `access:"authentication"`
|
||||
PublicCertificateFile *string `access:"authentication"`
|
||||
PrivateKeyFile *string `access:"authentication"`
|
||||
QueryTimeout *int `access:"authentication"`
|
||||
MaxPageSize *int `access:"authentication"`
|
||||
|
||||
// Customization
|
||||
LoginFieldName *string `access:"authentication"`
|
||||
@@ -1983,6 +1985,14 @@ func (s *LdapSettings) SetDefaults() {
|
||||
s.ConnectionSecurity = NewString("")
|
||||
}
|
||||
|
||||
if s.PublicCertificateFile == nil {
|
||||
s.PublicCertificateFile = NewString("")
|
||||
}
|
||||
|
||||
if s.PrivateKeyFile == nil {
|
||||
s.PrivateKeyFile = NewString("")
|
||||
}
|
||||
|
||||
if s.BaseDN == nil {
|
||||
s.BaseDN = NewString("")
|
||||
}
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
package model
|
||||
|
||||
const (
|
||||
USER_AUTH_SERVICE_LDAP = "ldap"
|
||||
USER_AUTH_SERVICE_LDAP = "ldap"
|
||||
LDAP_PUBIC_CERTIFICATE_NAME = "ldap-public.crt"
|
||||
LDAP_PRIVATE_KEY_NAME = "ldap-private.key"
|
||||
)
|
||||
|
||||
@@ -606,6 +606,8 @@ func (ts *TelemetryService) trackConfig() {
|
||||
"isempty_guest_filter": isDefault(*cfg.LdapSettings.GuestFilter, ""),
|
||||
"isempty_admin_filter": isDefault(*cfg.LdapSettings.AdminFilter, ""),
|
||||
"isnotempty_picture_attribute": !isDefault(*cfg.LdapSettings.PictureAttribute, ""),
|
||||
"isnotempty_public_certificate": !isDefault(*cfg.LdapSettings.PublicCertificateFile, ""),
|
||||
"isnotempty_private_key": !isDefault(*cfg.LdapSettings.PrivateKeyFile, ""),
|
||||
})
|
||||
|
||||
ts.sendTelemetry(TRACK_CONFIG_COMPLIANCE, map[string]interface{}{
|
||||
|
||||
Reference in New Issue
Block a user