mirror of
https://github.com/hanzoai/team.git
synced 2026-11-01 07:55:31 +00:00
* MM-69063 - Add team ABAC model and constants foundation
* Add team ABAC store EXISTS, channel Type retrofit, policy count split, and index migration
* Add team ABAC app layer: access gate, hydrators, assign/unassign, cleanup, and GetTeamMembersToRemove store
* Enforce team membership ABAC on join and hide policy governed teams from non-qualifying users in the directory
* Add team_ids to access policy assign/unassign, expose per-team policy GET, and support abac_match_only for not_in_team user listing
* Add team ABAC client methods, websocket handler, per-team System Console policy UI, and hide policy-governed teams from non-qualifying users
* Make team ABAC mode-aware: advisory on public teams, strict on private, and surface governed private teams to qualifying users in directory listings
* Flag-gate team ABAC mutation/read APIs and fix policy-save error handling, member-removal limit, team-id validation, export, and audit cleanup
* coderabbit feedback; Broadcast team policy enforcement updates on policy create/update and activation, not only on delete
* Update team access control policy schema to allow nullable policies and enhance test cases with channel counts
* Enhance access control policy tests to include team policy search alongside channel policy search
* Add team membership access control feature flag to docker-compose generation
* Implement team access control policy checks and refactor related components
* MM-69100 - add team membership ABAC sync worker with mode-aware removal and auto-add
* Add team ABAC removal/auto-add notifications, cascade audit, and team custom-rules save backend
* Add Team Settings team membership tab with custom-rules editor, auto-add toggle, save confirmation, and mode-flip sync trigger
* Add team access control policy panels, job details team list, and e2e coverage for membership tab and discoverability cards
* ix save double-submit with loading state, team privacy via updateTeamPrivacy, mode-flip count accuracy, sync trigger logic, i18n keys, and e2e test cleanup
* Add team membership policy notices to invite and add-to-team flows, team access control system messages, and a team policy attributes endpoint
* Add team membership recommended tag for qualifying users and team requirements notice in the members modal
* temp
* adjust styling, add new e2e, fix team admin job permissions issues
* e2e clean items created
* fix directory and team integration e2e tests
* Surface team sync results in the Sync Job Details modal via a Teams tab linked from the chained channel job
* code clean up, adjust styling
* Add access control attributes endpoint and enhance E2E tests for team membership
* Enhance team access control by updating permission checks and syncing jobs
* Add API-level enforcement gate tests for ABAC team membership
* Fix formatting in team channel settings component by adding missing semicolon
* Fix formatting in Client4 class by adjusting type annotations for clarity
* revert unwanted changes in package-lock
* Refactor job permission test and simplify error handling in team details component
* Update test assertions and enhance auto-add functionality in team membership tab
* Update onboarding tests to use 'Public Team' card for team access settings
* Refactor AccessSettings and TeamPolicyEditor components to replace allowOpenInviteCheckbox with public and private team buttons, and update access control job dispatching logic.
* Remove redundant state updates for channel assignments in access control policy reducer
* Implement email suppression for team membership notifications and add related tests
* Authorize ABAC team self-join by attribute match instead of the join_private_teams role
* comments clean up
* Gate team privacy type normalization behind active ABAC, keep legacy allow_open_invite-only path otherwise
* adjust job details styles
* Add team membership policy disconnect confirmation, fix linked-policy affected-member count, and polish job details modal
* Combine custom rules with system policy expressions for accurate confirm counts
* Implement child resource count stamping and deletion gating for access control policies
* Enhance team sync job triggering logic for membership changes without auto-add
* Refactor team privacy handling to align with open-directory model; update comments for clarity on allow_open_invite logic.
* Deduplicate team ABAC sync jobs per policy to prevent concurrent runs emitting duplicate DMs and audit records on HA clusters
* fix linter warnings
* Add confirmation for policy removal in TeamDetails tests
* Update delete policy message to include Teams in the warning
* Add linked teams warning to policy deletion and update translations
* Add Auto-add feature for policy selection and team management
- Enhanced PolicySelectionModal to include an Auto-add checkbox for each policy.
- Updated PolicyList to manage Auto-add state and reflect changes in the UI.
- Modified TeamAccessControl to handle Auto-add functionality and persist changes.
- Added tests to ensure Auto-add behavior works as expected in various scenarios.
- Updated translations for Auto-add related strings.
* Add team membership sync footer and enhance job fetching with policy ID
* Refactor team membership policy handling and sync footer implementation
* Add team sync channel cascade test for private channel membership
---------
(cherry picked from commit a582f03570)
Co-authored-by: Pablo Vélez <[email protected]>
396 lines
16 KiB
Go
396 lines
16 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
)
|
|
|
|
// getChannelIDFromJobData extracts channel ID from access control sync job data.
|
|
// Returns channel ID if the job is for a specific channel, empty string if it's a system-wide job.
|
|
func (a *App) getChannelIDFromJobData(jobData model.StringMap) string {
|
|
policyID, ok := jobData["policy_id"]
|
|
if !ok || policyID == "" {
|
|
return ""
|
|
}
|
|
|
|
// In the access control system:
|
|
// - Channel policies have ID == channelID
|
|
// - Parent policies have their own system-wide ID
|
|
//
|
|
// For channel admin jobs: policy_id is channelID (since channel policy ID equals channel ID)
|
|
// For system admin jobs: policy_id could be either channel policy ID or parent policy ID
|
|
//
|
|
// We return the parent_id as channelID because:
|
|
// 1. If it's a channel policy ID, it equals the channel ID
|
|
// 2. If it's a parent policy ID, the permission check will fail safely
|
|
// 3. This maintains security: only users with permission to that specific ID can create the job
|
|
return policyID
|
|
}
|
|
|
|
func (a *App) GetJob(rctx request.CTX, id string) (*model.Job, *model.AppError) {
|
|
job, err := a.Srv().Store().Job().Get(rctx, id)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
switch {
|
|
case errors.As(err, &nfErr):
|
|
return nil, model.NewAppError("GetJob", "app.job.get.app_error", nil, "", http.StatusNotFound).Wrap(err)
|
|
default:
|
|
return nil, model.NewAppError("GetJob", "app.job.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
}
|
|
|
|
return job, nil
|
|
}
|
|
|
|
func (a *App) GetJobsByTypePage(rctx request.CTX, jobType string, page int, perPage int) ([]*model.Job, *model.AppError) {
|
|
jobs, err := a.Srv().Store().Job().GetAllByTypePage(rctx, jobType, page, perPage)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetJobsByType", "app.job.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (a *App) GetJobsByTypesPage(rctx request.CTX, jobType []string, page int, perPage int) ([]*model.Job, *model.AppError) {
|
|
jobs, err := a.Srv().Store().Job().GetAllByTypesPage(rctx, jobType, page, perPage)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetJobsByType", "app.job.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (a *App) GetJobsByTypeAndData(rctx request.CTX, jobType string, data map[string]string) ([]*model.Job, *model.AppError) {
|
|
jobs, err := a.Srv().Store().Job().GetByTypeAndData(rctx, jobType, data, false)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetJobsByTypeAndData", "app.job.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (a *App) GetJobsByTypesAndStatuses(rctx request.CTX, jobTypes []string, status []string, page int, perPage int) ([]*model.Job, *model.AppError) {
|
|
jobs, err := a.Srv().Store().Job().GetAllByTypesAndStatusesPage(rctx, jobTypes, status, page*perPage, perPage)
|
|
if err != nil {
|
|
return nil, model.NewAppError("GetAllByTypesAndStatusesPage", "app.job.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (a *App) CreateJob(rctx request.CTX, job *model.Job) (*model.Job, *model.AppError) {
|
|
switch job.Type {
|
|
case model.JobTypeAccessControlSync:
|
|
// Route ABAC jobs to specialized deduplication handler
|
|
return a.CreateAccessControlSyncJob(rctx, job.Data)
|
|
case model.JobTypeAccessControlTeamSync:
|
|
// Route team ABAC jobs to specialized deduplication handler
|
|
return a.CreateAccessControlTeamSyncJob(rctx, job.Data)
|
|
default:
|
|
return a.Srv().Jobs.CreateJob(rctx, job.Type, job.Data)
|
|
}
|
|
}
|
|
|
|
func (a *App) CreateAccessControlSyncJob(rctx request.CTX, jobData map[string]string) (*model.Job, *model.AppError) {
|
|
policyID := jobData["policy_id"]
|
|
teamID := jobData["team_id"]
|
|
requesterID := jobData["requester_id"]
|
|
isAdmin := jobData["requester_is_admin"] == "true"
|
|
|
|
// Remove transient fields before persisting.
|
|
delete(jobData, "requester_id")
|
|
delete(jobData, "requester_is_admin")
|
|
|
|
if policyID != "" {
|
|
a.cancelExistingAccessControlSyncJobs(rctx, model.JobTypeAccessControlSync, map[string]string{"policy_id": policyID}, "policy_id", policyID)
|
|
}
|
|
|
|
if teamID != "" && policyID == "" {
|
|
a.cancelExistingAccessControlSyncJobs(rctx, model.JobTypeAccessControlSync, map[string]string{"team_id": teamID}, "team_id", teamID)
|
|
}
|
|
|
|
// For team-scoped syncs by non-admins, resolve which channels the
|
|
// requester can sync based on self-inclusion (attribute match).
|
|
if teamID != "" && policyID == "" && requesterID != "" && !isAdmin {
|
|
channelIDs, appErr := a.resolveTeamSyncChannelIDs(rctx, teamID, requesterID)
|
|
if appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
if len(channelIDs) > 0 {
|
|
jobData["channel_ids"] = strings.Join(channelIDs, ",")
|
|
}
|
|
}
|
|
|
|
return a.Srv().Jobs.CreateJob(rctx, model.JobTypeAccessControlSync, jobData)
|
|
}
|
|
|
|
// CreateAccessControlTeamSyncJob creates a team membership sync job, first
|
|
// canceling any pending/in-progress sync for the same policy so two never run
|
|
// concurrently. A team-type policy's ID equals its team ID, so deduplicating on
|
|
// policy_id deduplicates per team. Without this, overlapping syncs on separate
|
|
// HA nodes emit duplicate removal/addition DMs and audit records for one change.
|
|
func (a *App) CreateAccessControlTeamSyncJob(rctx request.CTX, jobData map[string]string) (*model.Job, *model.AppError) {
|
|
policyID := jobData["policy_id"]
|
|
|
|
// Strip transient fields before persisting. Only channel syncs inject these,
|
|
// so this is defensive against a hand-crafted request body.
|
|
delete(jobData, "requester_id")
|
|
delete(jobData, "requester_is_admin")
|
|
|
|
if policyID != "" {
|
|
a.cancelExistingAccessControlSyncJobs(rctx, model.JobTypeAccessControlTeamSync, map[string]string{"policy_id": policyID}, "policy_id", policyID)
|
|
}
|
|
|
|
return a.Srv().Jobs.CreateJob(rctx, model.JobTypeAccessControlTeamSync, jobData)
|
|
}
|
|
|
|
// cancelExistingAccessControlSyncJobs cancels pending/in-progress sync jobs of
|
|
// the given type matching the data filter, for deduplication.
|
|
func (a *App) cancelExistingAccessControlSyncJobs(rctx request.CTX, jobType string, dataFilter map[string]string, logKey, logValue string) {
|
|
existingJobs, err := a.Srv().Store().Job().GetByTypeAndData(rctx, jobType, dataFilter, true, model.JobStatusPending, model.JobStatusInProgress)
|
|
if err != nil {
|
|
rctx.Logger().Warn("Failed to query existing sync jobs for deduplication", mlog.Err(err))
|
|
return
|
|
}
|
|
|
|
for _, job := range existingJobs {
|
|
rctx.Logger().Info("Canceling existing access control sync job before creating new one",
|
|
mlog.String("job_id", job.Id),
|
|
mlog.String(logKey, logValue),
|
|
mlog.String("status", job.Status))
|
|
if err := a.Srv().Jobs.SetJobCanceled(job); err != nil {
|
|
rctx.Logger().Warn("Failed to cancel existing access control sync job",
|
|
mlog.String("job_id", job.Id),
|
|
mlog.String(logKey, logValue),
|
|
mlog.Err(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
// resolveTeamSyncChannelIDs returns channel IDs the requester can sync,
|
|
// filtered by self-inclusion (same logic as SearchTeamAccessPolicies).
|
|
func (a *App) resolveTeamSyncChannelIDs(rctx request.CTX, teamID, requesterID string) ([]string, *model.AppError) {
|
|
policies, _, appErr := a.SearchTeamAccessPolicies(rctx, teamID, requesterID, model.AccessControlPolicySearch{
|
|
Limit: teamPoliciesMaxFetch,
|
|
})
|
|
if appErr != nil {
|
|
return nil, appErr
|
|
}
|
|
|
|
var channelIDs []string
|
|
for _, policy := range policies {
|
|
if policy.Props == nil {
|
|
continue
|
|
}
|
|
childIDs, ok := policy.Props["child_ids"].([]string)
|
|
if !ok {
|
|
// Handle []any from JSON unmarshaling
|
|
if rawIDs, ok2 := policy.Props["child_ids"].([]any); ok2 {
|
|
for _, raw := range rawIDs {
|
|
if id, ok3 := raw.(string); ok3 {
|
|
channelIDs = append(channelIDs, id)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
continue
|
|
}
|
|
channelIDs = append(channelIDs, childIDs...)
|
|
}
|
|
|
|
return channelIDs, nil
|
|
}
|
|
|
|
func (a *App) CancelJob(rctx request.CTX, jobId string) *model.AppError {
|
|
return a.Srv().Jobs.RequestCancellation(rctx, jobId)
|
|
}
|
|
|
|
func (a *App) UpdateJobStatus(rctx request.CTX, job *model.Job, newStatus string) *model.AppError {
|
|
switch newStatus {
|
|
case model.JobStatusPending:
|
|
return a.Srv().Jobs.SetJobPending(job)
|
|
case model.JobStatusCancelRequested:
|
|
return a.Srv().Jobs.RequestCancellation(rctx, job.Id)
|
|
case model.JobStatusCanceled:
|
|
return a.Srv().Jobs.SetJobCanceled(job)
|
|
default:
|
|
return model.NewAppError("UpdateJobStatus", "app.job.update_status.app_error", nil, "", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (a *App) SessionHasPermissionToCreateJob(session model.Session, job *model.Job) (bool, *model.Permission) {
|
|
switch job.Type {
|
|
case model.JobTypeDataRetention:
|
|
return a.SessionHasPermissionTo(session, model.PermissionCreateDataRetentionJob), model.PermissionCreateDataRetentionJob
|
|
case model.JobTypeMessageExport:
|
|
return a.SessionHasPermissionTo(session, model.PermissionCreateComplianceExportJob), model.PermissionCreateComplianceExportJob
|
|
case model.JobTypeElasticsearchPostIndexing:
|
|
return a.SessionHasPermissionTo(session, model.PermissionCreateElasticsearchPostIndexingJob), model.PermissionCreateElasticsearchPostIndexingJob
|
|
case model.JobTypeElasticsearchPostAggregation:
|
|
return a.SessionHasPermissionTo(session, model.PermissionCreateElasticsearchPostAggregationJob), model.PermissionCreateElasticsearchPostAggregationJob
|
|
case model.JobTypeLdapSync:
|
|
return a.SessionHasPermissionTo(session, model.PermissionCreateLdapSyncJob), model.PermissionCreateLdapSyncJob
|
|
case
|
|
model.JobTypeMigrations,
|
|
model.JobTypePlugins,
|
|
model.JobTypeProductNotices,
|
|
model.JobTypeExpiryNotify,
|
|
model.JobTypeActiveUsers,
|
|
model.JobTypeImportProcess,
|
|
model.JobTypeImportDelete,
|
|
model.JobTypeExportProcess,
|
|
model.JobTypeExportDelete,
|
|
model.JobTypeCloud,
|
|
model.JobTypeExtractContent,
|
|
model.JobTypeCleanupExpiredAccessTokens,
|
|
model.JobTypeNotifyExpiringAccessTokens:
|
|
return a.SessionHasPermissionTo(session, model.PermissionManageJobs), model.PermissionManageJobs
|
|
case model.JobTypeAccessControlTeamSync:
|
|
if a.SessionHasPermissionTo(session, model.PermissionManageSystem) {
|
|
return true, model.PermissionManageSystem
|
|
}
|
|
// Team-type policies use the team ID as the policy ID.
|
|
// Allow a team admin to trigger a sync job for their own team's policy.
|
|
if policyID, ok := job.Data["policy_id"]; ok && policyID != "" && model.IsValidId(policyID) {
|
|
if a.SessionHasPermissionToTeam(session, policyID, model.PermissionManageTeamAccessRules) {
|
|
return true, model.PermissionManageTeamAccessRules
|
|
}
|
|
}
|
|
return false, model.PermissionManageSystem
|
|
case model.JobTypeAccessControlSync:
|
|
// Allow system admins to create access control sync jobs
|
|
hasSystemPermission := a.SessionHasPermissionTo(session, model.PermissionManageSystem)
|
|
if hasSystemPermission {
|
|
return true, model.PermissionManageSystem
|
|
}
|
|
|
|
// For channel admins, check if they have permission for the specific channel/policy
|
|
channelID := a.getChannelIDFromJobData(job.Data)
|
|
if channelID != "" {
|
|
// Check specific channel permission
|
|
ctx := request.EmptyContext(a.Srv().Log())
|
|
hasChannelPermission, _ := a.HasPermissionToChannel(ctx, session.UserId, channelID, model.PermissionManageChannelAccessRules)
|
|
if hasChannelPermission {
|
|
return true, model.PermissionManageChannelAccessRules
|
|
}
|
|
}
|
|
|
|
// Check team admin permission. This is needed for jobs that are scoped to a team but don't specify a channel (i.e. team-level syncs).
|
|
teamID, hasTeamID := job.Data["team_id"]
|
|
if hasTeamID && teamID != "" && model.IsValidId(teamID) && job.Data["policy_id"] == "" {
|
|
if a.SessionHasPermissionToTeam(session, teamID, model.PermissionManageTeamAccessRules) {
|
|
return true, model.PermissionManageTeamAccessRules
|
|
}
|
|
}
|
|
|
|
// Check team admin permission for policy-scoped syncs (e.g. triggered after policy creation).
|
|
// Verify the team admin owns the policy via ValidateTeamAdminPolicyOwnership.
|
|
policyID, hasPolicyID := job.Data["policy_id"]
|
|
if hasPolicyID && policyID != "" && model.IsValidId(policyID) && hasTeamID && teamID != "" && model.IsValidId(teamID) {
|
|
if a.SessionHasPermissionToTeam(session, teamID, model.PermissionManageTeamAccessRules) {
|
|
ctx := request.EmptyContext(a.Srv().Log())
|
|
if owned, appErr := a.ValidateTeamAdminPolicyOwnership(ctx, teamID, policyID); appErr == nil && owned {
|
|
return true, model.PermissionManageTeamAccessRules
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, model.PermissionManageSystem
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (a *App) SessionHasPermissionToManageJob(session model.Session, job *model.Job) (bool, *model.Permission) {
|
|
var permission *model.Permission
|
|
|
|
switch job.Type {
|
|
case model.JobTypeDataRetention:
|
|
permission = model.PermissionManageDataRetentionJob
|
|
case model.JobTypeMessageExport:
|
|
permission = model.PermissionManageComplianceExportJob
|
|
case model.JobTypeElasticsearchPostIndexing:
|
|
permission = model.PermissionManageElasticsearchPostIndexingJob
|
|
case model.JobTypeElasticsearchPostAggregation:
|
|
permission = model.PermissionManageElasticsearchPostAggregationJob
|
|
case model.JobTypeLdapSync:
|
|
permission = model.PermissionManageLdapSyncJob
|
|
case
|
|
model.JobTypeMigrations,
|
|
model.JobTypePlugins,
|
|
model.JobTypeProductNotices,
|
|
model.JobTypeExpiryNotify,
|
|
model.JobTypeActiveUsers,
|
|
model.JobTypeImportProcess,
|
|
model.JobTypeImportDelete,
|
|
model.JobTypeExportProcess,
|
|
model.JobTypeExportDelete,
|
|
model.JobTypeCloud,
|
|
model.JobTypeExtractContent,
|
|
model.JobTypeCleanupExpiredAccessTokens:
|
|
permission = model.PermissionManageJobs
|
|
case model.JobTypeAccessControlSync:
|
|
permission = model.PermissionManageSystem
|
|
case model.JobTypeAccessControlTeamSync:
|
|
if a.SessionHasPermissionTo(session, model.PermissionManageSystem) {
|
|
return true, model.PermissionManageSystem
|
|
}
|
|
if policyID, ok := job.Data["policy_id"]; ok && policyID != "" && model.IsValidId(policyID) {
|
|
if a.SessionHasPermissionToTeam(session, policyID, model.PermissionManageTeamAccessRules) {
|
|
return true, model.PermissionManageTeamAccessRules
|
|
}
|
|
}
|
|
return false, model.PermissionManageSystem
|
|
}
|
|
|
|
if permission == nil {
|
|
return false, nil
|
|
}
|
|
|
|
return a.SessionHasPermissionTo(session, permission), permission
|
|
}
|
|
|
|
func (a *App) SessionHasPermissionToReadJob(session model.Session, jobType string) (bool, *model.Permission) {
|
|
switch jobType {
|
|
case model.JobTypeDataRetention:
|
|
return a.SessionHasPermissionTo(session, model.PermissionReadDataRetentionJob), model.PermissionReadDataRetentionJob
|
|
case model.JobTypeMessageExport:
|
|
return a.SessionHasPermissionTo(session, model.PermissionReadComplianceExportJob), model.PermissionReadComplianceExportJob
|
|
case model.JobTypeElasticsearchPostIndexing:
|
|
return a.SessionHasPermissionTo(session, model.PermissionReadElasticsearchPostIndexingJob), model.PermissionReadElasticsearchPostIndexingJob
|
|
case model.JobTypeElasticsearchPostAggregation:
|
|
return a.SessionHasPermissionTo(session, model.PermissionReadElasticsearchPostAggregationJob), model.PermissionReadElasticsearchPostAggregationJob
|
|
case model.JobTypeLdapSync:
|
|
return a.SessionHasPermissionTo(session, model.PermissionReadLdapSyncJob), model.PermissionReadLdapSyncJob
|
|
case
|
|
model.JobTypeMigrations,
|
|
model.JobTypePlugins,
|
|
model.JobTypeProductNotices,
|
|
model.JobTypeExpiryNotify,
|
|
model.JobTypeActiveUsers,
|
|
model.JobTypeImportProcess,
|
|
model.JobTypeImportDelete,
|
|
model.JobTypeExportProcess,
|
|
model.JobTypeExportDelete,
|
|
model.JobTypeCloud,
|
|
model.JobTypeMobileSessionMetadata,
|
|
model.JobTypeExtractContent,
|
|
model.JobTypeCleanupExpiredAccessTokens:
|
|
return a.SessionHasPermissionTo(session, model.PermissionReadJobs), model.PermissionReadJobs
|
|
case model.JobTypeAccessControlSync:
|
|
return a.SessionHasPermissionTo(session, model.PermissionManageSystem), model.PermissionManageSystem
|
|
case model.JobTypeAccessControlTeamSync:
|
|
if a.SessionHasPermissionTo(session, model.PermissionManageSystem) {
|
|
return true, model.PermissionManageSystem
|
|
}
|
|
return a.SessionHasPermissionTo(session, model.PermissionManageTeamAccessRules), model.PermissionManageTeamAccessRules
|
|
}
|
|
|
|
return false, nil
|
|
}
|