* 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 удалений

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

@@ -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.