mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
399 lines
14 KiB
Go
399 lines
14 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/mod/semver"
|
|
)
|
|
|
|
const (
|
|
AccessControlPolicyTypeParent = "parent"
|
|
AccessControlPolicyTypeChannel = "channel"
|
|
AccessControlPolicyTypePermission = "permission"
|
|
|
|
MaxPolicyNameLength = 128
|
|
|
|
AccessControlPolicyVersionV0_1 = "v0.1"
|
|
AccessControlPolicyVersionV0_2 = "v0.2"
|
|
AccessControlPolicyVersionV0_3 = "v0.3"
|
|
|
|
AccessControlPolicyActionMembership = "membership"
|
|
AccessControlPolicyActionUploadFileAttachment = "upload_file_attachment"
|
|
AccessControlPolicyActionDownloadFileAttachment = "download_file_attachment"
|
|
|
|
AccessControlPolicyScopeTeam = "team"
|
|
)
|
|
|
|
var allowedActionsV0_3 = map[string]bool{
|
|
AccessControlPolicyActionMembership: true,
|
|
AccessControlPolicyActionUploadFileAttachment: true,
|
|
AccessControlPolicyActionDownloadFileAttachment: true,
|
|
}
|
|
|
|
// 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"`
|
|
IDs []string `json:"ids"`
|
|
Cursor AccessControlPolicyCursor `json:"cursor"`
|
|
Limit int `json:"limit"`
|
|
IncludeChildren bool `json:"include_children"`
|
|
Active bool `json:"active"`
|
|
TeamID string `json:"team_id"`
|
|
Scope string `json:"scope,omitempty"`
|
|
ScopeID string `json:"scope_id,omitempty"`
|
|
Actions []string `json:"actions"`
|
|
}
|
|
|
|
type AccessControlPolicyCursor struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
type AccessControlPoliciesWithCount struct {
|
|
Policies []*AccessControlPolicy `json:"policies"`
|
|
Total int64 `json:"total"`
|
|
}
|
|
|
|
type AccessControlPolicy struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Active bool `json:"active"`
|
|
CreateAt int64 `json:"create_at"`
|
|
|
|
Revision int `json:"revision"`
|
|
Version string `json:"version"`
|
|
|
|
Roles []string `json:"roles"`
|
|
Imports []string `json:"imports"`
|
|
Rules []AccessControlPolicyRule `json:"rules"`
|
|
|
|
Scope string `json:"scope,omitempty"` // "" (system) or "team"
|
|
ScopeID string `json:"scope_id,omitempty"` // team ID when scope="team"
|
|
|
|
Props map[string]any `json:"props"` // add auto-sync property here, also maybe the attributes being used in the expression
|
|
}
|
|
|
|
type AccessControlPolicyRule struct {
|
|
Actions []string `json:"actions"`
|
|
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"`
|
|
}
|
|
|
|
// AccessControlPolicyActiveUpdate represents a single policy's active status update.
|
|
type AccessControlPolicyActiveUpdate struct {
|
|
ID string `json:"id"`
|
|
Active bool `json:"active"`
|
|
}
|
|
|
|
// AccessControlPolicyActiveUpdateRequest is used in the API to update active status for multiple policies.
|
|
type AccessControlPolicyActiveUpdateRequest struct {
|
|
Entries []AccessControlPolicyActiveUpdate `json:"entries"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
}
|
|
|
|
func (r *AccessControlPolicyActiveUpdateRequest) Auditable() map[string]any {
|
|
entries := make([]map[string]any, 0, len(r.Entries))
|
|
for _, entry := range r.Entries {
|
|
entries = append(entries, map[string]any{
|
|
"id": entry.ID,
|
|
"active": entry.Active,
|
|
})
|
|
}
|
|
result := map[string]any{
|
|
"entries": entries,
|
|
}
|
|
if r.TeamID != "" {
|
|
result["team_id"] = r.TeamID
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (p *AccessControlPolicy) IsValid() *AppError {
|
|
if p.Scope != "" || p.ScopeID != "" {
|
|
if appErr := p.validateScope(); appErr != nil {
|
|
return appErr
|
|
}
|
|
}
|
|
|
|
switch p.Version {
|
|
case AccessControlPolicyVersionV0_1:
|
|
return p.accessPolicyVersionV0_1()
|
|
case AccessControlPolicyVersionV0_2:
|
|
return p.accessPolicyVersionV0_2()
|
|
case AccessControlPolicyVersionV0_3:
|
|
return p.accessPolicyVersionV0_3()
|
|
default:
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.version.app_error", nil, "", 400)
|
|
}
|
|
}
|
|
|
|
func (p *AccessControlPolicy) validateScope() *AppError {
|
|
switch p.Scope {
|
|
case "":
|
|
if p.ScopeID != "" {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.scope_id_without_scope.app_error", nil, "", 400)
|
|
}
|
|
case AccessControlPolicyScopeTeam:
|
|
if !IsValidId(p.ScopeID) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.scope_id.app_error", nil, "", 400)
|
|
}
|
|
default:
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.scope.app_error", nil, "", 400)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *AccessControlPolicy) accessPolicyVersionV0_1() *AppError {
|
|
if !slices.Contains([]string{AccessControlPolicyTypeParent, AccessControlPolicyTypeChannel}, p.Type) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.type.app_error", nil, "", 400)
|
|
}
|
|
|
|
if !IsValidId(p.ID) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.id.app_error", nil, "", 400)
|
|
}
|
|
|
|
if p.Type == AccessControlPolicyTypeParent && (p.Name == "" || len(p.Name) > MaxPolicyNameLength) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.name.app_error", nil, "", 400)
|
|
}
|
|
|
|
if p.Revision < 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.revision.app_error", nil, "", 400)
|
|
}
|
|
|
|
if !semver.IsValid(p.Version) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.version.app_error", nil, "", 400)
|
|
}
|
|
|
|
switch p.Type {
|
|
case AccessControlPolicyTypeParent:
|
|
if len(p.Rules) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules.app_error", nil, "", 400)
|
|
}
|
|
|
|
if len(p.Imports) > 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
|
}
|
|
case AccessControlPolicyTypeChannel:
|
|
if len(p.Rules) == 0 && len(p.Imports) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules_imports.app_error", nil, "", 400)
|
|
}
|
|
|
|
if len(p.Rules) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules.app_error", nil, "", 400)
|
|
}
|
|
|
|
if len(p.Imports) > 1 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *AccessControlPolicy) accessPolicyVersionV0_2() *AppError {
|
|
if !slices.Contains([]string{AccessControlPolicyTypeParent, AccessControlPolicyTypeChannel}, p.Type) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.type.app_error", nil, "", 400)
|
|
}
|
|
|
|
if !IsValidId(p.ID) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.id.app_error", nil, "", 400)
|
|
}
|
|
|
|
if p.Type == AccessControlPolicyTypeParent && (p.Name == "" || len(p.Name) > MaxPolicyNameLength) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.name.app_error", nil, "", 400)
|
|
}
|
|
|
|
if p.Revision < 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.revision.app_error", nil, "", 400)
|
|
}
|
|
|
|
if !semver.IsValid(p.Version) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.version.app_error", nil, "", 400)
|
|
}
|
|
|
|
switch p.Type {
|
|
case AccessControlPolicyTypeParent:
|
|
if len(p.Rules) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules.app_error", nil, "", 400)
|
|
}
|
|
|
|
if len(p.Imports) > 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
|
}
|
|
case AccessControlPolicyTypeChannel:
|
|
if len(p.Rules) == 0 && len(p.Imports) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules_imports.app_error", nil, "", 400)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *AccessControlPolicy) accessPolicyVersionV0_3() *AppError {
|
|
if !slices.Contains([]string{AccessControlPolicyTypeParent, AccessControlPolicyTypeChannel, AccessControlPolicyTypePermission}, p.Type) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.type.app_error", nil, "", 400)
|
|
}
|
|
|
|
if !IsValidId(p.ID) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.id.app_error", nil, "", 400)
|
|
}
|
|
|
|
if (p.Type == AccessControlPolicyTypeParent || p.Type == AccessControlPolicyTypePermission) && (p.Name == "" || len(p.Name) > MaxPolicyNameLength) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.name.app_error", nil, "", 400)
|
|
}
|
|
|
|
if p.Revision < 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.revision.app_error", nil, "", 400)
|
|
}
|
|
|
|
if !semver.IsValid(p.Version) {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.version.app_error", nil, "", 400)
|
|
}
|
|
|
|
switch p.Type {
|
|
case AccessControlPolicyTypeParent:
|
|
if len(p.Rules) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules.app_error", nil, "", 400)
|
|
}
|
|
|
|
if len(p.Imports) > 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
|
}
|
|
case AccessControlPolicyTypeChannel:
|
|
if len(p.Rules) == 0 && len(p.Imports) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules_imports.app_error", nil, "", 400)
|
|
}
|
|
case AccessControlPolicyTypePermission:
|
|
if len(p.Rules) == 0 && len(p.Imports) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules_imports.app_error", nil, "", 400)
|
|
}
|
|
|
|
// Permissions are only allowed to be applied to a single role as of v0.3
|
|
// role hierarchy is resolved at the PDP
|
|
if len(p.Roles) != 1 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.roles.app_error", nil, "", 400)
|
|
}
|
|
for _, role := range p.Roles {
|
|
if strings.TrimSpace(role) == "" {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.roles.app_error", nil, "", 400)
|
|
}
|
|
}
|
|
|
|
if len(p.Imports) > 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
|
}
|
|
}
|
|
|
|
for _, rule := range p.Rules {
|
|
if len(rule.Actions) == 0 {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.actions.app_error", nil, "actions must not be empty", 400)
|
|
}
|
|
for _, action := range rule.Actions {
|
|
if !allowedActionsV0_3[action] {
|
|
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.actions.app_error", nil, fmt.Sprintf("unrecognized action: %s", action), 400)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *AccessControlPolicy) Inherit(parent *AccessControlPolicy) *AppError {
|
|
rules := make([]AccessControlPolicyRule, len(p.Rules))
|
|
|
|
switch p.Version {
|
|
case AccessControlPolicyVersionV0_1:
|
|
p.Imports = []string{parent.ID}
|
|
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),
|
|
}
|
|
}
|
|
case AccessControlPolicyVersionV0_2:
|
|
if slices.Contains(p.Imports, parent.ID) {
|
|
return NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.already_imported.app_error", nil, "", 400)
|
|
}
|
|
p.Imports = append(p.Imports, parent.ID)
|
|
case AccessControlPolicyVersionV0_3:
|
|
if p.Type == AccessControlPolicyTypePermission || parent.Type == AccessControlPolicyTypePermission {
|
|
return NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.permission.app_error", nil, "", 400)
|
|
}
|
|
if parent.Version != AccessControlPolicyVersionV0_3 {
|
|
return NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.version.app_error", nil, "", 400)
|
|
}
|
|
if slices.Contains(p.Imports, parent.ID) {
|
|
return NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.already_imported.app_error", nil, "", 400)
|
|
}
|
|
p.Imports = append(p.Imports, parent.ID)
|
|
default:
|
|
return NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.version.app_error", nil, "", 400)
|
|
}
|
|
|
|
if appErr := p.IsValid(); appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
return 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,
|
|
}
|
|
}
|