Plugin groups (#30320)
* add new pluginapi methods * SAML login hook * set ReAddRemovedMembers to true for plugin groups * change to DoLogin signature for SAML
Этот коммит содержится в:
@@ -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")
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user