[MM-61756] Attribute Based Access Control - Phase 1 (#30785)
Attribute Based Access Control - Base * MM-63662 * MM-63919 * MM-63954 * MM-63955 * MM-63425 * MM-63426 * MM-63458 * MM-63459 * MM-63603 * MM-63845 * MM-64146 * MM-64199 * MM-64201 * MM-64233 * MM-64247 * MM-64268 --------- Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com> Co-authored-by: Pablo Andrés Vélez Vidal <pablovv2012@gmail.com> Co-authored-by: abhijit-singh <abhijitsingh0702@gmail.com> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
4b445cbf16
Коммит
a344b3225b
@@ -855,7 +855,7 @@ test-migration:
|
||||
# db_migrations differ due to a typo in the 92. migration name
|
||||
# for now we exclude plugins such as playbooks and focalboard
|
||||
# we also exlude systems table temporarily due to adding some keys while running the initial migration
|
||||
bin/dbcmp --source "${MYSQL_DSN}" --target "${POSTGRES_DSN}" --exclude="db_migrations","ir_","focalboard","systems"
|
||||
bin/dbcmp --source "${MYSQL_DSN}" --target "${POSTGRES_DSN}" --exclude="db_migrations","ir_","focalboard","systems","attributeview"
|
||||
|
||||
test-local-filestore: # Run tests for local filestore
|
||||
$(GO) test ./platform/shared/filestore -run '^TestLocalFileBackend' -v
|
||||
|
||||
515
server/channels/api4/access_control.go
Обычный файл
515
server/channels/api4/access_control.go
Обычный файл
@@ -0,0 +1,515 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
)
|
||||
|
||||
func (api *API) InitAccessControlPolicy() {
|
||||
if !api.srv.Config().FeatureFlags.AttributeBasedAccessControl {
|
||||
return
|
||||
}
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("", api.APISessionRequired(createAccessControlPolicy)).Methods(http.MethodPut)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/search", api.APISessionRequired(searchAccessControlPolicies)).Methods(http.MethodPost)
|
||||
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/check", api.APISessionRequired(checkExpression)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/test", api.APISessionRequired(testExpression)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/autocomplete/fields", api.APISessionRequired(getFieldsAutocomplete)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/visual_ast", api.APISessionRequired(convertToVisualAST)).Methods(http.MethodPost)
|
||||
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("", api.APISessionRequired(getAccessControlPolicy)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("", api.APISessionRequired(deleteAccessControlPolicy)).Methods(http.MethodDelete)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/activate", api.APISessionRequired(updateActiveStatus)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/assign", api.APISessionRequired(assignAccessPolicy)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/unassign", api.APISessionRequired(unassignAccessPolicy)).Methods(http.MethodDelete)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/resources/channels", api.APISessionRequired(getChannelsForAccessControlPolicy)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/resources/channels/search", api.APISessionRequired(searchChannelsForAccessControlPolicy)).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
func createAccessControlPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var policy model.AccessControlPolicy
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&policy); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("policy", jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("createAccessControlPolicy", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameterAuditable(auditRec, "requested", &policy)
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
np, appErr := c.App.CreateOrUpdateAccessControlPolicy(c.AppContext, &policy)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventObjectType("access_control_policy")
|
||||
auditRec.AddEventResultState(np)
|
||||
|
||||
js, err := json.Marshal(np)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("createAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func getAccessControlPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
policy, appErr := c.App.GetAccessControlPolicy(c.AppContext, policyID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func deleteAccessControlPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
auditRec := c.MakeAuditRecord("deleteAccessControlPolicy", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "id", policyID)
|
||||
|
||||
appErr := c.App.DeleteAccessControlPolicy(c.AppContext, policyID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func checkExpression(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
// request type reserved for future expansion
|
||||
// for now, we only support the expression check
|
||||
checkExpressionRequest := struct {
|
||||
Expression string `json:"expression"`
|
||||
}{}
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&checkExpressionRequest); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("user", jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
errs, appErr := c.App.CheckExpression(c.AppContext, checkExpressionRequest.Expression)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(errs)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("checkExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func testExpression(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var checkExpressionRequest model.QueryExpressionParams
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&checkExpressionRequest); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("user", jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
users, count, appErr := c.App.TestExpression(c.AppContext, checkExpressionRequest.Expression, model.SubjectSearchOptions{
|
||||
Term: checkExpressionRequest.Term,
|
||||
Limit: checkExpressionRequest.Limit,
|
||||
Cursor: model.SubjectCursor{
|
||||
TargetID: checkExpressionRequest.After,
|
||||
},
|
||||
})
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
resp := model.AccessControlPolicyTestResponse{
|
||||
Users: users,
|
||||
Total: count,
|
||||
}
|
||||
|
||||
js, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("checkExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func searchAccessControlPolicies(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
var props *model.AccessControlPolicySearch
|
||||
err := json.NewDecoder(r.Body).Decode(&props)
|
||||
if err != nil || props == nil {
|
||||
c.SetInvalidParamWithErr("access_control_policy_search", err)
|
||||
return
|
||||
}
|
||||
|
||||
policies, total, appErr := c.App.SearchAccessControlPolicies(c.AppContext, *props)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
result := model.AccessControlPoliciesWithCount{
|
||||
Policies: policies,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
js, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("searchAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func updateActiveStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
auditRec := c.MakeAuditRecord("updateActiveStatus", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "id", policyID)
|
||||
|
||||
active := r.URL.Query().Get("active")
|
||||
if active != "true" && active != "false" {
|
||||
c.SetInvalidParam("active")
|
||||
return
|
||||
}
|
||||
activeBool, err := strconv.ParseBool(active)
|
||||
if err != nil {
|
||||
c.SetInvalidParamWithErr("active", err)
|
||||
return
|
||||
}
|
||||
audit.AddEventParameter(auditRec, "active", activeBool)
|
||||
|
||||
appErr := c.App.UpdateAccessControlPolicyActive(c.AppContext, policyID, activeBool)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func assignAccessPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
var assignments struct {
|
||||
ChannelIds []string `json:"channel_ids"`
|
||||
}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&assignments)
|
||||
if err != nil {
|
||||
c.SetInvalidParamWithErr("assignments", err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("assignAccessPolicy", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "id", policyID)
|
||||
audit.AddEventParameter(auditRec, "channel_ids", assignments.ChannelIds)
|
||||
|
||||
if len(assignments.ChannelIds) != 0 {
|
||||
_, appErr := c.App.AssignAccessControlPolicyToChannels(c.AppContext, policyID, assignments.ChannelIds)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func unassignAccessPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
var assignments struct {
|
||||
ChannelIds []string `json:"channel_ids"`
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("unassignAccessPolicy", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "id", policyID)
|
||||
audit.AddEventParameter(auditRec, "channel_ids", assignments.ChannelIds)
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&assignments)
|
||||
if err != nil {
|
||||
c.SetInvalidParamWithErr("assignments", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(assignments.ChannelIds) != 0 {
|
||||
appErr := c.App.UnAssignPoliciesFromChannels(c.AppContext, policyID, assignments.ChannelIds)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func getChannelsForAccessControlPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
afterID := r.URL.Query().Get("after")
|
||||
if afterID != "" && !model.IsValidId(afterID) {
|
||||
c.SetInvalidParam("after")
|
||||
return
|
||||
}
|
||||
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getChannelsForAccessControlPolicy", "api.access_control_policy.get_channels.limit.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
channels, total, appErr := c.App.GetChannelsForPolicy(c.AppContext, policyID, model.AccessControlPolicyCursor{
|
||||
ID: afterID,
|
||||
}, limit)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
data := model.ChannelsWithCount{Channels: channels, TotalCount: total}
|
||||
|
||||
js, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getChannelsForAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func searchChannelsForAccessControlPolicy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var props *model.ChannelSearch
|
||||
err := json.NewDecoder(r.Body).Decode(&props)
|
||||
if err != nil || props == nil {
|
||||
c.SetInvalidParamWithErr("channel_search", err)
|
||||
return
|
||||
}
|
||||
|
||||
policyID := c.Params.PolicyId
|
||||
|
||||
c.RequirePolicyId()
|
||||
|
||||
opts := model.ChannelSearchOpts{
|
||||
Deleted: props.Deleted,
|
||||
IncludeDeleted: props.IncludeDeleted,
|
||||
Private: true,
|
||||
ExcludeGroupConstrained: true,
|
||||
TeamIds: props.TeamIds,
|
||||
ParentAccessControlPolicyId: policyID,
|
||||
}
|
||||
|
||||
channels, total, appErr := c.App.SearchAllChannels(c.AppContext, props.Term, opts)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
data := model.ChannelsWithCount{Channels: channels, TotalCount: total}
|
||||
|
||||
channelsJSON, jsonErr := json.Marshal(data)
|
||||
if jsonErr != nil {
|
||||
c.Err = model.NewAppError("searchChannelsInPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(channelsJSON); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func getFieldsAutocomplete(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
after := r.URL.Query().Get("after")
|
||||
if after != "" && !model.IsValidId(after) {
|
||||
c.SetInvalidParam("after")
|
||||
return
|
||||
} else if after == "" {
|
||||
after = strings.Repeat("0", 26)
|
||||
}
|
||||
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getFieldsAutocomplete", "api.access_control_policy.get_fields.limit.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
c.Err = model.NewAppError("getFieldsAutocomplete", "api.access_control_policy.get_fields.limit.app_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ac, appErr := c.App.GetAccessControlFieldsAutocomplete(c.AppContext, after, limit)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(ac)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getExpressionAutocomplete", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(js); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func convertToVisualAST(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
var cel struct {
|
||||
Expression string `json:"expression"`
|
||||
}
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&cel); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("user", jsonErr)
|
||||
return
|
||||
}
|
||||
visualAST, appErr := c.App.ExpressionToVisualAST(c.AppContext, cel.Expression)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(visualAST)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("convertToVisualAST", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(b); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
27
server/channels/api4/access_control_local.go
Обычный файл
27
server/channels/api4/access_control_local.go
Обычный файл
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import "net/http"
|
||||
|
||||
func (api *API) InitAccessControlPolicyLocal() {
|
||||
if !api.srv.Config().FeatureFlags.AttributeBasedAccessControl {
|
||||
return
|
||||
}
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("", api.APILocal(createAccessControlPolicy)).Methods(http.MethodPut)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/search", api.APILocal(searchAccessControlPolicies)).Methods(http.MethodPost)
|
||||
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/check", api.APILocal(checkExpression)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/test", api.APILocal(testExpression)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/autocomplete/fields", api.APILocal(getFieldsAutocomplete)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicies.Handle("/cel/visual_ast", api.APILocal(convertToVisualAST)).Methods(http.MethodPost)
|
||||
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("", api.APILocal(getAccessControlPolicy)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("", api.APILocal(deleteAccessControlPolicy)).Methods(http.MethodDelete)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/activate", api.APILocal(updateActiveStatus)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/assign", api.APILocal(assignAccessPolicy)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/unassign", api.APILocal(unassignAccessPolicy)).Methods(http.MethodDelete)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/resources/channels", api.APILocal(getChannelsForAccessControlPolicy)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.AccessControlPolicy.Handle("/resources/channels/search", api.APILocal(searchChannelsForAccessControlPolicy)).Methods(http.MethodPost)
|
||||
}
|
||||
613
server/channels/api4/access_control_test.go
Обычный файл
613
server/channels/api4/access_control_test.go
Обычный файл
@@ -0,0 +1,613 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateAccessControlPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Revision: 1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Expression: "user.attributes.team == 'engineering'",
|
||||
Actions: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("CreateAccessControlPolicy without license", func(t *testing.T) {
|
||||
_, resp, err := th.SystemAdminClient.CreateAccessControlPolicy(context.Background(), samplePolicy)
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("CreateAccessControlPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
// Create and set up the mock
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.CreateAccessControlPolicy(context.Background(), samplePolicy)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
// Set up a test license with Data Retention enabled
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
// Create and set up the mock
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
// Set up mock expectations
|
||||
mockAccessControlService.On("SavePolicy", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.AccessControlPolicy")).Return(samplePolicy, nil).Times(1)
|
||||
|
||||
// Set the mock on the app
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := client.CreateAccessControlPolicy(context.Background(), samplePolicy)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
}, "CreateAccessControlPolicy with system admin")
|
||||
}
|
||||
|
||||
func TestGetAccessControlPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Revision: 1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Expression: "user.attributes.team == 'engineering'",
|
||||
Actions: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("GetAccessControlPolicy without license", func(t *testing.T) {
|
||||
_, resp, err := th.SystemAdminClient.GetAccessControlPolicy(context.Background(), samplePolicy.ID)
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("GetAccessControlPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
// Create and set up the mock
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.GetAccessControlPolicy(context.Background(), samplePolicy.ID)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
// Create and set up the mock
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := client.GetAccessControlPolicy(context.Background(), samplePolicy.ID)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
}, "GetAccessControlPolicy with system admin")
|
||||
}
|
||||
|
||||
func TestDeleteAccessControlPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicyID := model.NewId()
|
||||
|
||||
t.Run("DeleteAccessControlPolicy without license", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.DeleteAccessControlPolicy(context.Background(), samplePolicyID)
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("DeleteAccessControlPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
resp, err := th.Client.DeleteAccessControlPolicy(context.Background(), samplePolicyID)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("DeletePolicy", mock.AnythingOfType("*request.Context"), samplePolicyID).Return(nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
resp, err := client.DeleteAccessControlPolicy(context.Background(), samplePolicyID)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckExpression(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
t.Run("CheckExpression without license", func(t *testing.T) {
|
||||
_, resp, err := th.SystemAdminClient.CheckExpression(context.Background(), "true")
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("CheckExpression with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.CheckExpression(context.Background(), "true")
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("CheckExpression", mock.AnythingOfType("*request.Context"), "true").Return([]model.CELExpressionError{}, nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
errors, resp, err := client.CheckExpression(context.Background(), "true")
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
require.Empty(t, errors, "expected no errors")
|
||||
}, "CheckExpression with system admin")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("CheckExpression", mock.AnythingOfType("*request.Context"), "true").Return([]model.CELExpressionError{
|
||||
{
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
Message: "Syntax error",
|
||||
},
|
||||
}, nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
errors, resp, err := client.CheckExpression(context.Background(), "true")
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NotEmpty(t, errors, "expected errors")
|
||||
}, "CheckExpression with system admin errors returned")
|
||||
}
|
||||
|
||||
func TestTestExpression(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
t.Run("TestExpression without license", func(t *testing.T) {
|
||||
_, resp, err := th.SystemAdminClient.TestExpression(context.Background(), model.QueryExpressionParams{})
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("TestExpression with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.TestExpression(context.Background(), model.QueryExpressionParams{})
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("QueryUsersForExpression", mock.AnythingOfType("*request.Context"), "true", model.SubjectSearchOptions{}).Return([]*model.User{}, int64(0), nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
usersResp, resp, err := client.TestExpression(context.Background(), model.QueryExpressionParams{
|
||||
Expression: "true",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
require.Empty(t, usersResp.Users, "expected no users")
|
||||
require.Equal(t, int64(0), usersResp.Total, "expected count 0 users")
|
||||
}, "TestExpression with system admin")
|
||||
}
|
||||
|
||||
func TestSearchAccessControlPolicies(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
t.Run("SearchAccessControlPolicies without license", func(t *testing.T) {
|
||||
_, resp, err := th.SystemAdminClient.SearchAccessControlPolicies(context.Background(), model.AccessControlPolicySearch{})
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("SearchAccessControlPolicies with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.SearchAccessControlPolicies(context.Background(), model.AccessControlPolicySearch{})
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("SearchPolicies", mock.AnythingOfType("*request.Context"), model.AccessControlPolicySearch{
|
||||
Term: "engineering",
|
||||
}).Return([]*model.AccessControlPolicy{}, int64(0), nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
policiesResp, resp, err := client.SearchAccessControlPolicies(context.Background(), model.AccessControlPolicySearch{
|
||||
Term: "engineering",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
require.Empty(t, policiesResp.Policies, "expected no policies")
|
||||
require.Equal(t, int64(0), policiesResp.Total, "expected count 0 policies")
|
||||
}, "SearchAccessControlPolicies with system admin")
|
||||
}
|
||||
|
||||
func TestAssignAccessPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Revision: 1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Expression: "user.attributes.team == 'engineering'",
|
||||
Actions: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("AssignAccessPolicy without license", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.AssignAccessControlPolicies(context.Background(), model.NewId(), []string{model.NewId()})
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("AssignAccessPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
resp, err := th.Client.AssignAccessControlPolicies(context.Background(), model.NewId(), []string{model.NewId()})
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
resourceID := model.NewId()
|
||||
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
child, appErr := samplePolicy.Inherit(resourceID, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
||||
mockAccessControlService.On("SavePolicy", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.AccessControlPolicy")).Return(child, nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
resp, err := client.AssignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{resourceID})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
}, "AssignAccessPolicy with system admin")
|
||||
}
|
||||
|
||||
func TestUnassignAccessPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Revision: 1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Expression: "user.attributes.team == 'engineering'",
|
||||
Actions: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("UnassignAccessPolicy without license", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.UnassignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{model.NewId()})
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("UnassignAccessPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
resp, err := th.Client.UnassignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{model.NewId()})
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
resourceID := model.NewId()
|
||||
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
child, appErr := samplePolicy.Inherit(resourceID, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
||||
mockAccessControlService.On("SearchPolicies", mock.AnythingOfType("*request.Context"), model.AccessControlPolicySearch{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ParentID: samplePolicy.ID,
|
||||
}).Return([]*model.AccessControlPolicy{child}, nil).Times(1)
|
||||
mockAccessControlService.On("DeletePolicy", mock.AnythingOfType("*request.Context"), child.ID).Return(nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
resp, err := client.UnassignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{child.ID})
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
}, "UnassignAccessPolicy with system admin")
|
||||
}
|
||||
|
||||
func TestGetChannelsForAccessControlPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Revision: 1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Expression: "user.attributes.team == 'engineering'",
|
||||
Actions: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("GetChannelsForAccessControlPolicy without license", func(t *testing.T) {
|
||||
_, resp, err := th.SystemAdminClient.GetChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, "", 1000)
|
||||
require.Error(t, err)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("GetChannelsForAccessControlPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.GetChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, "", 1000)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
channelsResp, resp, err := client.GetChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, "", 1000)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, resp)
|
||||
require.Empty(t, channelsResp.Channels, "expected no channels")
|
||||
require.Equal(t, int64(0), channelsResp.TotalCount, "expected count 0 channels")
|
||||
}, "GetChannelsForAccessControlPolicy with system admin")
|
||||
}
|
||||
|
||||
func TestSearchChannelsForAccessControlPolicy(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL", "true")
|
||||
th := Setup(t)
|
||||
t.Cleanup(func() {
|
||||
th.TearDown()
|
||||
os.Unsetenv("MM_FEATUREFLAGS_ATTRIBUTEBASEDACCESSCONTROL")
|
||||
})
|
||||
|
||||
samplePolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Revision: 1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Expression: "user.attributes.team == 'engineering'",
|
||||
Actions: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("SearchChannelsForAccessControlPolicy with regular user", func(t *testing.T) {
|
||||
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
require.True(t, ok, "SetLicense should return true")
|
||||
|
||||
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.SearchChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, model.ChannelSearch{})
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
}
|
||||
@@ -158,6 +158,9 @@ type Routes struct {
|
||||
CustomProfileAttributesValues *mux.Router // 'api/v4/custom_profile_attributes/values'
|
||||
|
||||
AuditLogs *mux.Router // 'api/v4/audit_logs'
|
||||
|
||||
AccessControlPolicies *mux.Router // 'api/v4/access_control_policies'
|
||||
AccessControlPolicy *mux.Router // 'api/v4/access_control_policies/{policy_id:[A-Za-z0-9]+}'
|
||||
}
|
||||
|
||||
type API struct {
|
||||
@@ -302,6 +305,9 @@ func Init(srv *app.Server) (*API, error) {
|
||||
|
||||
api.BaseRoutes.AuditLogs = api.BaseRoutes.APIRoot.PathPrefix("/audit_logs").Subrouter()
|
||||
|
||||
api.BaseRoutes.AccessControlPolicies = api.BaseRoutes.APIRoot.PathPrefix("/access_control_policies").Subrouter()
|
||||
api.BaseRoutes.AccessControlPolicy = api.BaseRoutes.APIRoot.PathPrefix("/access_control_policies/{policy_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
api.InitUser()
|
||||
api.InitBot()
|
||||
api.InitTeam()
|
||||
@@ -354,6 +360,7 @@ func Init(srv *app.Server) (*API, error) {
|
||||
api.InitScheduledPost()
|
||||
api.InitCustomProfileAttributes()
|
||||
api.InitAuditLogging()
|
||||
api.InitAccessControlPolicy()
|
||||
|
||||
// If we allow testing then listen for manual testing URL hits
|
||||
if *srv.Config().ServiceSettings.EnableTesting {
|
||||
@@ -441,6 +448,9 @@ func InitLocal(srv *app.Server) *API {
|
||||
api.BaseRoutes.CustomProfileAttributesField = api.BaseRoutes.CustomProfileAttributesFields.PathPrefix("/{field_id:[A-Za-z0-9]+}").Subrouter()
|
||||
api.BaseRoutes.CustomProfileAttributesValues = api.BaseRoutes.CustomProfileAttributes.PathPrefix("/values").Subrouter()
|
||||
|
||||
api.BaseRoutes.AccessControlPolicies = api.BaseRoutes.APIRoot.PathPrefix("/access_control_policies").Subrouter()
|
||||
api.BaseRoutes.AccessControlPolicy = api.BaseRoutes.APIRoot.PathPrefix("/access_control_policies/{policy_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
api.InitUserLocal()
|
||||
api.InitTeamLocal()
|
||||
api.InitChannelLocal()
|
||||
@@ -462,6 +472,7 @@ func InitLocal(srv *app.Server) *API {
|
||||
api.InitJobLocal()
|
||||
api.InitSamlLocal()
|
||||
api.InitCustomProfileAttributesLocal()
|
||||
api.InitAccessControlPolicyLocal()
|
||||
|
||||
srv.LocalRouter.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ func (api *API) InitChannel() {
|
||||
api.BaseRoutes.Channel.Handle("/member_counts_by_group", api.APISessionRequired(channelMemberCountsByGroup)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.Channel.Handle("/common_teams", api.APISessionRequired(getGroupMessageMembersCommonTeams)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.Channel.Handle("/convert_to_channel", api.APISessionRequired(convertGroupMessageToChannel)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.Channel.Handle("/access_control/attributes", api.APISessionRequired(getChannelAccessControlAttributes)).Methods(http.MethodGet)
|
||||
|
||||
api.BaseRoutes.ChannelForUser.Handle("/unread", api.APISessionRequired(getChannelUnread)).Methods(http.MethodGet)
|
||||
|
||||
@@ -823,11 +824,18 @@ func getAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Params.ExcludeAccessControlPolicyEnforced && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
c.SetPermissionError(model.PermissionManageSystem)
|
||||
return
|
||||
}
|
||||
|
||||
opts := model.ChannelSearchOpts{
|
||||
NotAssociatedToGroup: c.Params.NotAssociatedToGroup,
|
||||
ExcludeDefaultChannels: c.Params.ExcludeDefaultChannels,
|
||||
IncludeDeleted: c.Params.IncludeDeleted,
|
||||
ExcludePolicyConstrained: c.Params.ExcludePolicyConstrained,
|
||||
NotAssociatedToGroup: c.Params.NotAssociatedToGroup,
|
||||
ExcludeDefaultChannels: c.Params.ExcludeDefaultChannels,
|
||||
IncludeDeleted: c.Params.IncludeDeleted,
|
||||
ExcludePolicyConstrained: c.Params.ExcludePolicyConstrained,
|
||||
AccessControlPolicyEnforced: c.Params.AccessControlPolicyEnforced,
|
||||
ExcludeAccessControlPolicyEnforced: c.Params.ExcludeAccessControlPolicyEnforced,
|
||||
}
|
||||
if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadComplianceDataRetentionPolicy) {
|
||||
opts.IncludePolicyID = true
|
||||
@@ -1309,20 +1317,23 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
includeDeleted, _ := strconv.ParseBool(r.URL.Query().Get("include_deleted"))
|
||||
includeDeleted = includeDeleted || props.IncludeDeleted
|
||||
opts := model.ChannelSearchOpts{
|
||||
NotAssociatedToGroup: props.NotAssociatedToGroup,
|
||||
ExcludeDefaultChannels: props.ExcludeDefaultChannels,
|
||||
TeamIds: props.TeamIds,
|
||||
GroupConstrained: props.GroupConstrained,
|
||||
ExcludeGroupConstrained: props.ExcludeGroupConstrained,
|
||||
ExcludePolicyConstrained: props.ExcludePolicyConstrained,
|
||||
IncludeSearchById: props.IncludeSearchById,
|
||||
ExcludeRemote: props.ExcludeRemote,
|
||||
Public: props.Public,
|
||||
Private: props.Private,
|
||||
IncludeDeleted: includeDeleted,
|
||||
Deleted: props.Deleted,
|
||||
Page: props.Page,
|
||||
PerPage: props.PerPage,
|
||||
NotAssociatedToGroup: props.NotAssociatedToGroup,
|
||||
ExcludeDefaultChannels: props.ExcludeDefaultChannels,
|
||||
TeamIds: props.TeamIds,
|
||||
GroupConstrained: props.GroupConstrained,
|
||||
ExcludeGroupConstrained: props.ExcludeGroupConstrained,
|
||||
ExcludePolicyConstrained: props.ExcludePolicyConstrained,
|
||||
IncludeSearchById: props.IncludeSearchById,
|
||||
ExcludeRemote: props.ExcludeRemote,
|
||||
Public: props.Public,
|
||||
Private: props.Private,
|
||||
IncludeDeleted: includeDeleted,
|
||||
Deleted: props.Deleted,
|
||||
Page: props.Page,
|
||||
PerPage: props.PerPage,
|
||||
AccessControlPolicyEnforced: props.AccessControlPolicyEnforced,
|
||||
ExcludeAccessControlPolicyEnforced: props.ExcludeAccessControlPolicyEnforced,
|
||||
ParentAccessControlPolicyId: props.ParentAccessControlPolicyId,
|
||||
}
|
||||
if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadComplianceDataRetentionPolicy) {
|
||||
opts.IncludePolicyID = true
|
||||
@@ -2478,3 +2489,25 @@ func canEditChannelBanner(c *Context, originalChannel *model.Channel) {
|
||||
c.Err = model.NewAppError("patchChannel", "api.channel.update_channel.banner_info.channel_type.not_allowed", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelAccessControlAttributes(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannel) {
|
||||
c.SetPermissionError(model.PermissionReadChannel)
|
||||
return
|
||||
}
|
||||
|
||||
attributes, err := c.App.GetAccessControlPolicyAttributes(c.AppContext, c.Params.ChannelId, "*")
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(attributes); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
293
server/channels/app/access_control.go
Обычный файл
293
server/channels/app/access_control.go
Обычный файл
@@ -0,0 +1,293 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
|
||||
func (a *App) GetChannelsForPolicy(rctx request.CTX, policyID string, cursor model.AccessControlPolicyCursor, limit int) ([]*model.ChannelWithTeamData, int64, *model.AppError) {
|
||||
policy, appErr := a.GetAccessControlPolicy(rctx, policyID)
|
||||
if appErr != nil {
|
||||
return nil, 0, appErr
|
||||
}
|
||||
|
||||
switch policy.Type {
|
||||
case model.AccessControlPolicyTypeParent:
|
||||
policies, total, err := a.Srv().Store().AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ParentID: policyID,
|
||||
Cursor: cursor,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
channelIDs := make([]string, 0, len(policies))
|
||||
|
||||
for _, p := range policies {
|
||||
channelIDs = append(channelIDs, p.ID)
|
||||
}
|
||||
|
||||
chs, err := a.Srv().Store().Channel().GetChannelsWithTeamDataByIds(channelIDs, true)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return chs, total, nil
|
||||
case model.AccessControlPolicyTypeChannel:
|
||||
chs, err := a.Srv().Store().Channel().GetChannelsWithTeamDataByIds([]string{policyID}, true)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
total := int64(len(chs))
|
||||
return chs, total, nil
|
||||
default:
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, "Invalid policy type", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlPolicy(rctx request.CTX, id string) (*model.AccessControlPolicy, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("GetPolicy", "app.pap.get_policy.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
policy, appErr := acs.GetPolicy(rctx, id)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateOrUpdateAccessControlPolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("CreateAccessControlPolicy", "app.pap.create_access_control_policy.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if policy.ID == "" {
|
||||
policy.ID = model.NewId()
|
||||
}
|
||||
|
||||
var appErr *model.AppError
|
||||
policy, appErr = acs.SavePolicy(rctx, policy)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteAccessControlPolicy(rctx request.CTX, id string) *model.AppError {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return model.NewAppError("DeleteAccessControlPolicy", "app.pap.delete_access_control_policy.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
appErr := acs.DeletePolicy(rctx, id)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) CheckExpression(rctx request.CTX, expression string) ([]model.CELExpressionError, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("CheckExpression", "app.pap.check_expression.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
errs, appErr := acs.CheckExpression(rctx, expression)
|
||||
if appErr != nil {
|
||||
return nil, model.NewAppError("CheckExpression", "app.pap.check_expression.app_error", nil, appErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return errs, nil
|
||||
}
|
||||
|
||||
func (a *App) TestExpression(rctx request.CTX, expression string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, 0, model.NewAppError("TestExpression", "app.pap.check_expression.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
res, count, err := acs.QueryUsersForExpression(rctx, expression, opts)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("TestExpression", "app.pap.check_expression.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return res, count, nil
|
||||
}
|
||||
|
||||
func (a *App) AssignAccessControlPolicyToChannels(rctx request.CTX, parentID string, channelIDs []string) ([]*model.AccessControlPolicy, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
policy, appErr := a.GetAccessControlPolicy(rctx, parentID)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if policy.Type != model.AccessControlPolicyTypeParent {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Policy is not of type parent", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
channels, err := a.GetChannels(rctx, channelIDs)
|
||||
if err != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
policies := make([]*model.AccessControlPolicy, 0, len(channelIDs))
|
||||
for _, channel := range channels {
|
||||
if channel.Type != model.ChannelTypePrivate || channel.IsGroupConstrained() {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Channel is not of type private", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if channel.IsShared() {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Channel is shared", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
newPolicy, appErr := policy.Inherit(channel.Id, model.AccessControlPolicyTypeChannel)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
newPolicy, appErr = acs.SavePolicy(rctx, newPolicy)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
policies = append(policies, newPolicy)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
func (a *App) UnAssignPoliciesFromChannels(rctx request.CTX, policyID string, channelIDs []string) *model.AppError {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return model.NewAppError("UnAssignPoliciesFromChannels", "app.pap.unassign_access_control_policy_from_channels.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
cps, _, err := a.Srv().Store().AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ParentID: policyID,
|
||||
})
|
||||
if err != nil {
|
||||
return model.NewAppError("UnAssignPoliciesFromChannels", "app.pap.unassign_access_control_policy_from_channels.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
childPolicies := make(map[string]bool)
|
||||
for _, p := range cps {
|
||||
childPolicies[p.ID] = true
|
||||
}
|
||||
|
||||
for _, channelID := range channelIDs {
|
||||
if _, ok := childPolicies[channelID]; !ok {
|
||||
mlog.Warn("Policy is not assigned to the parent policy", mlog.String("channel_id", channelID), mlog.String("parent_policy_id", policyID))
|
||||
continue
|
||||
}
|
||||
|
||||
appErr := acs.DeletePolicy(rctx, channelID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SearchAccessControlPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, 0, model.NewAppError("SearchAccessControlPolicies", "app.pap.search_access_control_policies.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
policies, total, err := a.Srv().Store().AccessControlPolicy().SearchPolicies(rctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("SearchAccessControlPolicies", "app.pap.search_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for i, policy := range policies {
|
||||
if policy.Type != model.AccessControlPolicyTypeParent {
|
||||
continue
|
||||
}
|
||||
|
||||
normlizedPolicy, appErr := acs.NormalizePolicy(rctx, policy)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to normalize policy", mlog.String("policy_id", policy.ID), mlog.Err(appErr))
|
||||
continue
|
||||
}
|
||||
policies[i] = normlizedPolicy
|
||||
}
|
||||
|
||||
return policies, total, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlPolicyAttributes(rctx request.CTX, channelID string, action string) (map[string][]string, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("GetChannelAccessControlAttributes", "app.pap.get_channel_access_control_attributes.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
attributes, appErr := acs.GetPolicyRuleAttributes(rctx, channelID, action)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlFieldsAutocomplete(rctx request.CTX, after string, limit int) ([]*model.PropertyField, *model.AppError) {
|
||||
cpaGroupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAccessControlAutoComplete", "app.pap.get_access_control_auto_complete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
fields, err := a.Srv().Store().PropertyField().SearchPropertyFields(model.PropertyFieldSearchOpts{
|
||||
GroupID: cpaGroupID,
|
||||
Cursor: model.PropertyFieldSearchCursor{
|
||||
PropertyFieldID: after,
|
||||
CreateAt: 1,
|
||||
},
|
||||
PerPage: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAccessControlAutoComplete", "app.pap.get_access_control_auto_complete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateAccessControlPolicyActive(rctx request.CTX, policyID string, active bool) *model.AppError {
|
||||
_, err := a.Srv().Store().AccessControlPolicy().SetActiveStatus(rctx, policyID, active)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdateAccessControlPolicyActive", "app.pap.update_access_control_policy_active.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ExpressionToVisualAST(rctx request.CTX, expression string) (*model.VisualExpression, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("ExpressionToVisualAST", "app.pap.expression_to_visual_ast.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
visualAST, appErr := acs.ExpressionToVisualAST(rctx, expression)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return visualAST, nil
|
||||
}
|
||||
455
server/channels/app/access_control_test.go
Обычный файл
455
server/channels/app/access_control_test.go
Обычный файл
@@ -0,0 +1,455 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
mocks "github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
)
|
||||
|
||||
func TestGetChannelsForPolicy(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
policyID := "policyID"
|
||||
cursor := model.AccessControlPolicyCursor{}
|
||||
limit := 10
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
th.App.Srv().ch.AccessControl = nil
|
||||
|
||||
channels, total, err := th.App.GetChannelsForPolicy(rctx, policyID, cursor, limit)
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, channels)
|
||||
assert.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Invalid policy type", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", mock.AnythingOfType("*request.Context"), policyID).Return(&model.AccessControlPolicy{Type: "invalid"}, nil)
|
||||
|
||||
channels, total, err := th.App.GetChannelsForPolicy(rctx, policyID, cursor, limit)
|
||||
require.NotNil(t, err)
|
||||
require.Nil(t, channels)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Valid policy type - no channels", func(t *testing.T) {
|
||||
pID := model.NewId()
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: pID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, pID).Return(parentPolicy, nil)
|
||||
|
||||
channels, total, err := th.App.GetChannelsForPolicy(rctx, pID, cursor, limit)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, channels)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Valid policy type - with channels", func(t *testing.T) {
|
||||
pID := model.NewId()
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: pID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ch := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
|
||||
childPolicy, appErr := parentPolicy.Inherit(ch.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
var err error
|
||||
childPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, childPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, childPolicy)
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, pID).Return(parentPolicy, nil)
|
||||
|
||||
channels, total, appErr := th.App.GetChannelsForPolicy(rctx, pID, cursor, limit)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channels)
|
||||
require.Equal(t, int64(1), total)
|
||||
assert.Equal(t, ch.Id, channels[0].Id)
|
||||
|
||||
mockAccessControl.On("GetPolicy", rctx, ch.Id).Return(childPolicy, nil)
|
||||
channels, total, appErr = th.App.GetChannelsForPolicy(rctx, ch.Id, cursor, limit)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channels)
|
||||
require.Equal(t, int64(1), total)
|
||||
assert.Equal(t, ch.Id, channels[0].Id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSearchAccessControlPolicies(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.NotNil(t, err)
|
||||
require.Empty(t, policies)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Empty search result", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, policies)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Single search result", func(t *testing.T) {
|
||||
pID := model.NewId()
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: pID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
parentPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, parentPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
defer func() {
|
||||
dErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, parentPolicy.ID)
|
||||
require.NoError(t, dErr)
|
||||
}()
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("NormalizePolicy", rctx, parentPolicy).Return(parentPolicy, nil)
|
||||
|
||||
t.Run("With no term", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Equal(t, int64(1), total)
|
||||
require.Equal(t, parentPolicy.ID, policies[0].ID)
|
||||
})
|
||||
|
||||
t.Run("With term", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Term: "parent",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Equal(t, int64(1), total)
|
||||
require.Equal(t, parentPolicy.ID, policies[0].ID)
|
||||
})
|
||||
|
||||
t.Run("With term and no results", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Term: "something else",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, policies)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssignAccessControlPolicyToChannels(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
parentID := model.NewId()
|
||||
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: parentID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
parentPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, parentPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
t.Cleanup(func() {
|
||||
dErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, parentPolicy.ID)
|
||||
require.NoError(t, dErr)
|
||||
})
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
th.App.Srv().ch.AccessControl = nil
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
assert.Equal(t, "app.pap.assign_access_control_policy_to_channels.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("Error saving policy", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(parentPolicy, nil)
|
||||
mockAccessControl.On("SavePolicy", rctx, mock.Anything).Return(nil, model.NewAppError("SavePolicy", "error", nil, "save error", http.StatusInternalServerError))
|
||||
|
||||
ch := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, ch)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{ch.Id})
|
||||
require.NotNil(t, err)
|
||||
require.Empty(t, policies)
|
||||
})
|
||||
|
||||
t.Run("Parent policy not found", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(nil, model.NewAppError("GetPolicy", "error", nil, "not found", http.StatusNotFound))
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
})
|
||||
|
||||
t.Run("Policy is not of type parent", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(&model.AccessControlPolicy{Type: model.AccessControlPolicyTypeChannel}, nil)
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
assert.Equal(t, "app.pap.assign_access_control_policy_to_channels.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("Channel is not private", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(&model.AccessControlPolicy{Type: model.AccessControlPolicyTypeParent}, nil)
|
||||
// Create a public channel
|
||||
publicChannel := th.CreateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, publicChannel)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{publicChannel.Id})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
assert.Contains(t, err.Error(), "Channel is not of type private")
|
||||
})
|
||||
|
||||
t.Run("Channel is shared", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(&model.AccessControlPolicy{Type: model.AccessControlPolicyTypeParent}, nil)
|
||||
|
||||
privateChannel := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, privateChannel)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
privateChannel.Shared = model.NewPointer(true)
|
||||
_, err := th.App.Srv().Store().Channel().Update(rctx, privateChannel)
|
||||
require.NoError(t, err)
|
||||
|
||||
policies, appErr := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{privateChannel.Id})
|
||||
require.NotNil(t, appErr)
|
||||
assert.Nil(t, policies)
|
||||
assert.Contains(t, appErr.Error(), "Channel is shared")
|
||||
})
|
||||
|
||||
t.Run("Successful assignment", func(t *testing.T) {
|
||||
ch1 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, ch1)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
ch2 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, ch2)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
childP1, appErr := parentPolicy.Inherit(ch1.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
childP2, appErr := parentPolicy.Inherit(ch2.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(parentPolicy, nil)
|
||||
mockAccessControl.On("SavePolicy", rctx, mock.MatchedBy(func(p *model.AccessControlPolicy) bool { return p.ID == ch1.Id })).Return(childP1, nil)
|
||||
mockAccessControl.On("SavePolicy", rctx, mock.MatchedBy(func(p *model.AccessControlPolicy) bool { return p.ID == ch2.Id })).Return(childP2, nil)
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{ch1.Id, ch2.Id})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 2)
|
||||
assert.ElementsMatch(t, []string{ch1.Id, ch2.Id}, []string{policies[0].ID, policies[1].ID})
|
||||
mockAccessControl.AssertCalled(t, "SavePolicy", rctx, mock.AnythingOfType("*model.AccessControlPolicy"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnAssignPoliciesFromChannels(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Name: "parent-for-unassign-tests",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{Actions: []string{"*"}, Expression: "true"},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
parentPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, parentPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, parentPolicy.ID)
|
||||
require.NoError(t, sErr)
|
||||
})
|
||||
|
||||
ch1 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.PermanentDeleteChannel(rctx, ch1)
|
||||
require.Nil(t, sErr)
|
||||
})
|
||||
ch2 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.PermanentDeleteChannel(rctx, ch2)
|
||||
require.Nil(t, sErr)
|
||||
})
|
||||
|
||||
childPolicy1, appErrInherit1 := parentPolicy.Inherit(ch1.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErrInherit1)
|
||||
childPolicy1, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, childPolicy1)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, childPolicy1)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, childPolicy1.ID)
|
||||
require.NoError(t, sErr)
|
||||
})
|
||||
|
||||
childPolicy2, appErrInherit2 := parentPolicy.Inherit(ch2.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErrInherit2)
|
||||
childPolicy2, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, childPolicy2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, childPolicy2)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, childPolicy2.ID)
|
||||
require.NoError(t, sErr)
|
||||
})
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
th.App.Srv().ch.AccessControl = nil
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id})
|
||||
require.NotNil(t, appErr)
|
||||
assert.Equal(t, "app.pap.unassign_access_control_policy_from_channels.app_error", appErr.Id)
|
||||
})
|
||||
|
||||
t.Run("Error deleting policy from AccessControlService", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
expectedErr := model.NewAppError("DeletePolicy", "mock.delete.error", nil, "failed to delete from acs", http.StatusInternalServerError)
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch1.Id).Return(expectedErr).Once()
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch2.Id).Return(nil).Maybe()
|
||||
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id})
|
||||
require.NotNil(t, appErr)
|
||||
assert.Equal(t, expectedErr.Id, appErr.Id)
|
||||
assert.Equal(t, expectedErr.Message, appErr.Message)
|
||||
|
||||
mockAccessControl.AssertCalled(t, "DeletePolicy", rctx, ch1.Id)
|
||||
mockAccessControl.AssertNotCalled(t, "DeletePolicy", rctx, ch2.Id)
|
||||
|
||||
p1, storeErr := th.App.Srv().Store().AccessControlPolicy().Get(rctx, ch1.Id)
|
||||
assert.NoError(t, storeErr)
|
||||
assert.NotNil(t, p1)
|
||||
p2, storeErr := th.App.Srv().Store().AccessControlPolicy().Get(rctx, ch2.Id)
|
||||
assert.NoError(t, storeErr)
|
||||
assert.NotNil(t, p2)
|
||||
})
|
||||
|
||||
t.Run("Channel not actually a child policy", func(t *testing.T) {
|
||||
ch3 := th.CreatePrivateChannel(rctx, th.BasicTeam) // Not a child of parentPolicy
|
||||
t.Cleanup(func() { _ = th.App.PermanentDeleteChannel(rctx, ch3) })
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch1.Id).Return(nil).Once()
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch2.Id).Return(nil).Once()
|
||||
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id, ch3.Id})
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("Successful unassignment", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch1.Id).Return(nil).Once()
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch2.Id).Return(nil).Once()
|
||||
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id})
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
}
|
||||
@@ -635,6 +635,14 @@ func (a *App) GetGroupChannel(c request.CTX, userIDs []string) (*model.Channel,
|
||||
|
||||
// UpdateChannel updates a given channel by its Id. It also publishes the CHANNEL_UPDATED event.
|
||||
func (a *App) UpdateChannel(c request.CTX, channel *model.Channel) (*model.Channel, *model.AppError) {
|
||||
ok, appErr := a.ChannelAccessControlled(c, channel.Id)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
if ok && channel.Type != model.ChannelTypePrivate {
|
||||
return nil, model.NewAppError("UpdateChannel", "api.channel.update_channel.not_allowed.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
_, err := a.Srv().Store().Channel().Update(c, channel)
|
||||
if err != nil {
|
||||
var appErr *model.AppError
|
||||
@@ -1576,6 +1584,40 @@ func (a *App) addUserToChannel(c request.CTX, user *model.User, channel *model.C
|
||||
newMember.SchemeAdmin = userShouldBeAdmin
|
||||
}
|
||||
|
||||
if channel.Type == model.ChannelTypePrivate {
|
||||
if ok, appErr := a.ChannelAccessControlled(c, channel.Id); ok {
|
||||
if acs := a.Srv().Channels().AccessControl; acs != nil {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil,
|
||||
fmt.Sprintf("failed to get group: %v, user_id: %s, channel_id: %s", err, user.Id, channel.Id), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
s, err := a.Srv().Store().Attributes().GetSubject(c, user.Id, groupID)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil,
|
||||
fmt.Sprintf("failed to get subject: %v, user_id: %s, channel_id: %s", err, user.Id, channel.Id), http.StatusNotFound)
|
||||
}
|
||||
|
||||
decision, evalErr := acs.AccessEvaluation(c, model.AccessRequest{
|
||||
Subject: *s,
|
||||
Resource: model.Resource{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ID: channel.Id,
|
||||
},
|
||||
Action: "join_channel",
|
||||
})
|
||||
if evalErr != nil {
|
||||
return nil, evalErr
|
||||
} else if !decision.Decision {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.rejected", nil, "", http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
} else if appErr != nil {
|
||||
c.Logger().Error("Error checking access control policy for channel", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
|
||||
newMember, nErr = a.Srv().Store().Channel().SaveMember(c, newMember)
|
||||
if nErr != nil {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil,
|
||||
@@ -1989,13 +2031,15 @@ func (a *App) GetAllChannels(c request.CTX, page, perPage int, opts model.Channe
|
||||
opts.ExcludeChannelNames = a.DefaultChannelNames(c)
|
||||
}
|
||||
storeOpts := store.ChannelSearchOpts{
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
AccessControlPolicyEnforced: opts.AccessControlPolicyEnforced,
|
||||
ExcludeAccessControlPolicyEnforced: opts.ExcludeAccessControlPolicyEnforced,
|
||||
}
|
||||
channels, err := a.Srv().Store().Channel().GetAllChannels(page*perPage, perPage, storeOpts)
|
||||
if err != nil {
|
||||
@@ -2962,22 +3006,25 @@ func (a *App) SearchAllChannels(c request.CTX, term string, opts model.ChannelSe
|
||||
opts.ExcludeChannelNames = a.DefaultChannelNames(c)
|
||||
}
|
||||
storeOpts := store.ChannelSearchOpts{
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
Deleted: opts.Deleted,
|
||||
TeamIds: opts.TeamIds,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
PolicyID: opts.PolicyID,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
IncludeSearchByID: opts.IncludeSearchById,
|
||||
ExcludeRemote: opts.ExcludeRemote,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
Public: opts.Public,
|
||||
Private: opts.Private,
|
||||
Page: opts.Page,
|
||||
PerPage: opts.PerPage,
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
Deleted: opts.Deleted,
|
||||
TeamIds: opts.TeamIds,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
PolicyID: opts.PolicyID,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
IncludeSearchByID: opts.IncludeSearchById,
|
||||
ExcludeRemote: opts.ExcludeRemote,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
Public: opts.Public,
|
||||
Private: opts.Private,
|
||||
Page: opts.Page,
|
||||
PerPage: opts.PerPage,
|
||||
AccessControlPolicyEnforced: opts.AccessControlPolicyEnforced,
|
||||
ExcludeAccessControlPolicyEnforced: opts.ExcludeAccessControlPolicyEnforced,
|
||||
ParentAccessControlPolicyId: opts.ParentAccessControlPolicyId,
|
||||
}
|
||||
|
||||
term = strings.TrimSpace(term)
|
||||
@@ -3815,3 +3862,19 @@ func (s *Server) getDirectChannel(c request.CTX, userID, otherUserID string) (*m
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (a *App) ChannelAccessControlled(c request.CTX, channelID string) (bool, *model.AppError) {
|
||||
if l := a.License(); !model.MinimumEnterpriseAdvancedLicense(l) || !*a.Config().AccessControlSettings.EnableAttributeBasedAccessControl {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, err := a.Srv().Store().AccessControlPolicy().Get(c, channelID)
|
||||
var nfErr *store.ErrNotFound
|
||||
if err != nil && !errors.As(err, &nfErr) {
|
||||
return false, model.NewAppError("ChannelIsAccessControlled", "app.channel.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
} else if errors.As(err, &nfErr) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ type Channels struct {
|
||||
Saml einterfaces.SamlInterface
|
||||
Notification einterfaces.NotificationInterface
|
||||
Ldap einterfaces.LdapInterface
|
||||
AccessControl einterfaces.AccessControlServiceInterface
|
||||
|
||||
// These are used to prevent concurrent upload requests
|
||||
// for a given upload session which could cause inconsistencies
|
||||
@@ -132,6 +133,23 @@ func NewChannels(s *Server) (*Channels, error) {
|
||||
}
|
||||
})
|
||||
}
|
||||
if accessControlServiceInterface != nil {
|
||||
app := New(ServerConnector(ch))
|
||||
ch.AccessControl = accessControlServiceInterface(app)
|
||||
|
||||
appErr := ch.AccessControl.Init(request.EmptyContext(s.Log()))
|
||||
if appErr != nil {
|
||||
s.Log().Error("An error occurred while initializing Access Control", mlog.Err(appErr))
|
||||
}
|
||||
|
||||
app.AddLicenseListener(func(newCfg, old *model.License) {
|
||||
if ch.AccessControl != nil {
|
||||
if appErr := ch.AccessControl.Init(request.EmptyContext(s.Log())); appErr != nil {
|
||||
s.Log().Error("An error occurred while initializing Access Control", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var imgErr error
|
||||
decoderConcurrency := int(*ch.cfgSvc.Config().FileSettings.MaxImageDecoderConcurrency)
|
||||
|
||||
@@ -98,6 +98,18 @@ func RegisterIPFilteringInterface(f func(*App) einterfaces.IPFilteringInterface)
|
||||
ipFilteringInterface = f
|
||||
}
|
||||
|
||||
var accessControlServiceInterface func(*App) einterfaces.AccessControlServiceInterface
|
||||
|
||||
func RegisterAccessControlServiceInterface(f func(*App) einterfaces.AccessControlServiceInterface) {
|
||||
accessControlServiceInterface = f
|
||||
}
|
||||
|
||||
var jobsAccessControlSyncJobInterface func(*Server) ejobs.AccessControlSyncJobInterface
|
||||
|
||||
func RegisterJobsAccessControlSyncJobInterface(f func(*Server) ejobs.AccessControlSyncJobInterface) {
|
||||
jobsAccessControlSyncJobInterface = f
|
||||
}
|
||||
|
||||
func (s *Server) initEnterprise() {
|
||||
if cloudInterface != nil {
|
||||
s.Cloud = cloudInterface(s)
|
||||
|
||||
@@ -108,6 +108,8 @@ func (a *App) SessionHasPermissionToCreateJob(session model.Session, job *model.
|
||||
model.JobTypeCloud,
|
||||
model.JobTypeExtractContent:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionManageJobs), model.PermissionManageJobs
|
||||
case model.JobTypeAccessControlSync:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionManageSystem), model.PermissionManageSystem
|
||||
}
|
||||
|
||||
return false, nil
|
||||
@@ -142,6 +144,8 @@ func (a *App) SessionHasPermissionToManageJob(session model.Session, job *model.
|
||||
model.JobTypeCloud,
|
||||
model.JobTypeExtractContent:
|
||||
permission = model.PermissionManageJobs
|
||||
case model.JobTypeAccessControlSync:
|
||||
permission = model.PermissionManageSystem
|
||||
}
|
||||
|
||||
if permission == nil {
|
||||
@@ -178,6 +182,8 @@ func (a *App) SessionHasPermissionToReadJob(session model.Session, jobType strin
|
||||
model.JobTypeMobileSessionMetadata,
|
||||
model.JobTypeExtractContent:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionReadJobs), model.PermissionReadJobs
|
||||
case model.JobTypeAccessControlSync:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionManageSystem), model.PermissionManageSystem
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
@@ -38,8 +38,8 @@ func RegisterMetricsInterface(f func(*PlatformService, string, string) einterfac
|
||||
metricsInterfaceFn = f
|
||||
}
|
||||
|
||||
var pdpInterface func(*PlatformService) einterfaces.PolicyDecisionPointInterface
|
||||
var accessControlServiceInterface func(*PlatformService) einterfaces.AccessControlServiceInterface
|
||||
|
||||
func RegisterPdpInterface(f func(*PlatformService) einterfaces.PolicyDecisionPointInterface) {
|
||||
pdpInterface = f
|
||||
func RegisterAccessControlServiceInterface(f func(*PlatformService) einterfaces.AccessControlServiceInterface) {
|
||||
accessControlServiceInterface = f
|
||||
}
|
||||
|
||||
@@ -477,8 +477,8 @@ func (ps *PlatformService) initEnterprise() {
|
||||
ps.licenseManager = licenseInterface(ps)
|
||||
}
|
||||
|
||||
if pdpInterface != nil {
|
||||
ps.pdpService = pdpInterface(ps)
|
||||
if accessControlServiceInterface != nil {
|
||||
ps.pdpService = accessControlServiceInterface(ps)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1499,6 +1499,11 @@ func (s *Server) initJobs() {
|
||||
s.Jobs.RegisterJobType(model.JobTypeLdapSync, builder.MakeWorker(), builder.MakeScheduler())
|
||||
}
|
||||
|
||||
if jobsAccessControlSyncJobInterface != nil {
|
||||
builder := jobsAccessControlSyncJobInterface(s)
|
||||
s.Jobs.RegisterJobType(model.JobTypeAccessControlSync, builder.MakeWorker(), builder.MakeScheduler())
|
||||
}
|
||||
|
||||
s.Jobs.RegisterJobType(
|
||||
model.JobTypeBlevePostIndexing,
|
||||
indexer.MakeWorker(s.Jobs, s.platform.SearchEngine.BleveEngine.(*bleveengine.BleveEngine)),
|
||||
|
||||
@@ -2090,6 +2090,26 @@ func (a *App) SearchUsersInChannel(channelID string, term string, options *model
|
||||
|
||||
func (a *App) SearchUsersNotInChannel(teamID string, channelID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
term = strings.TrimSpace(term)
|
||||
|
||||
ctx := request.EmptyContext(a.Log())
|
||||
if ok, err := a.ChannelAccessControlled(ctx, channelID); err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
acs := a.Srv().Channels().AccessControl
|
||||
if acs != nil {
|
||||
users, _, appErr := acs.QueryUsersForResource(ctx, channelID, "*", model.SubjectSearchOptions{
|
||||
Term: term,
|
||||
TeamID: teamID,
|
||||
Limit: options.Limit,
|
||||
})
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
users, err := a.Srv().Store().User().SearchNotInChannel(teamID, channelID, term, options)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SearchUsersNotInChannel", "app.user.search.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
|
||||
@@ -267,6 +267,8 @@ channels/db/migrations/mysql/000134_create_access_control_policies.down.sql
|
||||
channels/db/migrations/mysql/000134_create_access_control_policies.up.sql
|
||||
channels/db/migrations/mysql/000135_sidebarchannels_categoryid.down.sql
|
||||
channels/db/migrations/mysql/000135_sidebarchannels_categoryid.up.sql
|
||||
channels/db/migrations/mysql/000136_create_attribute_view.down.sql
|
||||
channels/db/migrations/mysql/000136_create_attribute_view.up.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.down.sql
|
||||
channels/db/migrations/postgres/000001_create_teams.up.sql
|
||||
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
||||
@@ -535,3 +537,5 @@ channels/db/migrations/postgres/000134_create_access_control_policies.down.sql
|
||||
channels/db/migrations/postgres/000134_create_access_control_policies.up.sql
|
||||
channels/db/migrations/postgres/000135_sidebarchannels_categoryid.down.sql
|
||||
channels/db/migrations/postgres/000135_sidebarchannels_categoryid.up.sql
|
||||
channels/db/migrations/postgres/000136_create_attribute_view.down.sql
|
||||
channels/db/migrations/postgres/000136_create_attribute_view.up.sql
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS AttributeView;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE OR REPLACE VIEW AttributeView AS
|
||||
SELECT
|
||||
pv.GroupID,
|
||||
pv.TargetID,
|
||||
pv.TargetType,
|
||||
JSON_OBJECTAGG(pf.Name, pv.Value)
|
||||
AS Attributes
|
||||
FROM PropertyValues pv
|
||||
LEFT JOIN PropertyFields pf ON pf.ID = pv.FieldID
|
||||
GROUP BY GroupID, TargetID, TargetType;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP MATERIALIZED VIEW IF EXISTS AttributeView;
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE OR REPLACE PROCEDURE create_attribute_view()
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE '
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS AttributeView AS
|
||||
SELECT
|
||||
pv.GroupID,
|
||||
pv.TargetID,
|
||||
pv.TargetType,
|
||||
jsonb_object_agg(
|
||||
pf.Name,
|
||||
CASE
|
||||
WHEN pf.Type = ''select'' THEN (
|
||||
SELECT to_jsonb(options.name)
|
||||
FROM jsonb_to_recordset(pf.Attrs->''options'') AS options(id text, name text)
|
||||
WHERE options.id = pv.Value #>> ''{}''
|
||||
LIMIT 1
|
||||
)
|
||||
WHEN pf.Type = ''multiselect'' THEN (
|
||||
SELECT jsonb_agg(option_names.name)
|
||||
FROM jsonb_array_elements_text(pv.Value) AS option_id
|
||||
JOIN jsonb_to_recordset(pf.Attrs->''options'') AS option_names(id text, name text)
|
||||
ON option_id = option_names.id
|
||||
)
|
||||
ELSE pv.Value
|
||||
END
|
||||
) AS Attributes FROM PropertyValues pv
|
||||
LEFT JOIN PropertyFields pf ON pf.ID = pv.FieldID
|
||||
WHERE pv.DeleteAt = 0 OR pv.DeleteAt IS NULL
|
||||
GROUP BY pv.GroupID, pv.TargetID, pv.TargetType
|
||||
';
|
||||
END;
|
||||
$$;
|
||||
|
||||
call create_attribute_view();
|
||||
DROP PROCEDURE create_attribute_view();
|
||||
@@ -24,6 +24,7 @@ const mySQLDeadlockCode = uint16(1213)
|
||||
type RetryLayer struct {
|
||||
store.Store
|
||||
AccessControlPolicyStore store.AccessControlPolicyStore
|
||||
AttributesStore store.AttributesStore
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
@@ -79,6 +80,10 @@ func (s *RetryLayer) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return s.AccessControlPolicyStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Attributes() store.AttributesStore {
|
||||
return s.AttributesStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Audit() store.AuditStore {
|
||||
return s.AuditStore
|
||||
}
|
||||
@@ -280,6 +285,11 @@ type RetryLayerAccessControlPolicyStore struct {
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerAttributesStore struct {
|
||||
store.AttributesStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerAuditStore struct {
|
||||
store.AuditStore
|
||||
Root *RetryLayer
|
||||
@@ -583,27 +593,6 @@ func (s *RetryLayerAccessControlPolicyStore) Get(c request.CTX, id string) (*mod
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAccessControlPolicyStore) GetAll(rctxc request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.AccessControlPolicyStore.GetAll(rctxc, opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -625,6 +614,27 @@ func (s *RetryLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.A
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.AccessControlPolicyStore.SearchPolicies(rctx, opts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, resultVar1, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, resultVar1, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -646,6 +656,90 @@ func (s *RetryLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id s
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.AttributesStore.GetChannelMembersToRemove(rctx, channelID, opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAttributesStore) GetSubject(rctx request.CTX, ID string, groupID string) (*model.Subject, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.AttributesStore.GetSubject(rctx, ID, groupID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAttributesStore) RefreshAttributes() error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.AttributesStore.RefreshAttributes()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.AttributesStore.SearchUsers(rctx, opts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, resultVar1, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, resultVar1, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAuditStore) Get(userID string, offset int, limit int) (model.Audits, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -16513,6 +16607,7 @@ func New(childStore store.Store) *RetryLayer {
|
||||
}
|
||||
|
||||
newStore.AccessControlPolicyStore = &RetryLayerAccessControlPolicyStore{AccessControlPolicyStore: childStore.AccessControlPolicy(), Root: &newStore}
|
||||
newStore.AttributesStore = &RetryLayerAttributesStore{AttributesStore: childStore.Attributes(), Root: &newStore}
|
||||
newStore.AuditStore = &RetryLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
|
||||
newStore.BotStore = &RetryLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
|
||||
newStore.ChannelStore = &RetryLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}
|
||||
|
||||
@@ -67,6 +67,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("PropertyGroup").Return(&mocks.PropertyGroupStore{})
|
||||
mock.On("PropertyValue").Return(&mocks.PropertyValueStore{})
|
||||
mock.On("AccessControlPolicy").Return(&mocks.AccessControlPolicyStore{})
|
||||
mock.On("Attributes").Return(&mocks.AttributesStore{})
|
||||
return mock
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -17,6 +18,8 @@ import (
|
||||
sq "github.com/mattermost/squirrel"
|
||||
)
|
||||
|
||||
const MaxPerPage = 1000
|
||||
|
||||
// Usually rules are how we define the policy, hence the versioning. For v0.1, we also
|
||||
// have the imports field which is used to link with the parent policy.
|
||||
type accessControlPolicyV0_1 struct {
|
||||
@@ -152,7 +155,7 @@ func newSqlAccessControlPolicyStore(sqlStore *SqlStore, metrics einterfaces.Metr
|
||||
return s
|
||||
}
|
||||
|
||||
func preSaveAccessControlPolicy(policy, existingPolicy *model.AccessControlPolicy) {
|
||||
func preSaveAccessControlPolicy(policy *storeAccessControlPolicy, existingPolicy *model.AccessControlPolicy) {
|
||||
// since policies are immutable, we need to create a new revision
|
||||
// also if it's going to be saved, eventually it will be the new one
|
||||
// we overwrite createAt to make sure it gets the correct timestamp before saving
|
||||
@@ -181,38 +184,6 @@ func (s *SqlAccessControlPolicyStore) Save(rctx request.CTX, policy *model.Acces
|
||||
return nil, errors.Wrapf(err, "failed to fetch policy with id=%s", policy.ID)
|
||||
}
|
||||
|
||||
if existingPolicy != nil {
|
||||
// move existing policy to history
|
||||
tmp, err2 := fromModel(existingPolicy)
|
||||
if err2 != nil {
|
||||
return nil, errors.Wrapf(err2, "failed to parse policy with id=%s", policy.ID)
|
||||
}
|
||||
|
||||
data := tmp.Data
|
||||
props := tmp.Props
|
||||
if s.IsBinaryParamEnabled() {
|
||||
data = AppendBinaryFlag(data)
|
||||
props = AppendBinaryFlag(props)
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Insert("AccessControlPolicyHistory").
|
||||
Columns(accessControlPolicyHistorySliceColumns()...).
|
||||
Values(tmp.ID, tmp.Name, tmp.Type, tmp.CreateAt, tmp.Revision, tmp.Version, data, props)
|
||||
|
||||
_, err = tx.ExecBuilder(query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to save policy with id=%s to history", policy.ID)
|
||||
}
|
||||
|
||||
err = s.deleteT(rctx, tx, existingPolicy.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to delete policy with id=%s", policy.ID)
|
||||
}
|
||||
}
|
||||
|
||||
preSaveAccessControlPolicy(policy, existingPolicy)
|
||||
|
||||
storePolicy, err := fromModel(policy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse policy with Id=%s", policy.ID)
|
||||
@@ -225,6 +196,57 @@ func (s *SqlAccessControlPolicyStore) Save(rctx request.CTX, policy *model.Acces
|
||||
props = AppendBinaryFlag(props)
|
||||
}
|
||||
|
||||
if existingPolicy != nil {
|
||||
if existingPolicy.Type != policy.Type {
|
||||
return nil, errors.New("cannot change type of existing policy")
|
||||
}
|
||||
|
||||
// move existing policy to history
|
||||
tmp, err2 := fromModel(existingPolicy)
|
||||
if err2 != nil {
|
||||
return nil, errors.Wrapf(err2, "failed to parse policy with id=%s", policy.ID)
|
||||
}
|
||||
|
||||
// Check if the policy has actually changed
|
||||
// We compare data, name, and version fields, and ensure type hasn't changed
|
||||
if bytes.Equal(storePolicy.Data, tmp.Data) &&
|
||||
storePolicy.Name == tmp.Name &&
|
||||
storePolicy.Version == tmp.Version {
|
||||
return existingPolicy, nil
|
||||
}
|
||||
|
||||
existingData := tmp.Data
|
||||
existingProps := tmp.Props
|
||||
if s.IsBinaryParamEnabled() {
|
||||
existingData = AppendBinaryFlag(existingData)
|
||||
existingProps = AppendBinaryFlag(existingProps)
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Insert("AccessControlPolicyHistory").
|
||||
Columns(accessControlPolicyHistorySliceColumns()...).
|
||||
Values(tmp.ID, tmp.Name, tmp.Type, tmp.CreateAt, tmp.Revision, tmp.Version, existingData, existingProps)
|
||||
|
||||
_, err = tx.ExecBuilder(query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to save policy with id=%s to history", policy.ID)
|
||||
}
|
||||
|
||||
err = s.deleteT(rctx, tx, existingPolicy.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to delete policy with id=%s", policy.ID)
|
||||
}
|
||||
} else {
|
||||
// if there is no existing policy, also check the history table
|
||||
// to make sure we are not overwriting an existing policy
|
||||
existingPolicy, err = s.getHistoryT(rctx, tx, policy.ID)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.Wrapf(err, "failed to fetch policy with id=%s", policy.ID)
|
||||
}
|
||||
}
|
||||
|
||||
preSaveAccessControlPolicy(storePolicy, existingPolicy)
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Insert("AccessControlPolicies").
|
||||
Columns(accessControlPolicySliceColumns()...).
|
||||
@@ -329,11 +351,29 @@ func (s *SqlAccessControlPolicyStore) SetActiveStatus(rctx request.CTX, id strin
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
|
||||
}
|
||||
_, err = tx.Query(query, args...)
|
||||
_, err = tx.Exec(query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update policy with id=%s", id)
|
||||
}
|
||||
|
||||
if existingPolicy.Type == model.AccessControlPolicyTypeParent {
|
||||
// if the policy is a parent, we need to update the child policies
|
||||
var expr sq.Sqlizer
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
expr = sq.Expr("Data->'imports' @> ?::jsonb", fmt.Sprintf("%q", id))
|
||||
} else {
|
||||
expr = sq.Expr("JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?)", fmt.Sprintf("%q", id))
|
||||
}
|
||||
query, args, err = s.getQueryBuilder().Update("AccessControlPolicies").Set("Active", active).Where(expr).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
|
||||
}
|
||||
_, err = tx.Exec(query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update child policies with id=%s", id)
|
||||
}
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "commit_transaction")
|
||||
}
|
||||
@@ -345,7 +385,7 @@ func (s *SqlAccessControlPolicyStore) Get(_ request.CTX, id string) (*model.Acce
|
||||
p := storeAccessControlPolicy{}
|
||||
query := s.selectQueryBuilder.Where(sq.Eq{"ID": id})
|
||||
|
||||
err := s.GetReplica().GetBuilder(&p, query)
|
||||
err := s.GetMaster().GetBuilder(&p, query)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("AccessControlPolicy", id)
|
||||
@@ -388,7 +428,35 @@ func (s *SqlAccessControlPolicyStore) getT(_ request.CTX, tx *sqlxTxWrapper, id
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (s *SqlAccessControlPolicyStore) GetAll(_ request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
|
||||
func (s *SqlAccessControlPolicyStore) getHistoryT(_ request.CTX, tx *sqlxTxWrapper, id string) (*model.AccessControlPolicy, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(accessControlPolicyHistorySliceColumns()...).
|
||||
From("AccessControlPolicyHistory").
|
||||
Where(
|
||||
sq.Eq{"ID": id},
|
||||
).OrderBy("Revision DESC").
|
||||
Limit(1)
|
||||
|
||||
sql, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
|
||||
}
|
||||
|
||||
var storePolicy storeAccessControlPolicy
|
||||
err = tx.Get(&storePolicy, sql, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policy, err := storePolicy.toModel()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse policy with id=%s", id)
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (s *SqlAccessControlPolicyStore) GetAll(_ request.CTX, opts model.GetAccessControlPolicyOptions) ([]*model.AccessControlPolicy, model.AccessControlPolicyCursor, error) {
|
||||
p := []storeAccessControlPolicy{}
|
||||
query := s.selectQueryBuilder
|
||||
|
||||
@@ -404,18 +472,156 @@ func (s *SqlAccessControlPolicyStore) GetAll(_ request.CTX, opts store.GetPolicy
|
||||
query = query.Where(sq.Eq{"Type": opts.Type})
|
||||
}
|
||||
|
||||
cursor := opts.Cursor
|
||||
|
||||
if !cursor.IsEmpty() {
|
||||
query = query.Where(sq.Or{
|
||||
sq.Gt{"Id": cursor.ID},
|
||||
})
|
||||
}
|
||||
|
||||
limit := uint64(opts.Limit)
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
} else if limit > MaxPerPage {
|
||||
limit = MaxPerPage
|
||||
}
|
||||
|
||||
query = query.Limit(limit)
|
||||
|
||||
err := s.GetReplica().SelectBuilder(&p, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find policies with opts={\"parentID\"=%q, \"resourceType\"=%q", opts.ParentID, opts.Type)
|
||||
return nil, cursor, errors.Wrapf(err, "failed to find policies with opts={\"parentID\"=%q, \"resourceType\"=%q", opts.ParentID, opts.Type)
|
||||
}
|
||||
|
||||
policies := make([]*model.AccessControlPolicy, len(p))
|
||||
for i := range p {
|
||||
policies[i], err = p[i].toModel()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse policy with id=%s", p[i].ID)
|
||||
return nil, cursor, errors.Wrapf(err, "failed to parse policy with id=%s", p[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
if len(policies) != 0 {
|
||||
cursor.ID = policies[len(policies)-1].ID
|
||||
}
|
||||
|
||||
return policies, cursor, nil
|
||||
}
|
||||
|
||||
func (s *SqlAccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
type wrapper struct {
|
||||
storeAccessControlPolicy
|
||||
ChildIDs json.RawMessage
|
||||
}
|
||||
|
||||
p := []wrapper{}
|
||||
var query sq.SelectBuilder
|
||||
if opts.IncludeChildren && opts.ParentID == "" {
|
||||
columns := accessControlPolicySliceColumns("p")
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
childIDs := `COALESCE((SELECT JSON_AGG(c.ID)
|
||||
FROM AccessControlPolicies c
|
||||
WHERE c.Type != 'parent'
|
||||
AND c.Data->'imports' @> JSONB_BUILD_ARRAY(p.ID)), '[]'::json) AS ChildIDs`
|
||||
columns = append(columns, childIDs)
|
||||
} else {
|
||||
childIDs := `COALESCE((SELECT JSON_ARRAYAGG(c.ID)
|
||||
FROM AccessControlPolicies c
|
||||
WHERE c.Type != 'parent'
|
||||
AND JSON_SEARCH(c.Data->'$.imports', 'one', p.ID) IS NOT NULL), JSON_ARRAY()) AS ChildIDs`
|
||||
columns = append(columns, childIDs)
|
||||
}
|
||||
query = s.getQueryBuilder().Select(columns...).From("AccessControlPolicies p")
|
||||
} else {
|
||||
query = s.selectQueryBuilder
|
||||
}
|
||||
|
||||
count := s.getQueryBuilder().Select("COUNT(*)").From("AccessControlPolicies")
|
||||
|
||||
if opts.Term != "" {
|
||||
condition := sq.Like{"Name": fmt.Sprintf("%%%s%%", opts.Term)}
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
}
|
||||
|
||||
if opts.Type != "" {
|
||||
condition := sq.Eq{"Type": opts.Type}
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
}
|
||||
|
||||
if opts.ParentID != "" {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
condition := sq.Expr("Data->'imports' @> ?", fmt.Sprintf("%q", opts.ParentID))
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
} else {
|
||||
condition := sq.Expr("JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?)", fmt.Sprintf("%q", opts.ParentID))
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Active {
|
||||
query = query.Where(sq.Eq{"Active": true})
|
||||
count = count.Where(sq.Eq{"Active": true})
|
||||
}
|
||||
|
||||
cursor := opts.Cursor
|
||||
|
||||
if !cursor.IsEmpty() {
|
||||
query = query.Where(sq.Gt{"Id": cursor.ID})
|
||||
}
|
||||
|
||||
limit := uint64(opts.Limit)
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
} else if limit > MaxPerPage {
|
||||
limit = MaxPerPage
|
||||
}
|
||||
|
||||
query = query.Limit(limit)
|
||||
|
||||
err := s.GetReplica().SelectBuilder(&p, query)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to find policies with opts={\"name\"=%q, \"resourceType\"=%q", opts.Term, opts.Type)
|
||||
}
|
||||
|
||||
policies := make([]*model.AccessControlPolicy, len(p))
|
||||
for i := range p {
|
||||
m, err2 := p[i].toModel()
|
||||
if err2 != nil {
|
||||
return nil, 0, errors.Wrapf(err2, "failed to parse policy with id=%s", p[i].ID)
|
||||
}
|
||||
|
||||
// Props field is not guaranteed to be persisted correctly, and it shouldn't be.
|
||||
// This is a field that we want to include metadata, some values may be stored but
|
||||
// not all of them. For example for the childs, we don't want to update it whenever a
|
||||
// child policy changes.
|
||||
if opts.IncludeChildren && opts.ParentID == "" {
|
||||
if m.Props == nil {
|
||||
m.Props = make(map[string]any)
|
||||
}
|
||||
// Unmarshal the JSON array into a slice of strings
|
||||
var childIDs []string
|
||||
if err = json.Unmarshal(p[i].ChildIDs, &childIDs); err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to unmarshal child IDs for policy with id=%s", p[i].ID)
|
||||
}
|
||||
m.Props["child_ids"] = childIDs
|
||||
}
|
||||
policies[i] = m
|
||||
}
|
||||
|
||||
var total int64
|
||||
err = s.GetReplica().GetBuilder(&total, count)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to count policies with opts={\"name\"=%q, \"resourceType\"=%q", opts.Term, opts.Type)
|
||||
}
|
||||
|
||||
if len(policies) != 0 {
|
||||
cursor.ID = policies[len(policies)-1].ID
|
||||
}
|
||||
|
||||
return policies, total, nil
|
||||
}
|
||||
|
||||
253
server/channels/store/sqlstore/attributes_store.go
Обычный файл
253
server/channels/store/sqlstore/attributes_store.go
Обычный файл
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SqlAttributesStore struct {
|
||||
*SqlStore
|
||||
metrics einterfaces.MetricsInterface
|
||||
|
||||
selectQueryBuilder sq.SelectBuilder
|
||||
}
|
||||
|
||||
func attributesSliceColumns(prefix ...string) []string {
|
||||
var p string
|
||||
if len(prefix) == 1 {
|
||||
p = prefix[0] + "."
|
||||
} else if len(prefix) > 1 {
|
||||
panic("cannot accept multiple prefixes")
|
||||
}
|
||||
|
||||
return []string{
|
||||
p + "TargetID as ID",
|
||||
p + "TargetType as Type",
|
||||
p + "Attributes",
|
||||
}
|
||||
}
|
||||
|
||||
func newSqlAttributesStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.AttributesStore {
|
||||
s := &SqlAttributesStore{
|
||||
SqlStore: sqlStore,
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
s.selectQueryBuilder = s.getQueryBuilder().Select(attributesSliceColumns()...).From("AttributeView")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) RefreshAttributes() error {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
if _, err := s.GetMaster().Exec("REFRESH MATERIALIZED VIEW AttributeView"); err != nil {
|
||||
return errors.Wrap(err, "error refreshing materialized view AttributeView")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) GetSubject(rctx request.CTX, ID, groupID string) (*model.Subject, error) {
|
||||
query := s.selectQueryBuilder.Where(sq.And{sq.Eq{"TargetID": ID}, sq.Eq{"GroupID": groupID}})
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query for subject")
|
||||
}
|
||||
|
||||
row := s.GetReplica().QueryRowxContext(rctx.Context(), q, args...)
|
||||
if err := row.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get subject")
|
||||
}
|
||||
|
||||
var subject model.Subject
|
||||
var properties []byte
|
||||
|
||||
if err := row.Scan(&subject.ID, &subject.Type, &properties); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("Attributes", ID)
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to scan subject row")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(properties, &subject.Attributes); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal attributes")
|
||||
}
|
||||
|
||||
return &subject, nil
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(getUsersColumns()...).From("Users").LeftJoin("AttributeView ON Users.Id = AttributeView.TargetID").
|
||||
OrderBy("Users.Id ASC")
|
||||
|
||||
count := s.getQueryBuilder().Select("COUNT(*)").From("Users").LeftJoin("AttributeView ON Users.Id = AttributeView.TargetID")
|
||||
|
||||
if opts.Query != "" {
|
||||
query = query.Where(sq.Expr(opts.Query, opts.Args...))
|
||||
count = count.Where(sq.Expr(opts.Query, opts.Args...))
|
||||
}
|
||||
|
||||
argCount := len(opts.Args)
|
||||
|
||||
if opts.Limit > 0 {
|
||||
query = query.Limit(uint64(opts.Limit))
|
||||
} else if opts.Limit > MaxPerPage {
|
||||
query = query.Limit(uint64(MaxPerPage))
|
||||
}
|
||||
|
||||
if !opts.AllowInactive {
|
||||
query = query.Where("Users.DeleteAt = 0")
|
||||
count = count.Where("Users.DeleteAt = 0")
|
||||
}
|
||||
|
||||
if opts.TeamID != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = ? AND DeleteAt = 0)", opts.TeamID)
|
||||
count = count.Where("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = ? AND DeleteAt = 0)", opts.TeamID)
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = $%d AND DeleteAt = 0)", argCount), opts.TeamID))
|
||||
count = count.Where(sq.Expr(fmt.Sprintf("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = $%d AND DeleteAt = 0)", argCount), opts.TeamID))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ExcludeChannelMembers != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Expr("NOT EXISTS (SELECT 1 FROM ChannelMembers WHERE ChannelMembers.UserId = Users.Id AND ChannelMembers.ChannelId = ?)", opts.ExcludeChannelMembers))
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("NOT EXISTS (SELECT 1 FROM ChannelMembers WHERE ChannelMembers.UserId = Users.Id AND ChannelMembers.ChannelId = $%d)", argCount), opts.ExcludeChannelMembers))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Cursor.TargetID != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Expr("TargetID > ?", opts.Cursor.TargetID))
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("TargetID > $%d", argCount), opts.Cursor.TargetID))
|
||||
}
|
||||
}
|
||||
|
||||
searchFields := make([]string, 0, len(UserSearchTypeNames))
|
||||
for _, field := range UserSearchTypeNames {
|
||||
searchFields = append(searchFields, strings.Join([]string{"Users", field}, "."))
|
||||
}
|
||||
|
||||
if term := opts.Term; strings.TrimSpace(term) != "" {
|
||||
_, query = generateSearchQueryForExpression(query, strings.Fields(term), searchFields, s.DriverName() == model.DatabaseDriverPostgres, argCount)
|
||||
_, count = generateSearchQueryForExpression(count, strings.Fields(term), searchFields, s.DriverName() == model.DatabaseDriverPostgres, argCount)
|
||||
}
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to build query for subjects")
|
||||
}
|
||||
|
||||
users := []*model.User{}
|
||||
if err = s.GetReplica().Select(&users, q, args...); err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to find Users with term=%s and searchType=%v", opts.Term, searchFields)
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
u.Sanitize(map[string]bool{})
|
||||
}
|
||||
|
||||
var total int64
|
||||
|
||||
if !opts.IgnoreCount {
|
||||
err = s.GetReplica().GetBuilder(&total, count)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to count Users with term=%s and searchType=%v", opts.Term, searchFields)
|
||||
}
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelMemberSliceColumns()...).From("ChannelMembers").LeftJoin("AttributeView ON ChannelMembers.UserId = AttributeView.TargetID").
|
||||
OrderBy("ChannelMembers.UserId ASC")
|
||||
|
||||
if opts.Query != "" {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("(NOT (%s) OR AttributeView.TargetID IS NULL)", opts.Query), opts.Args...))
|
||||
}
|
||||
|
||||
argCount := len(opts.Args)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Eq{"ChannelMembers.ChannelId": channelID})
|
||||
} else {
|
||||
argCount++
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("ChannelMembers.ChannelId = $%d", argCount), channelID))
|
||||
}
|
||||
|
||||
if opts.Limit > 0 {
|
||||
query = query.Limit(uint64(opts.Limit))
|
||||
} else if opts.Limit > MaxPerPage {
|
||||
query = query.Limit(uint64(MaxPerPage))
|
||||
}
|
||||
|
||||
if opts.Cursor.TargetID != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Expr("ChannelMembers.UserId > ?", opts.Cursor.TargetID))
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("ChannelMembers.UserId > $%d", argCount), opts.Cursor.TargetID))
|
||||
}
|
||||
}
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query for subjects")
|
||||
}
|
||||
|
||||
members := []*model.ChannelMember{}
|
||||
if err := s.GetReplica().Select(&members, q, args...); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find channel members with for channel id=%s", channelID)
|
||||
}
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func generateSearchQueryForExpression(query sq.SelectBuilder, terms []string, fields []string, isPostgreSQL bool, prevArgs int) (int, sq.SelectBuilder) {
|
||||
for _, term := range terms {
|
||||
searchFields := []string{}
|
||||
termArgs := []any{}
|
||||
for _, field := range fields {
|
||||
if isPostgreSQL {
|
||||
prevArgs++
|
||||
searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower($%d) escape '*' ", field, prevArgs))
|
||||
} else {
|
||||
searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field))
|
||||
}
|
||||
termArgs = append(termArgs, fmt.Sprintf("%%%s%%", strings.TrimLeft(term, "@")))
|
||||
}
|
||||
if isPostgreSQL {
|
||||
prevArgs++
|
||||
searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower($%d) escape '*' ", "Id", prevArgs))
|
||||
} else {
|
||||
searchFields = append(searchFields, "Id = ?")
|
||||
}
|
||||
termArgs = append(termArgs, strings.TrimLeft(term, "@"))
|
||||
query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...)
|
||||
}
|
||||
|
||||
return prevArgs, query
|
||||
}
|
||||
14
server/channels/store/sqlstore/attributes_store_test.go
Обычный файл
14
server/channels/store/sqlstore/attributes_store_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestAttributesStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestAttributesStore)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func channelMemberSliceColumns() []string {
|
||||
|
||||
// channelSliceColumns returns fields of the channel as a string slice.
|
||||
// Optionally, you can add a prefix (accepts only 1 value) to the fields.
|
||||
func channelSliceColumns(prefix ...string) []string {
|
||||
func channelSliceColumns(isSelect bool, prefix ...string) []string {
|
||||
var p string
|
||||
if len(prefix) == 1 {
|
||||
p = prefix[0] + "."
|
||||
@@ -116,7 +116,7 @@ func channelSliceColumns(prefix ...string) []string {
|
||||
panic("cannot accept multiple prefixes")
|
||||
}
|
||||
|
||||
return []string{
|
||||
columns := []string{
|
||||
p + "Id",
|
||||
p + "CreateAt",
|
||||
p + "UpdateAt",
|
||||
@@ -138,6 +138,16 @@ func channelSliceColumns(prefix ...string) []string {
|
||||
p + "LastRootPostAt",
|
||||
p + "BannerInfo",
|
||||
}
|
||||
|
||||
if isSelect {
|
||||
if p == "" {
|
||||
p = "Channels."
|
||||
}
|
||||
|
||||
columns = append(columns, fmt.Sprintf("EXISTS (SELECT 1 FROM AccessControlPolicies acp WHERE acp.ID = %sId) AS PolicyEnforced", p))
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
func channelToSlice(channel *model.Channel) []any {
|
||||
@@ -493,7 +503,7 @@ func newSqlChannelStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
s.tableSelectQuery = s.getQueryBuilder().Select(channelSliceColumns()...).From("Channels")
|
||||
s.tableSelectQuery = s.getQueryBuilder().Select(channelSliceColumns(true)...).From("Channels")
|
||||
|
||||
s.sidebarCategorySelectQuery = s.getQueryBuilder().
|
||||
Select("SidebarCategories.Id", "SidebarCategories.UserId", "SidebarCategories.TeamId", "SidebarCategories.SortOrder", "SidebarCategories.Sorting", "SidebarCategories.Type", "SidebarCategories.DisplayName", "SidebarCategories.Muted", "SidebarCategories.Collapsed").
|
||||
@@ -731,7 +741,7 @@ func (s SqlChannelStore) saveChannelT(transaction *sqlxTxWrapper, channel *model
|
||||
|
||||
insert := s.getQueryBuilder().
|
||||
Insert("Channels").
|
||||
Columns(channelSliceColumns()...).
|
||||
Columns(channelSliceColumns(false)...).
|
||||
Values(channelToSlice(channel)...)
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
insert = insert.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Id=Id"))
|
||||
@@ -908,7 +918,7 @@ func (s SqlChannelStore) Get(id string, allowFromCache bool) (*model.Channel, er
|
||||
//nolint:unparam
|
||||
func (s SqlChannelStore) GetMany(ids []string, allowFromCache bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Id": ids})
|
||||
sql, args, err := query.ToSql()
|
||||
@@ -1070,7 +1080,7 @@ func (s SqlChannelStore) PermanentDeleteMembersByChannel(rctx request.CTX, chann
|
||||
|
||||
func (s SqlChannelStore) GetChannels(teamId string, userId string, opts *model.ChannelSearchOpts) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("ch")...).
|
||||
Select(channelSliceColumns(true, "ch")...).
|
||||
From("Channels ch, ChannelMembers cm").
|
||||
Where(
|
||||
sq.And{
|
||||
@@ -1125,7 +1135,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string, opts *model.C
|
||||
|
||||
func (s SqlChannelStore) GetChannelsByUser(userId string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels, ChannelMembers").
|
||||
Where(
|
||||
sq.And{
|
||||
@@ -1233,7 +1243,7 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo
|
||||
Select("count(c.Id)")
|
||||
} else {
|
||||
selectQuery = s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...).
|
||||
Select(channelSliceColumns(true, "c")...).
|
||||
Columns(
|
||||
"Teams.DisplayName AS TeamDisplayName",
|
||||
"Teams.Name AS TeamName",
|
||||
@@ -1280,6 +1290,11 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo
|
||||
if opts.ExcludePolicyConstrained {
|
||||
query = query.Where("RetentionPoliciesChannels.ChannelId IS NULL")
|
||||
}
|
||||
if opts.ExcludeAccessControlPolicyEnforced {
|
||||
query = query.Where("c.Id NOT IN (SELECT ID From AccessControlPolicies WHERE Type = ?)", model.AccessControlPolicyTypeChannel)
|
||||
} else if opts.AccessControlPolicyEnforced {
|
||||
query = query.InnerJoin("AccessControlPolicies acp ON c.Id = acp.ID")
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -1296,7 +1311,7 @@ func (s SqlChannelStore) GetMoreChannels(teamId string, userId string, offset in
|
||||
})
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id = Channels.Id)").
|
||||
Where(sq.Eq{
|
||||
@@ -1321,7 +1336,7 @@ func (s SqlChannelStore) GetPrivateChannelsForTeam(teamId string, offset int, li
|
||||
channels := model.ChannelList{}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Type": model.ChannelTypePrivate, "TeamId": teamId, "DeleteAt": 0}).
|
||||
OrderBy("DisplayName").
|
||||
@@ -1342,7 +1357,7 @@ func (s SqlChannelStore) GetPrivateChannelsForTeam(teamId string, offset int, li
|
||||
|
||||
func (s SqlChannelStore) GetPublicChannelsForTeam(teamId string, offset int, limit int) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels pc ON (pc.Id = Channels.Id)").
|
||||
Where(sq.Eq{
|
||||
@@ -1386,7 +1401,7 @@ func (s SqlChannelStore) GetPublicChannelsByIdsForTeam(teamId string, channelIds
|
||||
var data model.ChannelList
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels pc ON (pc.Id = Channels.Id)").
|
||||
Where(sq.And{
|
||||
@@ -1481,7 +1496,7 @@ func (s SqlChannelStore) getByNames(teamId string, names []string, allowFromCach
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(cond)
|
||||
|
||||
@@ -1516,7 +1531,7 @@ func (s SqlChannelStore) GetByName(teamId string, name string, allowFromCache bo
|
||||
|
||||
func (s SqlChannelStore) getByName(teamId string, name string, includeDeleted bool, allowFromCache bool) (*model.Channel, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Name": name}).
|
||||
Where(sq.Or{
|
||||
@@ -1567,7 +1582,7 @@ func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId
|
||||
channels := model.ChannelList{}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Or{
|
||||
sq.Eq{"TeamId": teamId},
|
||||
@@ -2883,7 +2898,7 @@ func (s SqlChannelStore) GetAll(teamId string) ([]*model.Channel, error) {
|
||||
|
||||
func (s SqlChannelStore) GetChannelsByIds(channelIds []string, includeDeleted bool) ([]*model.Channel, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Id": channelIds}).
|
||||
OrderBy("Name")
|
||||
@@ -2907,7 +2922,7 @@ func (s SqlChannelStore) GetChannelsByIds(channelIds []string, includeDeleted bo
|
||||
|
||||
func (s SqlChannelStore) GetChannelsWithTeamDataByIds(channelIDs []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...).
|
||||
Select(channelSliceColumns(true, "c")...).
|
||||
Columns(
|
||||
"COALESCE(t.DisplayName, '') As TeamDisplayName",
|
||||
"COALESCE(t.Name, '') AS TeamName",
|
||||
@@ -2937,7 +2952,7 @@ func (s SqlChannelStore) GetChannelsWithTeamDataByIds(channelIDs []string, inclu
|
||||
|
||||
func (s SqlChannelStore) GetForPost(postId string) (*model.Channel, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("Posts ON Channels.Id = Posts.ChannelId").
|
||||
Where(sq.Eq{
|
||||
@@ -3110,7 +3125,7 @@ func (s SqlChannelStore) GetTeamMembersForChannel(channelID string) ([]string, e
|
||||
|
||||
func (s SqlChannelStore) Autocomplete(rctx request.CTX, userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...).
|
||||
Select(channelSliceColumns(true, "c")...).
|
||||
Columns(
|
||||
"t.DisplayName AS TeamDisplayName",
|
||||
"t.Name AS TeamName",
|
||||
@@ -3167,7 +3182,7 @@ func (s SqlChannelStore) Autocomplete(rctx request.CTX, userID, term string, inc
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) AutocompleteInTeam(rctx request.CTX, teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns()...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "c")...).
|
||||
From("Channels c").
|
||||
Where(sq.Eq{"c.TeamId": teamID}).
|
||||
OrderBy("c.DisplayName").
|
||||
@@ -3203,7 +3218,7 @@ func (s SqlChannelStore) AutocompleteInTeam(rctx request.CTX, teamID, userID, te
|
||||
|
||||
func (s SqlChannelStore) AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) {
|
||||
// shared query
|
||||
query := s.getSubQueryBuilder().Select(channelSliceColumns("C")...).
|
||||
query := s.getSubQueryBuilder().Select(channelSliceColumns(true, "C")...).
|
||||
From("Channels AS C").
|
||||
Join("ChannelMembers AS CM ON CM.ChannelId = C.Id").
|
||||
Limit(50).
|
||||
@@ -3294,7 +3309,7 @@ func (s SqlChannelStore) AutocompleteInTeamForSearch(teamID string, userID strin
|
||||
func (s SqlChannelStore) autocompleteInTeamForSearchDirectMessages(userID string, term string) ([]*model.Channel, error) {
|
||||
// create the main query
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("C")...).
|
||||
Select(channelSliceColumns(true, "C")...).
|
||||
Columns("OtherUsers.Username AS DisplayName").
|
||||
From("Channels AS C").
|
||||
Join("ChannelMembers AS CM ON CM.ChannelId = C.Id").
|
||||
@@ -3339,7 +3354,7 @@ func (s SqlChannelStore) autocompleteInTeamForSearchDirectMessages(userID string
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id = Channels.Id)").
|
||||
Where(sq.Eq{"c.TeamId": teamId}).
|
||||
@@ -3361,7 +3376,7 @@ func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchArchivedInTeam(teamId string, term string, userId string) (model.ChannelList, error) {
|
||||
queryBase := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
queryBase := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("Channels c ON (c.Id = Channels.Id)").
|
||||
Where(sq.And{
|
||||
@@ -3405,7 +3420,7 @@ func (s SqlChannelStore) SearchArchivedInTeam(teamId string, term string, userId
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchForUserInTeam(userId string, teamId string, term string, includeDeleted bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id = Channels.Id)").
|
||||
Join("ChannelMembers cm ON (c.Id = cm.ChannelId)").
|
||||
@@ -3441,7 +3456,7 @@ func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.Se
|
||||
selectQuery = s.getQueryBuilder().Select("count(*)")
|
||||
} else {
|
||||
selectQuery = s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...)
|
||||
Select(channelSliceColumns(true, "c")...)
|
||||
if opts.IncludeTeamInfo {
|
||||
selectQuery = selectQuery.Columns(
|
||||
"t.DisplayName AS TeamDisplayName",
|
||||
@@ -3557,6 +3572,18 @@ func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.Se
|
||||
})
|
||||
}
|
||||
|
||||
if opts.ExcludeAccessControlPolicyEnforced {
|
||||
query = query.Where("c.Id NOT IN (SELECT ID From AccessControlPolicies WHERE Type = ?)", model.AccessControlPolicyTypeChannel)
|
||||
} else if opts.ParentAccessControlPolicyId != "" {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
query = query.Where(sq.Expr("c.Id IN (SELECT ID From AccessControlPolicies WHERE Type = ? AND Data->'imports' @> ?)", model.AccessControlPolicyTypeChannel, fmt.Sprintf("%q", opts.ParentAccessControlPolicyId)))
|
||||
} else {
|
||||
query = query.Where(sq.Expr("c.Id IN (SELECT ID From AccessControlPolicies WHERE Type = ? AND JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?))", model.AccessControlPolicyTypeChannel, fmt.Sprintf("%q", opts.ParentAccessControlPolicyId)))
|
||||
}
|
||||
} else if opts.AccessControlPolicyEnforced {
|
||||
query = query.InnerJoin("AccessControlPolicies acp ON acp.ID = c.Id")
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -3601,7 +3628,7 @@ func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) (
|
||||
"c.DeleteAt": 0,
|
||||
})
|
||||
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id=Channels.Id)").
|
||||
Where(sq.And{
|
||||
@@ -3808,7 +3835,7 @@ func (s SqlChannelStore) searchGroupChannelsQuery(userId, term string, isPostgre
|
||||
Having(having).
|
||||
Limit(model.ChannelSearchDefaultLimit)
|
||||
|
||||
return s.getQueryBuilder().Select(channelSliceColumns()...).
|
||||
return s.getQueryBuilder().Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Expr("Id IN (?)", subq))
|
||||
}
|
||||
@@ -3820,7 +3847,7 @@ func (s SqlChannelStore) searchGroupChannelsQuery(userId, term string, isPostgre
|
||||
having = append(having, sq.Expr(baseLikeTerm, "%"+term+"%"))
|
||||
}
|
||||
|
||||
cc := s.getSubQueryBuilder().Select(channelSliceColumns("c")...).
|
||||
cc := s.getSubQueryBuilder().Select(channelSliceColumns(true, "c")...).
|
||||
From("Channels c").
|
||||
Join("ChannelMembers cm ON c.Id=cm.ChannelId").
|
||||
Join("Users u on u.Id = cm.UserId").
|
||||
@@ -4154,7 +4181,7 @@ func (s SqlChannelStore) ClearAllCustomRoleAssignments() (err error) {
|
||||
|
||||
func (s SqlChannelStore) GetAllChannelsForExportAfter(limit int, afterId string) ([]*model.ChannelForExport, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
Columns(
|
||||
"Teams.Name as TeamName",
|
||||
"Schemes.Name as SchemeName",
|
||||
@@ -4222,7 +4249,7 @@ func (s SqlChannelStore) GetChannelMembersForExport(userId string, teamId string
|
||||
func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId string, includeArchivedChannels bool) ([]*model.DirectChannelForExport, error) {
|
||||
directChannelsForExport := []*model.DirectChannelForExport{}
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Where(sq.And{
|
||||
sq.Gt{"Channels.Id": afterId},
|
||||
|
||||
@@ -119,6 +119,7 @@ type SqlStoreStores struct {
|
||||
propertyField store.PropertyFieldStore
|
||||
propertyValue store.PropertyValueStore
|
||||
accessControlPolicy store.AccessControlPolicyStore
|
||||
Attributes store.AttributesStore
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
@@ -265,6 +266,7 @@ func New(settings model.SqlSettings, logger mlog.LoggerIFace, metrics einterface
|
||||
store.stores.propertyField = newPropertyFieldStore(store)
|
||||
store.stores.propertyValue = newPropertyValueStore(store)
|
||||
store.stores.accessControlPolicy = newSqlAccessControlPolicyStore(store, metrics)
|
||||
store.stores.Attributes = newSqlAttributesStore(store, metrics)
|
||||
|
||||
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
|
||||
|
||||
@@ -1085,6 +1087,10 @@ func (ss *SqlStore) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return ss.stores.accessControlPolicy
|
||||
}
|
||||
|
||||
func (ss *SqlStore) Attributes() store.AttributesStore {
|
||||
return ss.stores.Attributes
|
||||
}
|
||||
|
||||
func (ss *SqlStore) DropAllTables() {
|
||||
if ss.DriverName() == model.DatabaseDriverPostgres {
|
||||
ss.masterX.Exec(`DO
|
||||
|
||||
@@ -96,6 +96,7 @@ type Store interface {
|
||||
PropertyField() PropertyFieldStore
|
||||
PropertyValue() PropertyValueStore
|
||||
AccessControlPolicy() AccessControlPolicyStore
|
||||
Attributes() AttributesStore
|
||||
}
|
||||
|
||||
type RetentionPolicyStore interface {
|
||||
@@ -1116,7 +1117,14 @@ type AccessControlPolicyStore interface {
|
||||
Delete(c request.CTX, id string) error
|
||||
SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error)
|
||||
Get(c request.CTX, id string) (*model.AccessControlPolicy, error)
|
||||
GetAll(rctxc request.CTX, opts GetPolicyOptions) ([]*model.AccessControlPolicy, error)
|
||||
SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error)
|
||||
}
|
||||
|
||||
type AttributesStore interface {
|
||||
RefreshAttributes() error
|
||||
GetSubject(rctx request.CTX, ID, groupID string) (*model.Subject, error)
|
||||
SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error)
|
||||
GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error)
|
||||
}
|
||||
|
||||
// ChannelSearchOpts contains options for searching channels.
|
||||
@@ -1129,27 +1137,30 @@ type AccessControlPolicyStore interface {
|
||||
// Page page requested, if results are paginated.
|
||||
// PerPage number of results per page, if paginated.
|
||||
type ChannelSearchOpts struct {
|
||||
Term string
|
||||
NotAssociatedToGroup string
|
||||
IncludeDeleted bool
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeTeamInfo bool
|
||||
IncludeSearchByID bool
|
||||
ExcludeRemote bool
|
||||
CountOnly bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int
|
||||
LastUpdateAt int
|
||||
Term string
|
||||
NotAssociatedToGroup string
|
||||
IncludeDeleted bool
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeTeamInfo bool
|
||||
IncludeSearchByID bool
|
||||
ExcludeRemote bool
|
||||
CountOnly bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int
|
||||
LastUpdateAt int
|
||||
AccessControlPolicyEnforced bool
|
||||
ExcludeAccessControlPolicyEnforced bool
|
||||
ParentAccessControlPolicyId string
|
||||
}
|
||||
|
||||
func (c *ChannelSearchOpts) IsPaginated() bool {
|
||||
@@ -1211,11 +1222,3 @@ type ThreadMembershipImportData struct {
|
||||
// UnreadMentions is the number of unread mentions to set the UnreadMentions field to.
|
||||
UnreadMentions int64
|
||||
}
|
||||
|
||||
// GetPolicyOptions contains options for filtering policy records.
|
||||
type GetPolicyOptions struct {
|
||||
// ParentID will filter policy records where they inherit parent with PolicyID.
|
||||
ParentID string
|
||||
// Type will filter policy records where they are associated with the Type.
|
||||
Type string
|
||||
}
|
||||
|
||||
@@ -290,24 +290,51 @@ func testAccessControlPolicyStoreGetAll(t *testing.T, rctx request.CTX, ss store
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
id3 := "zzz" + model.NewId()[3:] // ensure the order of the ID
|
||||
parentPolicy2 := &model.AccessControlPolicy{
|
||||
ID: id3,
|
||||
Name: "Name",
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Active: true,
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Imports: []string{},
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"action"},
|
||||
Expression: "user.properties.program == \"engineering\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
err = ss.AccessControlPolicy().Delete(rctx, id)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
_, err = ss.AccessControlPolicy().Save(rctx, parentPolicy2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
|
||||
resourcePolicy, err = ss.AccessControlPolicy().Save(rctx, resourcePolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resourcePolicy)
|
||||
t.Run("GetAll", func(t *testing.T) {
|
||||
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{})
|
||||
policies, _, err := ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 2)
|
||||
require.Len(t, policies, 3)
|
||||
})
|
||||
|
||||
t.Run("GetAll by type", func(t *testing.T) {
|
||||
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{Type: model.AccessControlPolicyTypeParent})
|
||||
policies, _, err := ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{Type: model.AccessControlPolicyTypeParent, IncludeChildren: true})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 1)
|
||||
require.Len(t, policies, 2)
|
||||
require.Equal(t, parentPolicy.ID, policies[0].ID)
|
||||
require.Equal(t, map[string]any{"child_ids": []string{resourcePolicy.ID}}, policies[0].Props)
|
||||
require.Equal(t, map[string]any{"child_ids": []string{}}, policies[1].Props)
|
||||
|
||||
policies, err = ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{Type: model.AccessControlPolicyTypeChannel})
|
||||
policies, _, err = ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{Type: model.AccessControlPolicyTypeChannel})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 1)
|
||||
@@ -315,13 +342,13 @@ func testAccessControlPolicyStoreGetAll(t *testing.T, rctx request.CTX, ss store
|
||||
})
|
||||
|
||||
t.Run("GetAll by parent", func(t *testing.T) {
|
||||
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{ParentID: parentPolicy.ID})
|
||||
policies, _, err := ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{ParentID: parentPolicy.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 1)
|
||||
require.Equal(t, resourcePolicy.ID, policies[0].ID)
|
||||
|
||||
policies, err = ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{ParentID: model.NewId()})
|
||||
policies, _, err = ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{ParentID: model.NewId()})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 0)
|
||||
|
||||
282
server/channels/store/storetest/attributes_store.go
Обычный файл
282
server/channels/store/storetest/attributes_store.go
Обычный файл
@@ -0,0 +1,282 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testPropertyGroupName = "test_property_group"
|
||||
testPropertyA = "test_property_a"
|
||||
testPropertyB = "test_property_b"
|
||||
testPropertyValueA1 = "value_a1"
|
||||
testPropertyValueA2 = "value_a2"
|
||||
testPropertyValueB1 = "value_b1"
|
||||
)
|
||||
|
||||
var (
|
||||
testTeamID = model.NewId()
|
||||
)
|
||||
|
||||
func TestAttributesStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("RefreshAndGet", func(t *testing.T) { testAttributesStoreRefresh(t, rctx, ss) })
|
||||
t.Run("SearchUsers", func(t *testing.T) { testAttributesStoreSearchUsers(t, rctx, ss, s) })
|
||||
}
|
||||
|
||||
func createTestUsers(t *testing.T, rctx request.CTX, ss store.Store) ([]*model.User, string, func()) {
|
||||
maxUsersPerTeam := 50
|
||||
|
||||
u1 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
|
||||
_, err := ss.User().Save(rctx, &u1)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
_, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: testTeamID, UserId: u1.Id}, maxUsersPerTeam)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
u2 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
_, err = ss.User().Save(rctx, &u2)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
_, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: testTeamID, UserId: u2.Id}, maxUsersPerTeam)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// user3 does not have any attributes
|
||||
u3 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
|
||||
_, err = ss.User().Save(rctx, &u3)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
// user3 does not have any attributes
|
||||
u4 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
|
||||
_, err = ss.User().Save(rctx, &u4)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
group, err := ss.PropertyGroup().Register(testPropertyGroupName)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, group.ID)
|
||||
require.Equal(t, testPropertyGroupName, group.Name)
|
||||
groupID := group.ID
|
||||
|
||||
fieldA, err := ss.PropertyField().Create(&model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: testPropertyA,
|
||||
Type: model.PropertyFieldTypeText,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
fieldB, err := ss.PropertyField().Create(&model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: testPropertyB,
|
||||
Type: model.PropertyFieldTypeText,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
vala1, err := json.Marshal(testPropertyValueA1)
|
||||
require.NoError(t, err)
|
||||
vala2, err := json.Marshal(testPropertyValueA2)
|
||||
require.NoError(t, err)
|
||||
valab1, err := json.Marshal(testPropertyValueB1)
|
||||
require.NoError(t, err)
|
||||
|
||||
pva1, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u1.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldA.ID,
|
||||
Value: vala1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pvb1, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u1.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldB.ID,
|
||||
Value: valab1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pva2, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u2.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldA.ID,
|
||||
Value: vala2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pva3, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u3.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldA.ID,
|
||||
Value: vala1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return []*model.User{&u1, &u2, &u3}, groupID, func() {
|
||||
for _, pv := range []*model.PropertyValue{pva1, pvb1, pva2, pva3} {
|
||||
dErr := ss.PropertyValue().Delete(groupID, pv.ID)
|
||||
require.NoError(t, dErr, "couldn't delete property value")
|
||||
}
|
||||
for _, field := range []*model.PropertyField{fieldA, fieldB} {
|
||||
dErr := ss.PropertyField().Delete(groupID, field.ID)
|
||||
require.NoError(t, dErr, "couldn't delete property field")
|
||||
}
|
||||
for _, u := range []*model.User{&u1, &u2, &u3, &u4} {
|
||||
dErr := ss.User().PermanentDelete(rctx, u.Id)
|
||||
require.NoError(t, dErr, "couldn't delete user")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testAttributesStoreRefresh(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
users, groupID, cleanup := createTestUsers(t, rctx, ss)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
t.Run("Refresh attributes", func(t *testing.T) {
|
||||
err := ss.Attributes().RefreshAttributes()
|
||||
require.NoError(t, err, "couldn't refresh attributes")
|
||||
|
||||
// Check if the attributes are set correctly
|
||||
for _, user := range users {
|
||||
subject, err := ss.Attributes().GetSubject(rctx, user.Id, groupID)
|
||||
require.NoError(t, err, "couldn't get subject")
|
||||
|
||||
require.Equal(t, user.Id, subject.ID)
|
||||
require.Equal(t, "user", subject.Type)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get non-existing subject", func(t *testing.T) {
|
||||
subject, err := ss.Attributes().GetSubject(rctx, "non-existing-id", groupID)
|
||||
require.Error(t, err, "expected error when getting non-existing subject")
|
||||
require.IsType(t, &store.ErrNotFound{}, err, "expected not found error")
|
||||
require.Nil(t, subject, "expected nil subject for non-existing ID")
|
||||
})
|
||||
}
|
||||
|
||||
func testAttributesStoreSearchUsers(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
users, _, cleanup := createTestUsers(t, rctx, ss)
|
||||
t.Cleanup(cleanup)
|
||||
require.Len(t, users, 3, "expected 3 users")
|
||||
|
||||
err := ss.Attributes().RefreshAttributes()
|
||||
require.NoError(t, err, "couldn't refresh attributes")
|
||||
|
||||
t.Run("Search users without query", func(t *testing.T) {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 4, "expected 4 users")
|
||||
require.Equal(t, int64(4), count, "expected count 4 users")
|
||||
})
|
||||
|
||||
t.Run("Search users without query, limit by team", func(t *testing.T) {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
TeamID: testTeamID,
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 2, "expected 2 users")
|
||||
require.Equal(t, int64(2), count, "expected count 2 users")
|
||||
})
|
||||
|
||||
t.Run("Search users with a random value query", func(t *testing.T) {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: "Attributes ->> '$." + testPropertyA + "' = ?",
|
||||
Args: []any{"random_value"},
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Empty(t, subjects, "expected no users with the query")
|
||||
require.Equal(t, int64(0), count, "expected count 0 users")
|
||||
})
|
||||
|
||||
t.Run("Search users with a valid value query", func(t *testing.T) {
|
||||
var query string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = "Attributes ->> '$." + testPropertyB + "' = ?"
|
||||
} else {
|
||||
query = "Attributes ->> '" + testPropertyB + "' = $1::text"
|
||||
}
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: query,
|
||||
Args: []any{testPropertyValueB1},
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 1, "expected 1 user with the query")
|
||||
require.Equal(t, subjects[0].Id, users[0].Id, "expected user ID to match")
|
||||
require.Equal(t, int64(1), count, "expected count 1 user")
|
||||
})
|
||||
|
||||
t.Run("Search users with a valid value query and limit", func(t *testing.T) {
|
||||
var query string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = "Attributes ->> '$." + testPropertyA + "' = ?"
|
||||
} else {
|
||||
query = "Attributes ->> '" + testPropertyA + "' = $1::text"
|
||||
}
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: query,
|
||||
Args: []any{testPropertyValueA1},
|
||||
Limit: 1,
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 1, "expected 1 user with the query")
|
||||
if users[0].Id < users[2].Id {
|
||||
require.Equal(t, subjects[0].Id, users[0].Id, "expected user ID to match")
|
||||
} else {
|
||||
require.Equal(t, subjects[0].Id, users[2].Id, "expected user ID to match")
|
||||
}
|
||||
require.Equal(t, int64(2), count, "expected count 1 user")
|
||||
})
|
||||
|
||||
t.Run("Search users with pagination", func(t *testing.T) {
|
||||
var query string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = "Attributes ->> '$." + testPropertyA + "' = ?"
|
||||
} else {
|
||||
query = "Attributes ->> '" + testPropertyA + "' = $1::text"
|
||||
}
|
||||
|
||||
cursor := strings.Repeat("0", 26)
|
||||
for i := 0; i < 5; i++ {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: query,
|
||||
Args: []any{testPropertyValueA1},
|
||||
Limit: 1,
|
||||
Cursor: model.SubjectCursor{
|
||||
TargetID: cursor,
|
||||
},
|
||||
})
|
||||
if len(subjects) == 0 {
|
||||
break
|
||||
}
|
||||
cursor = subjects[0].Id
|
||||
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 1, "expected 1 user with the query")
|
||||
require.Equal(t, int64(2), count, "expected count 2 user with the query")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
request "github.com/mattermost/mattermost/server/public/shared/request"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
store "github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
// AccessControlPolicyStore is an autogenerated mock type for the AccessControlPolicyStore type
|
||||
@@ -65,36 +63,6 @@ func (_m *AccessControlPolicyStore) Get(c request.CTX, id string) (*model.Access
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAll provides a mock function with given fields: rctxc, opts
|
||||
func (_m *AccessControlPolicyStore) GetAll(rctxc request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
|
||||
ret := _m.Called(rctxc, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAll")
|
||||
}
|
||||
|
||||
var r0 []*model.AccessControlPolicy
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, store.GetPolicyOptions) ([]*model.AccessControlPolicy, error)); ok {
|
||||
return rf(rctxc, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, store.GetPolicyOptions) []*model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctxc, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, store.GetPolicyOptions) error); ok {
|
||||
r1 = rf(rctxc, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: c, policy
|
||||
func (_m *AccessControlPolicyStore) Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
|
||||
ret := _m.Called(c, policy)
|
||||
@@ -125,6 +93,43 @@ func (_m *AccessControlPolicyStore) Save(c request.CTX, policy *model.AccessCont
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchPolicies provides a mock function with given fields: rctx, opts
|
||||
func (_m *AccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
ret := _m.Called(rctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SearchPolicies")
|
||||
}
|
||||
|
||||
var r0 []*model.AccessControlPolicy
|
||||
var r1 int64
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error)); ok {
|
||||
return rf(rctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessControlPolicySearch) []*model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, model.AccessControlPolicySearch) int64); ok {
|
||||
r1 = rf(rctx, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, model.AccessControlPolicySearch) error); ok {
|
||||
r2 = rf(rctx, opts)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// SetActiveStatus provides a mock function with given fields: c, id, active
|
||||
func (_m *AccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
|
||||
ret := _m.Called(c, id, active)
|
||||
|
||||
145
server/channels/store/storetest/mocks/AttributesStore.go
Обычный файл
145
server/channels/store/storetest/mocks/AttributesStore.go
Обычный файл
@@ -0,0 +1,145 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
request "github.com/mattermost/mattermost/server/public/shared/request"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// AttributesStore is an autogenerated mock type for the AttributesStore type
|
||||
type AttributesStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetChannelMembersToRemove provides a mock function with given fields: rctx, channelID, opts
|
||||
func (_m *AttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
ret := _m.Called(rctx, channelID, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetChannelMembersToRemove")
|
||||
}
|
||||
|
||||
var r0 []*model.ChannelMember
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) ([]*model.ChannelMember, error)); ok {
|
||||
return rf(rctx, channelID, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) []*model.ChannelMember); ok {
|
||||
r0 = rf(rctx, channelID, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.ChannelMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, model.SubjectSearchOptions) error); ok {
|
||||
r1 = rf(rctx, channelID, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSubject provides a mock function with given fields: rctx, ID, groupID
|
||||
func (_m *AttributesStore) GetSubject(rctx request.CTX, ID string, groupID string) (*model.Subject, error) {
|
||||
ret := _m.Called(rctx, ID, groupID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSubject")
|
||||
}
|
||||
|
||||
var r0 *model.Subject
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) (*model.Subject, error)); ok {
|
||||
return rf(rctx, ID, groupID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) *model.Subject); ok {
|
||||
r0 = rf(rctx, ID, groupID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Subject)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, string) error); ok {
|
||||
r1 = rf(rctx, ID, groupID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RefreshAttributes provides a mock function with given fields:
|
||||
func (_m *AttributesStore) RefreshAttributes() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RefreshAttributes")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SearchUsers provides a mock function with given fields: rctx, opts
|
||||
func (_m *AttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
ret := _m.Called(rctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SearchUsers")
|
||||
}
|
||||
|
||||
var r0 []*model.User
|
||||
var r1 int64
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.SubjectSearchOptions) ([]*model.User, int64, error)); ok {
|
||||
return rf(rctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.SubjectSearchOptions) []*model.User); ok {
|
||||
r0 = rf(rctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, model.SubjectSearchOptions) int64); ok {
|
||||
r1 = rf(rctx, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, model.SubjectSearchOptions) error); ok {
|
||||
r2 = rf(rctx, opts)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// NewAttributesStore creates a new instance of AttributesStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAttributesStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AttributesStore {
|
||||
mock := &AttributesStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -44,6 +44,26 @@ func (_m *Store) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Attributes provides a mock function with given fields:
|
||||
func (_m *Store) Attributes() store.AttributesStore {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Attributes")
|
||||
}
|
||||
|
||||
var r0 store.AttributesStore
|
||||
if rf, ok := ret.Get(0).(func() store.AttributesStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.AttributesStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Audit provides a mock function with given fields:
|
||||
func (_m *Store) Audit() store.AuditStore {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -70,6 +70,7 @@ type Store struct {
|
||||
PropertyFieldStore mocks.PropertyFieldStore
|
||||
PropertyValueStore mocks.PropertyValueStore
|
||||
AccessControlPolicyStore mocks.AccessControlPolicyStore
|
||||
AttributesStore mocks.AttributesStore
|
||||
}
|
||||
|
||||
func (s *Store) SetContext(context context.Context) { s.context = context }
|
||||
@@ -158,6 +159,9 @@ func (s *Store) ReplicaLagTime() error { return nil }
|
||||
func (s *Store) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return &s.AccessControlPolicyStore
|
||||
}
|
||||
func (s *Store) Attributes() store.AttributesStore {
|
||||
return &s.AttributesStore
|
||||
}
|
||||
|
||||
func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
return mock.AssertExpectationsForObjects(t,
|
||||
@@ -202,5 +206,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
&s.ChannelBookmarkStore,
|
||||
&s.ScheduledPostStore,
|
||||
&s.AccessControlPolicyStore,
|
||||
&s.AttributesStore,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type TimerLayer struct {
|
||||
store.Store
|
||||
Metrics einterfaces.MetricsInterface
|
||||
AccessControlPolicyStore store.AccessControlPolicyStore
|
||||
AttributesStore store.AttributesStore
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
@@ -75,6 +76,10 @@ func (s *TimerLayer) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return s.AccessControlPolicyStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Attributes() store.AttributesStore {
|
||||
return s.AttributesStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Audit() store.AuditStore {
|
||||
return s.AuditStore
|
||||
}
|
||||
@@ -276,6 +281,11 @@ type TimerLayerAccessControlPolicyStore struct {
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerAttributesStore struct {
|
||||
store.AttributesStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerAuditStore struct {
|
||||
store.AuditStore
|
||||
Root *TimerLayer
|
||||
@@ -553,22 +563,6 @@ func (s *TimerLayerAccessControlPolicyStore) Get(c request.CTX, id string) (*mod
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAccessControlPolicyStore) GetAll(rctxc request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.AccessControlPolicyStore.GetAll(rctxc, opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.GetAll", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -585,6 +579,22 @@ func (s *TimerLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.A
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, resultVar1, err := s.AccessControlPolicyStore.SearchPolicies(rctx, opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.SearchPolicies", success, elapsed)
|
||||
}
|
||||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -601,6 +611,70 @@ func (s *TimerLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id s
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.AttributesStore.GetChannelMembersToRemove(rctx, channelID, opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AttributesStore.GetChannelMembersToRemove", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) GetSubject(rctx request.CTX, ID string, groupID string) (*model.Subject, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.AttributesStore.GetSubject(rctx, ID, groupID)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AttributesStore.GetSubject", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) RefreshAttributes() error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.AttributesStore.RefreshAttributes()
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AttributesStore.RefreshAttributes", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, resultVar1, err := s.AttributesStore.SearchUsers(rctx, opts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AttributesStore.SearchUsers", success, elapsed)
|
||||
}
|
||||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAuditStore) Get(userID string, offset int, limit int) (model.Audits, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -13021,6 +13095,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
|
||||
}
|
||||
|
||||
newStore.AccessControlPolicyStore = &TimerLayerAccessControlPolicyStore{AccessControlPolicyStore: childStore.AccessControlPolicy(), Root: &newStore}
|
||||
newStore.AttributesStore = &TimerLayerAttributesStore{AttributesStore: childStore.Attributes(), Root: &newStore}
|
||||
newStore.AuditStore = &TimerLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
|
||||
newStore.BotStore = &TimerLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
|
||||
newStore.ChannelStore = &TimerLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}
|
||||
|
||||
@@ -25,86 +25,88 @@ const (
|
||||
)
|
||||
|
||||
type Params struct {
|
||||
UserId string
|
||||
TeamId string
|
||||
InviteId string
|
||||
TokenId string
|
||||
ThreadId string
|
||||
Timestamp int64
|
||||
TimeRange string
|
||||
ChannelId string
|
||||
PostId string
|
||||
PolicyId string
|
||||
FileId string
|
||||
Filename string
|
||||
UploadId string
|
||||
PluginId string
|
||||
CommandId string
|
||||
HookId string
|
||||
ReportId string
|
||||
EmojiId string
|
||||
AppId string
|
||||
Email string
|
||||
Username string
|
||||
TeamName string
|
||||
ChannelName string
|
||||
PreferenceName string
|
||||
EmojiName string
|
||||
Category string
|
||||
Service string
|
||||
JobId string
|
||||
JobType string
|
||||
ActionId string
|
||||
RoleId string
|
||||
RoleName string
|
||||
SchemeId string
|
||||
Scope string
|
||||
GroupId string
|
||||
Page int
|
||||
PerPage int
|
||||
LogsPerPage int
|
||||
Permanent bool
|
||||
RemoteId string
|
||||
SyncableId string
|
||||
SyncableType model.GroupSyncableType
|
||||
BotUserId string
|
||||
Q string
|
||||
IsLinked *bool
|
||||
IsConfigured *bool
|
||||
NotAssociatedToTeam string
|
||||
NotAssociatedToChannel string
|
||||
Paginate *bool
|
||||
IncludeMemberCount bool
|
||||
IncludeMemberIDs bool
|
||||
NotAssociatedToGroup string
|
||||
ExcludeDefaultChannels bool
|
||||
LimitAfter int
|
||||
LimitBefore int
|
||||
GroupIDs string
|
||||
IncludeTotalCount bool
|
||||
IncludeDeleted bool
|
||||
FilterAllowReference bool
|
||||
FilterArchived bool
|
||||
FilterParentTeamPermitted bool
|
||||
CategoryId string
|
||||
ExportName string
|
||||
ExcludePolicyConstrained bool
|
||||
GroupSource model.GroupSource
|
||||
FilterHasMember string
|
||||
IncludeChannelMemberCount string
|
||||
OutgoingOAuthConnectionID string
|
||||
ExcludeOffline bool
|
||||
InChannel string
|
||||
NotInChannel string
|
||||
Topic string
|
||||
CreatorId string
|
||||
OnlyConfirmed bool
|
||||
OnlyPlugins bool
|
||||
IncludeUnconfirmed bool
|
||||
ExcludeConfirmed bool
|
||||
ExcludePlugins bool
|
||||
ExcludeHome bool
|
||||
ExcludeRemote bool
|
||||
UserId string
|
||||
TeamId string
|
||||
InviteId string
|
||||
TokenId string
|
||||
ThreadId string
|
||||
Timestamp int64
|
||||
TimeRange string
|
||||
ChannelId string
|
||||
PostId string
|
||||
PolicyId string
|
||||
FileId string
|
||||
Filename string
|
||||
UploadId string
|
||||
PluginId string
|
||||
CommandId string
|
||||
HookId string
|
||||
ReportId string
|
||||
EmojiId string
|
||||
AppId string
|
||||
Email string
|
||||
Username string
|
||||
TeamName string
|
||||
ChannelName string
|
||||
PreferenceName string
|
||||
EmojiName string
|
||||
Category string
|
||||
Service string
|
||||
JobId string
|
||||
JobType string
|
||||
ActionId string
|
||||
RoleId string
|
||||
RoleName string
|
||||
SchemeId string
|
||||
Scope string
|
||||
GroupId string
|
||||
Page int
|
||||
PerPage int
|
||||
LogsPerPage int
|
||||
Permanent bool
|
||||
RemoteId string
|
||||
SyncableId string
|
||||
SyncableType model.GroupSyncableType
|
||||
BotUserId string
|
||||
Q string
|
||||
IsLinked *bool
|
||||
IsConfigured *bool
|
||||
NotAssociatedToTeam string
|
||||
NotAssociatedToChannel string
|
||||
Paginate *bool
|
||||
IncludeMemberCount bool
|
||||
IncludeMemberIDs bool
|
||||
NotAssociatedToGroup string
|
||||
ExcludeDefaultChannels bool
|
||||
LimitAfter int
|
||||
LimitBefore int
|
||||
GroupIDs string
|
||||
IncludeTotalCount bool
|
||||
IncludeDeleted bool
|
||||
FilterAllowReference bool
|
||||
FilterArchived bool
|
||||
FilterParentTeamPermitted bool
|
||||
CategoryId string
|
||||
ExportName string
|
||||
ExcludePolicyConstrained bool
|
||||
GroupSource model.GroupSource
|
||||
FilterHasMember string
|
||||
IncludeChannelMemberCount string
|
||||
OutgoingOAuthConnectionID string
|
||||
ExcludeOffline bool
|
||||
InChannel string
|
||||
NotInChannel string
|
||||
Topic string
|
||||
CreatorId string
|
||||
OnlyConfirmed bool
|
||||
OnlyPlugins bool
|
||||
IncludeUnconfirmed bool
|
||||
ExcludeConfirmed bool
|
||||
ExcludePlugins bool
|
||||
ExcludeHome bool
|
||||
ExcludeRemote bool
|
||||
AccessControlPolicyEnforced bool
|
||||
ExcludeAccessControlPolicyEnforced bool
|
||||
|
||||
//Bookmarks
|
||||
ChannelBookmarkId string
|
||||
@@ -277,6 +279,8 @@ func ParamsFromRequest(r *http.Request) *Params {
|
||||
params.IncludeDeleted, _ = strconv.ParseBool(query.Get("include_deleted"))
|
||||
params.ExportName = props["export_name"]
|
||||
params.ExcludePolicyConstrained, _ = strconv.ParseBool(query.Get("exclude_policy_constrained"))
|
||||
params.AccessControlPolicyEnforced, _ = strconv.ParseBool(query.Get("access_control_policy_enforced"))
|
||||
params.ExcludeAccessControlPolicyEnforced, _ = strconv.ParseBool(query.Get("exclude_access_control_policy_enforced"))
|
||||
|
||||
if val := query.Get("group_source"); val != "" {
|
||||
switch val {
|
||||
|
||||
12
server/einterfaces/access_control.go
Обычный файл
12
server/einterfaces/access_control.go
Обычный файл
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package einterfaces
|
||||
|
||||
// AccessControlServiceInterface is the interface that provides access control
|
||||
// services. It combines the PolicyAdministrationPointInterface and
|
||||
// PolicyDecisionPointInterface interfaces to provide a complete access control solution.
|
||||
type AccessControlServiceInterface interface {
|
||||
PolicyAdministrationPointInterface
|
||||
PolicyDecisionPointInterface
|
||||
}
|
||||
13
server/einterfaces/jobs/access_control.go
Обычный файл
13
server/einterfaces/jobs/access_control.go
Обычный файл
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type AccessControlSyncJobInterface interface {
|
||||
MakeWorker() model.Worker
|
||||
MakeScheduler() Scheduler
|
||||
}
|
||||
@@ -135,7 +135,7 @@ type MetricsInterface interface {
|
||||
ObserveDesktopCpuUsage(platform, version, process string, usage float64)
|
||||
ObserveDesktopMemoryUsage(platform, version, process string, usage float64)
|
||||
|
||||
ObserveAccessControlEngineInitDuration(value float64)
|
||||
ObserveAccessControlSearchQueryDuration(value float64)
|
||||
ObserveAccessControlExpressionCompileDuration(value float64)
|
||||
ObserveAccessControlEvaluateDuration(value float64)
|
||||
IncrementAccessControlCacheInvalidation()
|
||||
|
||||
402
server/einterfaces/mocks/AccessControlServiceInterface.go
Обычный файл
402
server/einterfaces/mocks/AccessControlServiceInterface.go
Обычный файл
@@ -0,0 +1,402 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make einterfaces-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
request "github.com/mattermost/mattermost/server/public/shared/request"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// AccessControlServiceInterface is an autogenerated mock type for the AccessControlServiceInterface type
|
||||
type AccessControlServiceInterface struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// AccessEvaluation provides a mock function with given fields: rctx, accessRequest
|
||||
func (_m *AccessControlServiceInterface) AccessEvaluation(rctx request.CTX, accessRequest model.AccessRequest) (model.AccessDecision, *model.AppError) {
|
||||
ret := _m.Called(rctx, accessRequest)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AccessEvaluation")
|
||||
}
|
||||
|
||||
var r0 model.AccessDecision
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessRequest) (model.AccessDecision, *model.AppError)); ok {
|
||||
return rf(rctx, accessRequest)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessRequest) model.AccessDecision); ok {
|
||||
r0 = rf(rctx, accessRequest)
|
||||
} else {
|
||||
r0 = ret.Get(0).(model.AccessDecision)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, model.AccessRequest) *model.AppError); ok {
|
||||
r1 = rf(rctx, accessRequest)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CheckExpression provides a mock function with given fields: rctx, expression
|
||||
func (_m *AccessControlServiceInterface) CheckExpression(rctx request.CTX, expression string) ([]model.CELExpressionError, *model.AppError) {
|
||||
ret := _m.Called(rctx, expression)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CheckExpression")
|
||||
}
|
||||
|
||||
var r0 []model.CELExpressionError
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) ([]model.CELExpressionError, *model.AppError)); ok {
|
||||
return rf(rctx, expression)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) []model.CELExpressionError); ok {
|
||||
r0 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]model.CELExpressionError)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeletePolicy provides a mock function with given fields: rctx, id
|
||||
func (_m *AccessControlServiceInterface) DeletePolicy(rctx request.CTX, id string) *model.AppError {
|
||||
ret := _m.Called(rctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeletePolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AppError); ok {
|
||||
r0 = rf(rctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ExpressionToVisualAST provides a mock function with given fields: rctx, expression
|
||||
func (_m *AccessControlServiceInterface) ExpressionToVisualAST(rctx request.CTX, expression string) (*model.VisualExpression, *model.AppError) {
|
||||
ret := _m.Called(rctx, expression)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ExpressionToVisualAST")
|
||||
}
|
||||
|
||||
var r0 *model.VisualExpression
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.VisualExpression, *model.AppError)); ok {
|
||||
return rf(rctx, expression)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.VisualExpression); ok {
|
||||
r0 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.VisualExpression)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetChannelMembersToRemove provides a mock function with given fields: rctx, channelID
|
||||
func (_m *AccessControlServiceInterface) GetChannelMembersToRemove(rctx request.CTX, channelID string) ([]*model.ChannelMember, *model.AppError) {
|
||||
ret := _m.Called(rctx, channelID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetChannelMembersToRemove")
|
||||
}
|
||||
|
||||
var r0 []*model.ChannelMember
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) ([]*model.ChannelMember, *model.AppError)); ok {
|
||||
return rf(rctx, channelID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) []*model.ChannelMember); ok {
|
||||
r0 = rf(rctx, channelID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.ChannelMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, channelID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPolicy provides a mock function with given fields: rctx, id
|
||||
func (_m *AccessControlServiceInterface) GetPolicy(rctx request.CTX, id string) (*model.AccessControlPolicy, *model.AppError) {
|
||||
ret := _m.Called(rctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AccessControlPolicy
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.AccessControlPolicy, *model.AppError)); ok {
|
||||
return rf(rctx, id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, id)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPolicyRuleAttributes provides a mock function with given fields: rctx, policyID, action
|
||||
func (_m *AccessControlServiceInterface) GetPolicyRuleAttributes(rctx request.CTX, policyID string, action string) (map[string][]string, *model.AppError) {
|
||||
ret := _m.Called(rctx, policyID, action)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPolicyRuleAttributes")
|
||||
}
|
||||
|
||||
var r0 map[string][]string
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) (map[string][]string, *model.AppError)); ok {
|
||||
return rf(rctx, policyID, action)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) map[string][]string); ok {
|
||||
r0 = rf(rctx, policyID, action)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string][]string)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, policyID, action)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Init provides a mock function with given fields: rctx
|
||||
func (_m *AccessControlServiceInterface) Init(rctx request.CTX) *model.AppError {
|
||||
ret := _m.Called(rctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Init")
|
||||
}
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX) *model.AppError); ok {
|
||||
r0 = rf(rctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NormalizePolicy provides a mock function with given fields: rctx, policy
|
||||
func (_m *AccessControlServiceInterface) NormalizePolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError) {
|
||||
ret := _m.Called(rctx, policy)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for NormalizePolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AccessControlPolicy
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError)); ok {
|
||||
return rf(rctx, policy)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) *model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, *model.AccessControlPolicy) *model.AppError); ok {
|
||||
r1 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// QueryUsersForExpression provides a mock function with given fields: rctx, expression, opts
|
||||
func (_m *AccessControlServiceInterface) QueryUsersForExpression(rctx request.CTX, expression string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError) {
|
||||
ret := _m.Called(rctx, expression, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for QueryUsersForExpression")
|
||||
}
|
||||
|
||||
var r0 []*model.User
|
||||
var r1 int64
|
||||
var r2 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError)); ok {
|
||||
return rf(rctx, expression, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) []*model.User); ok {
|
||||
r0 = rf(rctx, expression, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, model.SubjectSearchOptions) int64); ok {
|
||||
r1 = rf(rctx, expression, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, string, model.SubjectSearchOptions) *model.AppError); ok {
|
||||
r2 = rf(rctx, expression, opts)
|
||||
} else {
|
||||
if ret.Get(2) != nil {
|
||||
r2 = ret.Get(2).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// QueryUsersForResource provides a mock function with given fields: rctx, resourceID, action, opts
|
||||
func (_m *AccessControlServiceInterface) QueryUsersForResource(rctx request.CTX, resourceID string, action string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError) {
|
||||
ret := _m.Called(rctx, resourceID, action, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for QueryUsersForResource")
|
||||
}
|
||||
|
||||
var r0 []*model.User
|
||||
var r1 int64
|
||||
var r2 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string, model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError)); ok {
|
||||
return rf(rctx, resourceID, action, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string, model.SubjectSearchOptions) []*model.User); ok {
|
||||
r0 = rf(rctx, resourceID, action, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, string, model.SubjectSearchOptions) int64); ok {
|
||||
r1 = rf(rctx, resourceID, action, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, string, string, model.SubjectSearchOptions) *model.AppError); ok {
|
||||
r2 = rf(rctx, resourceID, action, opts)
|
||||
} else {
|
||||
if ret.Get(2) != nil {
|
||||
r2 = ret.Get(2).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// SavePolicy provides a mock function with given fields: rctx, policy
|
||||
func (_m *AccessControlServiceInterface) SavePolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError) {
|
||||
ret := _m.Called(rctx, policy)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SavePolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AccessControlPolicy
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError)); ok {
|
||||
return rf(rctx, policy)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) *model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, *model.AccessControlPolicy) *model.AppError); ok {
|
||||
r1 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewAccessControlServiceInterface creates a new instance of AccessControlServiceInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAccessControlServiceInterface(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AccessControlServiceInterface {
|
||||
mock := &AccessControlServiceInterface{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
71
server/einterfaces/mocks/AccessControlSyncJobInterface.go
Обычный файл
71
server/einterfaces/mocks/AccessControlSyncJobInterface.go
Обычный файл
@@ -0,0 +1,71 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make einterfaces-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
jobs "github.com/mattermost/mattermost/server/v8/einterfaces/jobs"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
// AccessControlSyncJobInterface is an autogenerated mock type for the AccessControlSyncJobInterface type
|
||||
type AccessControlSyncJobInterface struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// MakeScheduler provides a mock function with given fields:
|
||||
func (_m *AccessControlSyncJobInterface) MakeScheduler() jobs.Scheduler {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MakeScheduler")
|
||||
}
|
||||
|
||||
var r0 jobs.Scheduler
|
||||
if rf, ok := ret.Get(0).(func() jobs.Scheduler); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(jobs.Scheduler)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MakeWorker provides a mock function with given fields:
|
||||
func (_m *AccessControlSyncJobInterface) MakeWorker() model.Worker {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MakeWorker")
|
||||
}
|
||||
|
||||
var r0 model.Worker
|
||||
if rf, ok := ret.Get(0).(func() model.Worker); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(model.Worker)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewAccessControlSyncJobInterface creates a new instance of AccessControlSyncJobInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAccessControlSyncJobInterface(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AccessControlSyncJobInterface {
|
||||
mock := &AccessControlSyncJobInterface{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -308,11 +308,6 @@ func (_m *MetricsInterface) ObserveAPIEndpointDuration(endpoint string, method s
|
||||
_m.Called(endpoint, method, statusCode, originClient, pageLoadContext, elapsed)
|
||||
}
|
||||
|
||||
// ObserveAccessControlEngineInitDuration provides a mock function with given fields: value
|
||||
func (_m *MetricsInterface) ObserveAccessControlEngineInitDuration(value float64) {
|
||||
_m.Called(value)
|
||||
}
|
||||
|
||||
// ObserveAccessControlEvaluateDuration provides a mock function with given fields: value
|
||||
func (_m *MetricsInterface) ObserveAccessControlEvaluateDuration(value float64) {
|
||||
_m.Called(value)
|
||||
@@ -323,6 +318,11 @@ func (_m *MetricsInterface) ObserveAccessControlExpressionCompileDuration(value
|
||||
_m.Called(value)
|
||||
}
|
||||
|
||||
// ObserveAccessControlSearchQueryDuration provides a mock function with given fields: value
|
||||
func (_m *MetricsInterface) ObserveAccessControlSearchQueryDuration(value float64) {
|
||||
_m.Called(value)
|
||||
}
|
||||
|
||||
// ObserveClientChannelSwitchDuration provides a mock function with given fields: platform, agent, fresh, userID, elapsed
|
||||
func (_m *MetricsInterface) ObserveClientChannelSwitchDuration(platform string, agent string, fresh string, userID string, elapsed float64) {
|
||||
_m.Called(platform, agent, fresh, userID, elapsed)
|
||||
|
||||
372
server/einterfaces/mocks/PolicyAdministrationPointInterface.go
Обычный файл
372
server/einterfaces/mocks/PolicyAdministrationPointInterface.go
Обычный файл
@@ -0,0 +1,372 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make einterfaces-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
request "github.com/mattermost/mattermost/server/public/shared/request"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PolicyAdministrationPointInterface is an autogenerated mock type for the PolicyAdministrationPointInterface type
|
||||
type PolicyAdministrationPointInterface struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CheckExpression provides a mock function with given fields: rctx, expression
|
||||
func (_m *PolicyAdministrationPointInterface) CheckExpression(rctx request.CTX, expression string) ([]model.CELExpressionError, *model.AppError) {
|
||||
ret := _m.Called(rctx, expression)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CheckExpression")
|
||||
}
|
||||
|
||||
var r0 []model.CELExpressionError
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) ([]model.CELExpressionError, *model.AppError)); ok {
|
||||
return rf(rctx, expression)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) []model.CELExpressionError); ok {
|
||||
r0 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]model.CELExpressionError)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeletePolicy provides a mock function with given fields: rctx, id
|
||||
func (_m *PolicyAdministrationPointInterface) DeletePolicy(rctx request.CTX, id string) *model.AppError {
|
||||
ret := _m.Called(rctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeletePolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AppError); ok {
|
||||
r0 = rf(rctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ExpressionToVisualAST provides a mock function with given fields: rctx, expression
|
||||
func (_m *PolicyAdministrationPointInterface) ExpressionToVisualAST(rctx request.CTX, expression string) (*model.VisualExpression, *model.AppError) {
|
||||
ret := _m.Called(rctx, expression)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ExpressionToVisualAST")
|
||||
}
|
||||
|
||||
var r0 *model.VisualExpression
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.VisualExpression, *model.AppError)); ok {
|
||||
return rf(rctx, expression)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.VisualExpression); ok {
|
||||
r0 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.VisualExpression)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, expression)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetChannelMembersToRemove provides a mock function with given fields: rctx, channelID
|
||||
func (_m *PolicyAdministrationPointInterface) GetChannelMembersToRemove(rctx request.CTX, channelID string) ([]*model.ChannelMember, *model.AppError) {
|
||||
ret := _m.Called(rctx, channelID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetChannelMembersToRemove")
|
||||
}
|
||||
|
||||
var r0 []*model.ChannelMember
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) ([]*model.ChannelMember, *model.AppError)); ok {
|
||||
return rf(rctx, channelID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) []*model.ChannelMember); ok {
|
||||
r0 = rf(rctx, channelID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.ChannelMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, channelID)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPolicy provides a mock function with given fields: rctx, id
|
||||
func (_m *PolicyAdministrationPointInterface) GetPolicy(rctx request.CTX, id string) (*model.AccessControlPolicy, *model.AppError) {
|
||||
ret := _m.Called(rctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AccessControlPolicy
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.AccessControlPolicy, *model.AppError)); ok {
|
||||
return rf(rctx, id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, id)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPolicyRuleAttributes provides a mock function with given fields: rctx, policyID, action
|
||||
func (_m *PolicyAdministrationPointInterface) GetPolicyRuleAttributes(rctx request.CTX, policyID string, action string) (map[string][]string, *model.AppError) {
|
||||
ret := _m.Called(rctx, policyID, action)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPolicyRuleAttributes")
|
||||
}
|
||||
|
||||
var r0 map[string][]string
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) (map[string][]string, *model.AppError)); ok {
|
||||
return rf(rctx, policyID, action)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) map[string][]string); ok {
|
||||
r0 = rf(rctx, policyID, action)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string][]string)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, string) *model.AppError); ok {
|
||||
r1 = rf(rctx, policyID, action)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Init provides a mock function with given fields: rctx
|
||||
func (_m *PolicyAdministrationPointInterface) Init(rctx request.CTX) *model.AppError {
|
||||
ret := _m.Called(rctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Init")
|
||||
}
|
||||
|
||||
var r0 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX) *model.AppError); ok {
|
||||
r0 = rf(rctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NormalizePolicy provides a mock function with given fields: rctx, policy
|
||||
func (_m *PolicyAdministrationPointInterface) NormalizePolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError) {
|
||||
ret := _m.Called(rctx, policy)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for NormalizePolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AccessControlPolicy
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError)); ok {
|
||||
return rf(rctx, policy)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) *model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, *model.AccessControlPolicy) *model.AppError); ok {
|
||||
r1 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// QueryUsersForExpression provides a mock function with given fields: rctx, expression, opts
|
||||
func (_m *PolicyAdministrationPointInterface) QueryUsersForExpression(rctx request.CTX, expression string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError) {
|
||||
ret := _m.Called(rctx, expression, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for QueryUsersForExpression")
|
||||
}
|
||||
|
||||
var r0 []*model.User
|
||||
var r1 int64
|
||||
var r2 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError)); ok {
|
||||
return rf(rctx, expression, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) []*model.User); ok {
|
||||
r0 = rf(rctx, expression, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, model.SubjectSearchOptions) int64); ok {
|
||||
r1 = rf(rctx, expression, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, string, model.SubjectSearchOptions) *model.AppError); ok {
|
||||
r2 = rf(rctx, expression, opts)
|
||||
} else {
|
||||
if ret.Get(2) != nil {
|
||||
r2 = ret.Get(2).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// QueryUsersForResource provides a mock function with given fields: rctx, resourceID, action, opts
|
||||
func (_m *PolicyAdministrationPointInterface) QueryUsersForResource(rctx request.CTX, resourceID string, action string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError) {
|
||||
ret := _m.Called(rctx, resourceID, action, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for QueryUsersForResource")
|
||||
}
|
||||
|
||||
var r0 []*model.User
|
||||
var r1 int64
|
||||
var r2 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string, model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError)); ok {
|
||||
return rf(rctx, resourceID, action, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string, model.SubjectSearchOptions) []*model.User); ok {
|
||||
r0 = rf(rctx, resourceID, action, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, string, model.SubjectSearchOptions) int64); ok {
|
||||
r1 = rf(rctx, resourceID, action, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, string, string, model.SubjectSearchOptions) *model.AppError); ok {
|
||||
r2 = rf(rctx, resourceID, action, opts)
|
||||
} else {
|
||||
if ret.Get(2) != nil {
|
||||
r2 = ret.Get(2).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// SavePolicy provides a mock function with given fields: rctx, policy
|
||||
func (_m *PolicyAdministrationPointInterface) SavePolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError) {
|
||||
ret := _m.Called(rctx, policy)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SavePolicy")
|
||||
}
|
||||
|
||||
var r0 *model.AccessControlPolicy
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError)); ok {
|
||||
return rf(rctx, policy)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) *model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, *model.AccessControlPolicy) *model.AppError); ok {
|
||||
r1 = rf(rctx, policy)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewPolicyAdministrationPointInterface creates a new instance of PolicyAdministrationPointInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewPolicyAdministrationPointInterface(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *PolicyAdministrationPointInterface {
|
||||
mock := &PolicyAdministrationPointInterface{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -16,24 +16,22 @@ type PolicyDecisionPointInterface struct {
|
||||
}
|
||||
|
||||
// AccessEvaluation provides a mock function with given fields: rctx, accessRequest
|
||||
func (_m *PolicyDecisionPointInterface) AccessEvaluation(rctx request.CTX, accessRequest model.AccessRequest) (*model.AccessDecision, *model.AppError) {
|
||||
func (_m *PolicyDecisionPointInterface) AccessEvaluation(rctx request.CTX, accessRequest model.AccessRequest) (model.AccessDecision, *model.AppError) {
|
||||
ret := _m.Called(rctx, accessRequest)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AccessEvaluation")
|
||||
}
|
||||
|
||||
var r0 *model.AccessDecision
|
||||
var r0 model.AccessDecision
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessRequest) (*model.AccessDecision, *model.AppError)); ok {
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessRequest) (model.AccessDecision, *model.AppError)); ok {
|
||||
return rf(rctx, accessRequest)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessRequest) *model.AccessDecision); ok {
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessRequest) model.AccessDecision); ok {
|
||||
r0 = rf(rctx, accessRequest)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.AccessDecision)
|
||||
}
|
||||
r0 = ret.Get(0).(model.AccessDecision)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, model.AccessRequest) *model.AppError); ok {
|
||||
|
||||
42
server/einterfaces/pap.go
Обычный файл
42
server/einterfaces/pap.go
Обычный файл
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package einterfaces
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
|
||||
// PolicyAdministrationPointInterface is the service that manages access control policies.
|
||||
// It is responsible for creating, updating, and deleting policies.
|
||||
// Also, it provides methods to check the validity of expressions and to retrieve policies.
|
||||
type PolicyAdministrationPointInterface interface {
|
||||
// Init initializes the policy administration point and intiates the CEL engine.
|
||||
// It is an idempotent operation, meaning that it can be called multiple times.
|
||||
Init(rctx request.CTX) *model.AppError
|
||||
// GetPolicyRuleAttributes retrieves the attributes of the given policy.
|
||||
// It returns a map of attribute names to their values for given action.
|
||||
GetPolicyRuleAttributes(rctx request.CTX, policyID string, action string) (map[string][]string, *model.AppError)
|
||||
// CheckExpression checks the validity of the given expression using the CEL engine.
|
||||
// It returns a list of CELExpressionError if the expression is invalid.
|
||||
// If the expression is valid, it returns an empty list.
|
||||
CheckExpression(rctx request.CTX, expression string) ([]model.CELExpressionError, *model.AppError)
|
||||
// ExpressionToVisualAST converts the given expression to a visual AST.
|
||||
ExpressionToVisualAST(rctx request.CTX, expression string) (*model.VisualExpression, *model.AppError)
|
||||
// NormalizePolicy normalizes the given policy by restoring ids back to names.
|
||||
NormalizePolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError)
|
||||
// QueryUsersForExpression evaluates the given expression using the CEL engine.
|
||||
// It returns a list of users that match the expression.
|
||||
QueryUsersForExpression(rctx request.CTX, expression string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError)
|
||||
// QueryUsersForResource evaluates finds the users match to the resource.
|
||||
QueryUsersForResource(rctx request.CTX, resourceID, action string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError)
|
||||
// GetChannelMembersToRemove retrieves the channel members that need to be removed from the given channel.
|
||||
GetChannelMembersToRemove(rctx request.CTX, channelID string) ([]*model.ChannelMember, *model.AppError)
|
||||
// SavePolicy saves the given access control policy.
|
||||
SavePolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError)
|
||||
// GetPolicy retrieves the access control policy with the given ID.
|
||||
GetPolicy(rctx request.CTX, id string) (*model.AccessControlPolicy, *model.AppError)
|
||||
// DeletePolicy deletes the access control policy with the given ID.
|
||||
DeletePolicy(rctx request.CTX, id string) *model.AppError
|
||||
}
|
||||
@@ -12,5 +12,5 @@ import (
|
||||
// using the OpenID Auth API spec. It determines whether a subject can perform
|
||||
// an action on a resource based on the resource policy.
|
||||
type PolicyDecisionPointInterface interface {
|
||||
AccessEvaluation(rctx request.CTX, accessRequest model.AccessRequest) (*model.AccessDecision, *model.AppError)
|
||||
AccessEvaluation(rctx request.CTX, accessRequest model.AccessRequest) (model.AccessDecision, *model.AppError)
|
||||
}
|
||||
|
||||
@@ -34,4 +34,6 @@ import (
|
||||
_ "github.com/mattermost/enterprise/ip_filtering"
|
||||
// Needed to ensure the init() method in the EE gets run
|
||||
_ "github.com/mattermost/enterprise/outgoing_oauth_connections"
|
||||
// Needed to ensure the init() method in the EE gets run
|
||||
_ "github.com/mattermost/enterprise/access_control"
|
||||
)
|
||||
|
||||
@@ -236,9 +236,9 @@ type MetricsInterfaceImpl struct {
|
||||
DesktopClientCPUUsage *prometheus.HistogramVec
|
||||
DesktopClientMemoryUsage *prometheus.HistogramVec
|
||||
|
||||
AccessControlEngineInitDuration prometheus.Histogram
|
||||
AccessControlExpressionCompileDuration prometheus.Histogram
|
||||
AccessControlEvaluateDuration prometheus.Histogram
|
||||
AccessControlSearchQueryDuration prometheus.Histogram
|
||||
AccessControlCacheInvalidation prometheus.Counter
|
||||
}
|
||||
|
||||
@@ -1541,34 +1541,31 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf
|
||||
)
|
||||
m.Registry.MustRegister(m.DesktopClientMemoryUsage)
|
||||
|
||||
m.AccessControlEngineInitDuration = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: MetricsSubsystemAccessControl,
|
||||
Name: "engine_init_duration_seconds",
|
||||
Help: "Duration of the time taken to initialize the access control engine (seconds)",
|
||||
ConstLabels: additionalLabels,
|
||||
})
|
||||
m.Registry.MustRegister(m.AccessControlEngineInitDuration)
|
||||
m.AccessControlSearchQueryDuration = prometheus.NewHistogram(
|
||||
withLabels(prometheus.HistogramOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: MetricsSubsystemAccessControl,
|
||||
Name: "search_query_duration_seconds",
|
||||
Help: "Duration of the time taken to query users against an expression (seconds)",
|
||||
}))
|
||||
m.Registry.MustRegister(m.AccessControlSearchQueryDuration)
|
||||
|
||||
m.AccessControlEvaluateDuration = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: MetricsSubsystemAccessControl,
|
||||
Name: "evaluate_duration_seconds",
|
||||
Help: "Duration of the time taken to evaluate the access control engine (seconds)",
|
||||
ConstLabels: additionalLabels,
|
||||
})
|
||||
withLabels(prometheus.HistogramOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: MetricsSubsystemAccessControl,
|
||||
Name: "evaluate_duration_seconds",
|
||||
Help: "Duration of the time taken to evaluate the access control engine (seconds)",
|
||||
}))
|
||||
m.Registry.MustRegister(m.AccessControlEvaluateDuration)
|
||||
|
||||
m.AccessControlExpressionCompileDuration = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: MetricsSubsystemAccessControl,
|
||||
Name: "expression_compile_duration_seconds",
|
||||
Help: "Duration of the time taken to compile the access control engine expression (seconds)",
|
||||
ConstLabels: additionalLabels,
|
||||
})
|
||||
withLabels(prometheus.HistogramOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: MetricsSubsystemAccessControl,
|
||||
Name: "expression_compile_duration_seconds",
|
||||
Help: "Duration of the time taken to compile the access control engine expression (seconds)",
|
||||
}))
|
||||
m.Registry.MustRegister(m.AccessControlExpressionCompileDuration)
|
||||
|
||||
m.AccessControlCacheInvalidation = prometheus.NewCounter(
|
||||
@@ -2177,8 +2174,8 @@ func (mi *MetricsInterfaceImpl) ObserveMobileClientSessionMetadata(version, plat
|
||||
mi.MobileClientSessionMetadataGauge.With(prometheus.Labels{"version": version, "platform": platform, "notifications_disabled": notificationDisabled}).Set(value)
|
||||
}
|
||||
|
||||
func (mi *MetricsInterfaceImpl) ObserveAccessControlEngineInitDuration(value float64) {
|
||||
mi.AccessControlEngineInitDuration.Observe(value)
|
||||
func (mi *MetricsInterfaceImpl) ObserveAccessControlSearchQueryDuration(value float64) {
|
||||
mi.AccessControlSearchQueryDuration.Observe(value)
|
||||
}
|
||||
|
||||
func (mi *MetricsInterfaceImpl) ObserveAccessControlExpressionCompileDuration(value float64) {
|
||||
|
||||
@@ -4,6 +4,8 @@ go 1.23.0
|
||||
|
||||
toolchain go1.23.7
|
||||
|
||||
//replace github.com/mattermost/mattermost/server/public => /Users/ibrahim/go/src/github.com/mattermost/mattermost-server/server/public
|
||||
|
||||
require (
|
||||
code.sajari.com/docconv/v2 v2.0.0-pre.4
|
||||
github.com/Masterminds/semver/v3 v3.3.1
|
||||
@@ -224,9 +226,9 @@ require (
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
|
||||
@@ -670,10 +670,10 @@ go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
@@ -860,14 +860,14 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 h1:91mG8dNTpkC0uChJUQ9zCiRqx3GEEFOWaRZ0mI6Oj2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -875,8 +875,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
|
||||
@@ -47,6 +47,14 @@
|
||||
"id": "September",
|
||||
"translation": "September"
|
||||
},
|
||||
{
|
||||
"id": "api.access_control_policy.get_channels.limit.app_error",
|
||||
"translation": "Get channels limit is not valid."
|
||||
},
|
||||
{
|
||||
"id": "api.access_control_policy.get_fields.limit.app_error",
|
||||
"translation": "Get fields limit is not valid."
|
||||
},
|
||||
{
|
||||
"id": "api.acknowledgement.delete.archived_channel.app_error",
|
||||
"translation": "You cannot remove an acknowledgment in an archived channel."
|
||||
@@ -239,6 +247,10 @@
|
||||
"id": "api.channel.add_user.to.channel.failed.deleted.app_error",
|
||||
"translation": "Failed to add user to channel because they have been removed from the team."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.add_user.to.channel.rejected",
|
||||
"translation": "User does not have required attributes to join the channel."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.add_user_to_channel.type.app_error",
|
||||
"translation": "Can not add user to this channel type."
|
||||
@@ -531,6 +543,10 @@
|
||||
"id": "api.channel.update_channel.deleted.app_error",
|
||||
"translation": "The channel has been archived or deleted."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.update_channel.not_allowed.app_error",
|
||||
"translation": "Policy enforced channels cannot be updated."
|
||||
},
|
||||
{
|
||||
"id": "api.channel.update_channel.tried.app_error",
|
||||
"translation": "Tried to perform an invalid update of the default channel {{.Channel}}."
|
||||
@@ -4646,6 +4662,10 @@
|
||||
"id": "app.channel.delete.app_error",
|
||||
"translation": "Unable to delete the channel."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.get.app_error",
|
||||
"translation": "Could not get channel."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.get.existing.app_error",
|
||||
"translation": "Unable to find the existing channel {{.channel_id}}."
|
||||
@@ -6206,6 +6226,94 @@
|
||||
"id": "app.oauth.update_app.updating.app_error",
|
||||
"translation": "We encountered an error updating the app."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.assign_access_control_policy_to_channels.app_error",
|
||||
"translation": "Unable to assign access control policy to channels."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.check_expression.app_error",
|
||||
"translation": "Could not check expression."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.create_access_control_policy.app_error",
|
||||
"translation": "Could not create access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.delete_access_control_policy.app_error",
|
||||
"translation": "Could not delete access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.delete_policy.app_error",
|
||||
"translation": "Unable to delete access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.expression_to_visual_ast.app_error",
|
||||
"translation": "Could not genereate visual AST from expression."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.get_access_control_auto_complete.app_error",
|
||||
"translation": "Could not get access control auto complete."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.get_all_access_control_policies.app_error",
|
||||
"translation": "Could not get access control policies."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.get_channel_access_control_attributes.app_error",
|
||||
"translation": "Could not get attributes for channel."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.get_channel_members_to_remove.app_error",
|
||||
"translation": "Could not get channel members to remove."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.get_policy.app_error",
|
||||
"translation": "Unable to retrieve the access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.get_policy_attributes.app_error",
|
||||
"translation": "Could not get attributes for policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.init.app_error",
|
||||
"translation": "Unable to initialize access control service."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.is_ready.app_error",
|
||||
"translation": "Access control service is not ready."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.missing_attribute.app_error",
|
||||
"translation": "An attribute is missing from the expression."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.normalize_policy.app_error",
|
||||
"translation": "Could not normalize policy expression."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.query_expression.app_error",
|
||||
"translation": "Could not query for expression."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.save_policy.app_error",
|
||||
"translation": "Unable to save access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.search_access_control_policies.app_error",
|
||||
"translation": "Could not search access control policies."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.unassign_access_control_policy_from_channels.app_error",
|
||||
"translation": "Could not unassign access control policy from channels."
|
||||
},
|
||||
{
|
||||
"id": "app.pap.update_access_control_policy_active.app_error",
|
||||
"translation": "Could not change active status of access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.pdp.access_evaluation.app_error",
|
||||
"translation": "Failed evaluate access control policy."
|
||||
},
|
||||
{
|
||||
"id": "app.plugin.cluster.save_config.app_error",
|
||||
"translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled."
|
||||
@@ -7772,6 +7880,10 @@
|
||||
"id": "common.parse_error_int64",
|
||||
"translation": "Failed to parse the value:{{.Value}} to int64"
|
||||
},
|
||||
{
|
||||
"id": "ent.access_control.sync_job.app_error",
|
||||
"translation": "Failed to run access control sync job."
|
||||
},
|
||||
{
|
||||
"id": "ent.account_migration.get_all_failed",
|
||||
"translation": "Unable to get users."
|
||||
@@ -8552,6 +8664,10 @@
|
||||
"id": "model.access.is_valid.user_id.app_error",
|
||||
"translation": "Invalid user id."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.inherit.version.app_error",
|
||||
"translation": "Could not inherit access control policy."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.id.app_error",
|
||||
"translation": "Invalid policy id."
|
||||
|
||||
@@ -78,6 +78,7 @@ const (
|
||||
TrackConfigExport = "config_export"
|
||||
TrackConfigWrangler = "config_wrangler"
|
||||
TrackConfigConnectedWorkspaces = "config_connected_workspaces"
|
||||
TrackConfigAccessControl = "config_access_control"
|
||||
TrackFeatureFlags = "config_feature_flags"
|
||||
TrackPermissionsGeneral = "permissions_general"
|
||||
TrackPermissionsSystemScheme = "permissions_system_scheme"
|
||||
@@ -973,6 +974,11 @@ func (ts *TelemetryService) trackConfig() {
|
||||
"max_posts_per_sync": *cfg.ConnectedWorkspacesSettings.MaxPostsPerSync,
|
||||
}
|
||||
|
||||
configs[TrackConfigAccessControl] = map[string]any{
|
||||
"enable_attribute_based_access_control": *cfg.AccessControlSettings.EnableAttributeBasedAccessControl,
|
||||
"enable_channel_scope_access_control": *cfg.AccessControlSettings.EnableChannelScopeAccessControl,
|
||||
}
|
||||
|
||||
// Convert feature flags to map[string]any for sending
|
||||
flags := cfg.FeatureFlags.ToMap()
|
||||
interfaceFlags := make(map[string]any)
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
@@ -18,13 +20,41 @@ const (
|
||||
AccessControlPolicyVersionV0_1 = "v0.1"
|
||||
)
|
||||
|
||||
// ParentPolicy is a augmented version of AccessPolicy to be used in
|
||||
// system console and API responses.
|
||||
type ParentPolicy struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
Children []*AccessControlPolicy `json:"children"`
|
||||
// AccessControlAttribute represents a user attribute with its name and possible values
|
||||
type AccessControlAttribute struct {
|
||||
Attribute PropertyField `json:"attribute"`
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
type AccessControlPolicyTestResponse struct {
|
||||
Users []*User `json:"users"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type GetAccessControlPolicyOptions struct {
|
||||
Type string `json:"type"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Cursor AccessControlPolicyCursor `json:"cursor"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type AccessControlPolicySearch struct {
|
||||
Term string `json:"term"`
|
||||
Type string `json:"type"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Cursor AccessControlPolicyCursor `json:"cursor"`
|
||||
Limit int `json:"limit"`
|
||||
IncludeChildren bool `json:"include_children"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
type AccessControlPolicyCursor struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type AccessControlPoliciesWithCount struct {
|
||||
Policies []*AccessControlPolicy `json:"policies"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type AccessControlPolicy struct {
|
||||
@@ -48,6 +78,16 @@ type AccessControlPolicyRule struct {
|
||||
Expression string `json:"expression"`
|
||||
}
|
||||
|
||||
type CELExpressionError struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AccessControlQueryResult struct {
|
||||
MatchedSubjectIDs []string `json:"matched_subject_ids"`
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) IsValid() *AppError {
|
||||
switch p.Version {
|
||||
case AccessControlPolicyVersionV0_1:
|
||||
@@ -103,3 +143,63 @@ func (p *AccessControlPolicy) accessPolicyVersionV0_1() *AppError {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) Inherit(resourceID, resourceType string) (*AccessControlPolicy, *AppError) {
|
||||
rules := make([]AccessControlPolicyRule, len(p.Rules))
|
||||
|
||||
switch p.Version {
|
||||
case AccessControlPolicyVersionV0_1:
|
||||
for i, rule := range p.Rules {
|
||||
actions := make([]string, len(rule.Actions))
|
||||
copy(actions, rule.Actions)
|
||||
rules[i] = AccessControlPolicyRule{
|
||||
Actions: actions,
|
||||
Expression: fmt.Sprintf("policies.id_%s", p.ID),
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.version.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
child := &AccessControlPolicy{
|
||||
ID: resourceID,
|
||||
Type: resourceType,
|
||||
Active: p.Active,
|
||||
CreateAt: GetMillis(),
|
||||
Version: p.Version,
|
||||
Imports: []string{p.ID},
|
||||
Rules: rules,
|
||||
|
||||
Props: map[string]any{},
|
||||
}
|
||||
|
||||
if appErr := child.IsValid(); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return child, nil
|
||||
}
|
||||
|
||||
func (c *AccessControlPolicyCursor) IsEmpty() bool {
|
||||
return c.ID == ""
|
||||
}
|
||||
|
||||
func (c *AccessControlPolicyCursor) IsValid() error {
|
||||
if c.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !IsValidId(c.ID) {
|
||||
return errors.New("cursor id is invalid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) Auditable() map[string]any {
|
||||
return map[string]any{
|
||||
"id": p.ID,
|
||||
"type": p.Type,
|
||||
"revision": p.Revision,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,30 @@ type Subject struct {
|
||||
ID string `json:"id"`
|
||||
// Type specifies the type of the Subject, eg. user, bot, etc.
|
||||
Type string `json:"type"`
|
||||
// Properties are the key-value pairs assicuated with the subject.
|
||||
// Attributes are the key-value pairs assicuated with the subject.
|
||||
// An attribute may be single-valued or multi-valued and can be a primitive type
|
||||
// (string, boolean, number) or a complex type like a JSON object or array.
|
||||
Properties map[string]any `json:"properties"`
|
||||
Attributes map[string]any `json:"attributes"`
|
||||
}
|
||||
|
||||
type SubjectSearchOptions struct {
|
||||
Term string `json:"term"`
|
||||
TeamID string `json:"team_id"`
|
||||
// Query and Args should be generated within the Access Control Service
|
||||
// and passed here wrt database driver
|
||||
Query string `json:"query"`
|
||||
Args []any `json:"args"`
|
||||
Limit int `json:"limit"`
|
||||
Cursor SubjectCursor `json:"cursor"`
|
||||
AllowInactive bool `json:"allow_inactive"`
|
||||
IgnoreCount bool `json:"ignore_count"`
|
||||
// ExcludeChannelMembers is used to exclude members from the search results
|
||||
// specifically used when syncing channel members
|
||||
ExcludeChannelMembers string `json:"exclude_members"`
|
||||
}
|
||||
|
||||
type SubjectCursor struct {
|
||||
TargetID string `json:"target_id"`
|
||||
}
|
||||
|
||||
// Resource is the target of an access request.
|
||||
@@ -41,3 +61,10 @@ type AccessDecision struct {
|
||||
Decision bool `json:"decision"`
|
||||
Context map[string]any `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type QueryExpressionParams struct {
|
||||
Expression string `json:"expression"`
|
||||
Term string `json:"term"`
|
||||
Limit int `json:"limit"`
|
||||
After string `json:"after"`
|
||||
}
|
||||
|
||||
30
server/public/model/cel.go
Обычный файл
30
server/public/model/cel.go
Обычный файл
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
// ValueType indicates whether a value is a literal or another attribute.
|
||||
type ValueType int
|
||||
|
||||
const (
|
||||
LiteralValue ValueType = iota
|
||||
AttrValue
|
||||
)
|
||||
|
||||
// Condition represents a single logical condition (e.g., user.attributes.Team == "Engineering").
|
||||
type Condition struct {
|
||||
// Left-hand side attribute selector (e.g., "user.attributes.Team").
|
||||
Attribute string `json:"attribute"`
|
||||
// The comparison operator.
|
||||
Operator string `json:"operator"`
|
||||
// Right-hand side value(s). Can be a single value or a slice for 'in'.
|
||||
Value any `json:"value"`
|
||||
// Type of the Value (LiteralValue or AttributeValue). Needed for comparisons like user.attr1 == user.attr2.
|
||||
ValueType ValueType `json:"value_type"`
|
||||
}
|
||||
|
||||
// VisualExpression represents a series of conditions combined with logical AND.
|
||||
type VisualExpression struct {
|
||||
// Conditions is a list of individual conditions that will be ANDed together.
|
||||
Conditions []Condition `json:"conditions"`
|
||||
}
|
||||
@@ -99,6 +99,7 @@ type Channel struct {
|
||||
PolicyID *string `json:"policy_id"`
|
||||
LastRootPostAt int64 `json:"last_root_post_at"`
|
||||
BannerInfo *ChannelBannerInfo `json:"banner_info"`
|
||||
PolicyEnforced bool `json:"policy_enforced"`
|
||||
}
|
||||
|
||||
func (o *Channel) Auditable() map[string]any {
|
||||
@@ -119,6 +120,7 @@ func (o *Channel) Auditable() map[string]any {
|
||||
"total_msg_count_root": o.TotalMsgCountRoot,
|
||||
"type": o.Type,
|
||||
"update_at": o.UpdateAt,
|
||||
"policy_enforced": o.PolicyEnforced,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,26 +211,30 @@ type ChannelModeratedRolesPatch struct {
|
||||
// Paginate whether to paginate the results.
|
||||
// Page page requested, if results are paginated.
|
||||
// PerPage number of results per page, if paginated.
|
||||
// ExcludeAccessPolicyEnforced will exclude channels that are enforced by an access policy.
|
||||
type ChannelSearchOpts struct {
|
||||
NotAssociatedToGroup string
|
||||
ExcludeDefaultChannels bool
|
||||
IncludeDeleted bool // If true, deleted channels will be included in the results.
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeSearchById bool
|
||||
ExcludeRemote bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int // When combined with IncludeDeleted, only channels deleted after this time will be returned.
|
||||
LastUpdateAt int
|
||||
NotAssociatedToGroup string
|
||||
ExcludeDefaultChannels bool
|
||||
IncludeDeleted bool // If true, deleted channels will be included in the results.
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeSearchById bool
|
||||
ExcludeRemote bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int // When combined with IncludeDeleted, only channels deleted after this time will be returned.
|
||||
LastUpdateAt int
|
||||
AccessControlPolicyEnforced bool
|
||||
ExcludeAccessControlPolicyEnforced bool
|
||||
ParentAccessControlPolicyId string
|
||||
}
|
||||
|
||||
type ChannelMemberCountByGroup struct {
|
||||
|
||||
@@ -6,19 +6,22 @@ package model
|
||||
const ChannelSearchDefaultLimit = 50
|
||||
|
||||
type ChannelSearch struct {
|
||||
Term string `json:"term"`
|
||||
ExcludeDefaultChannels bool `json:"exclude_default_channels"`
|
||||
NotAssociatedToGroup string `json:"not_associated_to_group"`
|
||||
TeamIds []string `json:"team_ids"`
|
||||
GroupConstrained bool `json:"group_constrained"`
|
||||
ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
|
||||
ExcludePolicyConstrained bool `json:"exclude_policy_constrained"`
|
||||
Public bool `json:"public"`
|
||||
Private bool `json:"private"`
|
||||
IncludeDeleted bool `json:"include_deleted"`
|
||||
IncludeSearchById bool `json:"include_search_by_id"`
|
||||
ExcludeRemote bool `json:"exclude_remote"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Page *int `json:"page,omitempty"`
|
||||
PerPage *int `json:"per_page,omitempty"`
|
||||
Term string `json:"term"`
|
||||
ExcludeDefaultChannels bool `json:"exclude_default_channels"`
|
||||
NotAssociatedToGroup string `json:"not_associated_to_group"`
|
||||
TeamIds []string `json:"team_ids"`
|
||||
GroupConstrained bool `json:"group_constrained"`
|
||||
ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
|
||||
ExcludePolicyConstrained bool `json:"exclude_policy_constrained"`
|
||||
Public bool `json:"public"`
|
||||
Private bool `json:"private"`
|
||||
IncludeDeleted bool `json:"include_deleted"`
|
||||
IncludeSearchById bool `json:"include_search_by_id"`
|
||||
ExcludeRemote bool `json:"exclude_remote"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Page *int `json:"page,omitempty"`
|
||||
PerPage *int `json:"per_page,omitempty"`
|
||||
AccessControlPolicyEnforced bool `json:"access_control_policy_enforced"`
|
||||
ExcludeAccessControlPolicyEnforced bool `json:"exclude_access_control_policy_enforced"`
|
||||
ParentAccessControlPolicyId string `json:"parent_access_control_policy_id"`
|
||||
}
|
||||
|
||||
@@ -622,6 +622,18 @@ func (c *Client4) customProfileAttributeValuesRoute() string {
|
||||
return fmt.Sprintf("%s/values", c.customProfileAttributesRoute())
|
||||
}
|
||||
|
||||
func (c *Client4) accessControlPoliciesRoute() string {
|
||||
return "/access_control_policies"
|
||||
}
|
||||
|
||||
func (c *Client4) celRoute() string {
|
||||
return "/access_control_policies/cel"
|
||||
}
|
||||
|
||||
func (c *Client4) accessControlPolicyRoute(policyID string) string {
|
||||
return fmt.Sprintf(c.accessControlPoliciesRoute()+"/%v", policyID)
|
||||
}
|
||||
|
||||
func (c *Client4) GetServerLimits(ctx context.Context) (*ServerLimits, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.limitsRoute()+"/users", "")
|
||||
if err != nil {
|
||||
@@ -9564,3 +9576,191 @@ func (c *Client4) PatchCPAValues(ctx context.Context, values map[string]json.Raw
|
||||
|
||||
return patchedValues, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// Access Control Policies Section
|
||||
|
||||
// CreateAccessControlPolicy creates a new access control policy.
|
||||
func (c *Client4) CreateAccessControlPolicy(ctx context.Context, policy *AccessControlPolicy) (*AccessControlPolicy, *Response, error) {
|
||||
b, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("CreateAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPutBytes(ctx, c.accessControlPoliciesRoute(), b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var p AccessControlPolicy
|
||||
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
||||
return nil, nil, NewAppError("CreateAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &p, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetAccessControlPolicy(ctx context.Context, id string) (*AccessControlPolicy, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.accessControlPolicyRoute(id), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var policy AccessControlPolicy
|
||||
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
|
||||
return nil, nil, NewAppError("GetAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &policy, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) DeleteAccessControlPolicy(ctx context.Context, id string) (*Response, error) {
|
||||
r, err := c.DoAPIDelete(ctx, c.accessControlPolicyRoute(id))
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) CheckExpression(ctx context.Context, expression string) ([]CELExpressionError, *Response, error) {
|
||||
checkExpressionRequest := struct {
|
||||
Expression string `json:"expression"`
|
||||
}{
|
||||
Expression: expression,
|
||||
}
|
||||
b, err := json.Marshal(checkExpressionRequest)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("CheckExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.celRoute()+"/check", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var errors []CELExpressionError
|
||||
if err := json.NewDecoder(r.Body).Decode(&errors); err != nil {
|
||||
return nil, nil, NewAppError("CheckExpression", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return errors, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) TestExpression(ctx context.Context, params QueryExpressionParams) (*AccessControlPolicyTestResponse, *Response, error) {
|
||||
b, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("TestExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.celRoute()+"/test", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var testResponse AccessControlPolicyTestResponse
|
||||
if err := json.NewDecoder(r.Body).Decode(&testResponse); err != nil {
|
||||
return nil, nil, NewAppError("TestExpression", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &testResponse, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SearchAccessControlPolicies(ctx context.Context, options AccessControlPolicySearch) (*AccessControlPoliciesWithCount, *Response, error) {
|
||||
b, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("SearchAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.accessControlPoliciesRoute()+"/search", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var policies AccessControlPoliciesWithCount
|
||||
if err := json.NewDecoder(r.Body).Decode(&policies); err != nil {
|
||||
return nil, nil, NewAppError("SearchAccessControlPolicies", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &policies, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) AssignAccessControlPolicies(ctx context.Context, policyID string, resourceIDs []string) (*Response, error) {
|
||||
var assignments struct {
|
||||
ChannelIds []string `json:"channel_ids"`
|
||||
}
|
||||
assignments.ChannelIds = resourceIDs
|
||||
|
||||
b, err := json.Marshal(assignments)
|
||||
if err != nil {
|
||||
return nil, NewAppError("AssignAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.accessControlPolicyRoute(policyID)+"/assign", b)
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) UnassignAccessControlPolicies(ctx context.Context, policyID string, resourceIDs []string) (*Response, error) {
|
||||
var unassignments struct {
|
||||
ChannelIds []string `json:"channel_ids"`
|
||||
}
|
||||
unassignments.ChannelIds = resourceIDs
|
||||
|
||||
b, err := json.Marshal(unassignments)
|
||||
if err != nil {
|
||||
return nil, NewAppError("UnassignAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIDeleteBytes(ctx, c.accessControlPolicyRoute(policyID)+"/unassign", b)
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetChannelsForAccessControlPolicy(ctx context.Context, policyID string, after string, limit int) (*ChannelsWithCount, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels?after="+after+"&limit="+strconv.Itoa(limit), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var channels ChannelsWithCount
|
||||
if err := json.NewDecoder(r.Body).Decode(&channels); err != nil {
|
||||
return nil, nil, NewAppError("GetChannelsForAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &channels, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SearchChannelsForAccessControlPolicy(ctx context.Context, policyID string, options ChannelSearch) (*ChannelsWithCount, *Response, error) {
|
||||
b, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("SearchChannelsForAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels/search", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var channels ChannelsWithCount
|
||||
if err := json.NewDecoder(r.Body).Decode(&channels); err != nil {
|
||||
return nil, nil, NewAppError("SearchChannelsForAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &channels, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ type FeatureFlags struct {
|
||||
ExperimentalAuditSettingsSystemConsoleUI bool
|
||||
|
||||
CustomProfileAttributes bool
|
||||
|
||||
AttributeBasedAccessControl bool
|
||||
}
|
||||
|
||||
func (f *FeatureFlags) SetDefaults() {
|
||||
@@ -81,6 +83,7 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.NotificationMonitoring = true
|
||||
f.ExperimentalAuditSettingsSystemConsoleUI = false
|
||||
f.CustomProfileAttributes = false
|
||||
f.AttributeBasedAccessControl = false
|
||||
}
|
||||
|
||||
// ToMap returns the feature flags as a map[string]string
|
||||
|
||||
@@ -44,6 +44,7 @@ const (
|
||||
JobTypeExportUsersToCSV = "export_users_to_csv"
|
||||
JobTypeDeleteDmsPreferencesMigration = "delete_dms_preferences_migration"
|
||||
JobTypeMobileSessionMetadata = "mobile_session_metadata"
|
||||
JobTypeAccessControlSync = "access_control_sync"
|
||||
|
||||
JobStatusPending = "pending"
|
||||
JobStatusInProgress = "in_progress"
|
||||
|
||||
Ссылка в новой задаче
Block a user