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

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

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