diff --git a/api4/ldap.go b/api4/ldap.go index a5092e11a6..fcf93806d8 100644 --- a/api4/ldap.go +++ b/api4/ldap.go @@ -22,6 +22,7 @@ type mixedUnlinkedGroup struct { func (api *API) InitLdap() { api.BaseRoutes.LDAP.Handle("/sync", api.ApiSessionRequired(syncLdap)).Methods("POST") api.BaseRoutes.LDAP.Handle("/test", api.ApiSessionRequired(testLdap)).Methods("POST") + api.BaseRoutes.LDAP.Handle("/migrateid", api.ApiSessionRequired(migrateIdLdap)).Methods("POST") // GET /api/v4/ldap/groups?page=0&per_page=1000 api.BaseRoutes.LDAP.Handle("/groups", api.ApiSessionRequired(getLdapGroups)).Methods("GET") @@ -259,3 +260,33 @@ func unlinkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.Success() ReturnStatusOK(w) } + +func migrateIdLdap(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.StringInterfaceFromJson(r.Body) + toAttribute, ok := props["toAttribute"].(string) + if !ok || len(toAttribute) == 0 { + c.SetInvalidParam("toAttribute") + return + } + + auditRec := c.MakeAuditRecord("idMigrateLdap", audit.Fail) + defer c.LogAuditRec(auditRec) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if c.App.Srv().License() == nil || !*c.App.Srv().License().Features.LDAP { + c.Err = model.NewAppError("Api4.idMigrateLdap", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented) + return + } + + if err := c.App.MigrateIdLDAP(toAttribute); err != nil { + c.Err = err + return + } + + auditRec.Success() + ReturnStatusOK(w) +} diff --git a/api4/ldap_local.go b/api4/ldap_local.go index fbf82ffe70..101585db3c 100644 --- a/api4/ldap_local.go +++ b/api4/ldap_local.go @@ -4,6 +4,7 @@ package api4 func (api *API) InitLdapLocal() { + api.BaseRoutes.LDAP.Handle("/migrateid", api.ApiLocal(migrateIdLdap)).Methods("POST") 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") diff --git a/api4/ldap_test.go b/api4/ldap_test.go index 4979538c13..1e7418ddfc 100644 --- a/api4/ldap_test.go +++ b/api4/ldap_test.go @@ -96,3 +96,19 @@ func TestUnlinkLdapGroup(t *testing.T) { _, resp = th.SystemAdminClient.UnlinkLdapGroup(entryUUID) CheckNotImplementedStatus(t, resp) } + +func TestMigrateIdLdap(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + _, resp := th.Client.MigrateIdLdap("objectGUID") + CheckForbiddenStatus(t, resp) + + th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { + _, resp = client.MigrateIdLdap("") + CheckBadRequestStatus(t, resp) + + _, resp = client.MigrateIdLdap("objectGUID") + CheckNotImplementedStatus(t, resp) + }) +} diff --git a/app/app_iface.go b/app/app_iface.go index 08474a034f..35b7fbeeb0 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -746,6 +746,7 @@ type AppIface interface { MaxPostSize() int MessageExport() einterfaces.MessageExportInterface Metrics() einterfaces.MetricsInterface + MigrateIdLDAP(toAttribute string) *model.AppError MoveCommand(team *model.Team, command *model.Command) *model.AppError MoveFile(oldPath, newPath string) *model.AppError NewClusterDiscoveryService() *ClusterDiscoveryService diff --git a/app/ldap.go b/app/ldap.go index 043c960450..cef00e0807 100644 --- a/app/ldap.go +++ b/app/ldap.go @@ -160,3 +160,18 @@ func (a *App) SwitchLdapToEmail(ldapPassword, code, email, newPassword string) ( return "/login?extra=signin_change", nil } + +func (a *App) MigrateIdLDAP(toAttribute string) *model.AppError { + if ldapI := a.Ldap(); ldapI != nil { + if err := ldapI.MigrateIDAttribute(toAttribute); err != nil { + switch err := err.(type) { + case *model.AppError: + return err + default: + return model.NewAppError("IdMigrateLDAP", "ent.ldap_id_migrate.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + return nil + } + return model.NewAppError("IdMigrateLDAP", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented) +} diff --git a/app/opentracing_layer.go b/app/opentracing_layer.go index 698401c802..78ce395bd4 100644 --- a/app/opentracing_layer.go +++ b/app/opentracing_layer.go @@ -10054,6 +10054,28 @@ func (a *OpenTracingAppLayer) MigrateFilenamesToFileInfos(post *model.Post) []*m return resultVar0 } +func (a *OpenTracingAppLayer) MigrateIdLDAP(toAttribute string) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.MigrateIdLDAP") + + 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.MigrateIdLDAP(toAttribute) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) MoveChannel(team *model.Team, channel *model.Channel, user *model.User) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.MoveChannel") diff --git a/i18n/en.json b/i18n/en.json index 57af719740..47106c9715 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -4638,6 +4638,10 @@ "id": "ent.ldap_groups.reachable_groups_error", "translation": "error retrieving groups for user" }, + { + "id": "ent.ldap_id_migrate.app_error", + "translation": "unable to migrate." + }, { "id": "ent.message_export.global_relay.attach_file.app_error", "translation": "Unable to add attachment to the Global Relay export." diff --git a/model/client4.go b/model/client4.go index 0b0c05e4c7..1d1648d907 100644 --- a/model/client4.go +++ b/model/client4.go @@ -3820,6 +3820,18 @@ func (c *Client4) UnlinkLdapGroup(dn string) (*Group, *Response) { return GroupFromJson(r.Body), BuildResponse(r) } +// MigrateIdLdap migrates the LDAP enabled users to given attribute +func (c *Client4) MigrateIdLdap(toAttribute string) (bool, *Response) { + r, err := c.DoApiPost(c.GetLdapRoute()+"/migrateid", MapToJson(map[string]string{ + "toAttribute": toAttribute, + })) + if err != nil { + return false, BuildErrorResponse(r, err) + } + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) +} + // GetGroupsByChannel retrieves the Mattermost Groups associated with a given channel func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response) { path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v&filter_allow_reference=%v", c.GetChannelRoute(channelId), opts.Q, opts.IncludeMemberCount, opts.FilterAllowReference)