* add new pluginapi methods

* SAML login hook

* set ReAddRemovedMembers to true for plugin groups

* change to DoLogin signature for SAML
Этот коммит содержится в:
Ben Cooke
2025-03-13 12:00:15 -04:00
коммит произвёл GitHub
родитель 0e0e54446d
Коммит ccd8a60168
44 изменённых файлов: 2119 добавлений и 213 удалений

Просмотреть файл

@@ -394,7 +394,7 @@ func linkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventObjectType("group_syncable")
c.App.Srv().Go(func() {
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, false)
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId)
})
w.WriteHeader(http.StatusCreated)
@@ -579,7 +579,7 @@ func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventObjectType("group_syncable")
c.App.Srv().Go(func() {
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, false)
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId)
})
b, err := json.Marshal(groupSyncable)
@@ -641,7 +641,7 @@ func unlinkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
}
c.App.Srv().Go(func() {
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, false)
c.App.SyncRolesAndMembership(c.AppContext, syncableID, syncableType, c.Params.GroupId)
})
auditRec.Success()
@@ -655,7 +655,7 @@ func verifyLinkUnlinkPermission(c *Context, syncableType model.GroupSyncableType
return appErr
}
if group.Source != model.GroupSourceLdap {
if !group.IsSyncable() {
return model.NewAppError("Api4.linkGroupSyncable", "app.group.crud_permission", nil, "", http.StatusBadRequest)
}
@@ -1023,6 +1023,8 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) {
source := c.Params.GroupSource
onlySyncableSources := r.URL.Query().Get("only_syncable_sources") == "true"
if id := c.Params.NotAssociatedToTeam; model.IsValidId(id) {
teamID = id
}
@@ -1042,9 +1044,9 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
// If they don't specify a source and custom groups are disabled, ensure they only get ldap groups in the response
// If they don't specify a source and custom groups are disabled, ensure they only get the other sources
if !*c.App.Config().ServiceSettings.EnableCustomGroups {
source = model.GroupSourceLdap
onlySyncableSources = true
}
includeTimezones := r.URL.Query().Get("include_timezones") == "true"
@@ -1063,6 +1065,7 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) {
IncludeTimezones: includeTimezones,
IncludeMemberIDs: c.Params.IncludeMemberIDs,
IncludeArchived: includeArchived,
OnlySyncableSources: onlySyncableSources,
}
if teamID != "" {
@@ -1461,6 +1464,10 @@ func licensedAndConfiguredForGroupBySource(app *app.App, source model.GroupSourc
return model.NewAppError("", "api.ldap_groups.license_error", nil, "", http.StatusForbidden)
}
if strings.HasPrefix(string(source), string(model.GroupSourcePluginPrefix)) && !*lic.Features.LDAPGroups {
return model.NewAppError("", "api.ldap_groups.license_error", nil, "", http.StatusForbidden)
}
if source == model.GroupSourceCustom && lic.SkuShortName != model.LicenseShortSkuProfessional && lic.SkuShortName != model.LicenseShortSkuEnterprise {
return model.NewAppError("", "api.custom_groups.license_error", nil, "", http.StatusBadRequest)
}

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"context"
"fmt"
"net/http"
"strings"
"testing"
"time"
@@ -1543,7 +1544,7 @@ func TestGetGroups(t *testing.T) {
})
assert.Nil(t, appErr)
opts := model.GroupSearchOpts{
baseOpts := model.GroupSearchOpts{
Source: model.GroupSourceLdap,
PageOpts: &model.PageOpts{
Page: 0,
@@ -1551,142 +1552,222 @@ func TestGetGroups(t *testing.T) {
},
}
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
_, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
_, err = th.SystemAdminClient.UpdateChannelRoles(context.Background(), th.BasicChannel.Id, th.BasicUser.Id, "")
require.NoError(t, err)
opts.NotAssociatedToChannel = th.BasicChannel.Id
_, err = th.SystemAdminClient.UpdateChannelRoles(context.Background(), th.BasicChannel.Id, th.BasicUser.Id, "channel_user channel_admin")
require.NoError(t, err)
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.ElementsMatch(t, []*model.Group{group, th.Group}, groups)
assert.Nil(t, groups[0].MemberCount)
opts.IncludeMemberCount = true
groups, _, _ = th.SystemAdminClient.GetGroups(context.Background(), opts)
assert.NotNil(t, groups[0].MemberCount)
opts.IncludeMemberCount = false
opts.Q = "-fOo"
groups, _, _ = th.SystemAdminClient.GetGroups(context.Background(), opts)
assert.Len(t, groups, 1)
opts.Q = ""
_, err = th.SystemAdminClient.UpdateTeamMemberRoles(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
require.NoError(t, err)
opts.NotAssociatedToTeam = th.BasicTeam.Id
_, err = th.SystemAdminClient.UpdateTeamMemberRoles(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "team_user team_admin")
require.NoError(t, err)
_, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
// test "since", should only return group created in this test, not th.Group
opts.Since = start
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 1)
// test correct group returned
assert.Equal(t, groups[0].Id, group.Id)
// delete group, should still return
_, appErr = th.App.DeleteGroup(group.Id)
require.Nil(t, appErr)
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Id, group.Id)
// test with current since value, return none
opts.Since = model.GetMillis()
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Empty(t, groups)
// make sure delete group is not returned without Since
opts.Since = 0
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
//'Normal getGroups should not return delete groups
assert.Len(t, groups, 1)
// make sure it returned th.Group,not group
assert.Equal(t, groups[0].Id, th.Group.Id)
// Test include_archived parameter
opts.IncludeArchived = true
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 2)
opts.IncludeArchived = false
// Test returning only archived groups
opts.FilterArchived = true
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Id, group.Id)
opts.FilterArchived = false
opts.Source = model.GroupSourceCustom
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Id, group2.Id)
// Test IncludeChannelMemberCount url param is working
opts.IncludeChannelMemberCount = th.BasicChannel.Id
opts.IncludeTimezones = true
opts.Q = "-fOo"
opts.IncludeMemberCount = true
groups, _, _ = th.SystemAdminClient.GetGroups(context.Background(), opts)
assert.Equal(t, *groups[0].MemberCount, int(0))
assert.Equal(t, *groups[0].ChannelMemberCount, int(0))
_, appErr = th.App.UpsertGroupMember(group2.Id, th.BasicUser.Id)
assert.Nil(t, appErr)
groups, _, _ = th.SystemAdminClient.GetGroups(context.Background(), opts)
assert.NotNil(t, groups[0].MemberCount)
assert.Equal(t, *groups[0].ChannelMemberCount, int(1))
opts.IncludeChannelMemberCount = ""
opts.IncludeTimezones = false
opts.Q = ""
opts.IncludeMemberCount = false
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableCustomGroups = false
t.Run("without license", func(t *testing.T) {
opts := baseOpts
th.App.Srv().SetLicense(nil)
_, response, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.Error(t, err)
CheckNotImplementedStatus(t, response)
})
// Specify custom groups source when feature is disabled
opts.Source = model.GroupSourceCustom
_, response, err := th.Client.GetGroups(context.Background(), opts)
require.Error(t, err)
CheckBadRequestStatus(t, response)
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
// Specify ldap groups source when custom groups feature is disabled
opts.Source = model.GroupSourceLdap
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Source, model.GroupSourceLdap)
t.Run("basic search", func(t *testing.T) {
opts := baseOpts
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.ElementsMatch(t, []*model.Group{group, th.Group}, groups)
assert.Nil(t, groups[0].MemberCount)
})
// don't include source and should only get ldap groups in response
opts.Source = ""
groups, _, err = th.Client.GetGroups(context.Background(), opts)
assert.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Source, model.GroupSourceLdap)
t.Run("include member count", func(t *testing.T) {
opts := baseOpts
opts.IncludeMemberCount = true
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.NotNil(t, groups[0].MemberCount)
})
t.Run("search with Q parameter", func(t *testing.T) {
opts := baseOpts
opts.Q = "-fOo"
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 1)
})
t.Run("not associated to channel", func(t *testing.T) {
opts := baseOpts
_, err := th.SystemAdminClient.UpdateChannelRoles(context.Background(), th.BasicChannel.Id, th.BasicUser.Id, "")
require.NoError(t, err)
opts.NotAssociatedToChannel = th.BasicChannel.Id
_, err = th.SystemAdminClient.UpdateChannelRoles(context.Background(), th.BasicChannel.Id, th.BasicUser.Id, "channel_user channel_admin")
require.NoError(t, err)
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.ElementsMatch(t, []*model.Group{group, th.Group}, groups)
})
t.Run("not associated to team", func(t *testing.T) {
opts := baseOpts
_, err := th.SystemAdminClient.UpdateTeamMemberRoles(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
require.NoError(t, err)
opts.NotAssociatedToTeam = th.BasicTeam.Id
_, err = th.SystemAdminClient.UpdateTeamMemberRoles(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "team_user team_admin")
require.NoError(t, err)
_, _, err = th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
})
t.Run("since parameter", func(t *testing.T) {
opts := baseOpts
opts.Since = start
groups, _, err := th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Id, group.Id)
opts.Since = model.GetMillis()
groups, _, err = th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Empty(t, groups)
})
t.Run("archived groups", func(t *testing.T) {
opts := baseOpts
_, appErr = th.App.DeleteGroup(group.Id)
require.Nil(t, appErr)
// Test include_archived parameter
opts.IncludeArchived = true
groups, _, err := th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 2)
// Test returning only archived groups
opts.FilterArchived = true
groups, _, err = th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Id, group.Id)
})
t.Run("group source filtering", func(t *testing.T) {
opts := baseOpts
opts.Source = model.GroupSourceCustom
groups, _, err := th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Id, group2.Id)
})
t.Run("channel member counts", func(t *testing.T) {
opts := baseOpts
opts.IncludeChannelMemberCount = th.BasicChannel.Id
opts.IncludeTimezones = true
opts.Q = "-fOo"
opts.IncludeMemberCount = true
opts.Source = model.GroupSourceCustom // Switch to custom source to get group2
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
require.Len(t, groups, 1)
assert.Equal(t, *groups[0].MemberCount, int(0))
assert.Equal(t, *groups[0].ChannelMemberCount, int(0))
_, appErr = th.App.UpsertGroupMember(group2.Id, th.BasicUser.Id)
require.Nil(t, appErr)
groups, _, err = th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
require.Len(t, groups, 1)
assert.Equal(t, *groups[0].MemberCount, int(1))
assert.Equal(t, *groups[0].ChannelMemberCount, int(1))
})
t.Run("custom groups disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableCustomGroups = false
})
t.Run("custom source not allowed", func(t *testing.T) {
opts := baseOpts
opts.Source = model.GroupSourceCustom
_, response, err := th.Client.GetGroups(context.Background(), opts)
require.Error(t, err)
CheckBadRequestStatus(t, response)
})
t.Run("ldap source allowed", func(t *testing.T) {
opts := baseOpts
opts.Source = model.GroupSourceLdap
groups, _, err := th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Source, model.GroupSourceLdap)
})
t.Run("no source specified", func(t *testing.T) {
opts := baseOpts
opts.Source = ""
groups, _, err := th.Client.GetGroups(context.Background(), opts)
require.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, groups[0].Source, model.GroupSourceLdap)
})
})
t.Run("only_syncable_sources parameter", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableCustomGroups = true
})
// Create a syncable group with the plugin prefix
id := model.NewId()
_, appErr := th.App.CreateGroup(&model.Group{
DisplayName: "dn-foo_" + id,
Name: model.NewPointer("name" + id),
Source: model.GroupSourcePluginPrefix + "keycloak",
Description: "description_" + id,
RemoteId: model.NewPointer(model.NewId()),
})
require.Nil(t, appErr)
// First test without only_syncable_sources
opts := model.GroupSearchOpts{
PageOpts: &model.PageOpts{
Page: 0,
PerPage: 60,
},
}
groups, _, err := th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
// Should return all groups regardless of source when not specified
assert.Len(t, groups, 3) // group, and group2
// Test with custom groups disabled and only_syncable_sources=true
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableCustomGroups = false
})
groups, _, err = th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
// Should still only return LDAP groups
assert.Len(t, groups, 2)
for _, g := range groups {
assert.True(t, g.Source == model.GroupSourceLdap || strings.HasPrefix(string(g.Source), string(model.GroupSourcePluginPrefix)))
}
// Reset config
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableCustomGroups = true
})
// Test with only_syncable_sources=true
opts.OnlySyncableSources = true
groups, _, err = th.SystemAdminClient.GetGroups(context.Background(), opts)
require.NoError(t, err)
// Should only return groups from syncable sources (LDAP and plugin_ groups)
assert.Len(t, groups, 2)
for _, g := range groups {
assert.True(t, g.Source == model.GroupSourceLdap || strings.HasPrefix(string(g.Source), string(model.GroupSourcePluginPrefix)))
}
})
}
func TestGetGroupsByUserId(t *testing.T) {

Просмотреть файл

@@ -39,6 +39,14 @@ func NewPluginAPI(a *App, c request.CTX, manifest *model.Manifest) *PluginAPI {
}
}
func (api *PluginAPI) checkLDAPLicense() error {
license := api.GetLicense()
if license == nil || !*license.Features.LDAPGroups {
return fmt.Errorf("license does not support LDAP groups")
}
return nil
}
func (api *PluginAPI) LoadPluginConfiguration(dest any) error {
finalConfig := make(map[string]any)
@@ -656,6 +664,97 @@ func (api *PluginAPI) GetGroupsForUser(userID string) ([]*model.Group, *model.Ap
return api.app.GetGroupsByUserId(userID)
}
func (api *PluginAPI) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("UpsertGroupMember", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.UpsertGroupMember(groupID, userID)
}
func (api *PluginAPI) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("UpsertGroupMembers", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.UpsertGroupMembers(groupID, userIDs)
}
func (api *PluginAPI) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("GetGroupByRemoteID", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.GetGroupByRemoteID(remoteID, groupSource)
}
func (api *PluginAPI) CreateGroup(group *model.Group) (*model.Group, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("CreateGroup", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.CreateGroup(group)
}
func (api *PluginAPI) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("UpdateGroup", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.UpdateGroup(group)
}
func (api *PluginAPI) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("DeleteGroup", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.DeleteGroup(groupID)
}
func (api *PluginAPI) RestoreGroup(groupID string) (*model.Group, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("RestoreGroup", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.RestoreGroup(groupID)
}
func (api *PluginAPI) DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("DeleteGroupMember", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.DeleteGroupMember(groupID, userID)
}
func (api *PluginAPI) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("GetGroupSyncable", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.GetGroupSyncable(groupID, syncableID, syncableType)
}
func (api *PluginAPI) GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("GetGroupSyncables", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.GetGroupSyncables(groupID, syncableType)
}
func (api *PluginAPI) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("UpsertGroupSyncable", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.UpsertGroupSyncable(groupSyncable)
}
func (api *PluginAPI) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("UpdateGroupSyncable", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.UpdateGroupSyncable(groupSyncable)
}
func (api *PluginAPI) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("DeleteGroupSyncable", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.DeleteGroupSyncable(groupID, syncableID, syncableType)
}
func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
post.AddProp("from_plugin", "true")
@@ -1347,3 +1446,10 @@ func (api *PluginAPI) UninviteRemoteFromChannel(channelID string, remoteID strin
func (api *PluginAPI) GetPluginID() string {
return api.id
}
func (api *PluginAPI) GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError) {
if err := api.checkLDAPLicense(); err != nil {
return nil, model.NewAppError("GetGroups", "app.group.license_error", nil, err.Error(), http.StatusForbidden)
}
return api.app.GetGroups(page, perPage, opts, viewRestrictions)
}

Просмотреть файл

@@ -270,16 +270,27 @@ func (a *App) SyncSyncableRoles(rctx request.CTX, syncableID string, syncableTyp
// SyncRolesAndMembership updates the SchemeAdmin status and membership of all of the members of the given
// syncable.
func (a *App) SyncRolesAndMembership(rctx request.CTX, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) {
appErr := a.SyncSyncableRoles(rctx, syncableID, syncableType)
func (a *App) SyncRolesAndMembership(rctx request.CTX, syncableID string, syncableType model.GroupSyncableType, groupID string) {
group, appErr := a.GetGroup(groupID, nil, nil)
if appErr != nil {
rctx.Logger().Warn("Error getting group", mlog.Err(appErr))
return
}
appErr = a.SyncSyncableRoles(rctx, syncableID, syncableType)
if appErr != nil {
rctx.Logger().Warn("Error syncing syncable roles", mlog.Err(appErr))
}
lastJob, _ := a.Srv().Store().Job().GetNewestJobByStatusAndType(model.JobStatusSuccess, model.JobTypeLdapSync)
var since int64
if lastJob != nil {
since = lastJob.StartAt
includeRemovedMembers := true
if group.Source == model.GroupSourceLdap {
lastJob, _ := a.Srv().Store().Job().GetNewestJobByStatusAndType(model.JobStatusSuccess, model.JobTypeLdapSync)
if lastJob != nil {
since = lastJob.StartAt
}
includeRemovedMembers = false
}
params := model.CreateDefaultMembershipParams{Since: since, ReAddRemovedMembers: includeRemovedMembers}

Просмотреть файл

@@ -1620,6 +1620,18 @@ func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts,
if opts.Source != "" {
groupsQuery = groupsQuery.Where("g.Source = ?", opts.Source)
} else if opts.OnlySyncableSources {
sources := model.GetSyncableGroupSources()
sourcePrefixes := model.GetSyncableGroupSourcePrefixes()
orClauses := sq.Or{}
if len(sources) > 0 {
orClauses = append(orClauses, sq.Eq{"g.Source": sources})
}
for _, prefix := range sourcePrefixes {
orClauses = append(orClauses, sq.Like{"g.Source": string(prefix) + "%"})
}
groupsQuery = groupsQuery.Where(orClauses)
}
queryString, args, err := groupsQuery.ToSql()

Просмотреть файл

@@ -3963,6 +3963,36 @@ func testGetGroups(t *testing.T, rctx request.CTX, ss store.Store) {
},
Restrictions: nil,
},
{
Name: "Include syncable sources only",
Opts: model.GroupSearchOpts{OnlySyncableSources: true},
Page: 0,
PerPage: 100,
Resultf: func(groups []*model.Group) bool {
for _, g := range groups {
if g.Source != model.GroupSourceLdap && !strings.HasPrefix(string(g.Source), "plugin_") {
return false
}
}
return true
},
Restrictions: nil,
},
{
Name: "Include syncable sources with specific source",
Opts: model.GroupSearchOpts{OnlySyncableSources: true, Source: model.GroupSourceLdap},
Page: 0,
PerPage: 100,
Resultf: func(groups []*model.Group) bool {
for _, g := range groups {
if g.Source != model.GroupSourceLdap {
return false
}
}
return true
},
Restrictions: nil,
},
{
Name: "Include archived groups",
Opts: model.GroupSearchOpts{IncludeArchived: true, Q: "group-deleted"},

Просмотреть файл

@@ -12,6 +12,7 @@ import (
"time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/v8/channels/audit"
"github.com/mattermost/mattermost/server/v8/channels/utils"
)
@@ -135,7 +136,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
user, err := samlInterface.DoLogin(c.AppContext, encodedXML, relayProps)
user, assertion, err := samlInterface.DoLogin(c.AppContext, encodedXML, relayProps)
if err != nil {
c.LogAudit("fail")
handleError(err)
@@ -172,6 +173,18 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
})
}
pluginContext := &plugin.Context{
RequestId: c.AppContext.RequestId(),
SessionId: c.AppContext.Session().Id,
IPAddress: c.AppContext.IPAddress(),
AcceptLanguage: c.AppContext.AcceptLanguage(),
UserAgent: c.AppContext.UserAgent(),
}
c.App.Channels().RunMultiHook(func(hooks plugin.Hooks, manifest *model.Manifest) bool {
err := hooks.OnSAMLLogin(pluginContext, user, assertion)
return err == nil
}, plugin.OnSAMLLoginID)
auditRec.AddMeta("obtained_user_id", user.Id)
c.LogAuditWithUserId(user.Id, "obtained user")