* Initial models, API, app, and persistence of groups and group syncing.

* Consistent letter casing in ldif.

* Moves group-specific migrations into func.

* Adds API endpoint to retrieve LDAP groups (and associated MM groups) one tree level at a time.

* Adds mattermost group id to SCIMGroup (if available).

* Splits user and group creation so that memberOf works. Returns users from ldap interface.

* Updates method name.

* Returns users IDs instead of User.

* Removes non-essential group data.

* MM-11807: Add GroupFilter to LDAP config. (#9513)

* MM-11807: Add GroupFilter to LDAP config.

* Add diagnostic.

* Adds new config option for using 'memberOf' overlay.

* Adds API endpoint to link a group.

* Removes debug statements.

* Adds unlink group API endpoint.

* Fix to LDAP API. Adds API method to client4 and app.

* Adds some missing app methods. Renames API unexported func.

* Fixes link/unlink API path to accept valid DNs.

* Allow any character for DN portion of path.

* Switches from DN to objectGUID or entryUUID as the remote identifier linking LDAP groups to MM groups.

* Formatting.

* Formatting.

* Setting group name field to an ID for phase 1.

* Adds an LDAP config field to Setting up configuration for local LDAP.

* Changes to LDAP and GroupStore interfaces.

* Draft of nesting groups in API response.

* Removes unnecessary tree models.

* Updates group membershipt create store method to also restore.

* Adds new config to test config.

* Accept AD format length.

* Switches to SetUniqueTogether method.

* Updates revert.

* Tweaks to syncing queries .

* Updates query for pending team and channel memberships.

* Removes old GroupSyncableScanner usage. Some formatting and renaming.

* Fixes bug setting syncable type in selecting paged.

* Adds tests for syncables populator.

* Only add users to teams and channels that are not deleted.

* Renames method.

* Updates test LDAP setup.

* Removes memberof config stuff.

* Renames.

* Updates test data.

* Fix for gofmt.

* Adds missing license.

* Adds missing teardowns.

* Test fix.

* Adds a cycle to the groups test data.

* Changes API to return flat list.

* Removes some unused interface and app methods.

* Returns empty braces if results are empty.

* Adds more LDAP test data.

* Fix for test data error.

* Adds error.

* Moves test groups.

* Adds OU for load test data.

* Moves load test ou creation to load data.

* Adds a new bool flag to SCIMGroups.

* Removes SCIMGroup completely.

* Removes FULL JOIN because it is not supported in MySQL.

* Adds tests for sync queries; renames constant.

* Bad merge fix.

* Vet fix.

* Returning OK on delete ldap group link

* Removes foreign key constraints.

* Adding total to the ldap getAllGroups api endpoint

* Adds get group members page.

* Removes pagination from groups syncables list API.

* Adding syncable check now that foreign key constraint is removes.

* Joins teams and channels to group syncables.

* Adds group member count.

* Adding GetAllChannels and SearchAllChannels for system admins only

* Fix.

* Test fix from pagination removal.

* Orders groupmembers by createat.

* Fixing search of all channels

* Test fix after removing pagination.

* JSON syntax error fix.

* Changing tests (for now) pending investigation.

* Adding GetAllChannels and SearchAllChannels tests for the store

* Adding GetAllChannels and SearchAllChannels API tests

* Omit empty JSON values of group syncables.

* Fixing GetAllChannels and SearchAllChannels tests

* Fixing GetAllChannels and SearchAllChannels store tests

* Fixing GetAllChannels api tests

* Adds 'LDAP groups' feature flag. (#9861)

* Migrate new client functions to idiomatic error handling

* Test fixes.

* Simplification of groups api (#9860)

* Simplification of groups api

* Fixing RequireSyncableType

* Test fix.

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Update api4/group.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Fix copy/paste error.

* Fix copy/paste error.

* Adds missing return, changes to correct HTTP status code.

* Adds missing return, changes status codes.

* Check for license.

* Renames variable for new signature.

* Adds client method to get a group.

* Adds client method and tests for PatchGroup.

* Adds more API tests.

* Adds groups API tests.

* Adds client method and tests for getting group syncables.

* Adds tests for patching group teams and channels.

* Update to translations.

* Removes test.

* Fix incorrect conditional.

* Removes unnecessary nil check.

* Removes unnecessary return.

* Updates comment, removes unused variable.

* Uses consistent JSON unmarshal pattern.

* Uses consistent JSON unmarshal pattern.

* Moves const block.

* Switches 'already linked' from error to success response.

* Removes commented-out code.

* Switched to status ok.

* Add parens for readability.

* Fix copy/paste error.

* Unexport some structs.

* Removes repeated validity check.

* Return without attempting commit if there's a rollback.

* Fix incorrect HTTP status code.

* Update store/sqlstore/group_supplier.go

Co-Authored-By: mkraft <martinkraft@gmail.com>

* Adds utility methods for going from groupsyncable to groupteam and groupchannel.

* Fixing george suggestions (#9911)

* Test fix.

* Adds QA data to VC with visualization.

* Fixes typo in graph image.

* Update display name when re-linking in case it has changed in LDAP.

* Adds ability to configure group display name and unique identifier. (#9923)

* Adds ability to configure group display name and unique identifier.

* Adds some configs to confi-ldap make command.

* Fix for move of session.

* Exposes method for use by SAML package.

* Switches GroupSyncableType from int to string.

* Update Jenkins build files.

* Removes unused variable assignment.

* Removes old unnecessary early return.

* Removes unnecessary variable.

* Moves param parsing before license and permissions checks.

* Removes old code.

* Compares agains underlying error rather than error id.

* Switches tests to assertions.

* Adds more assertions.

* Adds missing return.

* Adds space after comma for added legibility.

* Moves a view model to the api package.

* Unexports method.

* Uses id validator function.

* Fix docker-compose flag.

* Typo fix.

* Moves index creation to supplier.

* Removes bad merge.

* Renames parameter.

* Re-adds space.

* Removes unnecessary transaction.

* Escapes the Groups table name with backticks because it is a reserved keyword.

* Fix roles cache bug

* Removing unnecesiary deserializing function

* Switches table name rather than custom SQL everywhere for Postgres without backticks.

* Removes redundant check for sql.ErrNoRows.

* Removes redundant check for sql.ErrNoRows.

* Removes data integrity check and redundant nil conditional.

* Removes redundant check for sql.ErrNoRows.

* Removes unnecessary query.

* Removes ID length validation from persistence tier.

* Makes some supplier methods idempotent.

* Removes some empty switch defaults.

* Renames Group Type field to Source.

* Fix for mistaken field name change.

* Uses IsValidId function.

* Removes comment.

* Changes json key name.

* Removes test because no longer validating user.

* Moves model state validation to app layer.

* Don't create Groups.CanLeave column until phase 2.

* Removes state validation until properties are used in phase 2.

* Removes duplicated check.

* Removes state validation until properties are used in phase 2.

* Removes some tests until phase 2.

* Comment-out a bunch of test related to CanLeave.

* Extra unmarshal validation check. Removes more code for CanLeave.

* Removes tests for CanLeave.

* Explict error msg.

* Rewrite queries.

* Changes index name. Adds index.

* Removes assertion.

* Adds experimental feature flag.
Этот коммит содержится в:
Martin Kraft
2019-01-10 15:17:31 -05:00
коммит произвёл GitHub
родитель f8b87cbe2d
Коммит 6d30b21dd2
62 изменённых файлов: 7781 добавлений и 57 удалений

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

@@ -156,15 +156,14 @@ ifeq ($(BUILD_ENTERPRISE_READY),true)
-e LDAP_ORGANISATION="Mattermost Test" \
-e LDAP_DOMAIN="mm.test.com" \
-e LDAP_ADMIN_PASSWORD="mostest" \
-d osixia/openldap:1.1.6 > /dev/null;\
-d osixia/openldap:1.2.2 > /dev/null;\
sleep 10; \
docker exec -ti mattermost-openldap bash -c 'echo -e "dn: ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: organizationalunit" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
docker exec -ti mattermost-openldap bash -c 'echo -e "dn: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: iNetOrgPerson\nsn: User\ncn: Test1\nmail: success+testone@simulator.amazonses.com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
docker exec -ti mattermost-openldap bash -c 'ldappasswd -s Password1 -D "cn=admin,dc=mm,dc=test,dc=com" -x "uid=test.one,ou=testusers,dc=mm,dc=test,dc=com" -w mostest';\
docker exec -ti mattermost-openldap bash -c 'echo -e "dn: uid=test.two,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: iNetOrgPerson\nsn: User\ncn: Test2\nmail: success+testtwo@simulator.amazonses.com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
docker exec -ti mattermost-openldap bash -c 'ldappasswd -s Password1 -D "cn=admin,dc=mm,dc=test,dc=com" -x "uid=test.two,ou=testusers,dc=mm,dc=test,dc=com" -w mostest';\
docker exec -ti mattermost-openldap bash -c 'echo -e "dn: cn=tgroup,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: groupOfUniqueNames\nuniqueMember: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';\
elif [ $(shell docker ps --no-trunc --quiet --filter name=^/mattermost-openldap$$ | wc -l) -eq 0 ]; then \
docker cp tests/add-users.ldif mattermost-openldap:/add-users.ldif;\
docker cp tests/add-groups.ldif mattermost-openldap:/add-groups.ldif;\
docker cp tests/qa-data.ldif mattermost-openldap:/qa-data.ldif;\
docker exec -ti mattermost-openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest -f /add-users.ldif';\
docker exec -ti mattermost-openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest -f /add-groups.ldif';\
elif [ $(shell docker ps | grep -ci mattermost-openldap) -eq 0 ]; then \
echo restarting mattermost-openldap; \
docker start mattermost-openldap > /dev/null; \
sleep 10; \
@@ -510,7 +509,7 @@ config-ldap: ## Configures LDAP.
@echo Setting up configuration for local LDAP
@sed -i'' -e 's|"LdapServer": ".*"|"LdapServer": "dockerhost"|g' config/config.json
@sed -i'' -e 's|"BaseDN": ".*"|"BaseDN": "ou=testusers,dc=mm,dc=test,dc=com"|g' config/config.json
@sed -i'' -e 's|"BaseDN": ".*"|"BaseDN": "dc=mm,dc=test,dc=com"|g' config/config.json
@sed -i'' -e 's|"BindUsername": ".*"|"BindUsername": "cn=admin,dc=mm,dc=test,dc=com"|g' config/config.json
@sed -i'' -e 's|"BindPassword": ".*"|"BindPassword": "mostest"|g' config/config.json
@sed -i'' -e 's|"FirstNameAttribute": ".*"|"FirstNameAttribute": "cn"|g' config/config.json
@@ -519,6 +518,9 @@ config-ldap: ## Configures LDAP.
@sed -i'' -e 's|"EmailAttribute": ".*"|"EmailAttribute": "mail"|g' config/config.json
@sed -i'' -e 's|"UsernameAttribute": ".*"|"UsernameAttribute": "uid"|g' config/config.json
@sed -i'' -e 's|"IdAttribute": ".*"|"IdAttribute": "uid"|g' config/config.json
@sed -i'' -e 's|"LoginIdAttribute": ".*"|"LoginIdAttribute": "uid"|g' config/config.json
@sed -i'' -e 's|"GroupDisplayNameAttribute": ".*"|"GroupDisplayNameAttribute": "cn"|g' config/config.json
@sed -i'' -e 's|"GroupIdAttribute": ".*"|"GroupIdAttribute": "entryUUID"|g' config/config.json
config-reset: ## Resets the config/config.json file to the default.
@echo Resetting configuration to default

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

@@ -108,6 +108,7 @@ type Routes struct {
ReactionByNameForPostForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}/reactions/{emoji_name:[A-Za-z0-9_-+]+}'
TermsOfService *mux.Router // 'api/v4/terms_of_service
Groups *mux.Router // 'api/v4/groups'
}
type API struct {
@@ -205,6 +206,7 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp
api.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter()
api.BaseRoutes.TermsOfService = api.BaseRoutes.ApiRoot.PathPrefix("/terms_of_service").Subrouter()
api.BaseRoutes.Groups = api.BaseRoutes.ApiRoot.PathPrefix("/groups").Subrouter()
api.InitUser()
api.InitTeam()
@@ -234,6 +236,7 @@ func Init(configservice configservice.ConfigService, globalOptionsFunc app.AppOp
api.InitScheme()
api.InitImage()
api.InitTermsOfService()
api.InitGroup()
api.InitAction()
root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))

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

@@ -11,8 +11,10 @@ import (
)
func (api *API) InitChannel() {
api.BaseRoutes.Channels.Handle("", api.ApiSessionRequired(getAllChannels)).Methods("GET")
api.BaseRoutes.Channels.Handle("", api.ApiSessionRequired(createChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/direct", api.ApiSessionRequired(createDirectChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/search", api.ApiSessionRequired(searchAllChannels)).Methods("POST")
api.BaseRoutes.Channels.Handle("/group", api.ApiSessionRequired(createGroupChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/members/{user_id:[A-Za-z0-9]+}/view", api.ApiSessionRequired(viewChannel)).Methods("POST")
api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/scheme", api.ApiSessionRequired(updateChannelScheme)).Methods("PUT")
@@ -490,6 +492,21 @@ func getPinnedPosts(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(clientPostList.ToJson()))
}
func getAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
channels, err := c.App.GetAllChannels(c.Params.Page, c.Params.PerPage, false)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channels.ToJson()))
}
func getPublicChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId()
if c.Err != nil {
@@ -693,6 +710,31 @@ func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(channels.ToJson()))
}
func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.ChannelSearchFromJson(r.Body)
if props == nil {
c.SetInvalidParam("channel_search")
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
includeDeleted := r.URL.Query().Get("include_deleted") == "true"
channels, err := c.App.SearchAllChannels(props.Term, includeDeleted)
if err != nil {
c.Err = err
return
}
// Don't fill in channels props, since unused by client and potentially expensive.
w.Write([]byte(channels.ToJson()))
}
func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireChannelId()
if c.Err != nil {

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

@@ -772,6 +772,36 @@ func TestGetChannelsForTeamForUser(t *testing.T) {
CheckNoError(t, resp)
}
func TestGetAllChannels(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
channels, resp := th.SystemAdminClient.GetAllChannels(0, 20, "")
CheckNoError(t, resp)
// At least, all the not-deleted channels created during the InitBasic
require.True(t, len(*channels) >= 3)
for _, c := range *channels {
require.NotEqual(t, c.TeamId, "")
}
channels, resp = th.SystemAdminClient.GetAllChannels(0, 10, "")
CheckNoError(t, resp)
require.True(t, len(*channels) >= 3)
channels, resp = th.SystemAdminClient.GetAllChannels(1, 1, "")
CheckNoError(t, resp)
require.Len(t, *channels, 1)
channels, resp = th.SystemAdminClient.GetAllChannels(10000, 10000, "")
CheckNoError(t, resp)
require.Len(t, *channels, 0)
_, resp = Client.GetAllChannels(0, 20, "")
CheckForbiddenStatus(t, resp)
}
func TestSearchChannels(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -827,6 +857,37 @@ func TestSearchChannels(t *testing.T) {
CheckNoError(t, resp)
}
func TestSearchAllChannels(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
search := &model.ChannelSearch{Term: th.BasicChannel.Name}
channels, resp := th.SystemAdminClient.SearchAllChannels(search)
CheckNoError(t, resp)
assert.Len(t, *channels, 1)
assert.Equal(t, (*channels)[0].Id, th.BasicChannel.Id)
search.Term = th.BasicPrivateChannel.Name
channels, resp = th.SystemAdminClient.SearchAllChannels(search)
CheckNoError(t, resp)
assert.Len(t, *channels, 1)
assert.Equal(t, (*channels)[0].Id, th.BasicPrivateChannel.Id)
search.Term = ""
channels, resp = th.SystemAdminClient.SearchAllChannels(search)
CheckNoError(t, resp)
// At least, all the not-deleted channels created during the InitBasic
assert.True(t, len(*channels) >= 3)
search.Term = th.BasicChannel.Name
_, resp = Client.SearchAllChannels(search)
CheckForbiddenStatus(t, resp)
}
func TestDeleteChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -2345,7 +2406,6 @@ func TestUpdateChannelScheme(t *testing.T) {
// Test an invalid scheme scope.
_, resp = th.SystemAdminClient.UpdateChannelScheme(channel.Id, teamScheme.Id)
fmt.Printf("resp: %+v\n", resp)
CheckBadRequestStatus(t, resp)
// Test that an unauthenticated user gets rejected.

433
api4/group.go Обычный файл
Просмотреть файл

@@ -0,0 +1,433 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/mattermost/mattermost-server/model"
)
const (
groupMemberActionCreate = iota
groupMemberActionDelete
)
func (api *API) InitGroup() {
// GET /api/v4/groups/:group_id
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}",
api.ApiSessionRequired(getGroup)).Methods("GET")
// PUT /api/v4/groups/:group_id/patch
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/patch",
api.ApiSessionRequired(patchGroup)).Methods("PUT")
// POST /api/v4/groups/:group_id/teams/:team_id/link
// POST /api/v4/groups/:group_id/channels/:channel_id/link
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link",
api.ApiSessionRequired(linkGroupSyncable)).Methods("POST")
// DELETE /api/v4/groups/:group_id/teams/:team_id/link
// DELETE /api/v4/groups/:group_id/channels/:channel_id/link
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link",
api.ApiSessionRequired(unlinkGroupSyncable)).Methods("DELETE")
// GET /api/v4/groups/:group_id/teams/:team_id
// GET /api/v4/groups/:group_id/channels/:channel_id
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}",
api.ApiSessionRequired(getGroupSyncable)).Methods("GET")
// GET /api/v4/groups/:group_id/teams
// GET /api/v4/groups/:group_id/channels
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}",
api.ApiSessionRequired(getGroupSyncables)).Methods("GET")
// PUT /api/v4/groups/:group_id/teams/:team_id/patch
// PUT /api/v4/groups/:group_id/channels/:channel_id/patch
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/patch",
api.ApiSessionRequired(patchGroupSyncable)).Methods("PUT")
// GET /api/v4/groups/:group_id/members?page=0&per_page=100
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
api.ApiSessionRequired(getGroupMembers)).Methods("GET")
}
func getGroup(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.getGroup", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
group, err := c.App.GetGroup(c.Params.GroupId)
if err != nil {
c.Err = err
return
}
b, marshalErr := json.Marshal(group)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.getGroup", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
groupPatch := model.GroupPatchFromJson(r.Body)
if groupPatch == nil {
c.SetInvalidParam("group")
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.patchGroup", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
group, err := c.App.GetGroup(c.Params.GroupId)
if err != nil {
c.Err = err
return
}
group.Patch(groupPatch)
group, err = c.App.UpdateGroup(group)
if err != nil {
c.Err = err
return
}
b, marshalErr := json.Marshal(group)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.patchGroup", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func linkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
c.RequireSyncableId()
if c.Err != nil {
return
}
syncableID := c.Params.SyncableId
c.RequireSyncableType()
if c.Err != nil {
return
}
syncableType := c.Params.SyncableType
body, err := ioutil.ReadAll(r.Body)
if err != nil {
c.Err = model.NewAppError("Api4.createGroupSyncable", "api.io_error", nil, err.Error(), http.StatusBadRequest)
return
}
var patch *model.GroupSyncablePatch
err = json.Unmarshal(body, &patch)
if err != nil || patch == nil {
c.SetInvalidParam(fmt.Sprintf("Group%s", syncableType.String()))
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.createGroupSyncable", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
groupSyncable, appErr := c.App.GetGroupSyncable(c.Params.GroupId, syncableID, syncableType)
if appErr != nil && appErr.DetailedError != sql.ErrNoRows.Error() {
c.Err = appErr
return
}
if groupSyncable == nil {
groupSyncable = &model.GroupSyncable{
GroupId: c.Params.GroupId,
SyncableId: syncableID,
Type: syncableType,
}
groupSyncable.Patch(patch)
groupSyncable, appErr = c.App.CreateGroupSyncable(groupSyncable)
if appErr != nil {
c.Err = appErr
return
}
} else {
groupSyncable.DeleteAt = 0
groupSyncable.Patch(patch)
groupSyncable, appErr = c.App.UpdateGroupSyncable(groupSyncable)
if appErr != nil {
c.Err = appErr
return
}
}
w.WriteHeader(http.StatusCreated)
b, marshalErr := json.Marshal(groupSyncable)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.createGroupSyncable", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func getGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
c.RequireSyncableId()
if c.Err != nil {
return
}
syncableID := c.Params.SyncableId
c.RequireSyncableType()
if c.Err != nil {
return
}
syncableType := c.Params.SyncableType
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.getGroupSyncable", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
groupSyncable, err := c.App.GetGroupSyncable(c.Params.GroupId, syncableID, syncableType)
if err != nil {
c.Err = err
return
}
b, marshalErr := json.Marshal(groupSyncable)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.getGroupSyncable", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func getGroupSyncables(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
c.RequireSyncableType()
if c.Err != nil {
return
}
syncableType := c.Params.SyncableType
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.getGroupSyncables", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
groupSyncables, err := c.App.GetGroupSyncables(c.Params.GroupId, syncableType)
if err != nil {
c.Err = err
return
}
b, marshalErr := json.Marshal(groupSyncables)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.getGroupSyncables", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
c.RequireSyncableId()
if c.Err != nil {
return
}
syncableID := c.Params.SyncableId
c.RequireSyncableType()
if c.Err != nil {
return
}
syncableType := c.Params.SyncableType
body, err := ioutil.ReadAll(r.Body)
if err != nil {
c.Err = model.NewAppError("Api4.patchGroupSyncable", "api.io_error", nil, err.Error(), http.StatusBadRequest)
return
}
var patch *model.GroupSyncablePatch
err = json.Unmarshal(body, &patch)
if err != nil || patch == nil {
c.SetInvalidParam(fmt.Sprintf("Group[%s]Patch", syncableType.String()))
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.patchGroupSyncable", "api.ldap_groups.license_error", nil, "",
http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
groupSyncable, appErr := c.App.GetGroupSyncable(c.Params.GroupId, syncableID, syncableType)
if appErr != nil {
c.Err = appErr
return
}
groupSyncable.Patch(patch)
groupSyncable, appErr = c.App.UpdateGroupSyncable(groupSyncable)
if appErr != nil {
c.Err = appErr
return
}
b, marshalErr := json.Marshal(groupSyncable)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.patchGroupSyncable", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func unlinkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
c.RequireSyncableId()
if c.Err != nil {
return
}
syncableID := c.Params.SyncableId
c.RequireSyncableType()
if c.Err != nil {
return
}
syncableType := c.Params.SyncableType
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.unlinkGroupSyncable", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
_, err := c.App.DeleteGroupSyncable(c.Params.GroupId, syncableID, syncableType)
if err != nil {
c.Err = err
return
}
ReturnStatusOK(w)
}
func getGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireGroupId()
if c.Err != nil {
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.getGroupMembers", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
members, count, err := c.App.GetGroupMemberUsersPage(c.Params.GroupId, c.Params.Page, c.Params.PerPage)
if err != nil {
c.Err = err
return
}
b, marshalErr := json.Marshal(struct {
Members []*model.User `json:"members"`
Count int `json:"total_member_count"`
}{
Members: members,
Count: count,
})
if marshalErr != nil {
c.Err = model.NewAppError("Api4.getGroupMembers", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}

622
api4/group_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,622 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model"
)
func TestGetGroup(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
_, response := th.Client.GetGroup(g.Id, "")
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.GetGroup(g.Id, "")
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
group, response := th.SystemAdminClient.GetGroup(g.Id, "")
CheckNoError(t, response)
assert.Equal(t, g.DisplayName, group.DisplayName)
assert.Equal(t, g.Name, group.Name)
assert.Equal(t, g.Source, group.Source)
assert.Equal(t, g.Description, group.Description)
assert.Equal(t, g.RemoteId, group.RemoteId)
assert.Equal(t, g.CreateAt, group.CreateAt)
assert.Equal(t, g.UpdateAt, group.UpdateAt)
assert.Equal(t, g.DeleteAt, group.DeleteAt)
_, response = th.SystemAdminClient.GetGroup(model.NewId(), "")
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.GetGroup("12345", "")
CheckBadRequestStatus(t, response)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.GetGroup(group.Id, "")
CheckUnauthorizedStatus(t, response)
}
func TestPatchGroup(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
updateFmt := "%s_updated"
newName := fmt.Sprintf(updateFmt, g.Name)
newDisplayName := fmt.Sprintf(updateFmt, g.DisplayName)
newDescription := fmt.Sprintf(updateFmt, g.Description)
gp := &model.GroupPatch{
Name: &newName,
DisplayName: &newDisplayName,
Description: &newDescription,
}
_, response := th.Client.PatchGroup(g.Id, gp)
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.PatchGroup(g.Id, gp)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
group2, response := th.SystemAdminClient.PatchGroup(g.Id, gp)
CheckOKStatus(t, response)
group, response := th.SystemAdminClient.GetGroup(g.Id, "")
CheckNoError(t, response)
assert.Equal(t, *gp.DisplayName, group.DisplayName)
assert.Equal(t, *gp.DisplayName, group2.DisplayName)
assert.Equal(t, *gp.Name, group.Name)
assert.Equal(t, *gp.Name, group2.Name)
assert.Equal(t, *gp.Description, group.Description)
assert.Equal(t, *gp.Description, group2.Description)
assert.Equal(t, group2.UpdateAt, group.UpdateAt)
assert.Equal(t, g.Source, group.Source)
assert.Equal(t, g.Source, group2.Source)
assert.Equal(t, g.RemoteId, group.RemoteId)
assert.Equal(t, g.RemoteId, group2.RemoteId)
assert.Equal(t, g.CreateAt, group.CreateAt)
assert.Equal(t, g.CreateAt, group2.CreateAt)
assert.Equal(t, g.DeleteAt, group.DeleteAt)
assert.Equal(t, g.DeleteAt, group2.DeleteAt)
_, response = th.SystemAdminClient.PatchGroup(model.NewId(), gp)
CheckNotFoundStatus(t, response)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.PatchGroup(group.Id, gp)
CheckUnauthorizedStatus(t, response)
}
func TestLinkGroupTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
_, response := th.Client.LinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
groupTeam, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
assert.NotNil(t, groupTeam)
}
func TestLinkGroupChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
_, response := th.Client.LinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
_, response = th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
}
func TestUnlinkGroupTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
th.App.SetLicense(model.NewTestLicense("ldap"))
_, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
th.App.SetLicense(nil)
response = th.Client.UnlinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
CheckNotImplementedStatus(t, response)
response = th.SystemAdminClient.UnlinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
response = th.SystemAdminClient.UnlinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
CheckOKStatus(t, response)
}
func TestUnlinkGroupChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
th.App.SetLicense(model.NewTestLicense("ldap"))
_, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
th.App.SetLicense(nil)
response = th.Client.UnlinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
CheckNotImplementedStatus(t, response)
response = th.SystemAdminClient.UnlinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
response = th.SystemAdminClient.UnlinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
CheckOKStatus(t, response)
}
func TestGetGroupTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
_, response := th.Client.GetGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, "")
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, "")
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
_, response = th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
groupSyncable, response := th.SystemAdminClient.GetGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, "")
CheckOKStatus(t, response)
assert.NotNil(t, groupSyncable)
assert.Equal(t, g.Id, groupSyncable.GroupId)
assert.Equal(t, th.BasicTeam.Id, groupSyncable.SyncableId)
assert.Equal(t, *patch.AutoAdd, groupSyncable.AutoAdd)
// assert.Equal(t, *patch.CanLeave, groupSyncable.CanLeave) // TODO: Re-add this test in phase 2 of LDAP groups sync.
_, response = th.SystemAdminClient.GetGroupSyncable(model.NewId(), th.BasicTeam.Id, model.GroupSyncableTypeTeam, "")
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, model.NewId(), model.GroupSyncableTypeTeam, "")
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable("asdfasdfe3", th.BasicTeam.Id, model.GroupSyncableTypeTeam, "")
CheckBadRequestStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, "asdfasdfe3", model.GroupSyncableTypeTeam, "")
CheckBadRequestStatus(t, response)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, "")
CheckUnauthorizedStatus(t, response)
}
func TestGetGroupChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
_, response := th.Client.GetGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, "")
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, "")
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
_, response = th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
groupSyncable, response := th.SystemAdminClient.GetGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, "")
CheckOKStatus(t, response)
assert.NotNil(t, groupSyncable)
assert.Equal(t, g.Id, groupSyncable.GroupId)
assert.Equal(t, th.BasicChannel.Id, groupSyncable.SyncableId)
assert.Equal(t, *patch.AutoAdd, groupSyncable.AutoAdd)
// assert.Equal(t, *patch.CanLeave, groupSyncable.CanLeave) // TODO: Re-add this test in phase 2 of LDAP groups sync.
_, response = th.SystemAdminClient.GetGroupSyncable(model.NewId(), th.BasicChannel.Id, model.GroupSyncableTypeChannel, "")
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, model.NewId(), model.GroupSyncableTypeChannel, "")
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable("asdfasdfe3", th.BasicChannel.Id, model.GroupSyncableTypeChannel, "")
CheckBadRequestStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, "asdfasdfe3", model.GroupSyncableTypeChannel, "")
CheckBadRequestStatus(t, response)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.GetGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, "")
CheckUnauthorizedStatus(t, response)
}
func TestGetGroupTeams(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
th.App.SetLicense(model.NewTestLicense("ldap"))
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
for i := 0; i < 10; i++ {
team := th.CreateTeam()
_, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, team.Id, model.GroupSyncableTypeTeam, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
}
th.App.SetLicense(nil)
_, response := th.Client.GetGroupSyncables(g.Id, model.GroupSyncableTypeTeam, "")
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncables(g.Id, model.GroupSyncableTypeTeam, "")
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
_, response = th.Client.GetGroupSyncables(g.Id, model.GroupSyncableTypeTeam, "")
assert.Equal(t, http.StatusForbidden, response.StatusCode)
groupSyncables, response := th.SystemAdminClient.GetGroupSyncables(g.Id, model.GroupSyncableTypeTeam, "")
CheckOKStatus(t, response)
assert.Len(t, groupSyncables, 10)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.GetGroupSyncables(g.Id, model.GroupSyncableTypeTeam, "")
CheckUnauthorizedStatus(t, response)
}
func TestGetGroupChannels(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
th.App.SetLicense(model.NewTestLicense("ldap"))
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
for i := 0; i < 10; i++ {
channel := th.CreatePublicChannel()
_, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, channel.Id, model.GroupSyncableTypeChannel, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
}
th.App.SetLicense(nil)
_, response := th.Client.GetGroupSyncables(g.Id, model.GroupSyncableTypeChannel, "")
CheckNotImplementedStatus(t, response)
_, response = th.SystemAdminClient.GetGroupSyncables(g.Id, model.GroupSyncableTypeChannel, "")
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
_, response = th.Client.GetGroupSyncables(g.Id, model.GroupSyncableTypeChannel, "")
assert.Equal(t, http.StatusForbidden, response.StatusCode)
groupSyncables, response := th.SystemAdminClient.GetGroupSyncables(g.Id, model.GroupSyncableTypeChannel, "")
CheckOKStatus(t, response)
assert.Len(t, groupSyncables, 10)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.GetGroupSyncables(g.Id, model.GroupSyncableTypeChannel, "")
CheckUnauthorizedStatus(t, response)
}
func TestPatchGroupTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
th.App.SetLicense(model.NewTestLicense("ldap"))
groupSyncable, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
assert.NotNil(t, groupSyncable)
// assert.True(t, groupSyncable.CanLeave) // TODO: Re-add this test in phase 2 of LDAP groups sync.
assert.True(t, groupSyncable.AutoAdd)
_, response = th.Client.PatchGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
assert.Equal(t, http.StatusForbidden, response.StatusCode)
th.App.SetLicense(nil)
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
patch.AutoAdd = model.NewBool(false)
groupSyncable, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckOKStatus(t, response)
assert.False(t, groupSyncable.AutoAdd)
assert.Equal(t, g.Id, groupSyncable.GroupId)
assert.Equal(t, th.BasicTeam.Id, groupSyncable.SyncableId)
assert.Equal(t, model.GroupSyncableTypeTeam, groupSyncable.Type)
// TODO: Re-add this test in phase 2 of LDAP groups sync.
// patch.CanLeave = model.NewBool(false)
// _, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
// CheckBadRequestStatus(t, response)
patch.AutoAdd = model.NewBool(true)
groupSyncable, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckOKStatus(t, response)
assert.False(t, groupSyncable.CanLeave)
_, response = th.SystemAdminClient.PatchGroupSyncable(model.NewId(), th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, model.NewId(), model.GroupSyncableTypeTeam, patch)
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.PatchGroupSyncable("abc", th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckBadRequestStatus(t, response)
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, "abc", model.GroupSyncableTypeTeam, patch)
CheckBadRequestStatus(t, response)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam, patch)
CheckUnauthorizedStatus(t, response)
}
func TestPatchGroupChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
g, err := th.App.CreateGroup(&model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
})
assert.Nil(t, err)
patch := &model.GroupSyncablePatch{
CanLeave: model.NewBool(true),
AutoAdd: model.NewBool(true),
}
th.App.SetLicense(model.NewTestLicense("ldap"))
groupSyncable, response := th.SystemAdminClient.LinkGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
assert.Equal(t, http.StatusCreated, response.StatusCode)
assert.NotNil(t, groupSyncable)
// assert.True(t, groupSyncable.CanLeave) // TODO: Re-add this test in phase 2 of LDAP groups sync.
assert.True(t, groupSyncable.AutoAdd)
_, response = th.Client.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
assert.Equal(t, http.StatusForbidden, response.StatusCode)
th.App.SetLicense(nil)
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap"))
patch.AutoAdd = model.NewBool(false)
groupSyncable, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckOKStatus(t, response)
assert.False(t, groupSyncable.AutoAdd)
assert.Equal(t, g.Id, groupSyncable.GroupId)
assert.Equal(t, th.BasicChannel.Id, groupSyncable.SyncableId)
assert.Equal(t, model.GroupSyncableTypeChannel, groupSyncable.Type)
// TODO: Re-add this test in phase 2 of LDAP groups sync.
// patch.CanLeave = model.NewBool(false)
// _, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
// CheckBadRequestStatus(t, response)
patch.AutoAdd = model.NewBool(true)
groupSyncable, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckOKStatus(t, response)
assert.False(t, groupSyncable.CanLeave)
_, response = th.SystemAdminClient.PatchGroupSyncable(model.NewId(), th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, model.NewId(), model.GroupSyncableTypeChannel, patch)
CheckNotFoundStatus(t, response)
_, response = th.SystemAdminClient.PatchGroupSyncable("abc", th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckBadRequestStatus(t, response)
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, "abc", model.GroupSyncableTypeChannel, patch)
CheckBadRequestStatus(t, response)
th.SystemAdminClient.Logout()
_, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch)
CheckUnauthorizedStatus(t, response)
}

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

@@ -4,14 +4,32 @@
package api4
import (
"database/sql"
"encoding/json"
"net/http"
"github.com/mattermost/mattermost-server/model"
)
type mixedUnlinkedGroup struct {
Id *string `json:"mattermost_group_id"`
DisplayName string `json:"name"`
RemoteId string `json:"primary_key"`
HasSyncables *bool `json:"has_syncables"`
}
func (api *API) InitLdap() {
api.BaseRoutes.LDAP.Handle("/sync", api.ApiSessionRequired(syncLdap)).Methods("POST")
api.BaseRoutes.LDAP.Handle("/test", api.ApiSessionRequired(testLdap)).Methods("POST")
// GET /api/v4/ldap/groups?page=0&per_page=1000
api.BaseRoutes.LDAP.Handle("/groups", api.ApiSessionRequired(getLdapGroups)).Methods("GET")
// POST /api/v4/ldap/groups/:remote_id/link
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.ApiSessionRequired(linkLdapGroup)).Methods("POST")
// DELETE /api/v4/ldap/groups/:remote_id/link
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.ApiSessionRequired(unlinkLdapGroup)).Methods("DELETE")
}
func syncLdap(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -38,3 +56,159 @@ func testLdap(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
func getLdapGroups(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.getLdapGroups", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
groups, total, err := c.App.GetAllLdapGroupsPage(c.Params.Page, c.Params.PerPage)
if err != nil {
c.Err = err
return
}
mugs := []*mixedUnlinkedGroup{}
for _, group := range groups {
mug := &mixedUnlinkedGroup{
DisplayName: group.DisplayName,
RemoteId: group.RemoteId,
}
if len(group.Id) == 26 {
mug.Id = &group.Id
mug.HasSyncables = &group.HasSyncables
}
mugs = append(mugs, mug)
}
b, marshalErr := json.Marshal(struct {
Count int `json:"count"`
Groups []*mixedUnlinkedGroup `json:"groups"`
}{Count: total, Groups: mugs})
if marshalErr != nil {
c.Err = model.NewAppError("Api4.getLdapGroups", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.Write(b)
}
func linkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireRemoteId()
if c.Err != nil {
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.linkLdapGroup", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
ldapGroup, err := c.App.GetLdapGroup(c.Params.RemoteId)
if err != nil {
c.Err = err
return
}
if ldapGroup == nil {
c.Err = model.NewAppError("Api4.linkLdapGroup", "api.ldap_group.not_found", nil, "", http.StatusNotFound)
return
}
group, err := c.App.GetGroupByRemoteID(ldapGroup.RemoteId, model.GroupSourceLdap)
if err != nil && err.DetailedError != sql.ErrNoRows.Error() {
c.Err = err
return
}
var status int
var newOrUpdatedGroup *model.Group
// Group has been previously linked
if group != nil {
if group.DeleteAt == 0 {
newOrUpdatedGroup = group
} else {
group.DeleteAt = 0
group.DisplayName = ldapGroup.DisplayName
group.RemoteId = ldapGroup.RemoteId
newOrUpdatedGroup, err = c.App.UpdateGroup(group)
if err != nil {
c.Err = err
return
}
}
status = http.StatusOK
} else {
// Group has never been linked
//
// TODO: In a future phase of LDAP groups sync `Name` will be used for at-mentions and will be editable on
// the front-end so it will not have an initial value of `model.NewId()` but rather a slugified version of
// the LDAP group name with an appended duplicate-breaker.
newGroup := &model.Group{
Name: model.NewId(),
DisplayName: ldapGroup.DisplayName,
RemoteId: ldapGroup.RemoteId,
Source: model.GroupSourceLdap,
}
newOrUpdatedGroup, err = c.App.CreateGroup(newGroup)
if err != nil {
c.Err = err
return
}
status = http.StatusCreated
}
b, marshalErr := json.Marshal(newOrUpdatedGroup)
if marshalErr != nil {
c.Err = model.NewAppError("Api4.linkLdapGroup", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(status)
w.Write(b)
}
func unlinkLdapGroup(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireRemoteId()
if c.Err != nil {
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
if c.App.License() == nil || !*c.App.License().Features.LDAPGroups {
c.Err = model.NewAppError("Api4.unlinkLdapGroup", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
return
}
group, err := c.App.GetGroupByRemoteID(c.Params.RemoteId, model.GroupSourceLdap)
if err != nil {
c.Err = err
return
}
if group.DeleteAt == 0 {
_, err = c.App.DeleteGroup(group.Id)
if err != nil {
c.Err = err
return
}
}
ReturnStatusOK(w)
}

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

@@ -7,7 +7,7 @@ import (
"testing"
)
func TestLdapTest(t *testing.T) {
func TestTestLdap(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -18,7 +18,7 @@ func TestLdapTest(t *testing.T) {
CheckNotImplementedStatus(t, resp)
}
func TestLdapSync(t *testing.T) {
func TestSyncLdap(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -28,3 +28,40 @@ func TestLdapSync(t *testing.T) {
_, resp = th.Client.SyncLdap()
CheckForbiddenStatus(t, resp)
}
func TestGetLdapGroups(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
_, resp := th.Client.GetLdapGroups()
CheckForbiddenStatus(t, resp)
_, resp = th.SystemAdminClient.GetLdapGroups()
CheckNotImplementedStatus(t, resp)
}
func TestLinkLdapGroup(t *testing.T) {
const entryUUID string = "foo"
th := Setup().InitBasic()
defer th.TearDown()
_, resp := th.Client.LinkLdapGroup(entryUUID)
CheckForbiddenStatus(t, resp)
_, resp = th.SystemAdminClient.LinkLdapGroup(entryUUID)
CheckNotImplementedStatus(t, resp)
}
func TestUnlinkLdapGroup(t *testing.T) {
const entryUUID string = "foo"
th := Setup().InitBasic()
defer th.TearDown()
_, resp := th.Client.UnlinkLdapGroup(entryUUID)
CheckForbiddenStatus(t, resp)
_, resp = th.SystemAdminClient.UnlinkLdapGroup(entryUUID)
CheckNotImplementedStatus(t, resp)
}

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

@@ -67,10 +67,6 @@ func getScheme(c *Context, w http.ResponseWriter, r *http.Request) {
}
func getSchemes(c *Context, w http.ResponseWriter, r *http.Request) {
if c.Err != nil {
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return

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

@@ -2203,7 +2203,6 @@ func TestUpdateTeamScheme(t *testing.T) {
// Test an invalid scheme scope.
_, resp = th.SystemAdminClient.UpdateTeamScheme(team.Id, channelScheme.Id)
fmt.Printf("resp: %+v\n", resp)
CheckBadRequestStatus(t, resp)
// Test that an unauthenticated user gets rejected.

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

@@ -1116,6 +1116,14 @@ func (a *App) GetChannelsForUser(teamId string, userId string, includeDeleted bo
return result.Data.(*model.ChannelList), nil
}
func (a *App) GetAllChannels(page, perPage int, includeDeleted bool) (*model.ChannelListWithTeamData, *model.AppError) {
result := <-a.Srv.Store.Channel().GetAllChannels(page*perPage, perPage, includeDeleted)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.ChannelListWithTeamData), nil
}
func (a *App) GetDeletedChannels(teamId string, offset int, limit int) (*model.ChannelList, *model.AppError) {
result := <-a.Srv.Store.Channel().GetDeleted(teamId, offset, limit)
if result.Err != nil {
@@ -1577,6 +1585,14 @@ func (a *App) AutocompleteChannelsForSearch(teamId string, userId string, term s
return result.Data.(*model.ChannelList), nil
}
func (a *App) SearchAllChannels(term string, includeDeleted bool) (*model.ChannelListWithTeamData, *model.AppError) {
result := <-a.Srv.Store.Channel().SearchAllChannels(term, *a.Config().TeamSettings.ExperimentalViewArchivedChannels && includeDeleted)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.ChannelListWithTeamData), nil
}
func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *model.AppError) {
includeDeleted := *a.Config().TeamSettings.ExperimentalViewArchivedChannels

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

@@ -264,6 +264,7 @@ func (a *App) trackConfig() {
"experimental_enable_hardened_mode": *cfg.ServiceSettings.ExperimentalEnableHardenedMode,
"enable_email_invitations": *cfg.ServiceSettings.EnableEmailInvitations,
"experimental_channel_organization": *cfg.ServiceSettings.ExperimentalChannelOrganization,
"experimental_ldap_group_sync": *cfg.ServiceSettings.ExperimentalLdapGroupSync,
})
a.SendDiagnostic(TRACK_CONFIG_TEAM, map[string]interface{}{
@@ -418,25 +419,28 @@ func (a *App) trackConfig() {
})
a.SendDiagnostic(TRACK_CONFIG_LDAP, map[string]interface{}{
"enable": *cfg.LdapSettings.Enable,
"enable_sync": *cfg.LdapSettings.EnableSync,
"connection_security": *cfg.LdapSettings.ConnectionSecurity,
"skip_certificate_verification": *cfg.LdapSettings.SkipCertificateVerification,
"sync_interval_minutes": *cfg.LdapSettings.SyncIntervalMinutes,
"query_timeout": *cfg.LdapSettings.QueryTimeout,
"max_page_size": *cfg.LdapSettings.MaxPageSize,
"isdefault_first_name_attribute": isDefault(*cfg.LdapSettings.FirstNameAttribute, model.LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE),
"isdefault_last_name_attribute": isDefault(*cfg.LdapSettings.LastNameAttribute, model.LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE),
"isdefault_email_attribute": isDefault(*cfg.LdapSettings.EmailAttribute, model.LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE),
"isdefault_username_attribute": isDefault(*cfg.LdapSettings.UsernameAttribute, model.LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE),
"isdefault_nickname_attribute": isDefault(*cfg.LdapSettings.NicknameAttribute, model.LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE),
"isdefault_id_attribute": isDefault(*cfg.LdapSettings.IdAttribute, model.LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE),
"isdefault_position_attribute": isDefault(*cfg.LdapSettings.PositionAttribute, model.LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE),
"isdefault_login_id_attribute": isDefault(*cfg.LdapSettings.LoginIdAttribute, ""),
"isdefault_login_field_name": isDefault(*cfg.LdapSettings.LoginFieldName, model.LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME),
"isdefault_login_button_color": isDefault(*cfg.LdapSettings.LoginButtonColor, ""),
"isdefault_login_button_border_color": isDefault(*cfg.LdapSettings.LoginButtonBorderColor, ""),
"isdefault_login_button_text_color": isDefault(*cfg.LdapSettings.LoginButtonTextColor, ""),
"enable": *cfg.LdapSettings.Enable,
"enable_sync": *cfg.LdapSettings.EnableSync,
"connection_security": *cfg.LdapSettings.ConnectionSecurity,
"skip_certificate_verification": *cfg.LdapSettings.SkipCertificateVerification,
"sync_interval_minutes": *cfg.LdapSettings.SyncIntervalMinutes,
"query_timeout": *cfg.LdapSettings.QueryTimeout,
"max_page_size": *cfg.LdapSettings.MaxPageSize,
"isdefault_first_name_attribute": isDefault(*cfg.LdapSettings.FirstNameAttribute, model.LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE),
"isdefault_last_name_attribute": isDefault(*cfg.LdapSettings.LastNameAttribute, model.LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE),
"isdefault_email_attribute": isDefault(*cfg.LdapSettings.EmailAttribute, model.LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE),
"isdefault_username_attribute": isDefault(*cfg.LdapSettings.UsernameAttribute, model.LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE),
"isdefault_nickname_attribute": isDefault(*cfg.LdapSettings.NicknameAttribute, model.LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE),
"isdefault_id_attribute": isDefault(*cfg.LdapSettings.IdAttribute, model.LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE),
"isdefault_position_attribute": isDefault(*cfg.LdapSettings.PositionAttribute, model.LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE),
"isdefault_login_id_attribute": isDefault(*cfg.LdapSettings.LoginIdAttribute, ""),
"isdefault_login_field_name": isDefault(*cfg.LdapSettings.LoginFieldName, model.LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME),
"isdefault_login_button_color": isDefault(*cfg.LdapSettings.LoginButtonColor, ""),
"isdefault_login_button_border_color": isDefault(*cfg.LdapSettings.LoginButtonBorderColor, ""),
"isdefault_login_button_text_color": isDefault(*cfg.LdapSettings.LoginButtonTextColor, ""),
"isempty_group_filter": isDefault(*cfg.LdapSettings.GroupFilter, ""),
"isdefault_group_display_name_attribute": isDefault(*cfg.LdapSettings.GroupDisplayNameAttribute, model.LDAP_SETTINGS_DEFAULT_GROUP_DISPLAY_NAME_ATTRIBUTE),
"isdefault_group_id_attribute": isDefault(*cfg.LdapSettings.GroupIdAttribute, model.LDAP_SETTINGS_DEFAULT_GROUP_ID_ATTRIBUTE),
})
a.SendDiagnostic(TRACK_CONFIG_COMPLIANCE, map[string]interface{}{

150
app/group.go Обычный файл
Просмотреть файл

@@ -0,0 +1,150 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"github.com/mattermost/mattermost-server/model"
)
func (a *App) GetGroup(id string) (*model.Group, *model.AppError) {
result := <-a.Srv.Store.Group().Get(id)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Group), nil
}
func (a *App) GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError) {
result := <-a.Srv.Store.Group().GetByRemoteID(remoteID, groupSource)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Group), nil
}
func (a *App) GetGroupsBySource(groupSource model.GroupSource) ([]*model.Group, *model.AppError) {
result := <-a.Srv.Store.Group().GetAllBySource(groupSource)
if result.Err != nil {
return nil, result.Err
}
return result.Data.([]*model.Group), nil
}
func (a *App) CreateGroup(group *model.Group) (*model.Group, *model.AppError) {
result := <-a.Srv.Store.Group().Create(group)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Group), nil
}
func (a *App) UpdateGroup(group *model.Group) (*model.Group, *model.AppError) {
result := <-a.Srv.Store.Group().Update(group)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Group), nil
}
func (a *App) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
result := <-a.Srv.Store.Group().Delete(groupID)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.Group), nil
}
func (a *App) GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppError) {
result := <-a.Srv.Store.Group().GetMemberUsers(groupID)
if result.Err != nil {
return nil, result.Err
}
return result.Data.([]*model.User), nil
}
func (a *App) GetGroupMemberUsersPage(groupID string, page int, perPage int) ([]*model.User, int, *model.AppError) {
result := <-a.Srv.Store.Group().GetMemberUsersPage(groupID, page, perPage)
if result.Err != nil {
return nil, 0, result.Err
}
members := result.Data.([]*model.User)
result = <-a.Srv.Store.Group().GetMemberCount(groupID)
if result.Err != nil {
return nil, 0, result.Err
}
count := int(result.Data.(int64))
return members, count, nil
}
func (a *App) CreateOrRestoreGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
result := <-a.Srv.Store.Group().CreateOrRestoreMember(groupID, userID)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.GroupMember), nil
}
func (a *App) DeleteGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError) {
result := <-a.Srv.Store.Group().DeleteMember(groupID, userID)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.GroupMember), nil
}
func (a *App) CreateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
result := <-a.Srv.Store.Group().CreateGroupSyncable(groupSyncable)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.GroupSyncable), nil
}
func (a *App) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
result := <-a.Srv.Store.Group().GetGroupSyncable(groupID, syncableID, syncableType)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.GroupSyncable), nil
}
func (a *App) GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError) {
result := <-a.Srv.Store.Group().GetAllGroupSyncablesByGroupId(groupID, syncableType)
if result.Err != nil {
return nil, result.Err
}
return result.Data.([]*model.GroupSyncable), nil
}
func (a *App) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) (*model.GroupSyncable, *model.AppError) {
result := <-a.Srv.Store.Group().UpdateGroupSyncable(groupSyncable)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.GroupSyncable), nil
}
func (a *App) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
result := <-a.Srv.Store.Group().DeleteGroupSyncable(groupID, syncableID, syncableType)
if result.Err != nil {
return nil, result.Err
}
return result.Data.(*model.GroupSyncable), nil
}
func (a *App) PendingAutoAddTeamMembers(minGroupMembersCreateAt int64) ([]*model.UserTeamIDPair, *model.AppError) {
result := <-a.Srv.Store.Group().PendingAutoAddTeamMembers(minGroupMembersCreateAt)
if result.Err != nil {
return nil, result.Err
}
return result.Data.([]*model.UserTeamIDPair), nil
}
func (a *App) PendingAutoAddChannelMembers(minGroupMembersCreateAt int64) ([]*model.UserChannelIDPair, *model.AppError) {
result := <-a.Srv.Store.Group().PendingAutoAddChannelMembers(minGroupMembersCreateAt)
if result.Err != nil {
return nil, result.Err
}
return result.Data.([]*model.UserChannelIDPair), nil
}

223
app/group_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,223 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
func TestGetGroup(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
group, err := th.App.GetGroup(group.Id)
require.Nil(t, err)
require.NotNil(t, group)
group, err = th.App.GetGroup(model.NewId())
require.NotNil(t, err)
require.Nil(t, group)
}
func TestGetGroupByRemoteID(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
g, err := th.App.GetGroupByRemoteID(group.RemoteId, model.GroupSourceLdap)
require.Nil(t, err)
require.NotNil(t, g)
g, err = th.App.GetGroupByRemoteID(model.NewId(), model.GroupSourceLdap)
require.NotNil(t, err)
require.Nil(t, g)
}
func TestGetGroupsByType(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
th.CreateGroup()
th.CreateGroup()
th.CreateGroup()
groups, err := th.App.GetGroupsBySource(model.GroupSourceLdap)
require.Nil(t, err)
require.NotEmpty(t, groups)
groups, err = th.App.GetGroupsBySource(model.GroupSource("blah"))
require.Nil(t, err)
require.Empty(t, groups)
}
func TestCreateGroup(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
group := &model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
RemoteId: model.NewId(),
}
g, err := th.App.CreateGroup(group)
require.Nil(t, err)
require.NotNil(t, g)
g, err = th.App.CreateGroup(group)
require.NotNil(t, err)
require.Nil(t, g)
}
func TestUpdateGroup(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
group.DisplayName = model.NewId()
g, err := th.App.UpdateGroup(group)
require.Nil(t, err)
require.NotNil(t, g)
}
func TestDeleteGroup(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
g, err := th.App.DeleteGroup(group.Id)
require.Nil(t, err)
require.NotNil(t, g)
g, err = th.App.DeleteGroup(group.Id)
require.NotNil(t, err)
require.Nil(t, g)
}
func TestCreateOrRestoreGroupMember(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
g, err := th.App.CreateOrRestoreGroupMember(group.Id, th.BasicUser.Id)
require.Nil(t, err)
require.NotNil(t, g)
g, err = th.App.CreateOrRestoreGroupMember(group.Id, th.BasicUser.Id)
require.NotNil(t, err)
require.Nil(t, g)
}
func TestDeleteGroupMember(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
groupMember, err := th.App.CreateOrRestoreGroupMember(group.Id, th.BasicUser.Id)
require.Nil(t, err)
require.NotNil(t, groupMember)
groupMember, err = th.App.DeleteGroupMember(groupMember.GroupId, groupMember.UserId)
require.Nil(t, err)
require.NotNil(t, groupMember)
groupMember, err = th.App.DeleteGroupMember(groupMember.GroupId, groupMember.UserId)
require.NotNil(t, err)
require.Nil(t, groupMember)
}
func TestCreateGroupSyncable(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
groupSyncable := &model.GroupSyncable{
GroupId: group.Id,
CanLeave: true,
AutoAdd: false,
SyncableId: th.BasicTeam.Id,
Type: model.GroupSyncableTypeTeam,
}
gs, err := th.App.CreateGroupSyncable(groupSyncable)
require.Nil(t, err)
require.NotNil(t, gs)
gs, err = th.App.CreateGroupSyncable(groupSyncable)
require.NotNil(t, err)
require.Nil(t, gs)
}
func TestGetGroupSyncable(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
groupSyncable := &model.GroupSyncable{
GroupId: group.Id,
CanLeave: true,
AutoAdd: false,
SyncableId: th.BasicTeam.Id,
Type: model.GroupSyncableTypeTeam,
}
gs, err := th.App.CreateGroupSyncable(groupSyncable)
require.Nil(t, err)
require.NotNil(t, gs)
gs, err = th.App.GetGroupSyncable(group.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam)
require.Nil(t, err)
require.NotNil(t, gs)
}
func TestGetGroupSyncables(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
// Create a group team
groupSyncable := &model.GroupSyncable{
GroupId: group.Id,
CanLeave: true,
AutoAdd: false,
SyncableId: th.BasicTeam.Id,
Type: model.GroupSyncableTypeTeam,
}
gs, err := th.App.CreateGroupSyncable(groupSyncable)
require.Nil(t, err)
require.NotNil(t, gs)
groupTeams, err := th.App.GetGroupSyncables(group.Id, model.GroupSyncableTypeTeam)
require.Nil(t, err)
require.NotEmpty(t, groupTeams)
}
func TestDeleteGroupSyncable(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
group := th.CreateGroup()
groupChannel := &model.GroupSyncable{
GroupId: group.Id,
CanLeave: true,
AutoAdd: false,
SyncableId: th.BasicChannel.Id,
Type: model.GroupSyncableTypeChannel,
}
gs, err := th.App.CreateGroupSyncable(groupChannel)
require.Nil(t, err)
require.NotNil(t, gs)
gs, err = th.App.DeleteGroupSyncable(group.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.Nil(t, err)
require.NotNil(t, gs)
gs, err = th.App.DeleteGroupSyncable(group.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel)
require.NotNil(t, err)
require.Nil(t, gs)
}

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

@@ -343,6 +343,28 @@ func (me *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) {
return scheme, roles
}
func (me *TestHelper) CreateGroup() *model.Group {
id := model.NewId()
group := &model.Group{
DisplayName: "dn_" + id,
Name: "name" + id,
Source: model.GroupSourceLdap,
Description: "description_" + id,
RemoteId: model.NewId(),
}
utils.DisableDebugLogForTest()
var err *model.AppError
if group, err = me.App.CreateGroup(group); err != nil {
mlog.Error(err.Error())
time.Sleep(time.Second)
panic(err)
}
utils.EnableDebugLogForTest()
return group
}
func (me *TestHelper) CreateEmoji() *model.Emoji {
utils.DisableDebugLogForTest()

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

@@ -40,6 +40,46 @@ func (a *App) TestLdap() *model.AppError {
return nil
}
// GetLdapGroup retrieves a single LDAP group by the given LDAP group id.
func (a *App) GetLdapGroup(ldapGroupID string) (*model.Group, *model.AppError) {
var group *model.Group
if a.Ldap != nil {
var err *model.AppError
group, err = a.Ldap.GetGroup(ldapGroupID)
if err != nil {
return nil, err
}
} else {
ae := model.NewAppError("GetLdapGroup", "ent.ldap.app_error", nil, "", http.StatusNotImplemented)
mlog.Error(fmt.Sprintf("%v", ae.Error()))
return nil, ae
}
return group, nil
}
// GetAllLdapGroupsPage retrieves all LDAP groups under the configured base DN using the default or configured group
// filter.
func (a *App) GetAllLdapGroupsPage(page int, perPage int) ([]*model.Group, int, *model.AppError) {
var groups []*model.Group
var total int
if a.Ldap != nil {
var err *model.AppError
groups, total, err = a.Ldap.GetAllGroupsPage(page, perPage)
if err != nil {
return nil, 0, err
}
} else {
ae := model.NewAppError("GetAllLdapGroupsPage", "ent.ldap.app_error", nil, "", http.StatusNotImplemented)
mlog.Error(fmt.Sprintf("%v", ae.Error()))
return nil, 0, ae
}
return groups, total, nil
}
func (a *App) SwitchEmailToLdap(email, password, code, ldapLoginId, ldapPassword string) (string, *model.AppError) {
if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
return "", model.NewAppError("emailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusForbidden)

63
app/syncables.go Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"github.com/mattermost/mattermost-server/mlog"
)
// PopulateSyncablesSince adds users to teams and channels based on their group memberships and how those groups are
// configured to sync with teams and channels for group members on or after the given timestamp.
func (a *App) PopulateSyncablesSince(groupMembersCreatedAfter int64) error {
userTeamIDs, appErr := a.PendingAutoAddTeamMembers(groupMembersCreatedAfter)
if appErr != nil {
return appErr
}
for _, userTeam := range userTeamIDs {
_, err := a.AddTeamMember(userTeam.TeamID, userTeam.UserID)
if err != nil {
return err
}
a.Log.Info("added teammember",
mlog.String("user_id", userTeam.UserID),
mlog.String("team_id", userTeam.TeamID),
)
}
userChannelIDs, appErr := a.PendingAutoAddChannelMembers(groupMembersCreatedAfter)
if appErr != nil {
return appErr
}
for _, userChannel := range userChannelIDs {
channel, err := a.GetChannel(userChannel.ChannelID)
if err != nil {
return err
}
// First add user to team
_, err = a.AddTeamMember(channel.TeamId, userChannel.UserID)
if err != nil {
return err
}
a.Log.Info("added teammember",
mlog.String("user_id", userChannel.UserID),
mlog.String("team_id", channel.TeamId),
)
_, err = a.AddChannelMember(userChannel.UserID, channel, "", "", false)
if err != nil {
return err
}
a.Log.Info("added channelmember",
mlog.String("user_id", userChannel.UserID),
mlog.String("channel_id", userChannel.ChannelID),
)
}
return nil
}

346
app/syncables_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,346 @@
package app
import (
"testing"
"github.com/mattermost/mattermost-server/model"
)
func TestPopulateSyncablesSince(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
singersTeam, err := th.App.CreateTeam(&model.Team{
DisplayName: "Singers",
Name: model.NewId(),
Email: "singers@test.com",
Type: model.TEAM_OPEN,
})
if err != nil {
t.Errorf("test team not created: %s", err.Error())
}
nerdsTeam, err := th.App.CreateTeam(&model.Team{
DisplayName: "Nerds",
Name: model.NewId(),
Email: "nerds@test.com",
Type: model.TEAM_INVITE,
})
if err != nil {
t.Errorf("test team not created: %s", err.Error())
}
practiceChannel, err := th.App.CreateChannel(&model.Channel{
TeamId: singersTeam.Id,
DisplayName: "Practices",
Name: model.NewId(),
Type: model.CHANNEL_OPEN,
}, false)
if err != nil {
t.Errorf("test channel not created: %s", err.Error())
}
experimentsChannel, err := th.App.CreateChannel(&model.Channel{
TeamId: singersTeam.Id,
DisplayName: "Experiments",
Name: model.NewId(),
Type: model.CHANNEL_PRIVATE,
}, false)
if err != nil {
t.Errorf("test channel not created: %s", err.Error())
}
gleeGroup, err := th.App.CreateGroup(&model.Group{
Name: model.NewId(),
DisplayName: "Glee Club",
RemoteId: model.NewId(),
Source: model.GroupSourceLdap,
})
if err != nil {
t.Errorf("test group not created: %s", err.Error())
}
scienceGroup, err := th.App.CreateGroup(&model.Group{
Name: model.NewId(),
DisplayName: "Science Club",
RemoteId: model.NewId(),
Source: model.GroupSourceLdap,
})
if err != nil {
t.Errorf("test group not created: %s", err.Error())
}
_, err = th.App.CreateGroupSyncable(&model.GroupSyncable{
CanLeave: true,
AutoAdd: true,
GroupId: gleeGroup.Id,
SyncableId: practiceChannel.Id,
Type: model.GroupSyncableTypeChannel,
})
if err != nil {
t.Errorf("test groupchannel not created: %s", err.Error())
}
scienceTeamGroupSyncable, err := th.App.CreateGroupSyncable(&model.GroupSyncable{
CanLeave: true,
AutoAdd: false,
GroupId: scienceGroup.Id,
SyncableId: nerdsTeam.Id,
Type: model.GroupSyncableTypeTeam,
})
if err != nil {
t.Errorf("test groupteam not created: %s", err.Error())
}
scienceChannelGroupSyncable, err := th.App.CreateGroupSyncable(&model.GroupSyncable{
CanLeave: true,
AutoAdd: false,
GroupId: scienceGroup.Id,
SyncableId: experimentsChannel.Id,
Type: model.GroupSyncableTypeChannel,
})
if err != nil {
t.Errorf("test groupchannel not created: %s", err.Error())
}
singer1 := th.BasicUser
scientist1 := th.BasicUser2
_, err = th.App.CreateOrRestoreGroupMember(gleeGroup.Id, singer1.Id)
if err != nil {
t.Errorf("test groupmember not created: %s", err.Error())
}
scientistGroupMember, err := th.App.CreateOrRestoreGroupMember(scienceGroup.Id, scientist1.Id)
if err != nil {
t.Errorf("test groupmember not created: %s", err.Error())
}
pErr := th.App.PopulateSyncablesSince(0)
if pErr != nil {
t.Errorf("faild to populate syncables: %s", pErr.Error())
}
// Singer should be in team and channel
_, err = th.App.GetTeamMember(singersTeam.Id, singer1.Id)
if err != nil {
t.Errorf("error retrieving team member: %s", err.Error())
}
_, err = th.App.GetChannelMember(practiceChannel.Id, singer1.Id)
if err != nil {
t.Errorf("error retrieving channel member: %s", err.Error())
}
tMembers, err := th.App.GetTeamMembers(singersTeam.Id, 0, 999)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
expected := 1
actual := len(tMembers)
if actual != expected {
t.Errorf("expected %d team members but got %d", expected, actual)
}
cMembersCount, err := th.App.GetChannelMemberCount(practiceChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team member but got %d", expected, cMembersCount)
}
// Scientist should not be in team or channel
_, err = th.App.GetTeamMember(nerdsTeam.Id, scientist1.Id)
if err.Id != "store.sql_team.get_member.missing.app_error" {
t.Errorf("wrong error: %s", err.Id)
}
_, err = th.App.GetChannelMember(experimentsChannel.Id, scientist1.Id)
if err.Id != "store.sql_channel.get_member.missing.app_error" {
t.Errorf("wrong error: %s", err.Id)
}
tMembers, err = th.App.GetTeamMembers(nerdsTeam.Id, 0, 999)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
expected = 0
actual = len(tMembers)
if actual != expected {
t.Errorf("expected %d team members but got %d", expected, actual)
}
cMembersCount, err = th.App.GetChannelMemberCount(experimentsChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team members but got %d", expected, cMembersCount)
}
// update AutoAdd to true
scienceTeamGroupSyncable.AutoAdd = true
scienceTeamGroupSyncable, err = th.App.UpdateGroupSyncable(scienceTeamGroupSyncable)
if err != nil {
t.Errorf("error updating group syncable: %s", err.Error())
}
// Sync everything after syncable was created (proving that team updates trigger re-sync)
pErr = th.App.PopulateSyncablesSince(scientistGroupMember.CreateAt + 1)
if pErr != nil {
t.Errorf("faild to populate syncables: %s", pErr.Error())
}
// Scientist should be in team but not the channel
_, err = th.App.GetTeamMember(nerdsTeam.Id, scientist1.Id)
if err != nil {
t.Errorf("error retrieving team member: %s", err.Error())
}
_, err = th.App.GetChannelMember(experimentsChannel.Id, scientist1.Id)
if err.Id != "store.sql_channel.get_member.missing.app_error" {
t.Errorf("wrong error: %s", err.Id)
}
tMembers, err = th.App.GetTeamMembers(nerdsTeam.Id, 0, 999)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
expected = 1
actual = len(tMembers)
if actual != expected {
t.Errorf("expected %d team members but got %d", expected, actual)
}
expected = 0
cMembersCount, err = th.App.GetChannelMemberCount(experimentsChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team members but got %d", expected, cMembersCount)
}
// Update the channel syncable
scienceChannelGroupSyncable.AutoAdd = true
scienceChannelGroupSyncable, err = th.App.UpdateGroupSyncable(scienceChannelGroupSyncable)
if err != nil {
t.Errorf("error updating group syncable: %s", err.Error())
}
// Sync everything after syncable was created (proving that channel updates trigger re-sync)
pErr = th.App.PopulateSyncablesSince(scientistGroupMember.CreateAt + 1)
if pErr != nil {
t.Errorf("faild to populate syncables: %s", pErr.Error())
}
expected = 1
cMembersCount, err = th.App.GetChannelMemberCount(experimentsChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team members but got %d", expected, cMembersCount)
}
// singer leaves team and channel
err = th.App.LeaveChannel(practiceChannel.Id, singer1.Id)
if err != nil {
t.Errorf("error leaving channel: %s", err.Error())
}
err = th.App.LeaveTeam(singersTeam, singer1, "")
if err != nil {
t.Errorf("error leaving team: %s", err.Error())
}
// Even re-syncing from the beginning doesn't re-add to channel or team
pErr = th.App.PopulateSyncablesSince(0)
if pErr != nil {
t.Errorf("faild to populate syncables: %s", pErr.Error())
}
// Singer should not be in team or channel
tMember, err := th.App.GetTeamMember(singersTeam.Id, singer1.Id)
if err != nil {
t.Errorf("error retrieving team member: %s", err.Error())
}
if tMember.DeleteAt == 0 {
t.Error("expected team member to remain deleted")
}
_, err = th.App.GetChannelMember(practiceChannel.Id, singer1.Id)
if err == nil {
t.Error("Expected channel member to remain deleted")
}
// Ensure members are in channel
_, err = th.App.AddChannelMember(scientist1.Id, experimentsChannel, "", "", false)
if err != nil {
t.Errorf("unable to add user to channel: %s", err.Error())
}
// Add other user so that user can leave channel
_, err = th.App.AddTeamMember(singersTeam.Id, singer1.Id)
if err != nil {
t.Errorf("unable to add user to team: %s", err.Error())
}
_, err = th.App.AddChannelMember(singer1.Id, experimentsChannel, "", "", false)
if err != nil {
t.Errorf("unable to add user to channel: %s", err.Error())
}
// the channel syncable is updated
scienceChannelGroupSyncable.CanLeave = false
scienceChannelGroupSyncable, err = th.App.UpdateGroupSyncable(scienceChannelGroupSyncable)
if err != nil {
t.Errorf("error updating group syncable: %s", err.Error())
}
pErr = th.App.PopulateSyncablesSince(0)
if pErr != nil {
t.Errorf("faild to populate syncables: %s", pErr.Error())
}
timeBeforeLeaving := model.GetMillis()
// User leaves channel
err = th.App.LeaveChannel(experimentsChannel.Id, scientist1.Id)
if err != nil {
t.Errorf("unable to add user to channel: %s", err.Error())
}
timeAfterLeaving := model.GetMillis()
// Purging channelmemberhistory doesn't re-add user to channel
result := <-th.App.Srv.Store.ChannelMemberHistory().PermanentDeleteBatch(timeBeforeLeaving, 1000)
if result.Err != nil {
t.Errorf("error permanently deleting channelmemberhistory: %s", result.Err.Error())
}
pErr = th.App.PopulateSyncablesSince(scienceChannelGroupSyncable.UpdateAt)
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
_, err = th.App.GetChannelMember(experimentsChannel.Id, scientist1.Id)
if err == nil {
t.Error("Expected channel member to remain deleted")
}
// Purging channelmemberhistory doesn't re-add user to channel
result = <-th.App.Srv.Jobs.Store.ChannelMemberHistory().PermanentDeleteBatch(timeAfterLeaving, 1000)
if result.Err != nil {
t.Errorf("error permanently deleting channelmemberhistory: %s", result.Err.Error())
}
pErr = th.App.PopulateSyncablesSince(scienceChannelGroupSyncable.UpdateAt)
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
// Channel member is re-added.
_, err = th.App.GetChannelMember(experimentsChannel.Id, scientist1.Id)
if err != nil {
t.Errorf("expected channel member: %s", err.Error())
}
}

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

@@ -113,13 +113,8 @@ pipeline {
sh """
docker-compose --no-ansi run --rm start_dependencies
docker-compose --no-ansi ps
docker-compose --no-ansi exec -T openldap bash -c 'echo -e "dn: ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: organizationalunit" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T openldap bash -c 'echo -e "dn: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: iNetOrgPerson\nsn: User\ncn: Test1\nmail: success+testone@simulator.amazonses.com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T openldap bash -c 'ldappasswd -s Password1 -D "cn=admin,dc=mm,dc=test,dc=com" -x "uid=test.one,ou=testusers,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T openldap bash -c 'echo -e "dn: uid=test.two,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: iNetOrgPerson\nsn: User\ncn: Test2\nmail: success+testtwo@simulator.amazonses.com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T openldap bash -c 'ldappasswd -s Password1 -D "cn=admin,dc=mm,dc=test,dc=com" -x "uid=test.two,ou=testusers,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T openldap bash -c 'echo -e "dn: cn=tgroup,ou=testusers,dc=mm,dc=test,dc=com\nobjectclass: groupOfUniqueNames\nuniqueMember: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com" | ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest -f /add-users.ldif';
docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest -f /add-groups.ldif';
"""
}
}

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

@@ -38,7 +38,7 @@ services:
networks:
- mm-test
openldap:
image: "osixia/openldap:1.1.6"
image: "osixia/openldap:1.2.2"
restart: always
networks:
- mm-test
@@ -47,6 +47,9 @@ services:
LDAP_ORGANISATION: "Mattermost Test"
LDAP_DOMAIN: "mm.test.com"
LDAP_ADMIN_PASSWORD: "mostest"
volumes:
- "../tests/add-users.ldif:/add-users.ldif"
- "../tests/add-groups.ldif:/add-groups.ldif"
elasticsearch:
image: "mattermost/mattermost-elasticsearch-docker:6.5.1"
networks:

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

@@ -76,7 +76,8 @@
"ImageProxyURL": "",
"EnableAPITeamDeletion": false,
"ExperimentalEnableHardenedMode": false,
"EnableEmailInvitations": false
"EnableEmailInvitations": false,
"ExperimentalLdapGroupSync": false
},
"TeamSettings": {
"SiteName": "Mattermost",
@@ -277,6 +278,9 @@
"BindUsername": "",
"BindPassword": "",
"UserFilter": "",
"GroupFilter": "",
"GroupDisplayNameAttribute": "",
"GroupIdAttribute": "",
"FirstNameAttribute": "",
"LastNameAttribute": "",
"EmailAttribute": "",
@@ -410,4 +414,4 @@
"Plugins": {},
"PluginStates": {}
}
}
}

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

@@ -19,4 +19,7 @@ type LdapInterface interface {
RunTest() *model.AppError
GetAllLdapUsers() ([]*model.User, *model.AppError)
MigrateIDAttribute(toAttribute string) error
GetGroup(groupUID string) (*model.Group, *model.AppError)
GetAllGroupsPage(page int, perPage int) ([]*model.Group, int, *model.AppError)
FirstLoginSync(user *model.User) *model.AppError
}

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

@@ -6765,5 +6765,149 @@
{
"id": "web.incoming_webhook.user.app_error",
"translation": "Couldn't find the user"
},
{
"id": "api.io_error",
"translation": "input/output error"
},
{
"id": "api.marshal_error",
"translation": "marshal error"
},
{
"id": "api.ldap_groups.license_error",
"translation": "your license does not support ldap groups"
},
{
"id": "api.ldap_group.not_found",
"translation": "ldap group not found"
},
{
"id": "ent.ldap_groups.reachable_groups_error",
"translation": "error retrieving groups for user"
},
{
"id": "ent.ldap_groups.no_rows",
"translation": "no groups found with matching uid"
},
{
"id": "ent.ldap_groups.members_of_group_error",
"translation": "error retrieving members of group"
},
{
"id": "ent.ldap_groups.group_search_error",
"translation": "error retrieving ldap group"
},
{
"id": "ent.ldap_groups.groups_search_error",
"translation": "error retrieving ldap groups"
},
{
"id": "ent.ldap.syncronize.get_all_groups.app_error",
"translation": "error retrieving groups"
},
{
"id": "ent.ldap.syncronize.populate_syncables",
"translation": "error populating syncables"
},
{
"id": "model.group.create_at.app_error",
"translation": "invalid create at property for group"
},
{
"id": "model.group.description.app_error",
"translation": "invalid description property for group"
},
{
"id": "model.group.display_name.app_error",
"translation": "invalid display name property for group"
},
{
"id": "model.group.id.app_error",
"translation": "invalid id property for group"
},
{
"id": "model.group.name.app_error",
"translation": "invalid name property for group"
},
{
"id": "model.group.remote_id.app_error",
"translation": "invalid remote id property for group"
},
{
"id": "model.group.source.app_error",
"translation": "invalid source property for group"
},
{
"id": "model.group_syncable.type.app_error",
"translation": "invalid type property for group syncable"
},
{
"id": "model.group_syncable.unmarshaljson.duplicate_type_identifier",
"translation": "type identifier already exists: perhaps json contains both 'team_id' and 'channel_id'"
},
{
"id": "model.group.update_at.app_error",
"translation": "invalid update at property for group"
},
{
"id": "model.group_member.group_id.app_error",
"translation": "invalid group id property for group member"
},
{
"id": "model.group_member.user_id.app_error",
"translation": "invalid user id property for group member"
},
{
"id": "model.group_syncable.group_id.app_error",
"translation": "invalid group id property for group syncable"
},
{
"id": "model.group_syncable.syncable_id.app_error",
"translation": "invalid syncable id for group syncable"
},
{
"id": "store.sql_group.group_syncable_already_deleted",
"translation": "group syncable was already deleted"
},
{
"id": "store.insert_error",
"translation": "insert error"
},
{
"id": "model.group.delete_at.app_error",
"translation": "invalid delete at property for group"
},
{
"id": "store.sql_group.no_rows",
"translation": "no matching group found"
},
{
"id": "store.sql_group.no_rows_changed",
"translation": "no rows changed"
},
{
"id": "store.sql_group.nothing_to_update",
"translation": "nothing to update for group syncable"
},
{
"id": "store.select_error",
"translation": "select error"
},
{
"id": "store.sql_group.unique_constraint",
"translation": "a group with that name already exists"
},
{
"id": "store.sql_group.uniqueness_error",
"translation": "group member already exists"
},
{
"id": "store.update_error",
"translation": "update error"
},
{
"id": "ent.ldap.app_error",
"translation": "ldap interface was nil"
}
]
]

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

@@ -53,6 +53,13 @@ type Channel struct {
Props map[string]interface{} `json:"props" db:"-"`
}
type ChannelWithTeamData struct {
Channel
TeamDisplayName string `json:"team_display_name"`
TeamName string `json:"team_name"`
TeamUpdateAt int64 `json:"team_update_at"`
}
type ChannelPatch struct {
DisplayName *string `json:"display_name"`
Name *string `json:"name"`

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

@@ -51,3 +51,45 @@ func ChannelSliceFromJson(data io.Reader) []*Channel {
json.NewDecoder(data).Decode(&o)
return o
}
type ChannelListWithTeamData []*ChannelWithTeamData
func (o *ChannelListWithTeamData) ToJson() string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func (o *ChannelListWithTeamData) Etag() string {
id := "0"
var t int64 = 0
var delta int64 = 0
for _, v := range *o {
if v.LastPostAt > t {
t = v.LastPostAt
id = v.Id
}
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
}
if v.TeamUpdateAt > t {
t = v.TeamUpdateAt
id = v.Id
}
}
return Etag(id, t, delta, len(*o))
}
func ChannelListWithTeamDataFromJson(data io.Reader) *ChannelListWithTeamData {
var o *ChannelListWithTeamData
json.NewDecoder(data).Decode(&o)
return o
}

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

@@ -409,6 +409,22 @@ func (c *Client4) GetTermsOfServiceRoute() string {
return "/terms_of_service"
}
func (c *Client4) GetGroupsRoute() string {
return "/groups"
}
func (c *Client4) GetGroupRoute(groupID string) string {
return fmt.Sprintf("%s/%s", c.GetGroupsRoute(), groupID)
}
func (c *Client4) GetGroupSyncableRoute(groupID, syncableID string, syncableType GroupSyncableType) string {
return fmt.Sprintf("%s/%ss/%s", c.GetGroupRoute(groupID), strings.ToLower(syncableType.String()), syncableID)
}
func (c *Client4) GetGroupSyncablesRoute(groupID string, syncableType GroupSyncableType) string {
return fmt.Sprintf("%s/%ss", c.GetGroupRoute(groupID), strings.ToLower(syncableType.String()))
}
func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag)
}
@@ -1706,6 +1722,17 @@ func (c *Client4) RemoveTeamIcon(teamId string) (bool, *Response) {
// Channel Section
// GetAllChannels get all the channels. Must be a system administrator.
func (c *Client4) GetAllChannels(page int, perPage int, etag string) (*ChannelListWithTeamData, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
r, err := c.DoApiGet(c.GetChannelsRoute()+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return ChannelListWithTeamDataFromJson(r.Body), BuildResponse(r)
}
// CreateChannel creates a channel based on the provided channel struct.
func (c *Client4) CreateChannel(channel *Channel) (*Channel, *Response) {
r, err := c.DoApiPost(c.GetChannelsRoute(), channel.ToJson())
@@ -1870,6 +1897,16 @@ func (c *Client4) SearchChannels(teamId string, search *ChannelSearch) ([]*Chann
return ChannelSliceFromJson(r.Body), BuildResponse(r)
}
// SearchAllChannels search in all the channels. Must be a system administrator.
func (c *Client4) SearchAllChannels(search *ChannelSearch) (*ChannelListWithTeamData, *Response) {
r, err := c.DoApiPost(c.GetChannelsRoute()+"/search", search.ToJson())
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return ChannelListWithTeamDataFromJson(r.Body), BuildResponse(r)
}
// DeleteChannel deletes channel based on the provided channel id string.
func (c *Client4) DeleteChannel(channelId string) (bool, *Response) {
r, err := c.DoApiDelete(c.GetChannelRoute(channelId))
@@ -3066,6 +3103,45 @@ func (c *Client4) TestLdap() (bool, *Response) {
return CheckStatusOK(r), BuildResponse(r)
}
// GetLdapGroups retrieves the immediate child groups of the given parent group.
func (c *Client4) GetLdapGroups() ([]*Group, *Response) {
path := fmt.Sprintf("%s/groups", c.GetLdapRoute())
r, appErr := c.DoApiGet(path, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupsFromJson(r.Body), BuildResponse(r)
}
// LinkLdapGroup creates or undeletes a Mattermost group and associates it to the given LDAP group DN.
func (c *Client4) LinkLdapGroup(dn string) (*Group, *Response) {
path := fmt.Sprintf("%s/groups/%s/link", c.GetLdapRoute(), dn)
r, appErr := c.DoApiPost(path, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupFromJson(r.Body), BuildResponse(r)
}
// UnlinkLdapGroup deletes the Mattermost group associated with the given LDAP group DN.
func (c *Client4) UnlinkLdapGroup(dn string) (*Group, *Response) {
path := fmt.Sprintf("%s/groups/%s/link", c.GetLdapRoute(), dn)
r, appErr := c.DoApiDelete(path)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupFromJson(r.Body), BuildResponse(r)
}
// Audits Section
// GetAudits returns a list of audits for the whole system.
@@ -4030,3 +4106,71 @@ func (c *Client4) CreateTermsOfService(text, userId string) (*TermsOfService, *R
defer closeBody(r)
return TermsOfServiceFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) GetGroup(groupID, etag string) (*Group, *Response) {
r, appErr := c.DoApiGet(c.GetGroupRoute(groupID), etag)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) PatchGroup(groupID string, patch *GroupPatch) (*Group, *Response) {
payload, _ := json.Marshal(patch)
r, appErr := c.DoApiPut(c.GetGroupRoute(groupID)+"/patch", string(payload))
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) LinkGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType, patch *GroupSyncablePatch) (*GroupSyncable, *Response) {
payload, _ := json.Marshal(patch)
url := fmt.Sprintf("%s/link", c.GetGroupSyncableRoute(groupID, syncableID, syncableType))
r, appErr := c.DoApiPost(url, string(payload))
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupSyncableFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) UnlinkGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType) *Response {
url := fmt.Sprintf("%s/link", c.GetGroupSyncableRoute(groupID, syncableID, syncableType))
r, appErr := c.DoApiDelete(url)
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
func (c *Client4) GetGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType, etag string) (*GroupSyncable, *Response) {
r, appErr := c.DoApiGet(c.GetGroupSyncableRoute(groupID, syncableID, syncableType), etag)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupSyncableFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) GetGroupSyncables(groupID string, syncableType GroupSyncableType, etag string) ([]*GroupSyncable, *Response) {
r, appErr := c.DoApiGet(c.GetGroupSyncablesRoute(groupID, syncableType), etag)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupSyncablesFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) PatchGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType, patch *GroupSyncablePatch) (*GroupSyncable, *Response) {
payload, _ := json.Marshal(patch)
r, appErr := c.DoApiPut(c.GetGroupSyncableRoute(groupID, syncableID, syncableType)+"/patch", string(payload))
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return GroupSyncableFromJson(r.Body), BuildResponse(r)
}

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

@@ -23,6 +23,7 @@ const (
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_GROUPS = "inv_groups"
CLUSTER_SEND_BEST_EFFORT = "best_effort"
CLUSTER_SEND_RELIABLE = "reliable"

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

@@ -114,14 +114,16 @@ const (
SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com"
SUPPORT_SETTINGS_DEFAULT_RE_ACCEPTANCE_PERIOD = 365
LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME = ""
LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME = ""
LDAP_SETTINGS_DEFAULT_GROUP_DISPLAY_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_GROUP_ID_ATTRIBUTE = ""
SAML_SETTINGS_DEFAULT_ID_ATTRIBUTE = ""
SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
@@ -276,6 +278,7 @@ type ServiceSettings struct {
EnableAPITeamDeletion *bool
ExperimentalEnableHardenedMode *bool
EnableEmailInvitations *bool
ExperimentalLdapGroupSync *bool
}
func (s *ServiceSettings) SetDefaults() {
@@ -566,6 +569,10 @@ func (s *ServiceSettings) SetDefaults() {
if s.ExperimentalEnableHardenedMode == nil {
s.ExperimentalEnableHardenedMode = NewBool(false)
}
if s.ExperimentalLdapGroupSync == nil {
s.ExperimentalLdapGroupSync = NewBool(false)
}
}
type ClusterSettings struct {
@@ -1322,7 +1329,12 @@ type LdapSettings struct {
BindPassword *string
// Filtering
UserFilter *string
UserFilter *string
GroupFilter *string
// Group Mapping
GroupDisplayNameAttribute *string
GroupIdAttribute *string
// User Mapping
FirstNameAttribute *string
@@ -1388,6 +1400,18 @@ func (s *LdapSettings) SetDefaults() {
s.UserFilter = NewString("")
}
if s.GroupFilter == nil {
s.GroupFilter = NewString("")
}
if s.GroupDisplayNameAttribute == nil {
s.GroupDisplayNameAttribute = NewString(LDAP_SETTINGS_DEFAULT_GROUP_DISPLAY_NAME_ATTRIBUTE)
}
if s.GroupIdAttribute == nil {
s.GroupIdAttribute = NewString(LDAP_SETTINGS_DEFAULT_GROUP_ID_ATTRIBUTE)
}
if s.FirstNameAttribute == nil {
s.FirstNameAttribute = NewString(LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE)
}

135
model/group.go Обычный файл
Просмотреть файл

@@ -0,0 +1,135 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
const (
GroupSourceLdap GroupSource = "ldap"
GroupNameMaxLength = 64
GroupSourceMaxLength = 64
GroupDisplayNameMaxLength = 128
GroupDescriptionMaxLength = 1024
GroupRemoteIDMaxLength = 48
)
type GroupSource string
var allGroupSources = []GroupSource{
GroupSourceLdap,
}
var groupSourcesRequiringRemoteID = []GroupSource{
GroupSourceLdap,
}
type Group struct {
Id string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Source GroupSource `json:"source"`
RemoteId string `json:"remote_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
HasSyncables bool `db:"-" json:"has_syncables"`
}
type GroupPatch struct {
Name *string `json:"name"`
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
}
func (group *Group) Patch(patch *GroupPatch) {
if patch.Name != nil {
group.Name = *patch.Name
}
if patch.DisplayName != nil {
group.DisplayName = *patch.DisplayName
}
if patch.Description != nil {
group.Description = *patch.Description
}
}
func (group *Group) IsValidForCreate() *AppError {
if l := len(group.Name); l == 0 || l > GroupNameMaxLength {
return NewAppError("Group.IsValidForCreate", "model.group.name.app_error", map[string]interface{}{"GroupNameMaxLength": GroupNameMaxLength}, "", http.StatusBadRequest)
}
if l := len(group.DisplayName); l == 0 || l > GroupDisplayNameMaxLength {
return NewAppError("Group.IsValidForCreate", "model.group.display_name.app_error", map[string]interface{}{"GroupDisplayNameMaxLength": GroupDisplayNameMaxLength}, "", http.StatusBadRequest)
}
if len(group.Description) > GroupDescriptionMaxLength {
return NewAppError("Group.IsValidForCreate", "model.group.description.app_error", map[string]interface{}{"GroupDescriptionMaxLength": GroupDescriptionMaxLength}, "", http.StatusBadRequest)
}
isValidSource := false
for _, groupSource := range allGroupSources {
if group.Source == groupSource {
isValidSource = true
break
}
}
if !isValidSource {
return NewAppError("Group.IsValidForCreate", "model.group.source.app_error", nil, "", http.StatusBadRequest)
}
if len(group.RemoteId) > GroupRemoteIDMaxLength || (len(group.RemoteId) == 0 && group.requiresRemoteId()) {
return NewAppError("Group.IsValidForCreate", "model.group.remote_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (group *Group) requiresRemoteId() bool {
for _, groupSource := range groupSourcesRequiringRemoteID {
if groupSource == group.Source {
return true
}
}
return false
}
func (group *Group) IsValidForUpdate() *AppError {
if len(group.Id) != 26 {
return NewAppError("Group.IsValidForUpdate", "model.group.id.app_error", nil, "", http.StatusBadRequest)
}
if group.CreateAt == 0 {
return NewAppError("Group.IsValidForUpdate", "model.group.create_at.app_error", nil, "", http.StatusBadRequest)
}
if group.UpdateAt == 0 {
return NewAppError("Group.IsValidForUpdate", "model.group.update_at.app_error", nil, "", http.StatusBadRequest)
}
if err := group.IsValidForCreate(); err != nil {
return err
}
return nil
}
func GroupFromJson(data io.Reader) *Group {
var group *Group
json.NewDecoder(data).Decode(&group)
return group
}
func GroupsFromJson(data io.Reader) []*Group {
var groups []*Group
json.NewDecoder(data).Decode(&groups)
return groups
}
func GroupPatchFromJson(data io.Reader) *GroupPatch {
var groupPatch *GroupPatch
json.NewDecoder(data).Decode(&groupPatch)
return groupPatch
}

23
model/group_member.go Обычный файл
Просмотреть файл

@@ -0,0 +1,23 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import "net/http"
type GroupMember struct {
GroupId string `json:"group_id"`
UserId string `json:"user_id"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
}
func (gm *GroupMember) IsValid() *AppError {
if !IsValidId(gm.GroupId) {
return NewAppError("GroupMember.IsValid", "model.group_member.group_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(gm.UserId) {
return NewAppError("GroupMember.IsValid", "model.group_member.user_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}

169
model/group_syncable.go Обычный файл
Просмотреть файл

@@ -0,0 +1,169 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
)
type GroupSyncableType string
const (
GroupSyncableTypeTeam GroupSyncableType = "Team"
GroupSyncableTypeChannel GroupSyncableType = "Channel"
)
func (gst GroupSyncableType) String() string {
return string(gst)
}
type GroupSyncable struct {
GroupId string `json:"group_id"`
// SyncableId represents the Id of the model that is being synced with the group, for example a ChannelId or
// TeamId.
SyncableId string `db:"-" json:"-"`
CanLeave bool `db:"-" json:"can_leave"`
AutoAdd bool `json:"auto_add"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
UpdateAt int64 `json:"update_at"`
Type GroupSyncableType `db:"-" json:"-"`
// Values joined in from the associated team and/or channel
ChannelDisplayName string `db:"-" json:"-"`
TeamDisplayName string `db:"-" json:"-"`
TeamType string `db:"-" json:"-"`
ChannelType string `db:"-" json:"-"`
TeamID string `db:"-" json:"-"`
}
func (syncable *GroupSyncable) IsValid() *AppError {
if !IsValidId(syncable.GroupId) {
return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.group_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(syncable.SyncableId) {
return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.syncable_id.app_error", nil, "", http.StatusBadRequest)
}
// TODO: Add this validation check for phase 2 of LDAP group sync.
// if syncable.AutoAdd == false && syncable.CanLeave == false {
// return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.invalid_state", nil, "", http.StatusBadRequest)
// }
return nil
}
func (syncable *GroupSyncable) UnmarshalJSON(b []byte) error {
var kvp map[string]interface{}
err := json.Unmarshal(b, &kvp)
if err != nil {
return err
}
for key, value := range kvp {
switch key {
case "team_id":
syncable.SyncableId = value.(string)
syncable.Type = GroupSyncableTypeTeam
case "channel_id":
syncable.SyncableId = value.(string)
syncable.Type = GroupSyncableTypeChannel
case "group_id":
syncable.GroupId = value.(string)
case "can_leave":
syncable.CanLeave = value.(bool)
case "auto_add":
syncable.AutoAdd = value.(bool)
default:
}
}
return nil
}
func (syncable *GroupSyncable) MarshalJSON() ([]byte, error) {
type Alias GroupSyncable
switch syncable.Type {
case GroupSyncableTypeTeam:
return json.Marshal(&struct {
TeamID string `json:"team_id"`
TeamDisplayName string `json:"team_display_name,omitempty"`
TeamType string `json:"team_type,omitempty"`
*Alias
}{
TeamDisplayName: syncable.TeamDisplayName,
TeamType: syncable.TeamType,
TeamID: syncable.SyncableId,
Alias: (*Alias)(syncable),
})
case GroupSyncableTypeChannel:
return json.Marshal(&struct {
ChannelID string `json:"channel_id"`
ChannelDisplayName string `json:"channel_display_name,omitempty"`
ChannelType string `json:"channel_type,omitempty"`
TeamID string `json:"team_id,omitempty"`
TeamDisplayName string `json:"team_display_name,omitempty"`
TeamType string `json:"team_type,omitempty"`
*Alias
}{
ChannelID: syncable.SyncableId,
ChannelDisplayName: syncable.ChannelDisplayName,
ChannelType: syncable.ChannelType,
TeamID: syncable.TeamID,
TeamDisplayName: syncable.TeamDisplayName,
TeamType: syncable.TeamType,
Alias: (*Alias)(syncable),
})
default:
return nil, &json.MarshalerError{
Err: fmt.Errorf("unknown syncable type: %s", syncable.Type),
}
}
}
type GroupSyncablePatch struct {
CanLeave *bool `json:"can_leave"`
AutoAdd *bool `json:"auto_add"`
}
func (syncable *GroupSyncable) Patch(patch *GroupSyncablePatch) {
// TODO: Add this validation check for phase 2 of LDAP group sync.
// if patch.CanLeave != nil {
// syncable.CanLeave = *patch.CanLeave
// }
if patch.AutoAdd != nil {
syncable.AutoAdd = *patch.AutoAdd
}
}
type UserTeamIDPair struct {
UserID string
TeamID string
}
type UserChannelIDPair struct {
UserID string
ChannelID string
}
func GroupSyncableFromJson(data io.Reader) *GroupSyncable {
groupSyncable := &GroupSyncable{}
bodyBytes, _ := ioutil.ReadAll(data)
json.Unmarshal(bodyBytes, groupSyncable)
return groupSyncable
}
func GroupSyncablesFromJson(data io.Reader) []*GroupSyncable {
groupSyncables := []*GroupSyncable{}
bodyBytes, _ := ioutil.ReadAll(data)
json.Unmarshal(bodyBytes, &groupSyncables)
return groupSyncables
}

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

@@ -42,6 +42,7 @@ type Customer struct {
type Features struct {
Users *int `json:"users"`
LDAP *bool `json:"ldap"`
LDAPGroups *bool `json:"ldap_groups"`
MFA *bool `json:"mfa"`
GoogleOAuth *bool `json:"google_oauth"`
Office365OAuth *bool `json:"office365_oauth"`
@@ -66,6 +67,7 @@ type Features struct {
func (f *Features) ToMap() map[string]interface{} {
return map[string]interface{}{
"ldap": *f.LDAP,
"ldap_groups": *f.LDAPGroups,
"mfa": *f.MFA,
"google": *f.GoogleOAuth,
"office365": *f.Office365OAuth,
@@ -96,6 +98,10 @@ func (f *Features) SetDefaults() {
f.LDAP = NewBool(*f.FutureFeatures)
}
if f.LDAPGroups == nil {
f.LDAPGroups = NewBool(*f.FutureFeatures)
}
if f.MFA == nil {
f.MFA = NewBool(*f.FutureFeatures)
}

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

@@ -15,6 +15,7 @@ func TestLicenseFeaturesToMap(t *testing.T) {
m := f.ToMap()
CheckTrue(t, m["ldap"].(bool))
CheckTrue(t, m["ldap_groups"].(bool))
CheckTrue(t, m["mfa"].(bool))
CheckTrue(t, m["google"].(bool))
CheckTrue(t, m["office365"].(bool))
@@ -37,6 +38,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
CheckInt(t, *f.Users, 0)
CheckTrue(t, *f.LDAP)
CheckTrue(t, *f.LDAPGroups)
CheckTrue(t, *f.MFA)
CheckTrue(t, *f.GoogleOAuth)
CheckTrue(t, *f.Office365OAuth)
@@ -58,6 +60,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
*f.Users = 300
*f.FutureFeatures = false
*f.LDAP = true
*f.LDAPGroups = true
*f.MFA = true
*f.GoogleOAuth = true
*f.Office365OAuth = true
@@ -76,6 +79,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
CheckInt(t, *f.Users, 300)
CheckTrue(t, *f.LDAP)
CheckTrue(t, *f.LDAPGroups)
CheckTrue(t, *f.MFA)
CheckTrue(t, *f.GoogleOAuth)
CheckTrue(t, *f.Office365OAuth)
@@ -159,6 +163,7 @@ func TestLicenseToFromJson(t *testing.T) {
CheckInt(t, *f1.Users, *f.Users)
CheckBool(t, *f1.LDAP, *f.LDAP)
CheckBool(t, *f1.LDAPGroups, *f.LDAPGroups)
CheckBool(t, *f1.MFA, *f.MFA)
CheckBool(t, *f1.GoogleOAuth, *f.GoogleOAuth)
CheckBool(t, *f1.Office365OAuth, *f.Office365OAuth)

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

@@ -29,6 +29,7 @@ type LayeredStore struct {
LocalCacheLayer *LocalCacheSupplier
RedisLayer *RedisSupplier
LayerChainHead LayeredStoreSupplier
GroupStore GroupStore
}
func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) Store {
@@ -41,6 +42,7 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn
store.ReactionStore = &LayeredReactionStore{store}
store.RoleStore = &LayeredRoleStore{store}
store.SchemeStore = &LayeredSchemeStore{store}
store.GroupStore = &LayeredGroupStore{store}
// Setup the chain
if ENABLE_EXPERIMENTAL_REDIS {
@@ -181,6 +183,10 @@ func (s *LayeredStore) Scheme() SchemeStore {
return s.SchemeStore
}
func (s *LayeredStore) Group() GroupStore {
return s.GroupStore
}
func (s *LayeredStore) MarkSystemRanUnitTests() {
s.DatabaseLayer.MarkSystemRanUnitTests()
}
@@ -333,3 +339,115 @@ func (s *LayeredSchemeStore) PermanentDeleteAll() StoreChannel {
return supplier.SchemePermanentDeleteAll(s.TmpContext)
})
}
type LayeredGroupStore struct {
*LayeredStore
}
func (s *LayeredGroupStore) Create(group *model.Group) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupCreate(s.TmpContext, group)
})
}
func (s *LayeredGroupStore) Get(groupID string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGet(s.TmpContext, groupID)
})
}
func (s *LayeredGroupStore) GetByRemoteID(remoteID string, groupSource model.GroupSource) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetByRemoteID(s.TmpContext, remoteID, groupSource)
})
}
func (s *LayeredGroupStore) GetAllBySource(groupSource model.GroupSource) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetAllBySource(s.TmpContext, groupSource)
})
}
func (s *LayeredGroupStore) Update(group *model.Group) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupUpdate(s.TmpContext, group)
})
}
func (s *LayeredGroupStore) Delete(groupID string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupDelete(s.TmpContext, groupID)
})
}
func (s *LayeredGroupStore) GetMemberUsers(groupID string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetMemberUsers(s.TmpContext, groupID)
})
}
func (s *LayeredGroupStore) GetMemberUsersPage(groupID string, offset int, limit int) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetMemberUsersPage(s.TmpContext, groupID, offset, limit)
})
}
func (s *LayeredGroupStore) GetMemberCount(groupID string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetMemberCount(s.TmpContext, groupID)
})
}
func (s *LayeredGroupStore) CreateOrRestoreMember(groupID string, userID string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupCreateOrRestoreMember(s.TmpContext, groupID, userID)
})
}
func (s *LayeredGroupStore) DeleteMember(groupID string, userID string) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupDeleteMember(s.TmpContext, groupID, userID)
})
}
func (s *LayeredGroupStore) CreateGroupSyncable(groupSyncable *model.GroupSyncable) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupCreateGroupSyncable(s.TmpContext, groupSyncable)
})
}
func (s *LayeredGroupStore) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetGroupSyncable(s.TmpContext, groupID, syncableID, syncableType)
})
}
func (s *LayeredGroupStore) GetAllGroupSyncablesByGroupId(groupID string, syncableType model.GroupSyncableType) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupGetAllGroupSyncablesByGroup(s.TmpContext, groupID, syncableType)
})
}
func (s *LayeredGroupStore) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupUpdateGroupSyncable(s.TmpContext, groupSyncable)
})
}
func (s *LayeredGroupStore) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GroupDeleteGroupSyncable(s.TmpContext, groupID, syncableID, syncableType)
})
}
func (s *LayeredGroupStore) PendingAutoAddTeamMembers(minGroupMembersCreateAt int64) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.PendingAutoAddTeamMembers(s.TmpContext, minGroupMembersCreateAt)
})
}
func (s *LayeredGroupStore) PendingAutoAddChannelMembers(minGroupMembersCreateAt int64) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.PendingAutoAddChannelMembers(s.TmpContext, minGroupMembersCreateAt)
})
}

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

@@ -46,4 +46,27 @@ type LayeredStoreSupplier interface {
SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
SchemePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
// Groups
GroupCreate(ctx context.Context, group *model.Group, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGet(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetByRemoteID(ctx context.Context, remoteID string, groupSource model.GroupSource, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetAllBySource(ctx context.Context, groupSource model.GroupSource, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupUpdate(ctx context.Context, group *model.Group, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupDelete(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetMemberUsers(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetMemberUsersPage(ctx context.Context, groupID string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetMemberCount(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupCreateOrRestoreMember(ctx context.Context, groupID string, userID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupDeleteMember(ctx context.Context, groupID string, userID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupCreateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupGetAllGroupSyncablesByGroup(ctx context.Context, groupID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupUpdateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GroupDeleteGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
PendingAutoAddTeamMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
PendingAutoAddChannelMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
}

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

@@ -21,6 +21,9 @@ const (
SCHEME_CACHE_SIZE = 20000
SCHEME_CACHE_SEC = 30 * 60
GROUP_CACHE_SIZE = 20000
GROUP_CACHE_SEC = 30 * 60
CLEAR_CACHE_MESSAGE_DATA = ""
)
@@ -31,6 +34,7 @@ type LocalCacheSupplier struct {
schemeCache *utils.Cache
metrics einterfaces.MetricsInterface
cluster einterfaces.ClusterInterface
groupCache *utils.Cache
}
// Caching Interface
@@ -50,6 +54,7 @@ func NewLocalCacheSupplier(metrics einterfaces.MetricsInterface, cluster einterf
reactionCache: utils.NewLruWithParams(REACTION_CACHE_SIZE, "Reaction", REACTION_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS),
roleCache: utils.NewLruWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES),
schemeCache: utils.NewLruWithParams(SCHEME_CACHE_SIZE, "Scheme", SCHEME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES),
groupCache: utils.NewLruWithParams(GROUP_CACHE_SIZE, "Group", GROUP_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_GROUPS),
metrics: metrics,
cluster: cluster,
}
@@ -57,6 +62,7 @@ func NewLocalCacheSupplier(metrics einterfaces.MetricsInterface, cluster einterf
if cluster != nil {
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, supplier.handleClusterInvalidateReaction)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, supplier.handleClusterInvalidateRole)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_GROUPS, supplier.handleClusterInvalidateGroup)
}
return supplier

102
store/local_cache_supplier_groups.go Обычный файл
Просмотреть файл

@@ -0,0 +1,102 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"context"
"github.com/mattermost/mattermost-server/model"
)
func (s *LocalCacheSupplier) handleClusterInvalidateGroup(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.groupCache.Purge()
} else {
s.groupCache.Remove(msg.Data)
}
}
func (s *LocalCacheSupplier) GroupCreate(ctx context.Context, group *model.Group, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupCreate(ctx, group, hints...)
}
func (s *LocalCacheSupplier) GroupGet(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
if result := s.doStandardReadCache(ctx, s.groupCache, groupID, hints...); result != nil {
return result
}
result := s.Next().GroupGet(ctx, groupID, hints...)
s.doStandardAddToCache(ctx, s.groupCache, groupID, result, hints...)
return result
}
func (s *LocalCacheSupplier) GroupGetByRemoteID(ctx context.Context, remoteID string, groupSource model.GroupSource, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetByRemoteID(ctx, remoteID, groupSource, hints...)
}
func (s *LocalCacheSupplier) GroupGetAllBySource(ctx context.Context, groupSource model.GroupSource, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetAllBySource(ctx, groupSource, hints...)
}
func (s *LocalCacheSupplier) GroupUpdate(ctx context.Context, group *model.Group, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
defer s.doInvalidateCacheCluster(s.groupCache, group.Id)
return s.Next().GroupUpdate(ctx, group, hints...)
}
func (s *LocalCacheSupplier) GroupDelete(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
defer s.doInvalidateCacheCluster(s.groupCache, groupID)
defer s.doClearCacheCluster(s.groupCache)
return s.Next().GroupDelete(ctx, groupID, hints...)
}
func (s *LocalCacheSupplier) GroupGetMemberUsers(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetMemberUsers(ctx, groupID, hints...)
}
func (s *LocalCacheSupplier) GroupGetMemberUsersPage(ctx context.Context, groupID string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetMemberUsersPage(ctx, groupID, offset, limit, hints...)
}
func (s *LocalCacheSupplier) GroupGetMemberCount(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetMemberCount(ctx, groupID, hints...)
}
func (s *LocalCacheSupplier) GroupCreateOrRestoreMember(ctx context.Context, groupID string, userID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupCreateOrRestoreMember(ctx, groupID, userID, hints...)
}
func (s *LocalCacheSupplier) GroupDeleteMember(ctx context.Context, groupID string, userID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupDeleteMember(ctx, groupID, userID, hints...)
}
func (s *LocalCacheSupplier) GroupCreateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupCreateGroupSyncable(ctx, groupSyncable, hints...)
}
func (s *LocalCacheSupplier) GroupGetGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetGroupSyncable(ctx, groupID, syncableID, syncableType, hints...)
}
func (s *LocalCacheSupplier) GroupGetAllGroupSyncablesByGroup(ctx context.Context, groupID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupGetAllGroupSyncablesByGroup(ctx, groupID, syncableType, hints...)
}
func (s *LocalCacheSupplier) GroupUpdateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupUpdateGroupSyncable(ctx, groupSyncable, hints...)
}
func (s *LocalCacheSupplier) GroupDeleteGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GroupDeleteGroupSyncable(ctx, groupID, syncableID, syncableType, hints...)
}
func (s *LocalCacheSupplier) PendingAutoAddTeamMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().PendingAutoAddTeamMembers(ctx, minGroupMembersCreateAt, hints...)
}
func (s *LocalCacheSupplier) PendingAutoAddChannelMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().PendingAutoAddChannelMembers(ctx, minGroupMembersCreateAt, hints...)
}

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

@@ -18,7 +18,7 @@ func (s *LocalCacheSupplier) handleClusterInvalidateRole(msg *model.ClusterMessa
}
func (s *LocalCacheSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
if len(role.Id) != 0 {
if len(role.Name) != 0 {
defer s.doInvalidateCacheCluster(s.roleCache, role.Name)
}
return s.Next().RoleSave(ctx, role, hints...)

100
store/redis_supplier_groups.go Обычный файл
Просмотреть файл

@@ -0,0 +1,100 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"context"
"github.com/mattermost/mattermost-server/model"
)
func (s *RedisSupplier) GroupCreate(ctx context.Context, group *model.Group, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupCreate(ctx, group, hints...)
}
func (s *RedisSupplier) GroupGet(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGet(ctx, groupID, hints...)
}
func (s *RedisSupplier) GroupGetByRemoteID(ctx context.Context, remoteID string, groupSource model.GroupSource, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetByRemoteID(ctx, remoteID, groupSource, hints...)
}
func (s *RedisSupplier) GroupGetAllBySource(ctx context.Context, groupSource model.GroupSource, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetAllBySource(ctx, groupSource, hints...)
}
func (s *RedisSupplier) GroupUpdate(ctx context.Context, group *model.Group, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupUpdate(ctx, group, hints...)
}
func (s *RedisSupplier) GroupDelete(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupDelete(ctx, groupID, hints...)
}
func (s *RedisSupplier) GroupGetMemberUsers(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetMemberUsers(ctx, groupID, hints...)
}
func (s *RedisSupplier) GroupGetMemberUsersPage(ctx context.Context, groupID string, offset int, limit int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetMemberUsersPage(ctx, groupID, offset, limit, hints...)
}
func (s *RedisSupplier) GroupGetMemberCount(ctx context.Context, groupID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetMemberCount(ctx, groupID, hints...)
}
func (s *RedisSupplier) GroupCreateOrRestoreMember(ctx context.Context, groupID string, userID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupCreateOrRestoreMember(ctx, groupID, userID, hints...)
}
func (s *RedisSupplier) GroupDeleteMember(ctx context.Context, groupID string, userID string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupDeleteMember(ctx, groupID, userID, hints...)
}
func (s *RedisSupplier) GroupCreateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupCreateGroupSyncable(ctx, groupSyncable, hints...)
}
func (s *RedisSupplier) GroupGetGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetGroupSyncable(ctx, groupID, syncableID, syncableType, hints...)
}
func (s *RedisSupplier) GroupGetAllGroupSyncablesByGroup(ctx context.Context, groupID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupGetAllGroupSyncablesByGroup(ctx, groupID, syncableType, hints...)
}
func (s *RedisSupplier) GroupUpdateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupUpdateGroupSyncable(ctx, groupSyncable, hints...)
}
func (s *RedisSupplier) GroupDeleteGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().GroupDeleteGroupSyncable(ctx, groupID, syncableID, syncableType, hints...)
}
func (s *RedisSupplier) PendingAutoAddTeamMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().PendingAutoAddTeamMembers(ctx, minGroupMembersCreateAt, hints...)
}
func (s *RedisSupplier) PendingAutoAddChannelMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().PendingAutoAddChannelMembers(ctx, minGroupMembersCreateAt, hints...)
}

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

@@ -955,6 +955,27 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string, includeDelete
})
}
func (s SqlChannelStore) GetAllChannels(offset int, limit int, includeDeleted bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
deleteFilter := "AND c.DeleteAt = 0"
if includeDeleted {
deleteFilter = ""
}
query := "SELECT c.*, Teams.DisplayName AS TeamDisplayName, Teams.Name AS TeamName, Teams.UpdateAt as TeamUpdateAt FROM Channels AS c JOIN Teams ON Teams.Id = c.TeamId WHERE (c.Type = 'P' OR c.Type = 'O') " + deleteFilter + " ORDER BY c.DisplayName, Teams.DisplayName LIMIT :Limit OFFSET :Offset"
data := &model.ChannelListWithTeamData{}
_, err := s.GetReplica().Select(data, query, map[string]interface{}{"Limit": limit, "Offset": offset})
if err != nil {
result.Err = model.NewAppError("SqlChannelStore.GetAllChannels", "store.sql_channel.get_all_channels.get.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
result.Data = data
})
}
func (s SqlChannelStore) GetMoreChannels(teamId string, userId string, offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
data := &model.ChannelList{}
@@ -2072,6 +2093,36 @@ func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted
})
}
func (s SqlChannelStore) SearchAllChannels(term string, includeDeleted bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
parameters := map[string]interface{}{}
deleteFilter := "AND c.DeleteAt = 0"
if includeDeleted {
deleteFilter = ""
}
searchQuery := `SELECT c.*, t.DisplayName AS TeamDisplayName, t.Name AS TeamName, t.UpdateAt as TeamUpdateAt FROM Channels AS c JOIN Teams AS t ON t.Id = c.TeamId WHERE (c.Type = 'P' OR c.Type = 'O') ` + deleteFilter + ` SEARCH_CLAUSE ORDER BY c.DisplayName, t.DisplayName LIMIT 100`
likeClause, likeTerm := s.buildLIKEClause(term, "c.Name, c.DisplayName, c.Purpose")
if likeTerm == "" {
// If the likeTerm is empty after preparing, then don't bother searching.
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
} else {
parameters["LikeTerm"] = likeTerm
fulltextClause, fulltextTerm := s.buildFulltextClause(term, "c.Name, c.DisplayName, c.Purpose")
parameters["FulltextTerm"] = fulltextTerm
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "AND ("+likeClause+" OR "+fulltextClause+")", 1)
}
var channels model.ChannelListWithTeamData
if _, err := s.GetReplica().Select(&channels, searchQuery, parameters); err != nil {
result.Err = model.NewAppError("SqlChannelStore.Search", "store.sql_channel.search.app_error", nil, "term="+term+", "+", "+err.Error(), http.StatusInternalServerError)
}
result.Data = &channels
})
}
func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
*result = s.performSearch(`

14
store/sqlstore/group_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/store/storetest"
)
func TestGroupStore(t *testing.T) {
StoreTest(t, storetest.TestGroupStore)
}

771
store/sqlstore/group_supplier.go Обычный файл
Просмотреть файл

@@ -0,0 +1,771 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package sqlstore
import (
"context"
"database/sql"
"fmt"
"net/http"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
type groupTeam struct {
model.GroupSyncable
TeamId string `db:"TeamId"`
}
type groupChannel struct {
model.GroupSyncable
ChannelId string `db:"ChannelId"`
}
type groupTeamJoin struct {
groupTeam
TeamDisplayName string `db:"TeamDisplayName"`
TeamType string `db:"TeamType"`
}
type groupChannelJoin struct {
groupChannel
ChannelDisplayName string `db:"ChannelDisplayName"`
TeamDisplayName string `db:"TeamDisplayName"`
TeamType string `db:"TeamType"`
ChannelType string `db:"ChannelType"`
TeamID string `db:"TeamId"`
}
func initSqlSupplierGroups(sqlStore SqlStore) {
for _, db := range sqlStore.GetAllConns() {
groups := db.AddTableWithName(model.Group{}, "UserGroups").SetKeys(false, "Id")
groups.ColMap("Id").SetMaxSize(26)
groups.ColMap("Name").SetMaxSize(model.GroupNameMaxLength).SetUnique(true)
groups.ColMap("DisplayName").SetMaxSize(model.GroupDisplayNameMaxLength)
groups.ColMap("Description").SetMaxSize(model.GroupDescriptionMaxLength)
groups.ColMap("Source").SetMaxSize(model.GroupSourceMaxLength)
groups.ColMap("RemoteId").SetMaxSize(model.GroupRemoteIDMaxLength)
groups.SetUniqueTogether("Source", "RemoteId")
groupMembers := db.AddTableWithName(model.GroupMember{}, "GroupMembers").SetKeys(false, "GroupId", "UserId")
groupMembers.ColMap("GroupId").SetMaxSize(26)
groupMembers.ColMap("UserId").SetMaxSize(26)
groupTeams := db.AddTableWithName(groupTeam{}, "GroupTeams").SetKeys(false, "GroupId", "TeamId")
groupTeams.ColMap("GroupId").SetMaxSize(26)
groupTeams.ColMap("TeamId").SetMaxSize(26)
groupChannels := db.AddTableWithName(groupChannel{}, "GroupChannels").SetKeys(false, "GroupId", "ChannelId")
groupChannels.ColMap("GroupId").SetMaxSize(26)
groupChannels.ColMap("ChannelId").SetMaxSize(26)
}
}
func (s *SqlSupplier) CreateIndexesIfNotExistsGroups() {
s.CreateIndexIfNotExists("idx_groupmembers_create_at", "GroupMembers", "CreateAt")
s.CreateIndexIfNotExists("idx_usergroups_remote_id", "UserGroups", "RemoteId")
s.CreateIndexIfNotExists("idx_usergroups_delete_at", "UserGroups", "DeleteAt")
}
func (s *SqlSupplier) GroupCreate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
if len(group.Id) != 0 {
result.Err = model.NewAppError("SqlGroupStore.GroupCreate", "model.group.id.app_error", nil, "", http.StatusBadRequest)
return result
}
if err := group.IsValidForCreate(); err != nil {
result.Err = err
return result
}
group.Id = model.NewId()
group.CreateAt = model.GetMillis()
group.UpdateAt = group.CreateAt
if err := s.GetMaster().Insert(group); err != nil {
if IsUniqueConstraintError(err, []string{"Name", "groups_name_key"}) {
result.Err = model.NewAppError("SqlGroupStore.GroupCreate", "store.sql_group.unique_constraint", nil, err.Error(), http.StatusInternalServerError)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupCreate", "store.insert_error", nil, err.Error(), http.StatusInternalServerError)
}
return result
}
result.Data = group
return result
}
func (s *SqlSupplier) GroupGet(ctx context.Context, groupId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var group *model.Group
if err := s.GetReplica().SelectOne(&group, "SELECT * from UserGroups WHERE Id = :Id", map[string]interface{}{"Id": groupId}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupGet", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupGet", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return result
}
result.Data = group
return result
}
func (s *SqlSupplier) GroupGetByRemoteID(ctx context.Context, remoteID string, groupSource model.GroupSource, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var group *model.Group
if err := s.GetReplica().SelectOne(&group, "SELECT * from UserGroups WHERE RemoteId = :RemoteId AND Source = :Source", map[string]interface{}{"RemoteId": remoteID, "Source": groupSource}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupGetByRemoteID", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupGetByRemoteID", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return result
}
result.Data = group
return result
}
func (s *SqlSupplier) GroupGetAllBySource(ctx context.Context, groupSource model.GroupSource, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var groups []*model.Group
if _, err := s.GetReplica().Select(&groups, "SELECT * from UserGroups WHERE DeleteAt = 0 AND Source = :Source", map[string]interface{}{"Source": groupSource}); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupGetAllBySource", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = groups
return result
}
func (s *SqlSupplier) GroupUpdate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var retrievedGroup *model.Group
if err := s.GetMaster().SelectOne(&retrievedGroup, "SELECT * FROM UserGroups WHERE Id = :Id", map[string]interface{}{"Id": group.Id}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdate", "store.sql_group.no_rows", nil, "id="+group.Id+","+err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdate", "store.select_error", nil, "id="+group.Id+","+err.Error(), http.StatusInternalServerError)
}
return result
}
// If updating DeleteAt it can only be to 0
if group.DeleteAt != retrievedGroup.DeleteAt && group.DeleteAt != 0 {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdate", "model.group.delete_at.app_error", nil, "", http.StatusInternalServerError)
return result
}
// Reset these properties, don't update them based on input
group.CreateAt = retrievedGroup.CreateAt
group.UpdateAt = model.GetMillis()
if err := group.IsValidForUpdate(); err != nil {
result.Err = err
return result
}
rowsChanged, err := s.GetMaster().Update(group)
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdate", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
if rowsChanged != 1 {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdate", "store.sql_group.no_rows_changed", nil, "", http.StatusInternalServerError)
return result
}
result.Data = group
return result
}
func (s *SqlSupplier) GroupDelete(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var group *model.Group
if err := s.GetReplica().SelectOne(&group, "SELECT * from UserGroups WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": groupID}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupDelete", "store.sql_group.no_rows", nil, "Id="+groupID+", "+err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupDelete", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return result
}
time := model.GetMillis()
group.DeleteAt = time
group.UpdateAt = time
if _, err := s.GetMaster().Update(group); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupDelete", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
}
result.Data = group
return result
}
func (s *SqlSupplier) GroupGetMemberUsers(stc context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var groupMembers []*model.User
query := `
SELECT
Users.*
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
AND GroupId = :GroupId`
if _, err := s.GetReplica().Select(&groupMembers, query, map[string]interface{}{"GroupId": groupID}); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupGetAllBySource", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = groupMembers
return result
}
func (s *SqlSupplier) GroupGetMemberUsersPage(stc context.Context, groupID string, offset int, limit int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var groupMembers []*model.User
query := `
SELECT
Users.*
FROM
GroupMembers
JOIN Users ON Users.Id = GroupMembers.UserId
WHERE
GroupMembers.DeleteAt = 0
AND Users.DeleteAt = 0
AND GroupId = :GroupId
ORDER BY
GroupMembers.CreateAt DESC
LIMIT
:Limit
OFFSET
:Offset`
if _, err := s.GetReplica().Select(&groupMembers, query, map[string]interface{}{"GroupId": groupID, "Limit": limit, "Offset": offset}); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupGetMemberUsersPage", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = groupMembers
return result
}
func (s *SqlSupplier) GroupGetMemberCount(stc context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var count int64
var err error
query := `
SELECT
count(*)
FROM
GroupMembers
WHERE
GroupMembers.GroupId = :GroupId`
if count, err = s.GetReplica().SelectInt(query, map[string]interface{}{"GroupId": groupID}); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupGetMemberUsersPage", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = count
return result
}
func (s *SqlSupplier) GroupCreateOrRestoreMember(ctx context.Context, groupID string, userID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
member := &model.GroupMember{
GroupId: groupID,
UserId: userID,
CreateAt: model.GetMillis(),
}
if result.Err = member.IsValid(); result.Err != nil {
return result
}
var retrievedGroup *model.Group
if err := s.GetMaster().SelectOne(&retrievedGroup, "SELECT * FROM UserGroups WHERE Id = :Id", map[string]interface{}{"Id": groupID}); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.insert_error", nil, "group_id="+member.GroupId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError)
return result
}
var retrievedMember *model.GroupMember
if err := s.GetMaster().SelectOne(&retrievedMember, "SELECT * FROM GroupMembers WHERE GroupId = :GroupId AND UserId = :UserId", map[string]interface{}{"GroupId": member.GroupId, "UserId": member.UserId}); err != nil {
if err != sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.select_error", nil, "group_id="+member.GroupId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError)
return result
}
}
if retrievedMember != nil && retrievedMember.DeleteAt == 0 {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.sql_group.uniqueness_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId, http.StatusBadRequest)
return result
}
if retrievedMember == nil {
if err := s.GetMaster().Insert(member); err != nil {
if IsUniqueConstraintError(err, []string{"GroupId", "UserId", "groupmembers_pkey", "PRIMARY"}) {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.sql_group.uniqueness_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId+", "+err.Error(), http.StatusBadRequest)
return result
}
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.insert_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError)
return result
}
} else {
member.DeleteAt = 0
var rowsChanged int64
var err error
if rowsChanged, err = s.GetMaster().Update(member); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.update_error", nil, "group_id="+member.GroupId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError)
return result
}
if rowsChanged != 1 {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateOrRestoreMember", "store.sql_group.no_rows_changed", nil, "", http.StatusInternalServerError)
return result
}
}
result.Data = member
return result
}
func (s *SqlSupplier) GroupDeleteMember(ctx context.Context, groupID string, userID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
var retrievedMember *model.GroupMember
if err := s.GetMaster().SelectOne(&retrievedMember, "SELECT * FROM GroupMembers WHERE GroupId = :GroupId AND UserId = :UserId AND DeleteAt = 0", map[string]interface{}{"GroupId": groupID, "UserId": userID}); err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteMember", "store.sql_group.no_rows", nil, "group_id="+groupID+"user_id="+userID+","+err.Error(), http.StatusNotFound)
return result
}
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteMember", "store.select_error", nil, "group_id="+groupID+"user_id="+userID+","+err.Error(), http.StatusInternalServerError)
return result
}
retrievedMember.DeleteAt = model.GetMillis()
if _, err := s.GetMaster().Update(retrievedMember); err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteMember", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = retrievedMember
return result
}
func (s *SqlSupplier) GroupCreateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
if err := groupSyncable.IsValid(); err != nil {
result.Err = err
return result
}
// Reset values that shouldn't be updatable by parameter
groupSyncable.DeleteAt = 0
groupSyncable.CreateAt = model.GetMillis()
groupSyncable.UpdateAt = groupSyncable.CreateAt
var err error
switch groupSyncable.Type {
case model.GroupSyncableTypeTeam:
teamResult := <-s.Team().Get(groupSyncable.SyncableId)
if teamResult.Err != nil {
result.Err = teamResult.Err
return result
}
err = s.GetMaster().Insert(groupSyncableToGroupTeam(groupSyncable))
case model.GroupSyncableTypeChannel:
channelResult := <-s.Channel().Get(groupSyncable.SyncableId, false)
if channelResult.Err != nil {
result.Err = channelResult.Err
return result
}
err = s.GetMaster().Insert(groupSyncableToGroupChannel(groupSyncable))
default:
result.Err = model.NewAppError("SqlGroupStore.GroupCreateGroupSyncable", "model.group_syncable.type.app_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId+", "+err.Error(), http.StatusInternalServerError)
return result
}
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupCreateGroupSyncable", "store.insert_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId+", "+err.Error(), http.StatusInternalServerError)
return result
}
result.Data = groupSyncable
return result
}
func (s *SqlSupplier) GroupGetGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
groupSyncable, err := s.getGroupSyncable(groupID, syncableID, syncableType)
if err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupGetGroupSyncable", "store.sql_group.no_rows", nil, err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupGetGroupSyncable", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return result
}
result.Data = groupSyncable
return result
}
func (s *SqlSupplier) getGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, error) {
var err error
var result interface{}
switch syncableType {
case model.GroupSyncableTypeTeam:
result, err = s.GetMaster().Get(groupTeam{}, groupID, syncableID)
case model.GroupSyncableTypeChannel:
result, err = s.GetMaster().Get(groupChannel{}, groupID, syncableID)
}
if err != nil {
return nil, err
}
if result == nil {
return nil, sql.ErrNoRows
}
groupSyncable := model.GroupSyncable{}
switch syncableType {
case model.GroupSyncableTypeTeam:
groupTeam := result.(*groupTeam)
groupSyncable.SyncableId = groupTeam.TeamId
groupSyncable.GroupId = groupTeam.GroupId
groupSyncable.CanLeave = groupTeam.CanLeave
groupSyncable.AutoAdd = groupTeam.AutoAdd
groupSyncable.CreateAt = groupTeam.CreateAt
groupSyncable.DeleteAt = groupTeam.DeleteAt
groupSyncable.UpdateAt = groupTeam.UpdateAt
groupSyncable.Type = syncableType
case model.GroupSyncableTypeChannel:
groupChannel := result.(*groupChannel)
groupSyncable.SyncableId = groupChannel.ChannelId
groupSyncable.GroupId = groupChannel.GroupId
groupSyncable.CanLeave = groupChannel.CanLeave
groupSyncable.AutoAdd = groupChannel.AutoAdd
groupSyncable.CreateAt = groupChannel.CreateAt
groupSyncable.DeleteAt = groupChannel.DeleteAt
groupSyncable.UpdateAt = groupChannel.UpdateAt
groupSyncable.Type = syncableType
default:
return nil, fmt.Errorf("unable to convert syncableType: %s", syncableType.String())
}
return &groupSyncable, nil
}
func (s *SqlSupplier) GroupGetAllGroupSyncablesByGroup(ctx context.Context, groupID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
args := map[string]interface{}{"GroupId": groupID}
appErrF := func(msg string) *model.AppError {
return model.NewAppError("SqlGroupStore.GroupGetAllGroupSyncablesByGroup", "store.select_error", nil, msg, http.StatusInternalServerError)
}
groupSyncables := []*model.GroupSyncable{}
switch syncableType {
case model.GroupSyncableTypeTeam:
sqlQuery := `
SELECT
GroupTeams.*,
Teams.DisplayName AS TeamDisplayName,
Teams.Type AS TeamType
FROM
GroupTeams
JOIN Teams ON Teams.Id = GroupTeams.TeamId
WHERE
GroupId = :GroupId AND GroupTeams.DeleteAt = 0`
results := []*groupTeamJoin{}
_, err := s.GetMaster().Select(&results, sqlQuery, args)
if err != nil {
result.Err = appErrF(err.Error())
return result
}
for _, result := range results {
groupSyncable := &model.GroupSyncable{
SyncableId: result.TeamId,
GroupId: result.GroupId,
CanLeave: result.CanLeave,
AutoAdd: result.AutoAdd,
CreateAt: result.CreateAt,
DeleteAt: result.DeleteAt,
UpdateAt: result.UpdateAt,
Type: syncableType,
TeamDisplayName: result.TeamDisplayName,
TeamType: result.TeamType,
}
groupSyncables = append(groupSyncables, groupSyncable)
}
case model.GroupSyncableTypeChannel:
sqlQuery := `
SELECT
GroupChannels.*,
Channels.DisplayName AS ChannelDisplayName,
Teams.DisplayName AS TeamDisplayName,
Channels.Type As ChannelType,
Teams.Type As TeamType,
Teams.Id AS TeamId
FROM
GroupChannels
JOIN Channels ON Channels.Id = GroupChannels.ChannelId
JOIN Teams ON Teams.Id = Channels.TeamId
WHERE
GroupId = :GroupId AND GroupChannels.DeleteAt = 0`
results := []*groupChannelJoin{}
_, err := s.GetMaster().Select(&results, sqlQuery, args)
if err != nil {
result.Err = appErrF(err.Error())
return result
}
for _, result := range results {
groupSyncable := &model.GroupSyncable{
SyncableId: result.ChannelId,
GroupId: result.GroupId,
CanLeave: result.CanLeave,
AutoAdd: result.AutoAdd,
CreateAt: result.CreateAt,
DeleteAt: result.DeleteAt,
UpdateAt: result.UpdateAt,
Type: syncableType,
ChannelDisplayName: result.ChannelDisplayName,
ChannelType: result.ChannelType,
TeamDisplayName: result.TeamDisplayName,
TeamType: result.TeamType,
TeamID: result.TeamID,
}
groupSyncables = append(groupSyncables, groupSyncable)
}
}
result.Data = groupSyncables
return result
}
func (s *SqlSupplier) GroupUpdateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
retrievedGroupSyncable, err := s.getGroupSyncable(groupSyncable.GroupId, groupSyncable.SyncableId, groupSyncable.Type)
if err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "store.sql_group.no_rows", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Err = model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "store.select_error", nil, "GroupId="+groupSyncable.GroupId+", SyncableId="+groupSyncable.SyncableId+", SyncableType="+groupSyncable.Type.String()+", "+err.Error(), http.StatusInternalServerError)
return result
}
if err := groupSyncable.IsValid(); err != nil {
result.Err = err
return result
}
// If updating DeleteAt it can only be to 0
if groupSyncable.DeleteAt != retrievedGroupSyncable.DeleteAt && groupSyncable.DeleteAt != 0 {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "model.group.delete_at.app_error", nil, "", http.StatusInternalServerError)
return result
}
// Reset these properties, don't update them based on input
groupSyncable.CreateAt = retrievedGroupSyncable.CreateAt
groupSyncable.UpdateAt = model.GetMillis()
switch groupSyncable.Type {
case model.GroupSyncableTypeTeam:
_, err = s.GetMaster().Update(groupSyncableToGroupTeam(groupSyncable))
case model.GroupSyncableTypeChannel:
_, err = s.GetMaster().Update(groupSyncableToGroupChannel(groupSyncable))
default:
model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "model.group_syncable.type.app_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId+", "+err.Error(), http.StatusInternalServerError)
return result
}
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupUpdateGroupSyncable", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = groupSyncable
return result
}
func (s *SqlSupplier) GroupDeleteGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
groupSyncable, err := s.getGroupSyncable(groupID, syncableID, syncableType)
if err != nil {
if err == sql.ErrNoRows {
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.sql_group.no_rows", nil, "Id="+groupID+", "+err.Error(), http.StatusNotFound)
} else {
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
return result
}
if groupSyncable.DeleteAt != 0 {
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.sql_group.group_syncable_already_deleted", nil, "group_id="+groupID+"syncable_id="+syncableID, http.StatusBadRequest)
return result
}
time := model.GetMillis()
groupSyncable.DeleteAt = time
groupSyncable.UpdateAt = time
switch groupSyncable.Type {
case model.GroupSyncableTypeTeam:
_, err = s.GetMaster().Update(groupSyncableToGroupTeam(groupSyncable))
case model.GroupSyncableTypeChannel:
_, err = s.GetMaster().Update(groupSyncableToGroupChannel(groupSyncable))
default:
model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "model.group_syncable.type.app_error", nil, "group_id="+groupSyncable.GroupId+", syncable_id="+groupSyncable.SyncableId+", "+err.Error(), http.StatusInternalServerError)
return result
}
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GroupDeleteGroupSyncable", "store.update_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = groupSyncable
return result
}
// PendingAutoAddTeamMembers returns a slice of UserTeamIDPair that need newly created memberships
// based on the groups configurations.
//
// Typically since will be the last successful group sync time.
func (s *SqlSupplier) PendingAutoAddTeamMembers(ctx context.Context, since int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
sql := `
SELECT
GroupMembers.UserId, GroupTeams.TeamId
FROM
GroupMembers
JOIN GroupTeams
ON GroupTeams.GroupId = GroupMembers.GroupId
JOIN UserGroups ON UserGroups.Id = GroupMembers.GroupId
JOIN Teams ON Teams.Id = GroupTeams.TeamId
LEFT OUTER JOIN TeamMembers
ON
TeamMembers.TeamId = GroupTeams.TeamId
AND TeamMembers.UserId = GroupMembers.UserId
WHERE
TeamMembers.UserId IS NULL
AND UserGroups.DeleteAt = 0
AND GroupTeams.DeleteAt = 0
AND GroupTeams.AutoAdd = true
AND GroupMembers.DeleteAt = 0
AND Teams.DeleteAt = 0
AND (GroupMembers.CreateAt >= :Since
OR GroupTeams.UpdateAt >= :Since)`
var userTeamIDs []*model.UserTeamIDPair
_, err := s.GetMaster().Select(&userTeamIDs, sql, map[string]interface{}{"Since": since})
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.PendingAutoAddTeamMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
}
result.Data = userTeamIDs
return result
}
// PendingAutoAddChannelMembers returns a slice of UserChannelIDPair that need newly created memberships
// based on the groups configurations.
//
// Typically since will be the last successful group sync time.
func (s *SqlSupplier) PendingAutoAddChannelMembers(ctx context.Context, since int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
sql := `
SELECT
GroupMembers.UserId, GroupChannels.ChannelId
FROM
GroupMembers
JOIN GroupChannels ON GroupChannels.GroupId = GroupMembers.GroupId
JOIN UserGroups ON UserGroups.Id = GroupMembers.GroupId
JOIN Channels ON Channels.Id = GroupChannels.ChannelId
LEFT OUTER JOIN ChannelMemberHistory
ON
ChannelMemberHistory.ChannelId = GroupChannels.ChannelId
AND ChannelMemberHistory.UserId = GroupMembers.UserId
WHERE
ChannelMemberHistory.UserId IS NULL
AND ChannelMemberHistory.LeaveTime IS NULL
AND UserGroups.DeleteAt = 0
AND GroupChannels.DeleteAt = 0
AND GroupChannels.AutoAdd = true
AND GroupMembers.DeleteAt = 0
AND Channels.DeleteAt = 0
AND (GroupMembers.CreateAt >= :Since
OR GroupChannels.UpdateAt >= :Since)`
var userChannelIDs []*model.UserChannelIDPair
_, err := s.GetMaster().Select(&userChannelIDs, sql, map[string]interface{}{"Since": since})
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.PendingAutoAddChannelMembers", "store.select_error", nil, "", http.StatusInternalServerError)
}
result.Data = userChannelIDs
return result
}
func groupSyncableToGroupTeam(groupSyncable *model.GroupSyncable) *groupTeam {
return &groupTeam{
GroupSyncable: *groupSyncable,
TeamId: groupSyncable.SyncableId,
}
}
func groupSyncableToGroupChannel(groupSyncable *model.GroupSyncable) *groupChannel {
return &groupChannel{
GroupSyncable: *groupSyncable,
ChannelId: groupSyncable.SyncableId,
}
}

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

@@ -93,6 +93,7 @@ type SqlSupplierOldStores struct {
role store.RoleStore
scheme store.SchemeStore
TermsOfService store.TermsOfServiceStore
group store.GroupStore
UserTermsOfService store.UserTermsOfServiceStore
}
@@ -148,6 +149,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
initSqlSupplierReactions(supplier)
initSqlSupplierRoles(supplier)
initSqlSupplierSchemes(supplier)
initSqlSupplierGroups(supplier)
err := supplier.GetMaster().CreateTablesIfNotExists()
if err != nil {
@@ -182,6 +184,8 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.TermsOfService.(SqlTermsOfServiceStore).CreateIndexesIfNotExists()
supplier.oldStores.UserTermsOfService.(SqlUserTermsOfServiceStore).CreateIndexesIfNotExists()
supplier.CreateIndexesIfNotExistsGroups()
supplier.oldStores.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
return supplier
@@ -1024,6 +1028,10 @@ func (ss *SqlSupplier) Scheme() store.SchemeStore {
return ss.oldStores.scheme
}
func (ss *SqlSupplier) Group() store.GroupStore {
return ss.oldStores.group
}
func (ss *SqlSupplier) DropAllTables() {
ss.master.TruncateTables()
}

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

@@ -532,6 +532,7 @@ func UpgradeDatabaseToVersion56(sqlStore SqlStore) {
sqlStore.RemoveIndexIfExists("idx_users_firstname_lower", "lower(FirstName)")
sqlStore.RemoveIndexIfExists("idx_users_lastname_lower", "lower(LastName)")
}
saveSchemaVersion(sqlStore, VERSION_5_6_0)
}

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

@@ -66,6 +66,7 @@ type Store interface {
ChannelMemberHistory() ChannelMemberHistoryStore
Plugin() PluginStore
TermsOfService() TermsOfServiceStore
Group() GroupStore
UserTermsOfService() UserTermsOfServiceStore
MarkSystemRanUnitTests()
Close()
@@ -137,6 +138,7 @@ type ChannelStore interface {
GetDeletedByName(team_id string, name string) StoreChannel
GetDeleted(team_id string, offset int, limit int) StoreChannel
GetChannels(teamId string, userId string, includeDeleted bool) StoreChannel
GetAllChannels(page, perPage int, includeDeleted bool) StoreChannel
GetMoreChannels(teamId string, userId string, offset int, limit int) StoreChannel
GetPublicChannelsForTeam(teamId string, offset int, limit int) StoreChannel
GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) StoreChannel
@@ -168,6 +170,7 @@ type ChannelStore interface {
GetMembersForUser(teamId string, userId string) StoreChannel
AutocompleteInTeam(teamId string, term string, includeDeleted bool) StoreChannel
AutocompleteInTeamForSearch(teamId string, userId string, term string, includeDeleted bool) StoreChannel
SearchAllChannels(term string, includeDeleted bool) StoreChannel
SearchInTeam(teamId string, term string, includeDeleted bool) StoreChannel
SearchMore(userId string, teamId string, term string) StoreChannel
GetMembersByIds(channelId string, userIds []string) StoreChannel
@@ -536,3 +539,27 @@ type UserTermsOfServiceStore interface {
Save(userTermsOfService *model.UserTermsOfService) StoreChannel
Delete(userId, termsOfServiceId string) StoreChannel
}
type GroupStore interface {
Create(group *model.Group) StoreChannel
Get(groupID string) StoreChannel
GetByRemoteID(remoteID string, groupSource model.GroupSource) StoreChannel
GetAllBySource(groupSource model.GroupSource) StoreChannel
Update(group *model.Group) StoreChannel
Delete(groupID string) StoreChannel
GetMemberUsers(groupID string) StoreChannel
GetMemberUsersPage(groupID string, offset int, limit int) StoreChannel
GetMemberCount(groupID string) StoreChannel
CreateOrRestoreMember(groupID string, userID string) StoreChannel
DeleteMember(groupID string, userID string) StoreChannel
CreateGroupSyncable(groupSyncable *model.GroupSyncable) StoreChannel
GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) StoreChannel
GetAllGroupSyncablesByGroupId(groupID string, syncableType model.GroupSyncableType) StoreChannel
UpdateGroupSyncable(groupSyncable *model.GroupSyncable) StoreChannel
DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) StoreChannel
PendingAutoAddTeamMembers(minGroupMembersCreateAt int64) StoreChannel
PendingAutoAddChannelMembers(minGroupMembersCreateAt int64) StoreChannel
}

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

@@ -21,6 +21,17 @@ type SqlSupplier interface {
GetMaster() *gorp.DbMap
}
func cleanupChannels(t *testing.T, ss store.Store) {
result := <-ss.Channel().GetAllChannels(0, 100000, true)
if result.Err != nil {
t.Fatal("error cleaning all channels")
}
list := result.Data.(*model.ChannelListWithTeamData)
for _, channel := range *list {
ss.Channel().PermanentDelete(channel.Id)
}
}
func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
createDefaultRoles(t, ss)
@@ -40,6 +51,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("ChannelMemberStore", func(t *testing.T) { testChannelMemberStore(t, ss) })
t.Run("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(t, ss) })
t.Run("GetChannels", func(t *testing.T) { testChannelStoreGetChannels(t, ss) })
t.Run("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss) })
t.Run("GetMoreChannels", func(t *testing.T) { testChannelStoreGetMoreChannels(t, ss) })
t.Run("GetPublicChannelsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsForTeam(t, ss) })
t.Run("GetPublicChannelsByIdsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsByIdsForTeam(t, ss) })
@@ -53,6 +65,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetMemberCount", func(t *testing.T) { testGetMemberCount(t, ss) })
t.Run("SearchMore", func(t *testing.T) { testChannelStoreSearchMore(t, ss) })
t.Run("SearchInTeam", func(t *testing.T) { testChannelStoreSearchInTeam(t, ss) })
t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) })
t.Run("AutocompleteInTeamForSearch", func(t *testing.T) { testChannelStoreAutocompleteInTeamForSearch(t, ss) })
t.Run("GetMembersByIds", func(t *testing.T) { testChannelStoreGetMembersByIds(t, ss) })
t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) })
@@ -959,6 +972,81 @@ func testChannelStoreGetChannels(t *testing.T, ss store.Store) {
ss.Channel().InvalidateAllChannelMembersForUser(m1.UserId)
}
func testChannelStoreGetAllChannels(t *testing.T, ss store.Store) {
cleanupChannels(t, ss)
t1 := model.Team{}
t1.DisplayName = "Name"
t1.Name = model.NewId()
t1.Email = MakeEmail()
t1.Type = model.TEAM_OPEN
store.Must(ss.Team().Save(&t1))
t2 := model.Team{}
t2.DisplayName = "Name2"
t2.Name = model.NewId()
t2.Email = MakeEmail()
t2.Type = model.TEAM_OPEN
store.Must(ss.Team().Save(&t2))
c1 := model.Channel{}
c1.TeamId = t1.Id
c1.DisplayName = "Channel1" + model.NewId()
c1.Name = "zz" + model.NewId() + "b"
c1.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&c1, -1))
c2 := model.Channel{}
c2.TeamId = t1.Id
c2.DisplayName = "Channel2" + model.NewId()
c2.Name = "zz" + model.NewId() + "b"
c2.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&c2, -1))
c2.DeleteAt = model.GetMillis()
c2.UpdateAt = c2.DeleteAt
store.Must(ss.Channel().Delete(c2.Id, c2.DeleteAt))
c3 := model.Channel{}
c3.TeamId = t2.Id
c3.DisplayName = "Channel3" + model.NewId()
c3.Name = "zz" + model.NewId() + "b"
c3.Type = model.CHANNEL_PRIVATE
store.Must(ss.Channel().Save(&c3, -1))
store.Must(ss.Channel().CreateDirectChannel(model.NewId(), model.NewId()))
userIds := []string{model.NewId(), model.NewId(), model.NewId()}
c5 := model.Channel{}
c5.Name = model.GetGroupNameFromUserIds(userIds)
c5.DisplayName = "GroupChannel" + model.NewId()
c5.Name = "zz" + model.NewId() + "b"
c5.Type = model.CHANNEL_GROUP
store.Must(ss.Channel().Save(&c5, -1))
cresult := <-ss.Channel().GetAllChannels(0, 10, false)
list := cresult.Data.(*model.ChannelListWithTeamData)
assert.Len(t, *list, 2)
assert.Equal(t, (*list)[0].Id, c1.Id)
assert.Equal(t, (*list)[0].TeamDisplayName, "Name")
assert.Equal(t, (*list)[1].Id, c3.Id)
assert.Equal(t, (*list)[1].TeamDisplayName, "Name2")
cresult = <-ss.Channel().GetAllChannels(0, 10, true)
list = cresult.Data.(*model.ChannelListWithTeamData)
assert.Len(t, *list, 3)
assert.Equal(t, (*list)[0].Id, c1.Id)
assert.Equal(t, (*list)[0].TeamDisplayName, "Name")
assert.Equal(t, (*list)[1].Id, c2.Id)
assert.Equal(t, (*list)[2].Id, c3.Id)
cresult = <-ss.Channel().GetAllChannels(0, 1, true)
list = cresult.Data.(*model.ChannelListWithTeamData)
assert.Len(t, *list, 1)
assert.Equal(t, (*list)[0].Id, c1.Id)
assert.Equal(t, (*list)[0].TeamDisplayName, "Name")
}
func testChannelStoreGetMoreChannels(t *testing.T, ss store.Store) {
teamId := model.NewId()
otherTeamId := model.NewId()
@@ -2006,6 +2094,184 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) {
}
}
func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) {
cleanupChannels(t, ss)
t1 := model.Team{}
t1.DisplayName = "Name"
t1.Name = model.NewId()
t1.Email = MakeEmail()
t1.Type = model.TEAM_OPEN
store.Must(ss.Team().Save(&t1))
t2 := model.Team{}
t2.DisplayName = "Name2"
t2.Name = model.NewId()
t2.Email = MakeEmail()
t2.Type = model.TEAM_OPEN
store.Must(ss.Team().Save(&t2))
o1 := model.Channel{
TeamId: t1.Id,
DisplayName: "ChannelA",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o1, -1))
o2 := model.Channel{
TeamId: t2.Id,
DisplayName: "ChannelA",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o2, -1))
m1 := model.ChannelMember{
ChannelId: o1.Id,
UserId: model.NewId(),
NotifyProps: model.GetDefaultChannelNotifyProps(),
}
store.Must(ss.Channel().SaveMember(&m1))
m2 := model.ChannelMember{
ChannelId: o1.Id,
UserId: model.NewId(),
NotifyProps: model.GetDefaultChannelNotifyProps(),
}
store.Must(ss.Channel().SaveMember(&m2))
m3 := model.ChannelMember{
ChannelId: o2.Id,
UserId: model.NewId(),
NotifyProps: model.GetDefaultChannelNotifyProps(),
}
store.Must(ss.Channel().SaveMember(&m3))
o3 := model.Channel{
TeamId: t1.Id,
DisplayName: "ChannelA (alternate)",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o3, -1))
o4 := model.Channel{
TeamId: t1.Id,
DisplayName: "ChannelB",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_PRIVATE,
}
store.Must(ss.Channel().Save(&o4, -1))
o5 := model.Channel{
TeamId: t1.Id,
DisplayName: "ChannelC",
Name: "zz" + model.NewId() + "b",
Type: model.CHANNEL_PRIVATE,
}
store.Must(ss.Channel().Save(&o5, -1))
o6 := model.Channel{
TeamId: t1.Id,
DisplayName: "Off-Topic",
Name: "off-topic",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o6, -1))
o7 := model.Channel{
TeamId: t1.Id,
DisplayName: "Off-Set",
Name: "off-set",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o7, -1))
o8 := model.Channel{
TeamId: t1.Id,
DisplayName: "Off-Limit",
Name: "off-limit",
Type: model.CHANNEL_PRIVATE,
}
store.Must(ss.Channel().Save(&o8, -1))
o9 := model.Channel{
TeamId: t1.Id,
DisplayName: "Town Square",
Name: "town-square",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o9, -1))
o10 := model.Channel{
TeamId: t1.Id,
DisplayName: "The",
Name: "the",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o10, -1))
o11 := model.Channel{
TeamId: t1.Id,
DisplayName: "Native Mobile Apps",
Name: "native-mobile-apps",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o11, -1))
o12 := model.Channel{
TeamId: t1.Id,
DisplayName: "ChannelZ",
Purpose: "This can now be searchable!",
Name: "with-purpose",
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o12, -1))
o13 := model.Channel{
TeamId: t1.Id,
DisplayName: "ChannelA (deleted)",
Name: model.NewId(),
Type: model.CHANNEL_OPEN,
}
store.Must(ss.Channel().Save(&o13, -1))
o13.DeleteAt = model.GetMillis()
o13.UpdateAt = o13.DeleteAt
store.Must(ss.Channel().Delete(o13.Id, o13.DeleteAt))
testCases := []struct {
Description string
Term string
IncludeDeleted bool
ExpectedResults *model.ChannelList
}{
{"ChannelA", "ChannelA", false, &model.ChannelList{&o1, &o2, &o3}},
{"ChannelA, include deleted", "ChannelA", true, &model.ChannelList{&o1, &o2, &o3, &o13}},
{"empty string", "", false, &model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o12, &o11, &o8, &o7, &o6, &o10, &o9}},
{"no matches", "blargh", false, &model.ChannelList{}},
{"prefix", "off-", false, &model.ChannelList{&o8, &o7, &o6}},
{"full match with dash", "off-topic", false, &model.ChannelList{&o6}},
{"town square", "town square", false, &model.ChannelList{&o9}},
{"the in name", "the", false, &model.ChannelList{&o10}},
{"Mobile", "Mobile", false, &model.ChannelList{&o11}},
{"search purpose", "now searchable", false, &model.ChannelList{&o12}},
{"pipe ignored", "town square |", false, &model.ChannelList{&o9}},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
result := <-ss.Channel().SearchAllChannels(testCase.Term, testCase.IncludeDeleted)
require.Nil(t, result.Err)
channels := result.Data.(*model.ChannelListWithTeamData)
require.Equal(t, len(*channels), len(*testCase.ExpectedResults))
for i, expected := range *testCase.ExpectedResults {
require.Equal(t, (*channels)[i].Id, expected.Id)
}
})
}
}
func testChannelStoreAutocompleteInTeamForSearch(t *testing.T, ss store.Store) {
u1 := &model.User{}
u1.Email = MakeEmail()

1318
store/storetest/group_supplier.go Обычный файл

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@@ -194,6 +194,22 @@ func (_m *ChannelStore) GetAllChannelMembersNotifyPropsForChannel(channelId stri
return r0
}
// GetAllChannels provides a mock function with given fields: page, perPage, includeDeleted
func (_m *ChannelStore) GetAllChannels(page int, perPage int, includeDeleted bool) store.StoreChannel {
ret := _m.Called(page, perPage, includeDeleted)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int, int, bool) store.StoreChannel); ok {
r0 = rf(page, perPage, includeDeleted)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetAllChannelsForExportAfter provides a mock function with given fields: limit, afterId
func (_m *ChannelStore) GetAllChannelsForExportAfter(limit int, afterId string) store.StoreChannel {
ret := _m.Called(limit, afterId)
@@ -885,6 +901,22 @@ func (_m *ChannelStore) SaveMember(member *model.ChannelMember) store.StoreChann
return r0
}
// SearchAllChannels provides a mock function with given fields: term, includeDeleted
func (_m *ChannelStore) SearchAllChannels(term string, includeDeleted bool) store.StoreChannel {
ret := _m.Called(term, includeDeleted)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, bool) store.StoreChannel); ok {
r0 = rf(term, includeDeleted)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// SearchInTeam provides a mock function with given fields: teamId, term, includeDeleted
func (_m *ChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel {
ret := _m.Called(teamId, term, includeDeleted)

302
store/storetest/mocks/GroupStore.go Обычный файл
Просмотреть файл

@@ -0,0 +1,302 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import mock "github.com/stretchr/testify/mock"
import model "github.com/mattermost/mattermost-server/model"
import store "github.com/mattermost/mattermost-server/store"
// GroupStore is an autogenerated mock type for the GroupStore type
type GroupStore struct {
mock.Mock
}
// Create provides a mock function with given fields: group
func (_m *GroupStore) Create(group *model.Group) store.StoreChannel {
ret := _m.Called(group)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.Group) store.StoreChannel); ok {
r0 = rf(group)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// CreateGroupSyncable provides a mock function with given fields: groupSyncable
func (_m *GroupStore) CreateGroupSyncable(groupSyncable *model.GroupSyncable) store.StoreChannel {
ret := _m.Called(groupSyncable)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.GroupSyncable) store.StoreChannel); ok {
r0 = rf(groupSyncable)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// CreateOrRestoreMember provides a mock function with given fields: groupID, userID
func (_m *GroupStore) CreateOrRestoreMember(groupID string, userID string) store.StoreChannel {
ret := _m.Called(groupID, userID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok {
r0 = rf(groupID, userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Delete provides a mock function with given fields: groupID
func (_m *GroupStore) Delete(groupID string) store.StoreChannel {
ret := _m.Called(groupID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(groupID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// DeleteGroupSyncable provides a mock function with given fields: groupID, syncableID, syncableType
func (_m *GroupStore) DeleteGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) store.StoreChannel {
ret := _m.Called(groupID, syncableID, syncableType)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) store.StoreChannel); ok {
r0 = rf(groupID, syncableID, syncableType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// DeleteMember provides a mock function with given fields: groupID, userID
func (_m *GroupStore) DeleteMember(groupID string, userID string) store.StoreChannel {
ret := _m.Called(groupID, userID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok {
r0 = rf(groupID, userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Get provides a mock function with given fields: groupID
func (_m *GroupStore) Get(groupID string) store.StoreChannel {
ret := _m.Called(groupID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(groupID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetAllBySource provides a mock function with given fields: groupSource
func (_m *GroupStore) GetAllBySource(groupSource model.GroupSource) store.StoreChannel {
ret := _m.Called(groupSource)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(model.GroupSource) store.StoreChannel); ok {
r0 = rf(groupSource)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetAllGroupSyncablesByGroupId provides a mock function with given fields: groupID, syncableType
func (_m *GroupStore) GetAllGroupSyncablesByGroupId(groupID string, syncableType model.GroupSyncableType) store.StoreChannel {
ret := _m.Called(groupID, syncableType)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, model.GroupSyncableType) store.StoreChannel); ok {
r0 = rf(groupID, syncableType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetByRemoteID provides a mock function with given fields: remoteID, groupSource
func (_m *GroupStore) GetByRemoteID(remoteID string, groupSource model.GroupSource) store.StoreChannel {
ret := _m.Called(remoteID, groupSource)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, model.GroupSource) store.StoreChannel); ok {
r0 = rf(remoteID, groupSource)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetGroupSyncable provides a mock function with given fields: groupID, syncableID, syncableType
func (_m *GroupStore) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) store.StoreChannel {
ret := _m.Called(groupID, syncableID, syncableType)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) store.StoreChannel); ok {
r0 = rf(groupID, syncableID, syncableType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetMemberCount provides a mock function with given fields: groupID
func (_m *GroupStore) GetMemberCount(groupID string) store.StoreChannel {
ret := _m.Called(groupID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(groupID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetMemberUsers provides a mock function with given fields: groupID
func (_m *GroupStore) GetMemberUsers(groupID string) store.StoreChannel {
ret := _m.Called(groupID)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
r0 = rf(groupID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetMemberUsersPage provides a mock function with given fields: groupID, offset, limit
func (_m *GroupStore) GetMemberUsersPage(groupID string, offset int, limit int) store.StoreChannel {
ret := _m.Called(groupID, offset, limit)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, int, int) store.StoreChannel); ok {
r0 = rf(groupID, offset, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// PendingAutoAddChannelMembers provides a mock function with given fields: minGroupMembersCreateAt
func (_m *GroupStore) PendingAutoAddChannelMembers(minGroupMembersCreateAt int64) store.StoreChannel {
ret := _m.Called(minGroupMembersCreateAt)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int64) store.StoreChannel); ok {
r0 = rf(minGroupMembersCreateAt)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// PendingAutoAddTeamMembers provides a mock function with given fields: minGroupMembersCreateAt
func (_m *GroupStore) PendingAutoAddTeamMembers(minGroupMembersCreateAt int64) store.StoreChannel {
ret := _m.Called(minGroupMembersCreateAt)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int64) store.StoreChannel); ok {
r0 = rf(minGroupMembersCreateAt)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// Update provides a mock function with given fields: group
func (_m *GroupStore) Update(group *model.Group) store.StoreChannel {
ret := _m.Called(group)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.Group) store.StoreChannel); ok {
r0 = rf(group)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// UpdateGroupSyncable provides a mock function with given fields: groupSyncable
func (_m *GroupStore) UpdateGroupSyncable(groupSyncable *model.GroupSyncable) store.StoreChannel {
ret := _m.Called(groupSyncable)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(*model.GroupSyncable) store.StoreChannel); ok {
r0 = rf(groupSyncable)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}

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

@@ -168,6 +168,390 @@ func (_m *LayeredStoreDatabaseLayer) FileInfo() store.FileInfoStore {
return r0
}
// Group provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Group() store.GroupStore {
ret := _m.Called()
var r0 store.GroupStore
if rf, ok := ret.Get(0).(func() store.GroupStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.GroupStore)
}
}
return r0
}
// GroupCreate provides a mock function with given fields: ctx, group, hints
func (_m *LayeredStoreDatabaseLayer) GroupCreate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, group)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.Group, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, group, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupCreateGroupSyncable provides a mock function with given fields: ctx, groupSyncable, hints
func (_m *LayeredStoreDatabaseLayer) GroupCreateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupSyncable)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.GroupSyncable, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupSyncable, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupCreateOrRestoreMember provides a mock function with given fields: ctx, groupID, userID, hints
func (_m *LayeredStoreDatabaseLayer) GroupCreateOrRestoreMember(ctx context.Context, groupID string, userID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, userID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, userID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupDelete provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreDatabaseLayer) GroupDelete(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupDeleteGroupSyncable provides a mock function with given fields: ctx, groupID, syncableID, syncableType, hints
func (_m *LayeredStoreDatabaseLayer) GroupDeleteGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, syncableID, syncableType)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, model.GroupSyncableType, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, syncableID, syncableType, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupDeleteMember provides a mock function with given fields: ctx, groupID, userID, hints
func (_m *LayeredStoreDatabaseLayer) GroupDeleteMember(ctx context.Context, groupID string, userID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, userID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, userID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGet provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreDatabaseLayer) GroupGet(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetAllBySource provides a mock function with given fields: ctx, groupSource, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetAllBySource(ctx context.Context, groupSource model.GroupSource, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupSource)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, model.GroupSource, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupSource, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetAllGroupSyncablesByGroup provides a mock function with given fields: ctx, groupID, syncableType, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetAllGroupSyncablesByGroup(ctx context.Context, groupID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, syncableType)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSyncableType, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, syncableType, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetByRemoteID provides a mock function with given fields: ctx, remoteID, groupSource, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetByRemoteID(ctx context.Context, remoteID string, groupSource model.GroupSource, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, remoteID, groupSource)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSource, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, remoteID, groupSource, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetGroupSyncable provides a mock function with given fields: ctx, groupID, syncableID, syncableType, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, syncableID, syncableType)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, model.GroupSyncableType, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, syncableID, syncableType, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetMemberCount provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetMemberCount(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetMemberUsers provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetMemberUsers(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetMemberUsersPage provides a mock function with given fields: ctx, groupID, offset, limit, hints
func (_m *LayeredStoreDatabaseLayer) GroupGetMemberUsersPage(ctx context.Context, groupID string, offset int, limit int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, offset, limit)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, offset, limit, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupUpdate provides a mock function with given fields: ctx, group, hints
func (_m *LayeredStoreDatabaseLayer) GroupUpdate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, group)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.Group, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, group, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupUpdateGroupSyncable provides a mock function with given fields: ctx, groupSyncable, hints
func (_m *LayeredStoreDatabaseLayer) GroupUpdateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupSyncable)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.GroupSyncable, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupSyncable, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// Job provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Job() store.JobStore {
ret := _m.Called()
@@ -242,6 +626,52 @@ func (_m *LayeredStoreDatabaseLayer) OAuth() store.OAuthStore {
return r0
}
// PendingAutoAddChannelMembers provides a mock function with given fields: ctx, minGroupMembersCreateAt, hints
func (_m *LayeredStoreDatabaseLayer) PendingAutoAddChannelMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, minGroupMembersCreateAt)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, int64, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, minGroupMembersCreateAt, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// PendingAutoAddTeamMembers provides a mock function with given fields: ctx, minGroupMembersCreateAt, hints
func (_m *LayeredStoreDatabaseLayer) PendingAutoAddTeamMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, minGroupMembersCreateAt)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, int64, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, minGroupMembersCreateAt, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// Plugin provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Plugin() store.PluginStore {
ret := _m.Called()

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

@@ -14,6 +14,374 @@ type LayeredStoreSupplier struct {
mock.Mock
}
// GroupCreate provides a mock function with given fields: ctx, group, hints
func (_m *LayeredStoreSupplier) GroupCreate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, group)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.Group, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, group, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupCreateGroupSyncable provides a mock function with given fields: ctx, groupSyncable, hints
func (_m *LayeredStoreSupplier) GroupCreateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupSyncable)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.GroupSyncable, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupSyncable, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupCreateOrRestoreMember provides a mock function with given fields: ctx, groupID, userID, hints
func (_m *LayeredStoreSupplier) GroupCreateOrRestoreMember(ctx context.Context, groupID string, userID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, userID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, userID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupDelete provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreSupplier) GroupDelete(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupDeleteGroupSyncable provides a mock function with given fields: ctx, groupID, syncableID, syncableType, hints
func (_m *LayeredStoreSupplier) GroupDeleteGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, syncableID, syncableType)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, model.GroupSyncableType, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, syncableID, syncableType, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupDeleteMember provides a mock function with given fields: ctx, groupID, userID, hints
func (_m *LayeredStoreSupplier) GroupDeleteMember(ctx context.Context, groupID string, userID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, userID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, userID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGet provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreSupplier) GroupGet(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetAllBySource provides a mock function with given fields: ctx, groupSource, hints
func (_m *LayeredStoreSupplier) GroupGetAllBySource(ctx context.Context, groupSource model.GroupSource, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupSource)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, model.GroupSource, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupSource, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetAllGroupSyncablesByGroup provides a mock function with given fields: ctx, groupID, syncableType, hints
func (_m *LayeredStoreSupplier) GroupGetAllGroupSyncablesByGroup(ctx context.Context, groupID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, syncableType)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSyncableType, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, syncableType, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetByRemoteID provides a mock function with given fields: ctx, remoteID, groupSource, hints
func (_m *LayeredStoreSupplier) GroupGetByRemoteID(ctx context.Context, remoteID string, groupSource model.GroupSource, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, remoteID, groupSource)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSource, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, remoteID, groupSource, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetGroupSyncable provides a mock function with given fields: ctx, groupID, syncableID, syncableType, hints
func (_m *LayeredStoreSupplier) GroupGetGroupSyncable(ctx context.Context, groupID string, syncableID string, syncableType model.GroupSyncableType, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, syncableID, syncableType)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, string, model.GroupSyncableType, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, syncableID, syncableType, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetMemberCount provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreSupplier) GroupGetMemberCount(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetMemberUsers provides a mock function with given fields: ctx, groupID, hints
func (_m *LayeredStoreSupplier) GroupGetMemberUsers(ctx context.Context, groupID string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupGetMemberUsersPage provides a mock function with given fields: ctx, groupID, offset, limit, hints
func (_m *LayeredStoreSupplier) GroupGetMemberUsersPage(ctx context.Context, groupID string, offset int, limit int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupID, offset, limit)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupID, offset, limit, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupUpdate provides a mock function with given fields: ctx, group, hints
func (_m *LayeredStoreSupplier) GroupUpdate(ctx context.Context, group *model.Group, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, group)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.Group, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, group, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// GroupUpdateGroupSyncable provides a mock function with given fields: ctx, groupSyncable, hints
func (_m *LayeredStoreSupplier) GroupUpdateGroupSyncable(ctx context.Context, groupSyncable *model.GroupSyncable, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, groupSyncable)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, *model.GroupSyncable, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, groupSyncable, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// Next provides a mock function with given fields:
func (_m *LayeredStoreSupplier) Next() store.LayeredStoreSupplier {
ret := _m.Called()
@@ -30,6 +398,52 @@ func (_m *LayeredStoreSupplier) Next() store.LayeredStoreSupplier {
return r0
}
// PendingAutoAddChannelMembers provides a mock function with given fields: ctx, minGroupMembersCreateAt, hints
func (_m *LayeredStoreSupplier) PendingAutoAddChannelMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, minGroupMembersCreateAt)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, int64, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, minGroupMembersCreateAt, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// PendingAutoAddTeamMembers provides a mock function with given fields: ctx, minGroupMembersCreateAt, hints
func (_m *LayeredStoreSupplier) PendingAutoAddTeamMembers(ctx context.Context, minGroupMembersCreateAt int64, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, minGroupMembersCreateAt)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, int64, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, minGroupMembersCreateAt, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// ReactionDelete provides a mock function with given fields: ctx, reaction, hints
func (_m *LayeredStoreSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))

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

@@ -166,6 +166,22 @@ func (_m *Store) FileInfo() store.FileInfoStore {
return r0
}
// Group provides a mock function with given fields:
func (_m *Store) Group() store.GroupStore {
ret := _m.Called()
var r0 store.GroupStore
if rf, ok := ret.Get(0).(func() store.GroupStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.GroupStore)
}
}
return r0
}
// Job provides a mock function with given fields:
func (_m *Store) Job() store.JobStore {
ret := _m.Called()

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

@@ -46,6 +46,7 @@ type Store struct {
RoleStore mocks.RoleStore
SchemeStore mocks.SchemeStore
TermsOfServiceStore mocks.TermsOfServiceStore
GroupStore mocks.GroupStore
UserTermsOfServiceStore mocks.UserTermsOfServiceStore
}
@@ -79,6 +80,7 @@ func (s *Store) UserTermsOfService() store.UserTermsOfServiceStore { return &s.U
func (s *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore {
return &s.ChannelMemberHistoryStore
}
func (s *Store) Group() store.GroupStore { return &s.GroupStore }
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
func (s *Store) Close() { /* do nothing */ }
func (s *Store) LockToMaster() { /* do nothing */ }

81
tests/add-groups.ldif Обычный файл
Просмотреть файл

@@ -0,0 +1,81 @@
dn: ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: organizationalunit
# groupOfNames
dn: cn=outsiders,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=board.three,ou=testusers,dc=mm,dc=test,dc=com
dn: cn=board,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=board.one,ou=testusers,dc=mm,dc=test,dc=com
member: uid=board.two,ou=testusers,dc=mm,dc=test,dc=com
member: cn=outsiders,ou=testgroups,dc=mm,dc=test,dc=com
dn: cn=executive,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=exec.one,ou=testusers,dc=mm,dc=test,dc=com
member: uid=exec.two,ou=testusers,dc=mm,dc=test,dc=com
member: cn=board,ou=testgroups,dc=mm,dc=test,dc=com
dn: cn=tgroup-84,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: cn=tgroup-9,ou=testgroups,dc=mm,dc=test,dc=com
member: uid=test.five,ou=testusers,dc=mm,dc=test,dc=com
dn: cn=tgroup-9,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: cn=tgroup-97,ou=testgroups,dc=mm,dc=test,dc=com
dn: cn=tgroup-97,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=test.four,ou=testusers,dc=mm,dc=test,dc=com
# groupOfUniqueNames
dn: cn=tgroup,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com
dn: cn=ugroup,cn=tgroup,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=test.two,ou=testusers,dc=mm,dc=test,dc=com
dn: cn=vgroup,cn=tgroup,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=test.three,ou=testusers,dc=mm,dc=test,dc=com
# Adds a group with a cycle
dn: cn=team-one-a,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=dev.four,ou=testusers,dc=mm,dc=test,dc=com
uniqueMember: cn=developers,ou=testgroups,dc=mm,dc=test,dc=com
dn: cn=team-one,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=dev.one,ou=testusers,dc=mm,dc=test,dc=com
uniqueMember: uid=dev.three,ou=testusers,dc=mm,dc=test,dc=com
uniqueMember: cn=team-one-a,ou=testgroups,dc=mm,dc=test,dc=com
dn: cn=team-two,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=dev.two,ou=testusers,dc=mm,dc=test,dc=com
dn: cn=developers,ou=testgroups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfUniqueNames
uniqueMember: uid=dev-ops.one,ou=testusers,dc=mm,dc=test,dc=com
uniqueMember: cn=team-one,ou=testgroups,dc=mm,dc=test,dc=com
uniqueMember: cn=team-two,ou=testgroups,dc=mm,dc=test,dc=com

130
tests/add-users.ldif Обычный файл
Просмотреть файл

@@ -0,0 +1,130 @@
dn: ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: organizationalunit
# generic test users
dn: uid=test.one,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Test1
mail: success+testone@simulator.amazonses.com
userPassword: Password1
dn: uid=test.two,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Test2
mail: success+testtwo@simulator.amazonses.com
userPassword: Password1
dn: uid=test.three,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Test3
mail: success+testthree@simulator.amazonses.com
userPassword: Password1
dn: uid=test.four,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Test4
mail: success+testfour@simulator.amazonses.com
userPassword: Password1
dn: uid=test.five,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Test5
mail: success+testfive@simulator.amazonses.com
userPassword: Password1
# developers
dn: uid=dev-ops.one,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Dev3
mail: success+devopsone@simulator.amazonses.com
userPassword: Password1
dn: uid=dev.one,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Dev1
mail: success+devone@simulator.amazonses.com
userPassword: Password1
dn: uid=dev.two,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Dev2
mail: success+devtwo@simulator.amazonses.com
userPassword: Password1
dn: uid=dev.three,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Dev3
mail: success+devthree@simulator.amazonses.com
userPassword: Password1
dn: uid=dev.four,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Dev4
mail: success+devfour@simulator.amazonses.com
userPassword: Password1
# executive
dn: uid=exec.one,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Exec1
mail: success+execone@simulator.amazonses.com
userPassword: Password1
dn: uid=exec.two,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Exec2
mail: success+exectwo@simulator.amazonses.com
userPassword: Password1
# board of directors
dn: uid=board.one,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Board1
mail: success+boardone@simulator.amazonses.com
userPassword: Password1
dn: uid=board.two,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Board2
mail: success+boardtwo@simulator.amazonses.com
userPassword: Password1
dn: uid=board.three,ou=testusers,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
sn: User
cn: Board3
mail: success+boardthree@simulator.amazonses.com
userPassword: Password1

Двоичные данные
tests/qa-data-graph.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 40 KiB

103
tests/qa-data.ldif Обычный файл
Просмотреть файл

@@ -0,0 +1,103 @@
dn: ou=groups,dc=mm,dc=test,dc=com
changetype: add
objectclass: organizationalunit
dn: ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: organizationalunit
dn: uid=corey.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: Corey
sn: Test
mail: corey.test@simulator.amazonses.com
userPassword: Password1
dn: uid=william.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: William
sn: Test
mail: william.test@simulator.amazonses.com
userPassword: Password1
dn: uid=linda.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: Linda
sn: Test
mail: linda.test@simulator.amazonses.com
userPassword: Password1
dn: uid=lindy.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: Lindy
sn: Test
mail: lindy.test@simulator.amazonses.com
userPassword: Password1
dn: uid=george.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: George
sn: Test
mail: george.test@simulator.amazonses.com
userPassword: Password1
dn: uid=jesus.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: Jesus
sn: Test
mail: jesus.test@simulator.amazonses.com
userPassword: Password1
dn: uid=carlos.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: Carlos
sn: Test
mail: carlos.test@simulator.amazonses.com
userPassword: Password1
dn: uid=jesse.test,ou=users,dc=mm,dc=test,dc=com
changetype: add
objectclass: iNetOrgPerson
cn: Jesse
sn: Test
mail: jesse.test@simulator.amazonses.com
userPassword: Password1
dn: cn=engineering,ou=groups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=corey.test,ou=users,dc=mm,dc=test,dc=com
member: cn=qa,ou=groups,dc=mm,dc=test,dc=com
member: cn=developers,ou=groups,dc=mm,dc=test,dc=com
dn: cn=qa,ou=groups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=lindy.test,ou=users,dc=mm,dc=test,dc=com
member: uid=linda.test,ou=users,dc=mm,dc=test,dc=com
dn: cn=developers,ou=groups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=william.test,ou=users,dc=mm,dc=test,dc=com
member: cn=xyz,ou=groups,dc=mm,dc=test,dc=com
member: cn=abc,ou=groups,dc=mm,dc=test,dc=com
dn: cn=xyz,ou=groups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=jesus.test,ou=users,dc=mm,dc=test,dc=com
member: uid=george.test,ou=users,dc=mm,dc=test,dc=com
dn: cn=abc,ou=groups,dc=mm,dc=test,dc=com
changetype: add
objectclass: groupOfNames
member: uid=carlos.test,ou=users,dc=mm,dc=test,dc=com
member: uid=jesse.test,ou=users,dc=mm,dc=test,dc=com

402
tests/test-config.json Обычный файл
Просмотреть файл

@@ -0,0 +1,402 @@
{
"ServiceSettings": {
"SiteURL": "",
"WebsocketURL": "",
"LicenseFileLocation": "",
"ListenAddress": ":8065",
"ConnectionSecurity": "",
"TLSCertFile": "",
"TLSKeyFile": "",
"UseLetsEncrypt": false,
"LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache",
"Forward80To443": false,
"ReadTimeout": 300,
"WriteTimeout": 300,
"MaximumLoginAttempts": 10,
"GoroutineHealthThreshold": -1,
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
"EnableIncomingWebhooks": true,
"EnableOutgoingWebhooks": true,
"EnableCommands": true,
"EnableOnlyAdminIntegrations": true,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
"EnableLinkPreviews": false,
"EnableTesting": false,
"EnableDeveloper": false,
"EnableSecurityFixAlert": true,
"EnableInsecureOutgoingConnections": false,
"AllowedUntrustedInternalConnections": "",
"EnableMultifactorAuthentication": false,
"EnforceMultifactorAuthentication": false,
"EnableUserAccessTokens": false,
"AllowCorsFrom": "",
"AllowCookiesForSubdomains": false,
"SessionLengthWebInDays": 30,
"SessionLengthMobileInDays": 30,
"SessionLengthSSOInDays": 30,
"SessionCacheInMinutes": 10,
"SessionIdleTimeoutInMinutes": 0,
"WebsocketSecurePort": 443,
"WebsocketPort": 80,
"WebserverMode": "gzip",
"EnableCustomEmoji": false,
"EnableEmojiPicker": true,
"RestrictCustomEmojiCreation": "all",
"RestrictPostDelete": "all",
"AllowEditPost": "always",
"PostEditTimeLimit": -1,
"TimeBetweenUserTypingUpdatesMilliseconds": 5000,
"EnablePostSearch": true,
"EnableUserTypingMessages": true,
"EnableChannelViewedMessages": true,
"EnableUserStatuses": true,
"ExperimentalEnableAuthenticationTransfer": true,
"ClusterLogTimeoutMilliseconds": 2000,
"CloseUnusedDirectMessages": false,
"EnablePreviewFeatures": true,
"EnableTutorial": true,
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
"ExperimentalGroupUnreadChannels": "disabled",
"ImageProxyType": "",
"ImageProxyURL": "",
"ImageProxyOptions": "",
"EnableAPITeamDeletion": false,
"ExperimentalEnableHardenedMode": false,
"ExperimentalLdapGroupSync": true
},
"TeamSettings": {
"SiteName": "Mattermost",
"MaxUsersPerTeam": 50,
"EnableTeamCreation": true,
"EnableUserCreation": true,
"EnableOpenServer": false,
"EnableUserDeactivation": false,
"RestrictCreationToDomains": "",
"EnableCustomBrand": false,
"CustomBrandText": "",
"CustomDescriptionText": "",
"RestrictDirectMessage": "any",
"RestrictTeamInvite": "all",
"RestrictPublicChannelManagement": "all",
"RestrictPrivateChannelManagement": "all",
"RestrictPublicChannelCreation": "all",
"RestrictPrivateChannelCreation": "all",
"RestrictPublicChannelDeletion": "all",
"RestrictPrivateChannelDeletion": "all",
"RestrictPrivateChannelManageMembers": "all",
"EnableXToLeaveChannelsFromLHS": false,
"UserStatusAwayTimeout": 300,
"MaxChannelsPerTeam": 2000,
"MaxNotificationsPerChannel": 1000,
"EnableConfirmNotificationsToChannel": true,
"TeammateNameDisplay": "username",
"ExperimentalEnableAutomaticReplies": false,
"ExperimentalHideTownSquareinLHS": false,
"ExperimentalTownSquareIsReadOnly": false,
"ExperimentalPrimaryTeam": ""
},
"ClientRequirements": {
"AndroidLatestVersion": "",
"AndroidMinVersion": "",
"DesktopLatestVersion": "",
"DesktopMinVersion": "",
"IosLatestVersion": "",
"IosMinVersion": ""
},
"SqlSettings": {
"DriverName": "mysql",
"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s",
"DataSourceReplicas": [],
"DataSourceSearchReplicas": [],
"MaxIdleConns": 20,
"MaxOpenConns": 300,
"Trace": false,
"AtRestEncryptKey": "jdh9iergmse3w9mt53snasugmmi9r6it",
"QueryTimeout": 30
},
"LogSettings": {
"EnableConsole": true,
"ConsoleLevel": "DEBUG",
"ConsoleJson": true,
"EnableFile": true,
"FileLevel": "INFO",
"FileJson": true,
"FileLocation": "",
"EnableWebhookDebugging": true,
"EnableDiagnostics": true
},
"PasswordSettings": {
"MinimumLength": 5,
"Lowercase": false,
"Number": false,
"Uppercase": false,
"Symbol": false
},
"FileSettings": {
"EnableFileAttachments": true,
"EnableMobileUpload": true,
"EnableMobileDownload": true,
"MaxFileSize": 52428800,
"DriverName": "local",
"Directory": "./data/",
"EnablePublicLink": false,
"PublicLinkSalt": "3xh7ztscuezjp1jkdjybtejrtw59xjt1",
"InitialFont": "luximbi.ttf",
"AmazonS3AccessKeyId": "",
"AmazonS3SecretAccessKey": "",
"AmazonS3Bucket": "",
"AmazonS3Region": "",
"AmazonS3Endpoint": "s3.amazonaws.com",
"AmazonS3SSL": true,
"AmazonS3SignV2": false,
"AmazonS3SSE": false,
"AmazonS3Trace": false
},
"EmailSettings": {
"EnableSignUpWithEmail": true,
"EnableSignInWithEmail": true,
"EnableSignInWithUsername": true,
"SendEmailNotifications": true,
"UseChannelInEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
"FeedbackEmail": "test@example.com",
"FeedbackOrganization": "",
"EnableSMTPAuth": false,
"SMTPUsername": "",
"SMTPPassword": "",
"SMTPServer": "dockerhost",
"SMTPPort": "2500",
"ConnectionSecurity": "",
"InviteSalt": "n3mceqsek4j5ichs5hw9sudwx3cfbtqa",
"SendPushNotifications": false,
"PushNotificationServer": "",
"PushNotificationContents": "generic",
"EnableEmailBatching": false,
"EmailBatchingBufferSize": 256,
"EmailBatchingInterval": 30,
"EnablePreviewModeBanner": true,
"SkipServerCertificateVerification": false,
"EmailNotificationContentsType": "full",
"LoginButtonColor": "",
"LoginButtonBorderColor": "",
"LoginButtonTextColor": ""
},
"RateLimitSettings": {
"Enable": false,
"PerSec": 10,
"MaxBurst": 100,
"MemoryStoreSize": 10000,
"VaryByRemoteAddr": true,
"VaryByUser": false,
"VaryByHeader": ""
},
"PrivacySettings": {
"ShowEmailAddress": true,
"ShowFullName": true
},
"SupportSettings": {
"TermsOfServiceLink": "https://about.mattermost.com/default-terms/",
"PrivacyPolicyLink": "https://about.mattermost.com/default-privacy-policy/",
"AboutLink": "https://about.mattermost.com/default-about/",
"HelpLink": "https://about.mattermost.com/default-help/",
"ReportAProblemLink": "https://about.mattermost.com/default-report-a-problem/",
"SupportEmail": "feedback@mattermost.com"
},
"AnnouncementSettings": {
"EnableBanner": false,
"BannerText": "",
"BannerColor": "#f2a93b",
"BannerTextColor": "#333333",
"AllowBannerDismissal": true
},
"ThemeSettings": {
"EnableThemeSelection": true,
"DefaultTheme": "default",
"AllowCustomThemes": true,
"AllowedThemes": []
},
"GitLabSettings": {
"Enable": false,
"Secret": "",
"Id": "",
"Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
},
"GoogleSettings": {
"Enable": false,
"Secret": "",
"Id": "",
"Scope": "profile email",
"AuthEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"TokenEndpoint": "https://www.googleapis.com/oauth2/v4/token",
"UserApiEndpoint": "https://www.googleapis.com/plus/v1/people/me"
},
"Office365Settings": {
"Enable": false,
"Secret": "",
"Id": "",
"Scope": "User.Read",
"AuthEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
"TokenEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"UserApiEndpoint": "https://graph.microsoft.com/v1.0/me"
},
"LdapSettings": {
"Enable": false,
"EnableSync": false,
"LdapServer": "",
"LdapPort": 389,
"ConnectionSecurity": "",
"BaseDN": "",
"BindUsername": "",
"BindPassword": "",
"UserFilter": "",
"FirstNameAttribute": "",
"LastNameAttribute": "",
"EmailAttribute": "",
"UsernameAttribute": "",
"NicknameAttribute": "",
"IdAttribute": "",
"PositionAttribute": "",
"LoginIdAttribute": "",
"SyncIntervalMinutes": 60,
"SkipCertificateVerification": false,
"QueryTimeout": 60,
"MaxPageSize": 0,
"LoginFieldName": "",
"LoginButtonColor": "",
"LoginButtonBorderColor": "",
"LoginButtonTextColor": ""
},
"ComplianceSettings": {
"Enable": false,
"Directory": "./data/",
"EnableDaily": false
},
"LocalizationSettings": {
"DefaultServerLocale": "en",
"DefaultClientLocale": "en",
"AvailableLocales": ""
},
"SamlSettings": {
"Enable": false,
"EnableSyncWithLdap": false,
"Verify": true,
"Encrypt": true,
"IdpUrl": "",
"IdpDescriptorUrl": "",
"AssertionConsumerServiceURL": "",
"ScopingIDPProviderId": "",
"ScopingIDPName": "",
"IdpCertificateFile": "",
"PublicCertificateFile": "",
"PrivateKeyFile": "",
"FirstNameAttribute": "",
"LastNameAttribute": "",
"EmailAttribute": "",
"UsernameAttribute": "",
"NicknameAttribute": "",
"LocaleAttribute": "",
"PositionAttribute": "",
"LoginButtonText": "With SAML",
"LoginButtonColor": "",
"LoginButtonBorderColor": "",
"LoginButtonTextColor": ""
},
"NativeAppSettings": {
"AppDownloadLink": "https://about.mattermost.com/downloads/",
"AndroidAppDownloadLink": "https://about.mattermost.com/mattermost-android-app/",
"IosAppDownloadLink": "https://about.mattermost.com/mattermost-ios-app/"
},
"ClusterSettings": {
"Enable": false,
"ClusterName": "",
"OverrideHostname": "",
"UseIpAddress": true,
"UseExperimentalGossip": false,
"ReadOnlyConfig": true,
"GossipPort": 8074,
"StreamingPort": 8075,
"MaxIdleConns": 100,
"MaxIdleConnsPerHost": 128,
"IdleConnTimeoutMilliseconds": 90000
},
"MetricsSettings": {
"Enable": false,
"BlockProfileRate": 0,
"ListenAddress": ":8067"
},
"ExperimentalSettings": {
"ClientSideCertEnable": false,
"ClientSideCertCheck": "secondary"
},
"AnalyticsSettings": {
"MaxUsersForStatistics": 2500
},
"ElasticsearchSettings": {
"ConnectionUrl": "http://dockerhost:9200",
"Username": "elastic",
"Password": "changeme",
"EnableIndexing": false,
"EnableSearching": false,
"Sniff": true,
"PostIndexReplicas": 1,
"PostIndexShards": 1,
"AggregatePostsAfterDays": 365,
"PostsAggregatorJobStartTime": "03:00",
"IndexPrefix": "",
"LiveIndexingBatchSize": 1,
"BulkIndexingTimeWindowSeconds": 3600,
"RequestTimeoutSeconds": 30
},
"DataRetentionSettings": {
"EnableMessageDeletion": false,
"EnableFileDeletion": false,
"MessageRetentionDays": 365,
"FileRetentionDays": 365,
"DeletionJobStartTime": "02:00"
},
"MessageExportSettings": {
"EnableExport": false,
"ExportFormat": "actiance",
"DailyRunTime": "01:00",
"ExportFromTimestamp": 0,
"BatchSize": 10000,
"GlobalRelaySettings": {
"CustomerType": "A9",
"SmtpUsername": "",
"SmtpPassword": "",
"EmailAddress": ""
}
},
"JobSettings": {
"RunJobs": true,
"RunScheduler": true
},
"PluginSettings": {
"Enable": true,
"EnableUploads": true,
"Directory": "./test-plugins",
"ClientDirectory": "./test-client-plugins",
"Plugins": {},
"PluginStates": {
"jira": {
"Enable": true
},
"testplugin": {
"Enable": false
}
}
},
"DisplaySettings": {
"CustomUrlSchemes": [],
"ExperimentalTimezone": false
},
"TimezoneSettings": {
"SupportedTimezonesPath": "timezones.json"
}
}

Двоичные данные
tests/test-data-graph.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 38 KiB

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

@@ -133,6 +133,7 @@ func GetClientLicense(l *model.License) map[string]string {
props["SkuShortName"] = l.SkuShortName
props["Users"] = strconv.Itoa(*l.Features.Users)
props["LDAP"] = strconv.FormatBool(*l.Features.LDAP)
props["LDAPGroups"] = strconv.FormatBool(*l.Features.LDAPGroups)
props["MFA"] = strconv.FormatBool(*l.Features.MFA)
props["SAML"] = strconv.FormatBool(*l.Features.SAML)
props["Cluster"] = strconv.FormatBool(*l.Features.Cluster)

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

@@ -519,3 +519,47 @@ func (c *Context) RequireRoleName() *Context {
return c
}
func (c *Context) RequireGroupId() *Context {
if c.Err != nil {
return c
}
if len(c.Params.GroupId) != 26 {
c.SetInvalidUrlParam("group_id")
}
return c
}
func (c *Context) RequireRemoteId() *Context {
if c.Err != nil {
return c
}
if len(c.Params.RemoteId) == 0 {
c.SetInvalidUrlParam("remote_id")
}
return c
}
func (c *Context) RequireSyncableId() *Context {
if c.Err != nil {
return c
}
if len(c.Params.SyncableId) != 26 {
c.SetInvalidUrlParam("syncable_id")
}
return c
}
func (c *Context) RequireSyncableType() *Context {
if c.Err != nil {
return c
}
if c.Params.SyncableType != model.GroupSyncableTypeTeam && c.Params.SyncableType != model.GroupSyncableTypeChannel {
c.SetInvalidUrlParam("syncable_type")
}
return c
}

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

@@ -9,6 +9,7 @@ import (
"strings"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/model"
)
const (
@@ -49,10 +50,14 @@ type Params struct {
RoleName string
SchemeId string
Scope string
GroupId string
Page int
PerPage int
LogsPerPage int
Permanent bool
RemoteId string
SyncableId string
SyncableType model.GroupSyncableType
}
func ParamsFromRequest(r *http.Request) *Params {
@@ -173,6 +178,14 @@ func ParamsFromRequest(r *http.Request) *Params {
params.SchemeId = val
}
if val, ok := props["group_id"]; ok {
params.GroupId = val
}
if val, ok := props["remote_id"]; ok {
params.RemoteId = val
}
params.Scope = query.Get("scope")
if val, err := strconv.Atoi(query.Get("page")); err != nil || val < 0 {
@@ -201,5 +214,17 @@ func ParamsFromRequest(r *http.Request) *Params {
params.LogsPerPage = val
}
if val, ok := props["syncable_id"]; ok {
params.SyncableId = val
}
if val, ok := props["syncable_type"]; ok {
switch val {
case "teams":
params.SyncableType = model.GroupSyncableTypeTeam
case "channels":
params.SyncableType = model.GroupSyncableTypeChannel
}
}
return params
}