Plugin groups (#30320)
* add new pluginapi methods * SAML login hook * set ReAddRemovedMembers to true for plugin groups * change to DoLogin signature for SAML
Этот коммит содержится в:
@@ -1040,6 +1040,59 @@
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: include_member_count
|
||||
in: query
|
||||
description: Boolean which adds a `member_count` field to each group object.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: include_timezones
|
||||
in: query
|
||||
description: Boolean which adds timezone information for group members.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: include_total_count
|
||||
in: query
|
||||
description: Boolean which adds total count of groups in the response.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: include_archived
|
||||
in: query
|
||||
description: Boolean which includes archived groups in the response.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: filter_archived
|
||||
in: query
|
||||
description: Boolean which filters out archived groups from the response.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: filter_parent_team_permitted
|
||||
in: query
|
||||
description: Boolean which filters groups based on parent team permissions.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: filter_has_member
|
||||
in: query
|
||||
description: User ID to filter groups that have this member.
|
||||
schema:
|
||||
type: string
|
||||
- name: include_member_ids
|
||||
in: query
|
||||
description: Boolean which adds member IDs to the group objects.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: only_syncable_sources
|
||||
in: query
|
||||
description: Boolean which includes groups from syncable sources.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
"200":
|
||||
description: Group list retrieval successful
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
request "github.com/mattermost/mattermost/server/public/shared/request"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
)
|
||||
|
||||
// SamlInterface is an autogenerated mock type for the SamlInterface type
|
||||
@@ -84,7 +86,7 @@ func (_m *SamlInterface) ConfigureSP(c request.CTX) error {
|
||||
}
|
||||
|
||||
// DoLogin provides a mock function with given fields: c, encodedXML, relayState
|
||||
func (_m *SamlInterface) DoLogin(c request.CTX, encodedXML string, relayState map[string]string) (*model.User, *model.AppError) {
|
||||
func (_m *SamlInterface) DoLogin(c request.CTX, encodedXML string, relayState map[string]string) (*model.User, *saml2.AssertionInfo, *model.AppError) {
|
||||
ret := _m.Called(c, encodedXML, relayState)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -92,8 +94,9 @@ func (_m *SamlInterface) DoLogin(c request.CTX, encodedXML string, relayState ma
|
||||
}
|
||||
|
||||
var r0 *model.User
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, map[string]string) (*model.User, *model.AppError)); ok {
|
||||
var r1 *saml2.AssertionInfo
|
||||
var r2 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, map[string]string) (*model.User, *saml2.AssertionInfo, *model.AppError)); ok {
|
||||
return rf(c, encodedXML, relayState)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, map[string]string) *model.User); ok {
|
||||
@@ -104,15 +107,23 @@ func (_m *SamlInterface) DoLogin(c request.CTX, encodedXML string, relayState ma
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, map[string]string) *model.AppError); ok {
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, map[string]string) *saml2.AssertionInfo); ok {
|
||||
r1 = rf(c, encodedXML, relayState)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
r1 = ret.Get(1).(*saml2.AssertionInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, string, map[string]string) *model.AppError); ok {
|
||||
r2 = rf(c, encodedXML, relayState)
|
||||
} else {
|
||||
if ret.Get(2) != nil {
|
||||
r2 = ret.Get(2).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// GetMetadata provides a mock function with given fields: c
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package einterfaces
|
||||
|
||||
import (
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
type SamlInterface interface {
|
||||
ConfigureSP(c request.CTX) error
|
||||
BuildRequest(c request.CTX, relayState string) (*model.SamlAuthRequest, *model.AppError)
|
||||
DoLogin(c request.CTX, encodedXML string, relayState map[string]string) (*model.User, *model.AppError)
|
||||
DoLogin(c request.CTX, encodedXML string, relayState map[string]string) (*model.User, *saml2.AssertionInfo, *model.AppError)
|
||||
GetMetadata(c request.CTX) (string, *model.AppError)
|
||||
CheckProviderAttributes(c request.CTX, SS *model.SamlSettings, ouser *model.User, patch *model.UserPatch) string
|
||||
}
|
||||
|
||||
@@ -5314,6 +5314,10 @@
|
||||
"id": "app.group.id.app_error",
|
||||
"translation": "invalid id property for group."
|
||||
},
|
||||
{
|
||||
"id": "app.group.license_error",
|
||||
"translation": "LDAP license required."
|
||||
},
|
||||
{
|
||||
"id": "app.group.no_rows",
|
||||
"translation": "no matching group found"
|
||||
|
||||
@@ -17,6 +17,7 @@ require (
|
||||
github.com/hashicorp/go-plugin v1.6.3
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404
|
||||
github.com/mattermost/gosaml2 v0.8.0
|
||||
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956
|
||||
github.com/mattermost/logr/v2 v2.0.21
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.0
|
||||
@@ -37,6 +38,7 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
@@ -44,7 +46,9 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
@@ -52,6 +56,7 @@ require (
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.2.0 // indirect
|
||||
github.com/segmentio/backo-go v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
@@ -67,7 +72,6 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -13,6 +13,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
@@ -93,11 +95,14 @@ github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -111,10 +116,14 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
|
||||
github.com/mattermost/gosaml2 v0.8.0 h1:nkYiByawqwJ7KncK1LDWKwTx5aRarBTQsmH+XcCVsWQ=
|
||||
github.com/mattermost/gosaml2 v0.8.0/go.mod h1:1nMAdE2Psxaz+pj79Oytayi+hC3aZUi3SmJQlIe+sLM=
|
||||
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb2BTCsOdamENjjWCI6qmfHLbk6OZI=
|
||||
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=
|
||||
github.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4=
|
||||
github.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
@@ -152,11 +161,15 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rudderlabs/analytics-go v3.3.3+incompatible h1:OG0XlKoXfr539e2t1dXtTB+Gr89uFW+OUNQBVhHIIBY=
|
||||
github.com/rudderlabs/analytics-go v3.3.3+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30=
|
||||
github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg=
|
||||
github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/segmentio/backo-go v1.1.0 h1:cJIfHQUdmLsd8t9IXqf5J8SdrOMn9vMa7cIvOavHAhc=
|
||||
github.com/segmentio/backo-go v1.1.0/go.mod h1:ckenwdf+v/qbyhVdNPWHnqh2YdJBED1O9cidYyM5J18=
|
||||
@@ -339,8 +352,10 @@ google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40Rmc
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
@@ -349,6 +364,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
|
||||
@@ -5862,7 +5862,7 @@ func (c *Client4) GetGroupsAssociatedToChannelsByTeam(ctx context.Context, teamI
|
||||
// GetGroups retrieves Mattermost Groups
|
||||
func (c *Client4) GetGroups(ctx context.Context, opts GroupSearchOpts) ([]*Group, *Response, error) {
|
||||
path := fmt.Sprintf(
|
||||
"%s?include_member_count=%v¬_associated_to_team=%v¬_associated_to_channel=%v&filter_allow_reference=%v&q=%v&filter_parent_team_permitted=%v&group_source=%v&include_channel_member_count=%v&include_timezones=%v&include_archived=%v&filter_archived=%v",
|
||||
"%s?include_member_count=%v¬_associated_to_team=%v¬_associated_to_channel=%v&filter_allow_reference=%v&q=%v&filter_parent_team_permitted=%v&group_source=%v&include_channel_member_count=%v&include_timezones=%v&include_archived=%v&filter_archived=%v&only_syncable_sources=%v",
|
||||
c.groupsRoute(),
|
||||
opts.IncludeMemberCount,
|
||||
opts.NotAssociatedToTeam,
|
||||
@@ -5875,6 +5875,7 @@ func (c *Client4) GetGroups(ctx context.Context, opts GroupSearchOpts) ([]*Group
|
||||
opts.IncludeTimezones,
|
||||
opts.IncludeArchived,
|
||||
opts.FilterArchived,
|
||||
opts.OnlySyncableSources,
|
||||
)
|
||||
if opts.Since > 0 {
|
||||
path = fmt.Sprintf("%s&since=%v", path, opts.Since)
|
||||
|
||||
@@ -6,12 +6,16 @@ package model
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
GroupSourceLdap GroupSource = "ldap"
|
||||
GroupSourceCustom GroupSource = "custom"
|
||||
|
||||
// plugin groups must prefix their source with this
|
||||
GroupSourcePluginPrefix GroupSource = "plugin_"
|
||||
|
||||
GroupNameMaxLength = 64
|
||||
GroupSourceMaxLength = 64
|
||||
GroupDisplayNameMaxLength = 128
|
||||
@@ -21,15 +25,6 @@ const (
|
||||
|
||||
type GroupSource string
|
||||
|
||||
var allGroupSources = []GroupSource{
|
||||
GroupSourceLdap,
|
||||
GroupSourceCustom,
|
||||
}
|
||||
|
||||
var groupSourcesRequiringRemoteID = []GroupSource{
|
||||
GroupSourceLdap,
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Id string `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
@@ -157,6 +152,9 @@ type GroupSearchOpts struct {
|
||||
|
||||
// Only return archived groups
|
||||
FilterArchived bool
|
||||
|
||||
// OnlySyncableSources filters the groups to only those that are syncable
|
||||
OnlySyncableSources bool
|
||||
}
|
||||
|
||||
type GetGroupOpts struct {
|
||||
@@ -214,12 +212,12 @@ func (group *Group) IsValidForCreate() *AppError {
|
||||
}
|
||||
|
||||
isValidSource := false
|
||||
for _, groupSource := range allGroupSources {
|
||||
if group.Source == groupSource {
|
||||
isValidSource = true
|
||||
break
|
||||
}
|
||||
if group.Source == GroupSourceLdap ||
|
||||
group.Source == GroupSourceCustom ||
|
||||
strings.HasPrefix(string(group.Source), string(GroupSourcePluginPrefix)) {
|
||||
isValidSource = true
|
||||
}
|
||||
|
||||
if !isValidSource {
|
||||
return NewAppError("Group.IsValidForCreate", "model.group.source.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
@@ -232,12 +230,19 @@ func (group *Group) IsValidForCreate() *AppError {
|
||||
}
|
||||
|
||||
func (group *Group) requiresRemoteId() bool {
|
||||
for _, groupSource := range groupSourcesRequiringRemoteID {
|
||||
if groupSource == group.Source {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return group.Source == GroupSourceLdap || strings.HasPrefix(string(group.Source), string(GroupSourcePluginPrefix))
|
||||
}
|
||||
|
||||
func GetSyncableGroupSources() []GroupSource {
|
||||
return []GroupSource{GroupSourceLdap}
|
||||
}
|
||||
|
||||
func GetSyncableGroupSourcePrefixes() []GroupSource {
|
||||
return []GroupSource{GroupSourcePluginPrefix}
|
||||
}
|
||||
|
||||
func (group *Group) IsSyncable() bool {
|
||||
return group.Source == GroupSourceLdap || strings.HasPrefix(string(group.Source), string(GroupSourcePluginPrefix))
|
||||
}
|
||||
|
||||
func (group *Group) IsValidForUpdate() *AppError {
|
||||
|
||||
@@ -1300,6 +1300,87 @@ type API interface {
|
||||
// Minimum server version: 9.5
|
||||
UninviteRemoteFromChannel(channelID string, remoteID string) error
|
||||
|
||||
// UpsertGroupMember adds a user to a group or updates their existing membership.
|
||||
//
|
||||
// @tag Group
|
||||
// @tag User
|
||||
// Minimum server version: 10.7
|
||||
UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError)
|
||||
|
||||
// UpsertGroupMembers adds multiple users to a group or updates their existing memberships.
|
||||
//
|
||||
// @tag Group
|
||||
// @tag User
|
||||
// Minimum server version: 10.7
|
||||
UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError)
|
||||
|
||||
// GetGroupByRemoteID gets a group by its remote ID.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError)
|
||||
|
||||
// CreateGroup creates a new group.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
CreateGroup(group *model.Group) (*model.Group, *model.AppError)
|
||||
|
||||
// UpdateGroup updates a group.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
UpdateGroup(group *model.Group) (*model.Group, *model.AppError)
|
||||
|
||||
// DeleteGroup soft deletes a group.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
DeleteGroup(groupID string) (*model.Group, *model.AppError)
|
||||
|
||||
// RestoreGroup restores a soft deleted group.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
RestoreGroup(groupID string) (*model.Group, *model.AppError)
|
||||
|
||||
// DeleteGroupMember removes a user from a group.
|
||||
//
|
||||
// @tag Group
|
||||
// @tag User
|
||||
// Minimum server version: 10.7
|
||||
DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError)
|
||||
|
||||
// GetGroupSyncable gets a group syncable.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)
|
||||
|
||||
// GetGroupSyncables gets all group syncables for the given group.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError)
|
||||
|
||||
// UpsertGroupSyncable creates or updates a group syncable.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError)
|
||||
|
||||
// UpdateGroupSyncable updates a group syncable.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError)
|
||||
|
||||
// DeleteGroupSyncable deletes a group syncable.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)
|
||||
|
||||
// UpdateUserRoles updates the role for a user.
|
||||
//
|
||||
// @tag Team
|
||||
@@ -1312,6 +1393,12 @@ type API interface {
|
||||
// @tag Plugin
|
||||
// Minimum server version: 10.1
|
||||
GetPluginID() string
|
||||
|
||||
// GetGroups returns a list of all groups with the given options and restrictions.
|
||||
//
|
||||
// @tag Group
|
||||
// Minimum server version: 10.7
|
||||
GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError)
|
||||
}
|
||||
|
||||
var handshake = plugin.HandshakeConfig{
|
||||
|
||||
@@ -1372,6 +1372,97 @@ func (api *apiTimerLayer) UninviteRemoteFromChannel(channelID string, remoteID s
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.UpsertGroupMember(groupID, userID)
|
||||
api.recordTime(startTime, "UpsertGroupMember", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.UpsertGroupMembers(groupID, userIDs)
|
||||
api.recordTime(startTime, "UpsertGroupMembers", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.GetGroupByRemoteID(remoteID, groupSource)
|
||||
api.recordTime(startTime, "GetGroupByRemoteID", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) CreateGroup(group *model.Group) (*model.Group, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.CreateGroup(group)
|
||||
api.recordTime(startTime, "CreateGroup", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.UpdateGroup(group)
|
||||
api.recordTime(startTime, "UpdateGroup", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.DeleteGroup(groupID)
|
||||
api.recordTime(startTime, "DeleteGroup", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) RestoreGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.RestoreGroup(groupID)
|
||||
api.recordTime(startTime, "RestoreGroup", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.DeleteGroupMember(groupID, userID)
|
||||
api.recordTime(startTime, "DeleteGroupMember", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.GetGroupSyncable(groupID, syncableID, syncableType)
|
||||
api.recordTime(startTime, "GetGroupSyncable", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.GetGroupSyncables(groupID, syncableType)
|
||||
api.recordTime(startTime, "GetGroupSyncables", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.UpsertGroupSyncable(groupSyncable)
|
||||
api.recordTime(startTime, "UpsertGroupSyncable", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.UpdateGroupSyncable(groupSyncable)
|
||||
api.recordTime(startTime, "UpdateGroupSyncable", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.DeleteGroupSyncable(groupID, syncableID, syncableType)
|
||||
api.recordTime(startTime, "DeleteGroupSyncable", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) UpdateUserRoles(userID, newRoles string) (*model.User, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.UpdateUserRoles(userID, newRoles)
|
||||
@@ -1385,3 +1476,10 @@ func (api *apiTimerLayer) GetPluginID() string {
|
||||
api.recordTime(startTime, "GetPluginID", true)
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.GetGroups(page, perPage, opts, viewRestrictions)
|
||||
api.recordTime(startTime, "GetGroups", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
)
|
||||
@@ -1161,6 +1162,43 @@ func (s *hooksRPCServer) GenerateSupportData(args *Z_GenerateSupportDataArgs, re
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
hookNameToId["OnSAMLLogin"] = OnSAMLLoginID
|
||||
}
|
||||
|
||||
type Z_OnSAMLLoginArgs struct {
|
||||
A *Context
|
||||
B *model.User
|
||||
C *saml2.AssertionInfo
|
||||
}
|
||||
|
||||
type Z_OnSAMLLoginReturns struct {
|
||||
A error
|
||||
}
|
||||
|
||||
func (g *hooksRPCClient) OnSAMLLogin(c *Context, user *model.User, assertion *saml2.AssertionInfo) error {
|
||||
_args := &Z_OnSAMLLoginArgs{c, user, assertion}
|
||||
_returns := &Z_OnSAMLLoginReturns{}
|
||||
if g.implemented[OnSAMLLoginID] {
|
||||
if err := g.client.Call("Plugin.OnSAMLLogin", _args, _returns); err != nil {
|
||||
g.log.Error("RPC call OnSAMLLogin to plugin failed.", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
return _returns.A
|
||||
}
|
||||
|
||||
func (s *hooksRPCServer) OnSAMLLogin(args *Z_OnSAMLLoginArgs, returns *Z_OnSAMLLoginReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
OnSAMLLogin(c *Context, user *model.User, assertion *saml2.AssertionInfo) error
|
||||
}); ok {
|
||||
returns.A = hook.OnSAMLLogin(args.A, args.B, args.C)
|
||||
returns.A = encodableError(returns.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("Hook OnSAMLLogin called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_RegisterCommandArgs struct {
|
||||
A *model.Command
|
||||
}
|
||||
@@ -6601,6 +6639,392 @@ func (s *apiRPCServer) UninviteRemoteFromChannel(args *Z_UninviteRemoteFromChann
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpsertGroupMemberArgs struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
|
||||
type Z_UpsertGroupMemberReturns struct {
|
||||
A *model.GroupMember
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
|
||||
_args := &Z_UpsertGroupMemberArgs{groupID, userID}
|
||||
_returns := &Z_UpsertGroupMemberReturns{}
|
||||
if err := g.client.Call("Plugin.UpsertGroupMember", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to UpsertGroupMember API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) UpsertGroupMember(args *Z_UpsertGroupMemberArgs, returns *Z_UpsertGroupMemberReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.UpsertGroupMember(args.A, args.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API UpsertGroupMember called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpsertGroupMembersArgs struct {
|
||||
A string
|
||||
B []string
|
||||
}
|
||||
|
||||
type Z_UpsertGroupMembersReturns struct {
|
||||
A []*model.GroupMember
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError) {
|
||||
_args := &Z_UpsertGroupMembersArgs{groupID, userIDs}
|
||||
_returns := &Z_UpsertGroupMembersReturns{}
|
||||
if err := g.client.Call("Plugin.UpsertGroupMembers", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to UpsertGroupMembers API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) UpsertGroupMembers(args *Z_UpsertGroupMembersArgs, returns *Z_UpsertGroupMembersReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.UpsertGroupMembers(args.A, args.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API UpsertGroupMembers called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetGroupByRemoteIDArgs struct {
|
||||
A string
|
||||
B model.GroupSource
|
||||
}
|
||||
|
||||
type Z_GetGroupByRemoteIDReturns struct {
|
||||
A *model.Group
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
|
||||
_args := &Z_GetGroupByRemoteIDArgs{remoteID, groupSource}
|
||||
_returns := &Z_GetGroupByRemoteIDReturns{}
|
||||
if err := g.client.Call("Plugin.GetGroupByRemoteID", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to GetGroupByRemoteID API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) GetGroupByRemoteID(args *Z_GetGroupByRemoteIDArgs, returns *Z_GetGroupByRemoteIDReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.GetGroupByRemoteID(args.A, args.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API GetGroupByRemoteID called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_CreateGroupArgs struct {
|
||||
A *model.Group
|
||||
}
|
||||
|
||||
type Z_CreateGroupReturns struct {
|
||||
A *model.Group
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) CreateGroup(group *model.Group) (*model.Group, *model.AppError) {
|
||||
_args := &Z_CreateGroupArgs{group}
|
||||
_returns := &Z_CreateGroupReturns{}
|
||||
if err := g.client.Call("Plugin.CreateGroup", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to CreateGroup API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) CreateGroup(args *Z_CreateGroupArgs, returns *Z_CreateGroupReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
CreateGroup(group *model.Group) (*model.Group, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.CreateGroup(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API CreateGroup called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpdateGroupArgs struct {
|
||||
A *model.Group
|
||||
}
|
||||
|
||||
type Z_UpdateGroupReturns struct {
|
||||
A *model.Group
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) {
|
||||
_args := &Z_UpdateGroupArgs{group}
|
||||
_returns := &Z_UpdateGroupReturns{}
|
||||
if err := g.client.Call("Plugin.UpdateGroup", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to UpdateGroup API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) UpdateGroup(args *Z_UpdateGroupArgs, returns *Z_UpdateGroupReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
UpdateGroup(group *model.Group) (*model.Group, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.UpdateGroup(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API UpdateGroup called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_DeleteGroupArgs struct {
|
||||
A string
|
||||
}
|
||||
|
||||
type Z_DeleteGroupReturns struct {
|
||||
A *model.Group
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
_args := &Z_DeleteGroupArgs{groupID}
|
||||
_returns := &Z_DeleteGroupReturns{}
|
||||
if err := g.client.Call("Plugin.DeleteGroup", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to DeleteGroup API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) DeleteGroup(args *Z_DeleteGroupArgs, returns *Z_DeleteGroupReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
DeleteGroup(groupID string) (*model.Group, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.DeleteGroup(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API DeleteGroup called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_RestoreGroupArgs struct {
|
||||
A string
|
||||
}
|
||||
|
||||
type Z_RestoreGroupReturns struct {
|
||||
A *model.Group
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) RestoreGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
_args := &Z_RestoreGroupArgs{groupID}
|
||||
_returns := &Z_RestoreGroupReturns{}
|
||||
if err := g.client.Call("Plugin.RestoreGroup", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to RestoreGroup API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) RestoreGroup(args *Z_RestoreGroupArgs, returns *Z_RestoreGroupReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
RestoreGroup(groupID string) (*model.Group, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.RestoreGroup(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API RestoreGroup called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_DeleteGroupMemberArgs struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
|
||||
type Z_DeleteGroupMemberReturns struct {
|
||||
A *model.GroupMember
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
|
||||
_args := &Z_DeleteGroupMemberArgs{groupID, userID}
|
||||
_returns := &Z_DeleteGroupMemberReturns{}
|
||||
if err := g.client.Call("Plugin.DeleteGroupMember", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to DeleteGroupMember API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) DeleteGroupMember(args *Z_DeleteGroupMemberArgs, returns *Z_DeleteGroupMemberReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.DeleteGroupMember(args.A, args.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API DeleteGroupMember called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetGroupSyncableArgs struct {
|
||||
A string
|
||||
B string
|
||||
C model.GroupSyncableType
|
||||
}
|
||||
|
||||
type Z_GetGroupSyncableReturns struct {
|
||||
A *model.GroupSyncable
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
_args := &Z_GetGroupSyncableArgs{groupID, syncableID, syncableType}
|
||||
_returns := &Z_GetGroupSyncableReturns{}
|
||||
if err := g.client.Call("Plugin.GetGroupSyncable", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to GetGroupSyncable API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) GetGroupSyncable(args *Z_GetGroupSyncableArgs, returns *Z_GetGroupSyncableReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.GetGroupSyncable(args.A, args.B, args.C)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API GetGroupSyncable called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetGroupSyncablesArgs struct {
|
||||
A string
|
||||
B model.GroupSyncableType
|
||||
}
|
||||
|
||||
type Z_GetGroupSyncablesReturns struct {
|
||||
A []*model.GroupSyncable
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
|
||||
_args := &Z_GetGroupSyncablesArgs{groupID, syncableType}
|
||||
_returns := &Z_GetGroupSyncablesReturns{}
|
||||
if err := g.client.Call("Plugin.GetGroupSyncables", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to GetGroupSyncables API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) GetGroupSyncables(args *Z_GetGroupSyncablesArgs, returns *Z_GetGroupSyncablesReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.GetGroupSyncables(args.A, args.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API GetGroupSyncables called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpsertGroupSyncableArgs struct {
|
||||
A *model.GroupSyncable
|
||||
}
|
||||
|
||||
type Z_UpsertGroupSyncableReturns struct {
|
||||
A *model.GroupSyncable
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
|
||||
_args := &Z_UpsertGroupSyncableArgs{groupSyncable}
|
||||
_returns := &Z_UpsertGroupSyncableReturns{}
|
||||
if err := g.client.Call("Plugin.UpsertGroupSyncable", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to UpsertGroupSyncable API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) UpsertGroupSyncable(args *Z_UpsertGroupSyncableArgs, returns *Z_UpsertGroupSyncableReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.UpsertGroupSyncable(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API UpsertGroupSyncable called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpdateGroupSyncableArgs struct {
|
||||
A *model.GroupSyncable
|
||||
}
|
||||
|
||||
type Z_UpdateGroupSyncableReturns struct {
|
||||
A *model.GroupSyncable
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
|
||||
_args := &Z_UpdateGroupSyncableArgs{groupSyncable}
|
||||
_returns := &Z_UpdateGroupSyncableReturns{}
|
||||
if err := g.client.Call("Plugin.UpdateGroupSyncable", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to UpdateGroupSyncable API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) UpdateGroupSyncable(args *Z_UpdateGroupSyncableArgs, returns *Z_UpdateGroupSyncableReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.UpdateGroupSyncable(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API UpdateGroupSyncable called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_DeleteGroupSyncableArgs struct {
|
||||
A string
|
||||
B string
|
||||
C model.GroupSyncableType
|
||||
}
|
||||
|
||||
type Z_DeleteGroupSyncableReturns struct {
|
||||
A *model.GroupSyncable
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
_args := &Z_DeleteGroupSyncableArgs{groupID, syncableID, syncableType}
|
||||
_returns := &Z_DeleteGroupSyncableReturns{}
|
||||
if err := g.client.Call("Plugin.DeleteGroupSyncable", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to DeleteGroupSyncable API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) DeleteGroupSyncable(args *Z_DeleteGroupSyncableArgs, returns *Z_DeleteGroupSyncableReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.DeleteGroupSyncable(args.A, args.B, args.C)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API DeleteGroupSyncable called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_UpdateUserRolesArgs struct {
|
||||
A string
|
||||
B string
|
||||
@@ -6657,3 +7081,35 @@ func (s *apiRPCServer) GetPluginID(args *Z_GetPluginIDArgs, returns *Z_GetPlugin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetGroupsArgs struct {
|
||||
A int
|
||||
B int
|
||||
C model.GroupSearchOpts
|
||||
D *model.ViewUsersRestrictions
|
||||
}
|
||||
|
||||
type Z_GetGroupsReturns struct {
|
||||
A []*model.Group
|
||||
B *model.AppError
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError) {
|
||||
_args := &Z_GetGroupsArgs{page, perPage, opts, viewRestrictions}
|
||||
_returns := &Z_GetGroupsReturns{}
|
||||
if err := g.client.Call("Plugin.GetGroups", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to GetGroups API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) GetGroups(args *Z_GetGroupsArgs, returns *Z_GetGroupsReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.GetGroups(args.A, args.B, args.C, args.D)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API GetGroups called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
@@ -61,6 +62,7 @@ const (
|
||||
OnSharedChannelsAttachmentSyncMsgID = 43
|
||||
OnSharedChannelsProfileImageSyncMsgID = 44
|
||||
GenerateSupportDataID = 45
|
||||
OnSAMLLoginID = 46
|
||||
TotalHooksID = iota
|
||||
)
|
||||
|
||||
@@ -395,4 +397,9 @@ type Hooks interface {
|
||||
//
|
||||
// Minimum server version: 9.8
|
||||
GenerateSupportData(c *Context) ([]*model.FileData, error)
|
||||
|
||||
// OnSAMLLogin is invoked after a successful SAML login.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
OnSAMLLogin(c *Context, user *model.User, assertion *saml2.AssertionInfo) error
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
timePkg "time"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
@@ -291,3 +292,10 @@ func (hooks *hooksTimerLayer) GenerateSupportData(c *Context) ([]*model.FileData
|
||||
hooks.recordTime(startTime, "GenerateSupportData", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
func (hooks *hooksTimerLayer) OnSAMLLogin(c *Context, user *model.User, assertion *saml2.AssertionInfo) error {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA := hooks.hooksImpl.OnSAMLLogin(c, user, assertion)
|
||||
hooks.recordTime(startTime, "OnSAMLLogin", _returnsA == nil)
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
@@ -307,6 +307,15 @@ var hooksTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights R
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
)
|
||||
|
||||
{{range .HooksMethods}}
|
||||
|
||||
func init() {
|
||||
@@ -432,6 +441,7 @@ import (
|
||||
"net/http"
|
||||
timePkg "time"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
|
||||
@@ -272,6 +272,38 @@ func (_m *API) CreateCommand(cmd *model.Command) (*model.Command, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateGroup provides a mock function with given fields: group
|
||||
func (_m *API) CreateGroup(group *model.Group) (*model.Group, *model.AppError) {
|
||||
ret := _m.Called(group)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateGroup")
|
||||
}
|
||||
|
||||
var r0 *model.Group
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(*model.Group) (*model.Group, *model.AppError)); ok {
|
||||
return rf(group)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Group) *model.Group); ok {
|
||||
r0 = rf(group)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Group) *model.AppError); ok {
|
||||
r1 = rf(group)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateOAuthApp provides a mock function with given fields: app
|
||||
func (_m *API) CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) {
|
||||
ret := _m.Called(app)
|
||||
@@ -653,6 +685,102 @@ func (_m *API) DeleteEphemeralPost(userID string, postId string) {
|
||||
_m.Called(userID, postId)
|
||||
}
|
||||
|
||||
// DeleteGroup provides a mock function with given fields: groupID
|
||||
func (_m *API) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
ret := _m.Called(groupID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteGroup")
|
||||
}
|
||||
|
||||
var r0 *model.Group
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.Group, *model.AppError)); ok {
|
||||
return rf(groupID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.Group); ok {
|
||||
r0 = rf(groupID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
|
||||
r1 = rf(groupID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteGroupMember provides a mock function with given fields: groupID, userID
|
||||
func (_m *API) DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
|
||||
ret := _m.Called(groupID, userID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteGroupMember")
|
||||
}
|
||||
|
||||
var r0 *model.GroupMember
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, string) (*model.GroupMember, *model.AppError)); ok {
|
||||
return rf(groupID, userID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) *model.GroupMember); ok {
|
||||
r0 = rf(groupID, userID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.GroupMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
|
||||
r1 = rf(groupID, userID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteGroupSyncable provides a mock function with given fields: groupID, syncableID, syncableType
|
||||
func (_m *API) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
ret := _m.Called(groupID, syncableID, syncableType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteGroupSyncable")
|
||||
}
|
||||
|
||||
var r0 *model.GroupSyncable
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)); ok {
|
||||
return rf(groupID, syncableID, syncableType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) *model.GroupSyncable); ok {
|
||||
r0 = rf(groupID, syncableID, syncableType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.GroupSyncable)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, model.GroupSyncableType) *model.AppError); ok {
|
||||
r1 = rf(groupID, syncableID, syncableType)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteOAuthApp provides a mock function with given fields: appID
|
||||
func (_m *API) DeleteOAuthApp(appID string) *model.AppError {
|
||||
ret := _m.Called(appID)
|
||||
@@ -1758,6 +1886,38 @@ func (_m *API) GetGroupByName(name string) (*model.Group, *model.AppError) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetGroupByRemoteID provides a mock function with given fields: remoteID, groupSource
|
||||
func (_m *API) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
|
||||
ret := _m.Called(remoteID, groupSource)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetGroupByRemoteID")
|
||||
}
|
||||
|
||||
var r0 *model.Group
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, model.GroupSource) (*model.Group, *model.AppError)); ok {
|
||||
return rf(remoteID, groupSource)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, model.GroupSource) *model.Group); ok {
|
||||
r0 = rf(remoteID, groupSource)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, model.GroupSource) *model.AppError); ok {
|
||||
r1 = rf(remoteID, groupSource)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetGroupChannel provides a mock function with given fields: userIds
|
||||
func (_m *API) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
|
||||
ret := _m.Called(userIds)
|
||||
@@ -1822,6 +1982,102 @@ func (_m *API) GetGroupMemberUsers(groupID string, page int, perPage int) ([]*mo
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetGroupSyncable provides a mock function with given fields: groupID, syncableID, syncableType
|
||||
func (_m *API) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
ret := _m.Called(groupID, syncableID, syncableType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetGroupSyncable")
|
||||
}
|
||||
|
||||
var r0 *model.GroupSyncable
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)); ok {
|
||||
return rf(groupID, syncableID, syncableType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) *model.GroupSyncable); ok {
|
||||
r0 = rf(groupID, syncableID, syncableType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.GroupSyncable)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, model.GroupSyncableType) *model.AppError); ok {
|
||||
r1 = rf(groupID, syncableID, syncableType)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetGroupSyncables provides a mock function with given fields: groupID, syncableType
|
||||
func (_m *API) GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
|
||||
ret := _m.Called(groupID, syncableType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetGroupSyncables")
|
||||
}
|
||||
|
||||
var r0 []*model.GroupSyncable
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError)); ok {
|
||||
return rf(groupID, syncableType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, model.GroupSyncableType) []*model.GroupSyncable); ok {
|
||||
r0 = rf(groupID, syncableType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.GroupSyncable)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, model.GroupSyncableType) *model.AppError); ok {
|
||||
r1 = rf(groupID, syncableType)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetGroups provides a mock function with given fields: page, perPage, opts, viewRestrictions
|
||||
func (_m *API) GetGroups(page int, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError) {
|
||||
ret := _m.Called(page, perPage, opts, viewRestrictions)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetGroups")
|
||||
}
|
||||
|
||||
var r0 []*model.Group
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(int, int, model.GroupSearchOpts, *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError)); ok {
|
||||
return rf(page, perPage, opts, viewRestrictions)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int, int, model.GroupSearchOpts, *model.ViewUsersRestrictions) []*model.Group); ok {
|
||||
r0 = rf(page, perPage, opts, viewRestrictions)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int, int, model.GroupSearchOpts, *model.ViewUsersRestrictions) *model.AppError); ok {
|
||||
r1 = rf(page, perPage, opts, viewRestrictions)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetGroupsBySource provides a mock function with given fields: groupSource
|
||||
func (_m *API) GetGroupsBySource(groupSource model.GroupSource) ([]*model.Group, *model.AppError) {
|
||||
ret := _m.Called(groupSource)
|
||||
@@ -4087,6 +4343,38 @@ func (_m *API) RequestTrialLicense(requesterID string, users int, termsAccepted
|
||||
return r0
|
||||
}
|
||||
|
||||
// RestoreGroup provides a mock function with given fields: groupID
|
||||
func (_m *API) RestoreGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
ret := _m.Called(groupID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RestoreGroup")
|
||||
}
|
||||
|
||||
var r0 *model.Group
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.Group, *model.AppError)); ok {
|
||||
return rf(groupID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.Group); ok {
|
||||
r0 = rf(groupID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
|
||||
r1 = rf(groupID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RevokeSession provides a mock function with given fields: sessionID
|
||||
func (_m *API) RevokeSession(sessionID string) *model.AppError {
|
||||
ret := _m.Called(sessionID)
|
||||
@@ -4837,6 +5125,70 @@ func (_m *API) UpdateEphemeralPost(userID string, post *model.Post) *model.Post
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateGroup provides a mock function with given fields: group
|
||||
func (_m *API) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) {
|
||||
ret := _m.Called(group)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateGroup")
|
||||
}
|
||||
|
||||
var r0 *model.Group
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(*model.Group) (*model.Group, *model.AppError)); ok {
|
||||
return rf(group)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Group) *model.Group); ok {
|
||||
r0 = rf(group)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Group)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Group) *model.AppError); ok {
|
||||
r1 = rf(group)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateGroupSyncable provides a mock function with given fields: groupSyncable
|
||||
func (_m *API) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
|
||||
ret := _m.Called(groupSyncable)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateGroupSyncable")
|
||||
}
|
||||
|
||||
var r0 *model.GroupSyncable
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(*model.GroupSyncable) (*model.GroupSyncable, *model.AppError)); ok {
|
||||
return rf(groupSyncable)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.GroupSyncable) *model.GroupSyncable); ok {
|
||||
r0 = rf(groupSyncable)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.GroupSyncable)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.GroupSyncable) *model.AppError); ok {
|
||||
r1 = rf(groupSyncable)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateOAuthApp provides a mock function with given fields: app
|
||||
func (_m *API) UpdateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) {
|
||||
ret := _m.Called(app)
|
||||
@@ -5263,6 +5615,102 @@ func (_m *API) UploadFile(data []byte, channelId string, filename string) (*mode
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpsertGroupMember provides a mock function with given fields: groupID, userID
|
||||
func (_m *API) UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
|
||||
ret := _m.Called(groupID, userID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpsertGroupMember")
|
||||
}
|
||||
|
||||
var r0 *model.GroupMember
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, string) (*model.GroupMember, *model.AppError)); ok {
|
||||
return rf(groupID, userID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) *model.GroupMember); ok {
|
||||
r0 = rf(groupID, userID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.GroupMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
|
||||
r1 = rf(groupID, userID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpsertGroupMembers provides a mock function with given fields: groupID, userIDs
|
||||
func (_m *API) UpsertGroupMembers(groupID string, userIDs []string) ([]*model.GroupMember, *model.AppError) {
|
||||
ret := _m.Called(groupID, userIDs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpsertGroupMembers")
|
||||
}
|
||||
|
||||
var r0 []*model.GroupMember
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(string, []string) ([]*model.GroupMember, *model.AppError)); ok {
|
||||
return rf(groupID, userIDs)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, []string) []*model.GroupMember); ok {
|
||||
r0 = rf(groupID, userIDs)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.GroupMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, []string) *model.AppError); ok {
|
||||
r1 = rf(groupID, userIDs)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpsertGroupSyncable provides a mock function with given fields: groupSyncable
|
||||
func (_m *API) UpsertGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
|
||||
ret := _m.Called(groupSyncable)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpsertGroupSyncable")
|
||||
}
|
||||
|
||||
var r0 *model.GroupSyncable
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(*model.GroupSyncable) (*model.GroupSyncable, *model.AppError)); ok {
|
||||
return rf(groupSyncable)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.GroupSyncable) *model.GroupSyncable); ok {
|
||||
r0 = rf(groupSyncable)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.GroupSyncable)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.GroupSyncable) *model.AppError); ok {
|
||||
r1 = rf(groupSyncable)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewAPI creates a new instance of API. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAPI(t interface {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
|
||||
plugin "github.com/mattermost/mattermost/server/public/plugin"
|
||||
|
||||
saml2 "github.com/mattermost/gosaml2"
|
||||
)
|
||||
|
||||
// Hooks is an autogenerated mock type for the Hooks type
|
||||
@@ -384,6 +386,24 @@ func (_m *Hooks) OnPluginClusterEvent(c *plugin.Context, ev model.PluginClusterE
|
||||
_m.Called(c, ev)
|
||||
}
|
||||
|
||||
// OnSAMLLogin provides a mock function with given fields: c, user, assertion
|
||||
func (_m *Hooks) OnSAMLLogin(c *plugin.Context, user *model.User, assertion *saml2.AssertionInfo) error {
|
||||
ret := _m.Called(c, user, assertion)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OnSAMLLogin")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*plugin.Context, *model.User, *saml2.AssertionInfo) error); ok {
|
||||
r0 = rf(c, user, assertion)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// OnSendDailyTelemetry provides a mock function with given fields:
|
||||
func (_m *Hooks) OnSendDailyTelemetry() {
|
||||
_m.Called()
|
||||
|
||||
@@ -55,3 +55,115 @@ func (g *GroupService) ListForUser(userID string) ([]*model.Group, error) {
|
||||
|
||||
return groups, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// UpsertMember adds a user to a group or updates their existing membership.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) UpsertMember(groupID string, userID string) (*model.GroupMember, error) {
|
||||
member, appErr := g.api.UpsertGroupMember(groupID, userID)
|
||||
return member, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// UpsertMembers adds multiple users to a group or updates their existing memberships.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) UpsertMembers(groupID string, userIDs []string) ([]*model.GroupMember, error) {
|
||||
members, appErr := g.api.UpsertGroupMembers(groupID, userIDs)
|
||||
return members, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// GetByRemoteID gets a group by its remote ID.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) GetByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, error) {
|
||||
group, appErr := g.api.GetGroupByRemoteID(remoteID, groupSource)
|
||||
return group, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// Create creates a new group.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) Create(group *model.Group) (*model.Group, error) {
|
||||
group, appErr := g.api.CreateGroup(group)
|
||||
return group, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// Update updates a group.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) Update(group *model.Group) (*model.Group, error) {
|
||||
group, appErr := g.api.UpdateGroup(group)
|
||||
return group, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// Delete soft deletes a group.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) Delete(groupID string) (*model.Group, error) {
|
||||
group, appErr := g.api.DeleteGroup(groupID)
|
||||
return group, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// Restore restores a soft deleted group.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) Restore(groupID string) (*model.Group, error) {
|
||||
group, appErr := g.api.RestoreGroup(groupID)
|
||||
return group, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// DeleteMember removes a user from a group.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) DeleteMember(groupID string, userID string) (*model.GroupMember, error) {
|
||||
member, appErr := g.api.DeleteGroupMember(groupID, userID)
|
||||
return member, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// GetSyncable gets a group syncable.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) GetSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
|
||||
syncable, appErr := g.api.GetGroupSyncable(groupID, syncableID, syncableType)
|
||||
return syncable, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// GetSyncables gets all group syncables for the given group.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) GetSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, error) {
|
||||
syncables, appErr := g.api.GetGroupSyncables(groupID, syncableType)
|
||||
return syncables, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// UpsertSyncable creates or updates a group syncable.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) UpsertSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, error) {
|
||||
syncable, appErr := g.api.UpsertGroupSyncable(groupSyncable)
|
||||
return syncable, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// UpdateSyncable updates a group syncable.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) UpdateSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, error) {
|
||||
syncable, appErr := g.api.UpdateGroupSyncable(groupSyncable)
|
||||
return syncable, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// DeleteSyncable deletes a group syncable.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) DeleteSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
|
||||
syncable, appErr := g.api.DeleteGroupSyncable(groupID, syncableID, syncableType)
|
||||
return syncable, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
// GetGroups returns a list of all groups with the given options and restrictions.
|
||||
//
|
||||
// Minimum server version: 10.7
|
||||
func (g *GroupService) GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, error) {
|
||||
groups, appErr := g.api.GetGroups(page, perPage, opts, viewRestrictions)
|
||||
return groups, normalizeAppErr(appErr)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {IntlShape} from 'react-intl';
|
||||
import {injectIntl, FormattedMessage, defineMessage} from 'react-intl';
|
||||
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
import type {Group, SyncablePatch} from '@mattermost/types/groups';
|
||||
import type {Group, GroupSource, SyncablePatch} from '@mattermost/types/groups';
|
||||
import {SyncableType} from '@mattermost/types/groups';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
@@ -37,7 +37,7 @@ export type Props = {
|
||||
onAddCallback?: (groupIDs: string[]) => void;
|
||||
|
||||
actions: {
|
||||
getGroupsNotAssociatedToChannel: (channelID: string, q?: string, page?: number, perPage?: number, filterParentTeamPermitted?: boolean) => Promise<ActionResult>;
|
||||
getGroupsNotAssociatedToChannel: (channelID: string, q?: string, page?: number, perPage?: number, filterParentTeamPermitted?: boolean, source?: GroupSource | string, onlySyncableSources?: boolean) => Promise<ActionResult>;
|
||||
setModalSearchTerm: (term: string) => void;
|
||||
linkGroupSyncable: (groupID: string, syncableID: string, syncableType: SyncableType, patch: Partial<SyncablePatch>) => Promise<ActionResult>;
|
||||
getAllGroupsAssociatedToChannel: (channelID: string, filterAllowReference: boolean, includeMemberCount: boolean) => Promise<ActionResult>;
|
||||
@@ -80,7 +80,7 @@ export class AddGroupsToChannelModal extends React.PureComponent<Props, State> {
|
||||
Promise.all([
|
||||
this.props.actions.getTeam(this.props.teamID),
|
||||
this.props.actions.getAllGroupsAssociatedToTeam(this.props.teamID, false, true),
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, '', 0, GROUPS_PER_PAGE + 1, true),
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, '', 0, GROUPS_PER_PAGE + 1, true, '', true),
|
||||
this.props.actions.getAllGroupsAssociatedToChannel(this.props.currentChannelId, false, true),
|
||||
]).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
@@ -99,7 +99,7 @@ export class AddGroupsToChannelModal extends React.PureComponent<Props, State> {
|
||||
this.searchTimeoutId = window.setTimeout(
|
||||
async () => {
|
||||
this.setGroupsLoadingState(true);
|
||||
await this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, searchTerm, undefined, undefined, true);
|
||||
await this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, searchTerm, undefined, undefined, true, '', true);
|
||||
this.setGroupsLoadingState(false);
|
||||
},
|
||||
Constants.SEARCH_TIMEOUT_MILLISECONDS,
|
||||
@@ -168,7 +168,7 @@ export class AddGroupsToChannelModal extends React.PureComponent<Props, State> {
|
||||
handlePageChange = (page: number, prevPage: number) => {
|
||||
if (page > prevPage) {
|
||||
this.setGroupsLoadingState(true);
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1, true).then(() => {
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1, true, '', true).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {Modal} from 'react-bootstrap';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import {injectIntl, FormattedMessage, defineMessage} from 'react-intl';
|
||||
|
||||
import type {Group, SyncablePatch} from '@mattermost/types/groups';
|
||||
import type {Group, SyncablePatch, GroupSource} from '@mattermost/types/groups';
|
||||
import {SyncableType} from '@mattermost/types/groups';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
@@ -41,7 +41,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export type Actions = {
|
||||
getGroupsNotAssociatedToTeam: (teamID: string, q?: string, page?: number, perPage?: number) => Promise<ActionResult>;
|
||||
getGroupsNotAssociatedToTeam: (teamID: string, q?: string, page?: number, perPage?: number, source?: GroupSource | string, onlySyncableSources?: boolean) => Promise<ActionResult>;
|
||||
setModalSearchTerm: (term: string) => void;
|
||||
linkGroupSyncable: (groupID: string, syncableID: string, syncableType: SyncableType, patch: SyncablePatch) => Promise<ActionResult>;
|
||||
getAllGroupsAssociatedToTeam: (teamID: string, filterAllowReference: boolean, includeMemberCount: boolean) => Promise<ActionResult>;
|
||||
@@ -79,7 +79,7 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
|
||||
public componentDidMount() {
|
||||
Promise.all([
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, '', 0, GROUPS_PER_PAGE + 1),
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, '', 0, GROUPS_PER_PAGE + 1, '', true),
|
||||
this.props.actions.getAllGroupsAssociatedToTeam(this.props.currentTeamId, false, true),
|
||||
]).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
@@ -98,7 +98,7 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
this.searchTimeoutId = window.setTimeout(
|
||||
async () => {
|
||||
this.setGroupsLoadingState(true);
|
||||
await this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, searchTerm);
|
||||
await this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, searchTerm, 0, GROUPS_PER_PAGE + 1, '', true);
|
||||
this.setGroupsLoadingState(false);
|
||||
},
|
||||
Constants.SEARCH_TIMEOUT_MILLISECONDS,
|
||||
@@ -176,7 +176,7 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
public handlePageChange = (page: number, prevPage: number): void => {
|
||||
if (page > prevPage) {
|
||||
this.setGroupsLoadingState(true);
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1).then(() => {
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1, '', true).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ For more information on Groups, please see <link>documentation</link>."
|
||||
>
|
||||
<Connect(GroupsList) />
|
||||
</AdminPanel>
|
||||
<Pluggable
|
||||
pluggableName="SystemConsoleGroupTable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1399,3 +1399,224 @@ exports[`components/admin_console/group_settings/group_details/GroupUsers should
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/group_settings/group_details/GroupUsers should match snapshot, plugin group 1`] = `
|
||||
<div
|
||||
className="group-users"
|
||||
>
|
||||
<div
|
||||
className="group-users--header"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="This group is managed by a plugin."
|
||||
id="admin.group_settings.group_profile.group_users.pluginGroupText"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="group-users--body"
|
||||
>
|
||||
<div
|
||||
className="group-users-loading active"
|
||||
>
|
||||
<i
|
||||
className="fa fa-spinner fa-pulse fa-2x"
|
||||
/>
|
||||
</div>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name0 Surname0"
|
||||
email="test0@test.com"
|
||||
key="id0"
|
||||
lastPictureUpdate={0}
|
||||
userId="id0"
|
||||
username="username0"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name1 Surname1"
|
||||
email="test1@test.com"
|
||||
key="id1"
|
||||
lastPictureUpdate={1}
|
||||
userId="id1"
|
||||
username="username1"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name2 Surname2"
|
||||
email="test2@test.com"
|
||||
key="id2"
|
||||
lastPictureUpdate={2}
|
||||
userId="id2"
|
||||
username="username2"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name3 Surname3"
|
||||
email="test3@test.com"
|
||||
key="id3"
|
||||
lastPictureUpdate={3}
|
||||
userId="id3"
|
||||
username="username3"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name4 Surname4"
|
||||
email="test4@test.com"
|
||||
key="id4"
|
||||
lastPictureUpdate={4}
|
||||
userId="id4"
|
||||
username="username4"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name5 Surname5"
|
||||
email="test5@test.com"
|
||||
key="id5"
|
||||
lastPictureUpdate={5}
|
||||
userId="id5"
|
||||
username="username5"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name6 Surname6"
|
||||
email="test6@test.com"
|
||||
key="id6"
|
||||
lastPictureUpdate={6}
|
||||
userId="id6"
|
||||
username="username6"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name7 Surname7"
|
||||
email="test7@test.com"
|
||||
key="id7"
|
||||
lastPictureUpdate={7}
|
||||
userId="id7"
|
||||
username="username7"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name8 Surname8"
|
||||
email="test8@test.com"
|
||||
key="id8"
|
||||
lastPictureUpdate={8}
|
||||
userId="id8"
|
||||
username="username8"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name9 Surname9"
|
||||
email="test9@test.com"
|
||||
key="id9"
|
||||
lastPictureUpdate={9}
|
||||
userId="id9"
|
||||
username="username9"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name10 Surname10"
|
||||
email="test10@test.com"
|
||||
key="id10"
|
||||
lastPictureUpdate={10}
|
||||
userId="id10"
|
||||
username="username10"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name11 Surname11"
|
||||
email="test11@test.com"
|
||||
key="id11"
|
||||
lastPictureUpdate={11}
|
||||
userId="id11"
|
||||
username="username11"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name12 Surname12"
|
||||
email="test12@test.com"
|
||||
key="id12"
|
||||
lastPictureUpdate={12}
|
||||
userId="id12"
|
||||
username="username12"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name13 Surname13"
|
||||
email="test13@test.com"
|
||||
key="id13"
|
||||
lastPictureUpdate={13}
|
||||
userId="id13"
|
||||
username="username13"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name14 Surname14"
|
||||
email="test14@test.com"
|
||||
key="id14"
|
||||
lastPictureUpdate={14}
|
||||
userId="id14"
|
||||
username="username14"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name15 Surname15"
|
||||
email="test15@test.com"
|
||||
key="id15"
|
||||
lastPictureUpdate={15}
|
||||
userId="id15"
|
||||
username="username15"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name16 Surname16"
|
||||
email="test16@test.com"
|
||||
key="id16"
|
||||
lastPictureUpdate={16}
|
||||
userId="id16"
|
||||
username="username16"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name17 Surname17"
|
||||
email="test17@test.com"
|
||||
key="id17"
|
||||
lastPictureUpdate={17}
|
||||
userId="id17"
|
||||
username="username17"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name18 Surname18"
|
||||
email="test18@test.com"
|
||||
key="id18"
|
||||
lastPictureUpdate={18}
|
||||
userId="id18"
|
||||
username="username18"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name19 Surname19"
|
||||
email="test19@test.com"
|
||||
key="id19"
|
||||
lastPictureUpdate={19}
|
||||
userId="id19"
|
||||
username="username19"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="group-users--footer"
|
||||
>
|
||||
<div
|
||||
className="counter"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="{startCount, number} - {endCount, number} of {total, number}"
|
||||
id="admin.group_settings.groups_list.paginatorCount"
|
||||
values={
|
||||
Object {
|
||||
"endCount": 20,
|
||||
"startCount": 1,
|
||||
"total": 20,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-tertiary prev disabled"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Memo(PreviousIcon) />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-tertiary next disabled"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Memo(NextIcon) />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -761,6 +761,7 @@ class GroupDetails extends React.PureComponent<Props, State> {
|
||||
total={memberCount}
|
||||
groupID={this.props.groupID}
|
||||
getMembers={this.props.actions.getMembers}
|
||||
source={group.source}
|
||||
/>
|
||||
</AdminPanel>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {shallow} from 'enzyme';
|
||||
import range from 'lodash/range';
|
||||
import React from 'react';
|
||||
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import GroupUsers from 'components/admin_console/group_settings/group_details/group_users';
|
||||
@@ -23,6 +24,7 @@ describe('components/admin_console/group_settings/group_details/GroupUsers', ()
|
||||
groupID: 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
members: members.slice(0, 20),
|
||||
total: 20,
|
||||
source: GroupSource.Ldap,
|
||||
getMembers: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
};
|
||||
|
||||
@@ -36,6 +38,16 @@ describe('components/admin_console/group_settings/group_details/GroupUsers', ()
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, plugin group', () => {
|
||||
const wrapper = shallow(
|
||||
<GroupUsers
|
||||
{...defaultProps}
|
||||
source={PluginGroupSourcePrefix.Plugin + 'keycloak'}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on loading with data', () => {
|
||||
const wrapper = shallow(<GroupUsers {...defaultProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
@@ -19,6 +20,7 @@ type Props = {
|
||||
groupID: string;
|
||||
members: UserProfile[];
|
||||
total: number;
|
||||
source?: string;
|
||||
getMembers: (
|
||||
id: string,
|
||||
page?: number,
|
||||
@@ -154,23 +156,42 @@ export default class GroupUsers extends React.PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
renderHeader = () => {
|
||||
if (this.props.source === GroupSource.Ldap) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='admin.group_settings.group_profile.group_users.ldapConnectorText'
|
||||
defaultMessage={
|
||||
'AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>'
|
||||
}
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
<Link to='/admin_console/authentication/ldap'>
|
||||
{chunks}
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.source?.startsWith(PluginGroupSourcePrefix.Plugin)) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='admin.group_settings.group_profile.group_users.pluginGroupText'
|
||||
defaultMessage='This group is managed by a plugin.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div className='group-users'>
|
||||
<div className='group-users--header'>
|
||||
<FormattedMessage
|
||||
id='admin.group_settings.group_profile.group_users.ldapConnectorText'
|
||||
defaultMessage={
|
||||
'AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>'
|
||||
}
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
<Link to='/admin_console/authentication/ldap'>
|
||||
{chunks}
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{this.renderHeader()}
|
||||
</div>
|
||||
<div className='group-users--body'>
|
||||
<div
|
||||
|
||||
@@ -9,6 +9,7 @@ import ExternalLink from 'components/external_link';
|
||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
||||
|
||||
import Pluggable from 'plugins/pluggable';
|
||||
import {DocLinks} from 'utils/constants';
|
||||
import {getSiteURL} from 'utils/url';
|
||||
|
||||
@@ -66,6 +67,9 @@ const GroupSettings = ({isDisabled}: Props) => {
|
||||
readOnly={isDisabled}
|
||||
/>
|
||||
</AdminPanel>
|
||||
<Pluggable
|
||||
pluggableName='SystemConsoleGroupTable'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {GroupSource} from '@mattermost/types/groups';
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
import {isSyncableSource} from 'mattermost-redux/utils/group_utils';
|
||||
|
||||
import StatusIcon from 'components/status_icon';
|
||||
import Avatar from 'components/widgets/users/avatar';
|
||||
@@ -72,7 +72,7 @@ const ViewUserGroupListItem = (props: Props) => {
|
||||
{`@${user.username}`}
|
||||
</div>
|
||||
{
|
||||
(group.source.toLowerCase() !== GroupSource.Ldap && props.permissionToLeaveGroup) &&
|
||||
(!isSyncableSource(group.source.toLowerCase()) && props.permissionToLeaveGroup) &&
|
||||
<button
|
||||
type='button'
|
||||
className='remove-group-member btn btn-icon btn-xs'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {Modal} from 'react-bootstrap';
|
||||
import {defineMessage, FormattedMessage} from 'react-intl';
|
||||
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import {GroupSource} from '@mattermost/types/groups';
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
@@ -183,6 +183,15 @@ export default class ViewUserGroupModal extends React.PureComponent<Props, State
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
{
|
||||
group.source.toLowerCase().startsWith(PluginGroupSourcePrefix.Plugin) &&
|
||||
<span className='group-source'>
|
||||
<FormattedMessage
|
||||
id='view_user_group_modal.pluginSynced'
|
||||
defaultMessage='Plugin SYNCED'
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1112,6 +1112,7 @@
|
||||
"admin.group_settings.group_profile.group_teams_and_channels.name": "Name",
|
||||
"admin.group_settings.group_profile.group_teams_and_channels.type": "Type",
|
||||
"admin.group_settings.group_profile.group_users.ldapConnectorText": "AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>",
|
||||
"admin.group_settings.group_profile.group_users.pluginGroupText": "This group is managed by a plugin.",
|
||||
"admin.group_settings.group_row.configure": "Configure",
|
||||
"admin.group_settings.group_row.edit": "Edit",
|
||||
"admin.group_settings.group_row.link_failed": "Link failed",
|
||||
@@ -6006,6 +6007,7 @@
|
||||
"view_image.zoom_reset": "Reset Zoom",
|
||||
"view_user_group_modal.ldapSynced": "AD/LDAP SYNCED",
|
||||
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Member} other {Members}}",
|
||||
"view_user_group_modal.pluginSynced": "Plugin SYNCED",
|
||||
"web.footer.about": "About",
|
||||
"web.footer.help": "Help",
|
||||
"web.footer.privacy": "Privacy Policy",
|
||||
|
||||
@@ -171,7 +171,7 @@ export function getGroups(opts: GetGroupsParams) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, source = GroupSource.Ldap) {
|
||||
export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, source: GroupSource | string = GroupSource.Ldap, onlySyncableSources = false) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroupsNotAssociatedToTeam,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS],
|
||||
@@ -181,11 +181,12 @@ export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, p
|
||||
page,
|
||||
perPage,
|
||||
source,
|
||||
onlySyncableSources,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, filterParentTeamPermitted = false, source = GroupSource.Ldap) {
|
||||
export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, filterParentTeamPermitted = false, source: GroupSource | string = GroupSource.Ldap, onlySyncableSources = false) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroupsNotAssociatedToChannel,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS],
|
||||
@@ -196,6 +197,7 @@ export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page
|
||||
perPage,
|
||||
filterParentTeamPermitted,
|
||||
source,
|
||||
onlySyncableSources,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import type {UserMentionKey} from 'mattermost-redux/selectors/entities/users';
|
||||
import {filterGroupsMatchingTerm, sortGroups} from 'mattermost-redux/utils/group_utils';
|
||||
import {filterGroupsMatchingTerm, isSyncableSource, sortGroups} from 'mattermost-redux/utils/group_utils';
|
||||
|
||||
import {getCurrentUserLocale} from './i18n';
|
||||
|
||||
@@ -197,7 +197,7 @@ export const getGroupsNotAssociatedToTeam: (state: GlobalState, teamID: string)
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID, group]) => !teamGroupIDSet.has(groupID) && group.source === GroupSource.Ldap).map((entry) => entry[1]);
|
||||
return Object.entries(allGroups).filter(([groupID, group]) => !teamGroupIDSet.has(groupID) && isSyncableSource(group.source)).map((entry) => entry[1]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -217,7 +217,7 @@ export const getGroupsNotAssociatedToChannel: (state: GlobalState, channelID: st
|
||||
(state: GlobalState, channelID: string, teamID: string) => getTeam(state, teamID),
|
||||
(state: GlobalState, channelID: string, teamID: string) => getGroupsAssociatedToTeam(state, teamID),
|
||||
(allGroups, channelGroupIDSet, team, teamGroups) => {
|
||||
let result = Object.values(allGroups).filter((group) => !channelGroupIDSet.has(group.id) && group.source === GroupSource.Ldap);
|
||||
let result = Object.values(allGroups).filter((group) => !channelGroupIDSet.has(group.id) && isSyncableSource(group.source));
|
||||
if (team?.group_constrained) {
|
||||
const gids = teamGroups.map((group) => group.id);
|
||||
result = result.filter((group) => gids?.includes(group.id));
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
haveISystemPermission as haveISystemPermissionInternal,
|
||||
} from 'mattermost-redux/selectors/entities/roles_helpers';
|
||||
import {getTeamMemberships, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {isSyncableSource} from 'mattermost-redux/utils/group_utils';
|
||||
|
||||
// Re-define these types to ensure that these are typed correctly when mattermost-redux is published
|
||||
export const getMySystemPermissions: (state: GlobalState) => Set<string> = getMySystemPermissionsInternal;
|
||||
@@ -86,9 +87,9 @@ export const getGroupListPermissions: (state: GlobalState) => Record<string, Gro
|
||||
const groupPermissionsMap: Record<string, GroupPermissions> = {};
|
||||
groups.forEach((g) => {
|
||||
groupPermissionsMap[g.id] = {
|
||||
can_delete: permissions.has(Permissions.DELETE_CUSTOM_GROUP) && g.source.toLowerCase() !== 'ldap' && g.delete_at === 0,
|
||||
can_manage_members: permissions.has(Permissions.MANAGE_CUSTOM_GROUP_MEMBERS) && g.source.toLowerCase() !== 'ldap' && g.delete_at === 0,
|
||||
can_restore: permissions.has(Permissions.RESTORE_CUSTOM_GROUP) && g.source.toLowerCase() !== 'ldap' && g.delete_at !== 0,
|
||||
can_delete: permissions.has(Permissions.DELETE_CUSTOM_GROUP) && !isSyncableSource(g.source) && g.delete_at === 0,
|
||||
can_manage_members: permissions.has(Permissions.MANAGE_CUSTOM_GROUP_MEMBERS) && !isSyncableSource(g.source) && g.delete_at === 0,
|
||||
can_restore: permissions.has(Permissions.RESTORE_CUSTOM_GROUP) && !isSyncableSource(g.source) && g.delete_at !== 0,
|
||||
};
|
||||
});
|
||||
return groupPermissionsMap;
|
||||
@@ -191,13 +192,13 @@ export const haveIGroupPermission: (state: GlobalState, groupID: string, permiss
|
||||
(state: GlobalState, groupID: string, permission: string) => permission,
|
||||
(systemPermissions, permissionGroups, group, permission) => {
|
||||
if (permission === Permissions.RESTORE_CUSTOM_GROUP) {
|
||||
if ((group.source !== 'ldap' && group.delete_at !== 0) && (systemPermissions.has(permission) || (permissionGroups[group.id] && permissionGroups[group.id].has(permission)))) {
|
||||
if ((!isSyncableSource(group.source) && group.delete_at !== 0) && (systemPermissions.has(permission) || (permissionGroups[group.id] && permissionGroups[group.id].has(permission)))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (group.source === 'ldap' || group.delete_at !== 0) {
|
||||
if (isSyncableSource(group.source) || group.delete_at !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
|
||||
import {getSuggestionsSplitBy, getSuggestionsSplitByMultiple} from './user_utils';
|
||||
|
||||
import {General} from '../constants';
|
||||
|
||||
export function isSyncableSource(source: string): boolean {
|
||||
return source.toLowerCase() === GroupSource.Ldap || source.toLowerCase().startsWith(PluginGroupSourcePrefix.Plugin);
|
||||
}
|
||||
|
||||
export function filterGroupsMatchingTerm(groups: Group[], term: string): Group[] {
|
||||
const lowercasedTerm = term.toLowerCase();
|
||||
let trimmedTerm = lowercasedTerm;
|
||||
|
||||
@@ -1251,4 +1251,10 @@ export default class PluginRegistry {
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
||||
// Register a component to be displayed in the System Console Groups table.
|
||||
// Accepts a React component. Returns a unique identifier.
|
||||
registerSystemConsoleGroupTable = reArg(['component'], ({component}: DPluginComponentProp) => {
|
||||
return dispatchPluginComponentAction('SystemConsoleGroupTable', this.id, component);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ const initialComponents: PluginsState['components'] = {
|
||||
MessageWillBePosted: [],
|
||||
MessageWillBeUpdated: [],
|
||||
SlashCommandWillBePosted: [],
|
||||
SystemConsoleGroupTable: [],
|
||||
};
|
||||
|
||||
function components(state: PluginsState['components'] = initialComponents, action: MMAction) {
|
||||
|
||||
@@ -72,6 +72,7 @@ export type PluginsState = {
|
||||
MessageWillBePosted: MessageWillBePostedHook[];
|
||||
SlashCommandWillBePosted: SlashCommandWillBePostedHook[];
|
||||
MessageWillBeUpdated: MessageWillBeUpdatedHook[];
|
||||
SystemConsoleGroupTable: SystemConsoleGroupTableComponent[];
|
||||
};
|
||||
|
||||
postTypes: {
|
||||
@@ -484,3 +485,7 @@ export type AdminConsolePluginCustomSection = {
|
||||
key: string;
|
||||
component: React.Component;
|
||||
};
|
||||
|
||||
export type SystemConsoleGroupTableComponent = PluginComponent & {
|
||||
component: React.ComponentType<BasePluggableProps>;
|
||||
};
|
||||
|
||||
@@ -3713,14 +3713,14 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsNotAssociatedToTeam = (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, source = 'ldap') => {
|
||||
getGroupsNotAssociatedToTeam = (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, source = 'ldap', onlySyncableSources = false) => {
|
||||
return this.doFetch<Group[]>(
|
||||
`${this.getGroupsRoute()}${buildQueryString({not_associated_to_team: teamID, page, per_page: perPage, q, include_member_count: true, group_source: source})}`,
|
||||
`${this.getGroupsRoute()}${buildQueryString({not_associated_to_team: teamID, page, per_page: perPage, q, include_member_count: true, group_source: source, only_syncable_sources: onlySyncableSources})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsNotAssociatedToChannel = (channelID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, filterParentTeamPermitted = false, source = 'ldap') => {
|
||||
getGroupsNotAssociatedToChannel = (channelID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, filterParentTeamPermitted = false, source = 'ldap', onlySyncableSources = false) => {
|
||||
const query = {
|
||||
not_associated_to_channel: channelID,
|
||||
page,
|
||||
@@ -3729,6 +3729,7 @@ export default class Client4 {
|
||||
include_member_count: true,
|
||||
filter_parent_team_permitted: filterParentTeamPermitted,
|
||||
group_source: source,
|
||||
only_syncable_sources: onlySyncableSources,
|
||||
};
|
||||
return this.doFetch<Group[]>(
|
||||
`${this.getGroupsRoute()}${buildQueryString(query)}`,
|
||||
|
||||
@@ -43,11 +43,22 @@ export type Group = {
|
||||
member_ids?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Main sources for groups
|
||||
*/
|
||||
export enum GroupSource {
|
||||
Ldap = 'ldap',
|
||||
Custom = 'custom',
|
||||
}
|
||||
|
||||
/**
|
||||
* Special prefixes that can be added to group sources when they come from plugins
|
||||
* Used for identifying plugin-created groups by checking if source starts with this prefix
|
||||
*/
|
||||
export enum PluginGroupSourcePrefix {
|
||||
Plugin = 'plugin_'
|
||||
}
|
||||
|
||||
export type GroupTeam = {
|
||||
team_id: string;
|
||||
team_display_name: string;
|
||||
|
||||
Ссылка в новой задаче
Block a user